overpaint.ts 5.4 KB

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