clipping.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /**
  2. * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { Loci } from '../mol-model/loci';
  7. import { StructureElement, Structure } from '../mol-model/structure';
  8. import { Script } from '../mol-script/script';
  9. import { BitFlags } from '../mol-util/bit-flags';
  10. export { Clipping };
  11. type Clipping = {
  12. readonly layers: ReadonlyArray<Clipping.Layer>
  13. }
  14. function Clipping(layers: Clipping['layers']): Clipping {
  15. return { layers };
  16. }
  17. namespace Clipping {
  18. export type Layer = { readonly loci: StructureElement.Loci, readonly groups: Groups }
  19. export const Empty: Clipping = { layers: [] };
  20. export type Groups = BitFlags<Groups.Flag>
  21. export namespace Groups {
  22. export const is: (g: Groups, f: Flag) => boolean = BitFlags.has;
  23. export const enum Flag {
  24. None = 0x0,
  25. One = 0x1,
  26. Two = 0x2,
  27. Three = 0x4,
  28. Four = 0x8,
  29. Five = 0x10,
  30. Six = 0x20,
  31. }
  32. export function create(flags: Flag): Groups {
  33. return BitFlags.create(flags);
  34. }
  35. export const Names = {
  36. 'one': Flag.One,
  37. 'two': Flag.Two,
  38. 'three': Flag.Three,
  39. 'four': Flag.Four,
  40. 'five': Flag.Five,
  41. 'six': Flag.Six,
  42. };
  43. export type Names = keyof typeof Names
  44. export function isName(name: string): name is Names {
  45. return name in Names;
  46. }
  47. export function fromName(name: Names): Flag {
  48. switch (name) {
  49. case 'one': return Flag.One;
  50. case 'two': return Flag.Two;
  51. case 'three': return Flag.Three;
  52. case 'four': return Flag.Four;
  53. case 'five': return Flag.Five;
  54. case 'six': return Flag.Six;
  55. }
  56. }
  57. export function fromNames(names: Names[]): Flag {
  58. let f = Flag.None;
  59. for (let i = 0, il = names.length; i < il; ++i) {
  60. f |= fromName(names[i]);
  61. }
  62. return f;
  63. }
  64. export function toNames(groups: Groups): Names[] {
  65. const names: Names[] = [];
  66. if (is(groups, Flag.One)) names.push('one');
  67. if (is(groups, Flag.Two)) names.push('two');
  68. if (is(groups, Flag.Three)) names.push('three');
  69. if (is(groups, Flag.Four)) names.push('four');
  70. if (is(groups, Flag.Five)) names.push('five');
  71. if (is(groups, Flag.Six)) names.push('six');
  72. return names;
  73. }
  74. }
  75. export function areEqual(cA: Clipping, cB: Clipping) {
  76. if (cA.layers.length !== cB.layers.length) return false;
  77. for (let i = 0, il = cA.layers.length; i < il; ++i) {
  78. if (cA.layers[i].groups !== cB.layers[i].groups) return false;
  79. if (!Loci.areEqual(cA.layers[i].loci, cB.layers[i].loci)) return false;
  80. }
  81. return true;
  82. }
  83. /** Check if layers empty */
  84. export function isEmpty(clipping: Clipping) {
  85. return clipping.layers.length === 0;
  86. }
  87. /** Remap layers */
  88. export function remap(clipping: Clipping, structure: Structure): Clipping {
  89. const layers: Clipping.Layer[] = [];
  90. for (const layer of clipping.layers) {
  91. let { loci, groups } = layer;
  92. loci = StructureElement.Loci.remap(loci, structure);
  93. if (!StructureElement.Loci.isEmpty(loci)) {
  94. layers.push({ loci, groups });
  95. }
  96. }
  97. return { layers };
  98. }
  99. /** Merge layers */
  100. export function merge(clipping: Clipping): Clipping {
  101. if (isEmpty(clipping)) return clipping;
  102. const { structure } = clipping.layers[0].loci;
  103. const map = new Map<Groups, StructureElement.Loci>();
  104. let shadowed = StructureElement.Loci.none(structure);
  105. for (let i = 0, il = clipping.layers.length; i < il; ++i) {
  106. let { loci, groups } = clipping.layers[il - i - 1]; // process from end
  107. loci = StructureElement.Loci.subtract(loci, shadowed);
  108. shadowed = StructureElement.Loci.union(loci, shadowed);
  109. if (!StructureElement.Loci.isEmpty(loci)) {
  110. if (map.has(groups)) {
  111. loci = StructureElement.Loci.union(loci, map.get(groups)!);
  112. }
  113. map.set(groups, loci);
  114. }
  115. }
  116. const layers: Clipping.Layer[] = [];
  117. map.forEach((loci, groups) => {
  118. layers.push({ loci, groups });
  119. });
  120. return { layers };
  121. }
  122. /** Filter layers */
  123. export function filter(clipping: Clipping, filter: Structure): Clipping {
  124. if (isEmpty(clipping)) return clipping;
  125. const { structure } = clipping.layers[0].loci;
  126. const layers: Clipping.Layer[] = [];
  127. for (const layer of clipping.layers) {
  128. let { loci, groups } = layer;
  129. // filter by first map to the `filter` structure and
  130. // then map back to the original structure of the clipping loci
  131. const filtered = StructureElement.Loci.remap(loci, filter);
  132. loci = StructureElement.Loci.remap(filtered, structure);
  133. if (!StructureElement.Loci.isEmpty(loci)) {
  134. layers.push({ loci, groups });
  135. }
  136. }
  137. return { layers };
  138. }
  139. export type ScriptLayer = { script: Script, groups: Groups }
  140. export function ofScript(scriptLayers: ScriptLayer[], structure: Structure): Clipping {
  141. const layers: Clipping.Layer[] = [];
  142. for (let i = 0, il = scriptLayers.length; i < il; ++i) {
  143. const { script, groups } = scriptLayers[i];
  144. const loci = Script.toLoci(script, structure);
  145. if (!StructureElement.Loci.isEmpty(loci)) {
  146. layers.push({ loci, groups });
  147. }
  148. }
  149. return { layers };
  150. }
  151. export type BundleLayer = { bundle: StructureElement.Bundle, groups: Groups }
  152. export function ofBundle(bundleLayers: BundleLayer[], structure: Structure): Clipping {
  153. const layers: Clipping.Layer[] = [];
  154. for (let i = 0, il = bundleLayers.length; i < il; ++i) {
  155. const { bundle, groups } = bundleLayers[i];
  156. const loci = StructureElement.Bundle.toLoci(bundle, structure.root);
  157. layers.push({ loci, groups });
  158. }
  159. return { layers };
  160. }
  161. export function toBundle(clipping: Clipping) {
  162. const layers: BundleLayer[] = [];
  163. for (let i = 0, il = clipping.layers.length; i < il; ++i) {
  164. const { loci, groups } = clipping.layers[i];
  165. const bundle = StructureElement.Bundle.fromLoci(loci);
  166. layers.push({ bundle, groups });
  167. }
  168. return { layers };
  169. }
  170. }