Ver Fonte

overpaint improvements, merge and filter

Alexander Rose há 5 anos atrás
pai
commit
e28749b794

+ 3 - 3
src/mol-model/loci.ts

@@ -76,13 +76,13 @@ namespace Loci {
         return false
     }
 
-    export function isEmpty(loci: Loci) {
+    export function isEmpty(loci: Loci): boolean {
         if (isEveryLoci(loci)) return false
         if (isEmptyLoci(loci)) return true
         if (isDataLoci(loci)) return isDataLociEmpty(loci)
         if (Structure.isLoci(loci)) return Structure.isLociEmpty(loci)
-        if (StructureElement.Loci.is(loci)) StructureElement.Loci.isEmpty(loci)
-        if (Link.isLoci(loci)) Link.isLociEmpty(loci)
+        if (StructureElement.Loci.is(loci)) return StructureElement.Loci.isEmpty(loci)
+        if (Link.isLoci(loci)) return Link.isLociEmpty(loci)
         if (Shape.isLoci(loci)) return Shape.isLociEmpty(loci)
         if (ShapeGroup.isLoci(loci)) return ShapeGroup.isLociEmpty(loci)
         return false

+ 15 - 7
src/mol-plugin/util/structure-overpaint-helper.ts

@@ -10,6 +10,7 @@ import { StateSelection, StateObjectCell, StateTransform, StateBuilder } from '.
 import { Structure, StructureElement } from '../../mol-model/structure';
 import { PluginContext } from '../context';
 import { Color } from '../../mol-util/color';
+import { Overpaint } from '../../mol-theme/overpaint';
 
 type OverpaintEachReprCallback = (update: StateBuilder.Root, repr: StateObjectCell<PluginStateObject.Molecule.Structure.Representation3D, StateTransform<typeof StateTransforms.Representation.StructureRepresentation3D>>, overpaint?: StateObjectCell<any, StateTransform<typeof StateTransforms.Representation.OverpaintStructureRepresentation3DFromBundle>>) => void
 const OverpaintManagerTag = 'overpaint-controls'
@@ -29,12 +30,9 @@ export class StructureOverpaintHelper {
     }
 
     async set(color: Color | -1, lociGetter: (structure: Structure) => StructureElement.Loci, types?: string[]) {
-        await this.eachRepr((update, repr, overpaint) => {
+        await this.eachRepr((update, repr, overpaintCell) => {
             if (types && !types.includes(repr.params!.values.type.name)) return
 
-            // TODO merge overpaint layers, delete shadowed ones
-            // TODO filter overpaint layers for given structure
-
             const structure = repr.obj!.data.source.data
             // always use the root structure to get the loci so the overpaint
             // stays applicable as long as the root structure does not change
@@ -47,11 +45,14 @@ export class StructureOverpaintHelper {
                 clear: color === -1
             }
 
-            if (overpaint) {
-                update.to(overpaint).update({ layers: [ ...overpaint.params!.values.layers, layer ], alpha: 1 })
+            if (overpaintCell) {
+                const bundleLayers = [ ...overpaintCell.params!.values.layers, layer ]
+                const filtered = getFilteredBundle(bundleLayers, structure)
+                update.to(overpaintCell).update(Overpaint.toBundle(filtered, 1))
             } else {
+                const filtered = getFilteredBundle([ layer ], structure)
                 update.to(repr.transform.ref)
-                    .apply(StateTransforms.Representation.OverpaintStructureRepresentation3DFromBundle, { layers: [ layer ], alpha: 1 }, { tags: OverpaintManagerTag });
+                    .apply(StateTransforms.Representation.OverpaintStructureRepresentation3DFromBundle, Overpaint.toBundle(filtered, 1), { tags: OverpaintManagerTag });
             }
         })
     }
@@ -59,4 +60,11 @@ export class StructureOverpaintHelper {
     constructor(private plugin: PluginContext) {
 
     }
+}
+
+/** filter overpaint layers for given structure */
+function getFilteredBundle(layers: Overpaint.BundleLayer[], structure: Structure) {
+    const overpaint = Overpaint.ofBundle(layers, 1, structure.root)
+    const merged = Overpaint.merge(overpaint)
+    return Overpaint.filter(merged, structure)
 }

+ 4 - 2
src/mol-repr/structure/units-representation.ts

@@ -184,8 +184,10 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R
         if (pickable !== undefined) visuals.forEach(({ visual }) => visual.setPickable(pickable))
         if (overpaint !== undefined) {
             // Remap loci from equivalent structure to the current `_structure`
-            const remappedOverpaint = Overpaint.remap(overpaint, _structure)
-            visuals.forEach(({ visual }) => visual.setOverpaint(remappedOverpaint))
+            if (_structure) {
+                const remappedOverpaint = Overpaint.remap(overpaint, _structure)
+                visuals.forEach(({ visual }) => visual.setOverpaint(remappedOverpaint))
+            }
         }
         if (transparency !== undefined) visuals.forEach(({ visual }) => visual.setTransparency(transparency))
         if (transform !== undefined) visuals.forEach(({ visual }) => visual.setTransform(transform))

+ 76 - 7
src/mol-theme/overpaint.ts

@@ -11,10 +11,14 @@ import { Script } from '../mol-script/script';
 
 export { Overpaint }
 
-type Overpaint = { layers: ReadonlyArray<Overpaint.Layer>, readonly alpha: number }
+type Overpaint = { readonly layers: ReadonlyArray<Overpaint.Layer>, readonly alpha: number }
+
+function Overpaint(layers: ReadonlyArray<Overpaint.Layer>, alpha: number): Overpaint {
+    return { layers, alpha }
+}
 
 namespace Overpaint {
-    export type Layer = { readonly loci: Loci, readonly color: Color, readonly clear: boolean }
+    export type Layer = { readonly loci: StructureElement.Loci, readonly color: Color, readonly clear: boolean }
     export const Empty: Overpaint = { layers: [], alpha: 1 }
 
     export function areEqual(oA: Overpaint, oB: Overpaint) {
@@ -29,25 +33,80 @@ namespace Overpaint {
         return true
     }
 
+    export function isEmpty(overpaint: Overpaint) {
+        return overpaint.layers.length === 0
+    }
+
     export function remap(overpaint: Overpaint, structure: Structure) {
         const layers: Overpaint.Layer[] = []
         for (const layer of overpaint.layers) {
-            const { loci, color, clear } = layer
-            layers.push({ loci: Loci.remap(loci, structure), color, clear })
+            let { loci, color, clear } = layer
+            loci = StructureElement.Loci.remap(loci, structure)
+            if (!StructureElement.Loci.isEmpty(loci)) {
+                layers.push({ loci, color, clear })
+            }
+        }
+        return { layers, alpha: overpaint.alpha }
+    }
+
+    export function merge(overpaint: Overpaint): Overpaint {
+        if (isEmpty(overpaint)) return overpaint
+        const { structure } = overpaint.layers[0].loci
+        const map = new Map<Color | -1, StructureElement.Loci>()
+        let shadowed = StructureElement.Loci.none(structure)
+        for (let i = 0, il = overpaint.layers.length; i < il; ++i) {
+            let { loci, color, clear } = overpaint.layers[il - i - 1] // process from end
+            loci = StructureElement.Loci.subtract(loci, shadowed)
+            shadowed = StructureElement.Loci.union(loci, shadowed)
+            if (!StructureElement.Loci.isEmpty(loci)) {
+                const colorOrClear = clear ? -1 : color
+                if (map.has(colorOrClear)) {
+                    loci = StructureElement.Loci.union(loci, map.get(colorOrClear)!)
+                }
+                map.set(colorOrClear, loci)
+            }
         }
+        const layers: Overpaint.Layer[] = []
+        map.forEach((loci, colorOrClear) => {
+            const clear = colorOrClear === -1
+            const color = colorOrClear === -1 ? Color(0) : colorOrClear
+            layers.push({ loci, color, clear })
+        })
         return { layers, alpha: overpaint.alpha }
     }
 
-    export function ofScript(scriptLayers: { script: Script, color: Color, clear: boolean }[], alpha: number, structure: Structure): Overpaint {
+    export function filter(overpaint: Overpaint, filter: Structure): Overpaint {
+        if (isEmpty(overpaint)) return overpaint
+        const { structure } = overpaint.layers[0].loci
+        const layers: Overpaint.Layer[] = []
+        for (const layer of overpaint.layers) {
+            let { loci, color, clear } = layer
+            // filter by first map to the `filter` structure and
+            // then map back to the original structure of the overpaint loci
+            const filtered = StructureElement.Loci.remap(loci, filter)
+            loci = StructureElement.Loci.remap(filtered, structure)
+            if (!StructureElement.Loci.isEmpty(loci)) {
+                layers.push({ loci, color, clear })
+            }
+        }
+        return { layers, alpha: overpaint.alpha }
+    }
+
+    export type ScriptLayer = { script: Script, color: Color, clear: boolean }
+    export function ofScript(scriptLayers: ScriptLayer[], alpha: number, structure: Structure): Overpaint {
         const layers: Overpaint.Layer[] = []
         for (let i = 0, il = scriptLayers.length; i < il; ++i) {
             const { script, color, clear } = scriptLayers[i]
-            layers.push({ loci: Script.toLoci(script, structure), color, clear })
+            const loci = Script.toLoci(script, structure)
+            if (!StructureElement.Loci.isEmpty(loci)) {
+                layers.push({ loci, color, clear })
+            }
         }
         return { layers, alpha }
     }
 
-    export function ofBundle(bundleLayers: { bundle: StructureElement.Bundle, color: Color, clear: boolean }[], alpha: number, structure: Structure): Overpaint {
+    export type BundleLayer = { bundle: StructureElement.Bundle, color: Color, clear: boolean }
+    export function ofBundle(bundleLayers: BundleLayer[], alpha: number, structure: Structure): Overpaint {
         const layers: Overpaint.Layer[] = []
         for (let i = 0, il = bundleLayers.length; i < il; ++i) {
             const { bundle, color, clear } = bundleLayers[i]
@@ -56,4 +115,14 @@ namespace Overpaint {
         }
         return { layers, alpha }
     }
+
+    export function toBundle(overpaint: Overpaint, alpha: number) {
+        const layers: BundleLayer[] = []
+        for (let i = 0, il = overpaint.layers.length; i < il; ++i) {
+            let { loci, color, clear } = overpaint.layers[i]
+            const bundle = StructureElement.Bundle.fromLoci(loci)
+            layers.push({ bundle, color, clear })
+        }
+        return { layers, alpha }
+    }
 }