substance.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. /**
  2. * Copyright (c) 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 { Structure, StructureElement } from '../mol-model/structure';
  8. import { Script } from '../mol-script/script';
  9. import { Material } from '../mol-util/material';
  10. export { Substance };
  11. type Substance = { readonly layers: ReadonlyArray<Substance.Layer> }
  12. function Substance(layers: ReadonlyArray<Substance.Layer>): Substance {
  13. return { layers };
  14. }
  15. namespace Substance {
  16. export type Layer = { readonly loci: StructureElement.Loci, readonly material: Material, readonly clear: boolean }
  17. export const Empty: Substance = { layers: [] };
  18. export function areEqual(sA: Substance, sB: Substance) {
  19. if (sA.layers.length === 0 && sB.layers.length === 0) return true;
  20. if (sA.layers.length !== sB.layers.length) return false;
  21. for (let i = 0, il = sA.layers.length; i < il; ++i) {
  22. if (sA.layers[i].clear !== sB.layers[i].clear) return false;
  23. if (sA.layers[i].material !== sB.layers[i].material) return false;
  24. if (!Loci.areEqual(sA.layers[i].loci, sB.layers[i].loci)) return false;
  25. }
  26. return true;
  27. }
  28. export function isEmpty(overpaint: Substance) {
  29. return overpaint.layers.length === 0;
  30. }
  31. export function remap(substance: Substance, structure: Structure) {
  32. const layers: Substance.Layer[] = [];
  33. for (const layer of substance.layers) {
  34. let { loci, material, clear } = layer;
  35. loci = StructureElement.Loci.remap(loci, structure);
  36. if (!StructureElement.Loci.isEmpty(loci)) {
  37. layers.push({ loci, material, clear });
  38. }
  39. }
  40. return { layers };
  41. }
  42. export function merge(substance: Substance): Substance {
  43. if (isEmpty(substance)) return substance;
  44. const { structure } = substance.layers[0].loci;
  45. const map = new Map<Material | -1, StructureElement.Loci>();
  46. let shadowed = StructureElement.Loci.none(structure);
  47. for (let i = 0, il = substance.layers.length; i < il; ++i) {
  48. let { loci, material, clear } = substance.layers[il - i - 1]; // process from end
  49. loci = StructureElement.Loci.subtract(loci, shadowed);
  50. shadowed = StructureElement.Loci.union(loci, shadowed);
  51. if (!StructureElement.Loci.isEmpty(loci)) {
  52. const materialOrClear = clear ? -1 : material;
  53. if (map.has(materialOrClear)) {
  54. loci = StructureElement.Loci.union(loci, map.get(materialOrClear)!);
  55. }
  56. map.set(materialOrClear, loci);
  57. }
  58. }
  59. const layers: Substance.Layer[] = [];
  60. map.forEach((loci, materialOrClear) => {
  61. const clear = materialOrClear === -1;
  62. const material = clear ? Material(0) : materialOrClear;
  63. layers.push({ loci, material, clear });
  64. });
  65. return { layers };
  66. }
  67. export function filter(substance: Substance, filter: Structure): Substance {
  68. if (isEmpty(substance)) return substance;
  69. const { structure } = substance.layers[0].loci;
  70. const layers: Substance.Layer[] = [];
  71. for (const layer of substance.layers) {
  72. let { loci, material, clear } = layer;
  73. // filter by first map to the `filter` structure and
  74. // then map back to the original structure of the substance loci
  75. const filtered = StructureElement.Loci.remap(loci, filter);
  76. loci = StructureElement.Loci.remap(filtered, structure);
  77. if (!StructureElement.Loci.isEmpty(loci)) {
  78. layers.push({ loci, material, clear });
  79. }
  80. }
  81. return { layers };
  82. }
  83. export type ScriptLayer = { script: Script, material: Material, clear: boolean }
  84. export function ofScript(scriptLayers: ScriptLayer[], structure: Structure): Substance {
  85. const layers: Substance.Layer[] = [];
  86. for (let i = 0, il = scriptLayers.length; i < il; ++i) {
  87. const { script, material, clear } = scriptLayers[i];
  88. const loci = Script.toLoci(script, structure);
  89. if (!StructureElement.Loci.isEmpty(loci)) {
  90. layers.push({ loci, material, clear });
  91. }
  92. }
  93. return { layers };
  94. }
  95. export type BundleLayer = { bundle: StructureElement.Bundle, material: Material, clear: boolean }
  96. export function ofBundle(bundleLayers: BundleLayer[], structure: Structure): Substance {
  97. const layers: Substance.Layer[] = [];
  98. for (let i = 0, il = bundleLayers.length; i < il; ++i) {
  99. const { bundle, material, clear } = bundleLayers[i];
  100. const loci = StructureElement.Bundle.toLoci(bundle, structure.root);
  101. layers.push({ loci, material, clear });
  102. }
  103. return { layers };
  104. }
  105. export function toBundle(overpaint: Substance) {
  106. const layers: BundleLayer[] = [];
  107. for (let i = 0, il = overpaint.layers.length; i < il; ++i) {
  108. const { loci, material, clear } = overpaint.layers[i];
  109. const bundle = StructureElement.Bundle.fromLoci(loci);
  110. layers.push({ bundle, material, clear });
  111. }
  112. return { layers };
  113. }
  114. }