clipping.ts 6.9 KB

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