Browse Source

wip, repr state and overpaint refactoring

Alexander Rose 6 years ago
parent
commit
40b8e2089a

+ 6 - 6
src/mol-plugin/behavior/dynamic/representation.ts

@@ -193,27 +193,27 @@ export namespace ColorRepresentation3D {
             this.currentColorMappings = colorMappings;
             if (!this.repr.data || !this.structure.data) return true;
 
-            const list: Overpaint.Layer[] = []
+            const layers: Overpaint.Layer[] = []
             for (let i = 0, il = this.currentColorMappings.length; i < il; ++i) {
                 const { query, color } = this.currentColorMappings[i]
                 const compiled = compile<StructureSelection>(query);
                 const result = compiled(new QueryContext(this.structure.data));
                 const loci = StructureSelection.toLoci2(result)
-                list.push({ loci, color })
+                layers.push({ loci, color })
             }
-            return this.applyLayers({ alpha, list }, true)
+            return this.applyLayers({ alpha, layers })
         }
 
-        private applyLayers(layers: Overpaint.Layers, clear: boolean): boolean {
+        private applyLayers(overpaint: Overpaint): boolean {
             if (!this.repr.data) return true;
-            this.repr.data.repr.setOverpaint(layers)
+            this.repr.data.repr.setState({ overpaint })
             this.ctx.canvas3d.add(this.repr.data.repr);
             this.ctx.canvas3d.requestDraw(true);
             return true;
         }
 
         unregister(): void {
-            this.applyLayers(Overpaint.EmptyLayers, true) // clear
+            this.applyLayers(Overpaint.Empty) // clear
             this.repr.cell = void 0;
             this.structure.cell = void 0;
         }

+ 7 - 9
src/mol-repr/representation.ts

@@ -122,7 +122,6 @@ interface Representation<D, P extends PD.Params = {}, S extends Representation.S
     createOrUpdate: (props?: Partial<PD.Values<P>>, data?: D) => Task<void>
     setState: (state: Partial<S>) => void
     setTheme: (theme: Theme) => void
-    setOverpaint: (layers: Overpaint.Layers, clear?: boolean) => void
     getLoci: (pickingId: PickingId) => ModelLoci
     mark: (loci: ModelLoci, action: MarkerAction) => boolean
     destroy: () => void
@@ -145,18 +144,21 @@ namespace Representation {
         alphaFactor: number
         /** Controls if the representation's renderobjects are pickable or not */
         pickable: boolean
+        /** Overpaint applied to the representation's renderobjects */
+        overpaint: Overpaint
         /** Controls if the representation's renderobjects are synced automatically with GPU or not */
         syncManually: boolean
         /** A transformation applied to the representation's renderobjects */
         transform: Mat4
     }
     export function createState(): State {
-        return { visible: false, alphaFactor: 0, pickable: false, syncManually: false, transform: Mat4.identity() }
+        return { visible: false, alphaFactor: 0, pickable: false, syncManually: false, transform: Mat4.identity(), overpaint: Overpaint.Empty }
     }
     export function updateState(state: State, update: Partial<State>) {
         if (update.visible !== undefined) state.visible = update.visible
         if (update.alphaFactor !== undefined) state.alphaFactor = update.alphaFactor
         if (update.pickable !== undefined) state.pickable = update.pickable
+        if (update.overpaint !== undefined) state.overpaint = update.overpaint
         if (update.syncManually !== undefined) state.syncManually = update.syncManually
         if (update.transform !== undefined) Mat4.copy(state.transform, update.transform)
     }
@@ -172,7 +174,6 @@ namespace Representation {
         createOrUpdate: () => Task.constant('', undefined),
         setState: () => {},
         setTheme: () => {},
-        setOverpaint: () => false,
         getLoci: () => EmptyLoci,
         mark: () => false,
         destroy: () => {}
@@ -271,11 +272,6 @@ namespace Representation {
                     reprList[i].setTheme(theme)
                 }
             },
-            setOverpaint: (layers: Overpaint.Layers) => {
-                for (let i = 0, il = reprList.length; i < il; ++i) {
-                    reprList[i].setOverpaint(layers)
-                }
-            },
             destroy() {
                 for (let i = 0, il = reprList.length; i < il; ++i) {
                     reprList[i].destroy()
@@ -323,12 +319,14 @@ namespace Representation {
                 if (state.visible !== undefined) Visual.setVisibility(renderObject, state.visible)
                 if (state.alphaFactor !== undefined) Visual.setAlphaFactor(renderObject, state.alphaFactor)
                 if (state.pickable !== undefined) Visual.setPickable(renderObject, state.pickable)
+                if (state.overpaint !== undefined) {
+                    // TODO
+                }
                 if (state.transform !== undefined) Visual.setTransform(renderObject, state.transform)
 
                 Representation.updateState(currentState, state)
             },
             setTheme: () => { },
-            setOverpaint: () => false,
             destroy() { }
         }
     }

+ 14 - 51
src/mol-repr/shape/representation.ts

@@ -14,7 +14,7 @@ import { OrderedSet, Interval } from 'mol-data/int';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { createTransform, TransformData } from 'mol-geo/geometry/transform-data';
 import { PickingId } from 'mol-geo/geometry/picking';
-import { MarkerAction, applyMarkerAction, createMarkers } from 'mol-geo/geometry/marker-data';
+import { MarkerAction, createMarkers } from 'mol-geo/geometry/marker-data';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { createEmptyTheme, Theme } from 'mol-theme/theme';
 import { Subject } from 'rxjs';
@@ -26,8 +26,6 @@ import { Mat4 } from 'mol-math/linear-algebra';
 import { Visual } from 'mol-repr/visual';
 import { createSizes } from 'mol-geo/geometry/size-data';
 import { ShapeGroupSizeTheme } from 'mol-theme/size/shape-group';
-import { Overpaint } from 'mol-theme/overpaint';
-import { applyOverpaintColor, createOverpaint, clearOverpaint } from 'mol-geo/geometry/overpaint-data';
 
 export interface ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>> extends Representation<D, P> { }
 
@@ -151,6 +149,14 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
         });
     }
 
+    function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
+        if (isEveryLoci(loci) || (Shape.isLoci(loci) && loci.shape === _shape)) {
+            return apply(Interval.ofBounds(0, _shape.groupCount * _shape.transforms.length))
+        } else {
+            return eachShapeGroup(loci, _shape, apply)
+        }
+    }
+
     return {
         label: 'Shape geometry',
         get groupCount () { return locationIt ? locationIt.count : 0 },
@@ -169,32 +175,16 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
             return EmptyLoci
         },
         mark(loci: Loci, action: MarkerAction) {
-            if (!_renderObject) return false
-            const { tMarker } = _renderObject.values
-            const { groupCount, instanceCount } = locationIt
-
-            function apply(interval: Interval) {
-                const start = Interval.start(interval)
-                const end = Interval.end(interval)
-                return applyMarkerAction(tMarker.ref.value.array, start, end, action)
-            }
-
-            let changed = false
-            if (isEveryLoci(loci) || (Shape.isLoci(loci) && loci.shape === _shape)) {
-                changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
-            } else {
-                changed = eachShapeLocation(loci, _shape, apply)
-            }
-            if (changed) {
-                ValueCell.update(tMarker, tMarker.ref.value)
-            }
-            return changed
+            return Visual.mark(_renderObject, loci, action, lociApply)
         },
         setState(state: Partial<Representation.State>) {
             if (_renderObject) {
                 if (state.visible !== undefined) Visual.setVisibility(_renderObject, state.visible)
                 if (state.alphaFactor !== undefined) Visual.setAlphaFactor(_renderObject, state.alphaFactor)
                 if (state.pickable !== undefined) Visual.setPickable(_renderObject, state.pickable)
+                if (state.overpaint !== undefined) {
+                    Visual.setOverpaint(_renderObject, state.overpaint, lociApply, true)
+                }
                 if (state.transform !== undefined) Visual.setTransform(_renderObject, state.transform)
             }
 
@@ -203,33 +193,6 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
         setTheme(theme: Theme) {
             console.warn('The `ShapeRepresentation` theme is fixed to `ShapeGroupColorTheme` and `ShapeGroupSizeTheme`. Colors are taken from `Shape.getColor` and sizes from `Shape.getSize`')
         },
-        setOverpaint(layers: Overpaint.Layers, clear?: boolean) {
-            if (!_renderObject) return false
-            const { tOverpaint } = _renderObject.values
-            const count = locationIt.groupCount * locationIt.instanceCount
-
-            // ensure texture has right size
-            createOverpaint(layers.list.length ? count : 0, _renderObject.values)
-
-            // clear if requested
-            if (clear) clearOverpaint(tOverpaint.ref.value.array, 0, count)
-
-            for (let i = 0, il = layers.list.length; i < il; ++i) {
-                const { loci, color } = layers.list[i]
-                const apply = (interval: Interval) => {
-                    const start = Interval.start(interval)
-                    const end = Interval.end(interval)
-                    return applyOverpaintColor(tOverpaint.ref.value.array, start, end, color, layers.alpha)
-                }
-
-                if (isEveryLoci(loci) || (Shape.isLoci(loci) && loci.shape === _shape)) {
-                    apply(Interval.ofBounds(0, count))
-                } else {
-                    eachShapeLocation(loci, _shape, apply)
-                }
-            }
-            ValueCell.update(tOverpaint, tOverpaint.ref.value)
-        },
         destroy() {
             // TODO
             renderObjects.length = 0
@@ -246,7 +209,7 @@ function createShapeTransform(transforms: Mat4[], transformData?: TransformData)
     return createTransform(transformArray, transforms.length, transformData)
 }
 
-function eachShapeLocation(loci: Loci, shape: Shape, apply: (interval: Interval) => boolean) {
+function eachShapeGroup(loci: Loci, shape: Shape, apply: (interval: Interval) => boolean) {
     if (!Shape.isLoci(loci)) return false
     if (loci.shape !== shape) return false
     let changed = false

+ 2 - 6
src/mol-repr/structure/complex-representation.ts

@@ -17,7 +17,6 @@ import { Theme, createEmptyTheme } from 'mol-theme/theme';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Subject } from 'rxjs';
 import { GraphicsRenderObject } from 'mol-gl/render-object';
-import { Overpaint } from 'mol-theme/overpaint';
 
 export function ComplexRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: () => ComplexVisual<P>): StructureRepresentation<P> {
     let version = 0
@@ -66,7 +65,9 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
             // hide visual when _unitTransforms is set
             visual.setVisibility(state.visible && _state.unitTransforms === null)
         }
+        if (state.alphaFactor !== undefined && visual) visual.setAlphaFactor(state.alphaFactor)
         if (state.pickable !== undefined && visual) visual.setPickable(state.pickable)
+        if (state.overpaint !== undefined && visual) visual.setOverpaint(state.overpaint)
         if (state.transform !== undefined && visual) visual.setTransform(state.transform)
         if (state.unitTransforms !== undefined && visual) {
             // Since ComplexVisuals always renders geometries between units the application of `unitTransforms`
@@ -79,10 +80,6 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
         _theme = theme
     }
 
-    function setOverpaint(layers: Overpaint.Layers, clear?: boolean) {
-        if (visual) visual.setOverpaint(layers, clear)
-    }
-
     function destroy() {
         if (visual) visual.destroy()
     }
@@ -101,7 +98,6 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
         createOrUpdate,
         setState,
         setTheme,
-        setOverpaint,
         getLoci,
         mark,
         destroy

+ 12 - 48
src/mol-repr/structure/complex-visual.ts

@@ -18,7 +18,7 @@ import { Geometry, GeometryUtils } from 'mol-geo/geometry/geometry';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { createColors } from 'mol-geo/geometry/color-data';
-import { MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data';
+import { MarkerAction } from 'mol-geo/geometry/marker-data';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { VisualUpdateState } from 'mol-repr/util';
 import { Theme, createEmptyTheme } from 'mol-theme/theme';
@@ -29,7 +29,6 @@ import { DirectVolume } from 'mol-geo/geometry/direct-volume/direct-volume';
 import { Mat4 } from 'mol-math/linear-algebra';
 import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
 import { Overpaint } from 'mol-theme/overpaint';
-import { applyOverpaintColor, createOverpaint, clearOverpaint } from 'mol-geo/geometry/overpaint-data';
 
 export interface  ComplexVisual<P extends StructureParams> extends Visual<Structure, P> { }
 
@@ -159,6 +158,14 @@ export function ComplexVisual<G extends Geometry, P extends ComplexParams & Geom
         if (newGeometry) geometry = newGeometry
     }
 
+    function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
+        if (isEveryLoci(loci) || (Structure.isLoci(loci) && Structure.areEquivalent(loci.structure, currentStructure))) {
+            return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount))
+        } else {
+            return eachLocation(loci, currentStructure, apply)
+        }
+    }
+
     return {
         get groupCount() { return locationIt ? locationIt.count : 0 },
         get renderObject () { return locationIt && locationIt.count ? renderObject : undefined },
@@ -175,26 +182,7 @@ export function ComplexVisual<G extends Geometry, P extends ComplexParams & Geom
             return renderObject ? getLoci(pickingId, currentStructure, renderObject.id) : EmptyLoci
         },
         mark(loci: Loci, action: MarkerAction) {
-            if (!renderObject) return false
-            const { tMarker } = renderObject.values
-            const { groupCount, instanceCount } = locationIt
-
-            function apply(interval: Interval) {
-                const start = Interval.start(interval)
-                const end = Interval.end(interval)
-                return applyMarkerAction(tMarker.ref.value.array, start, end, action)
-            }
-
-            let changed = false
-            if (isEveryLoci(loci) || (Structure.isLoci(loci) && Structure.areEquivalent(loci.structure, currentStructure))) {
-                changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
-            } else {
-                changed = eachLocation(loci, currentStructure, apply)
-            }
-            if (changed) {
-                ValueCell.update(tMarker, tMarker.ref.value)
-            }
-            return changed
+            return Visual.mark(renderObject, loci, action, lociApply)
         },
         setVisibility(visible: boolean) {
             Visual.setVisibility(renderObject, visible)
@@ -208,32 +196,8 @@ export function ComplexVisual<G extends Geometry, P extends ComplexParams & Geom
         setTransform(matrix?: Mat4, instanceMatrices?: Float32Array | null) {
             Visual.setTransform(renderObject, matrix, instanceMatrices)
         },
-        setOverpaint(layers: Overpaint.Layers, clear = false) {
-            if (!renderObject) return false
-            const { tOverpaint } = renderObject.values
-            const count = locationIt.groupCount * locationIt.instanceCount
-
-            // ensure texture has right size
-            createOverpaint(layers.list.length ? count : 0, renderObject.values)
-
-            // clear if requested
-            if (clear) clearOverpaint(tOverpaint.ref.value.array, 0, count)
-
-            for (let i = 0, il = layers.list.length; i < il; ++i) {
-                const { loci, color } = layers.list[i]
-                const apply = (interval: Interval) => {
-                    const start = Interval.start(interval)
-                    const end = Interval.end(interval)
-                    return applyOverpaintColor(tOverpaint.ref.value.array, start, end, color, layers.alpha)
-                }
-
-                if (isEveryLoci(loci) || (Structure.isLoci(loci) && Structure.areEquivalent(loci.structure, currentStructure))) {
-                    apply(Interval.ofBounds(0, count))
-                } else {
-                    eachLocation(loci, currentStructure, apply)
-                }
-            }
-            ValueCell.update(tOverpaint, tOverpaint.ref.value)
+        setOverpaint(overpaint: Overpaint, clear = false) {
+            return Visual.setOverpaint(renderObject, overpaint, lociApply, true)
         },
         destroy() {
             // TODO

+ 3 - 7
src/mol-repr/structure/units-representation.ts

@@ -19,7 +19,6 @@ import { Theme, createEmptyTheme } from 'mol-theme/theme';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { UnitKind, UnitKindOptions } from './visual/util/common';
 import { Subject } from 'rxjs';
-import { Overpaint } from 'mol-theme/overpaint';
 
 export const UnitsParams = {
     ...StructureParams,
@@ -172,9 +171,11 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R
     }
 
     function setState(state: Partial<StructureRepresentationState>) {
-        const { visible, pickable, transform, unitTransforms } = state
+        const { visible, alphaFactor, pickable, overpaint, transform, unitTransforms } = state
         if (visible !== undefined) visuals.forEach(({ visual }) => visual.setVisibility(visible))
+        if (alphaFactor !== undefined) visuals.forEach(({ visual }) => visual.setAlphaFactor(alphaFactor))
         if (pickable !== undefined) visuals.forEach(({ visual }) => visual.setPickable(pickable))
+        if (overpaint !== undefined) visuals.forEach(({ visual }) => visual.setOverpaint(overpaint))
         if (transform !== undefined) visuals.forEach(({ visual }) => visual.setTransform(transform))
         if (unitTransforms !== undefined) {
             visuals.forEach(({ visual, group }) => {
@@ -194,10 +195,6 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R
         _theme = theme
     }
 
-    function setOverpaint(layers: Overpaint.Layers, clear?: boolean) {
-        visuals.forEach(({ visual }) => visual.setOverpaint(layers, clear))
-    }
-
     function destroy() {
         visuals.forEach(({ visual }) => visual.destroy())
         visuals.clear()
@@ -221,7 +218,6 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R
         createOrUpdate,
         setState,
         setTheme,
-        setOverpaint,
         getLoci,
         mark,
         destroy

+ 12 - 48
src/mol-repr/structure/units-visual.ts

@@ -16,7 +16,7 @@ import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Geometry, GeometryUtils } from 'mol-geo/geometry/geometry';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { PickingId } from 'mol-geo/geometry/picking';
-import { createMarkers, MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data';
+import { createMarkers, MarkerAction } from 'mol-geo/geometry/marker-data';
 import { createSizes } from 'mol-geo/geometry/size-data';
 import { createColors } from 'mol-geo/geometry/color-data';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
@@ -32,7 +32,6 @@ import { Mat4 } from 'mol-math/linear-algebra';
 import { Spheres } from 'mol-geo/geometry/spheres/spheres';
 import { createUnitsTransform, includesUnitKind } from './visual/util/common';
 import { Overpaint } from 'mol-theme/overpaint';
-import { applyOverpaintColor, createOverpaint, clearOverpaint } from 'mol-geo/geometry/overpaint-data';
 
 export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
 
@@ -205,6 +204,14 @@ export function UnitsVisual<G extends Geometry, P extends UnitsParams & Geometry
                 : createEmptyGeometry(geometry)
     }
 
+    function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
+        if (isEveryLoci(loci) || (Structure.isLoci(loci) && Structure.areEquivalent(loci.structure, currentStructureGroup.structure))) {
+            return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount))
+        } else {
+            return eachLocation(loci, currentStructureGroup, apply)
+        }
+    }
+
     return {
         get groupCount() { return locationIt ? locationIt.count : 0 },
         get renderObject () { return locationIt && locationIt.count ? renderObject : undefined },
@@ -221,26 +228,7 @@ export function UnitsVisual<G extends Geometry, P extends UnitsParams & Geometry
             return renderObject ? getLoci(pickingId, currentStructureGroup, renderObject.id) : EmptyLoci
         },
         mark(loci: Loci, action: MarkerAction) {
-            if (!renderObject) return false
-            const { tMarker } = renderObject.values
-            const { groupCount, instanceCount } = locationIt
-
-            function apply(interval: Interval) {
-                const start = Interval.start(interval)
-                const end = Interval.end(interval)
-                return applyMarkerAction(tMarker.ref.value.array, start, end, action)
-            }
-
-            let changed = false
-            if (isEveryLoci(loci) || (Structure.isLoci(loci) && Structure.areEquivalent(loci.structure, currentStructureGroup.structure))) {
-                changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
-            } else {
-                changed = eachLocation(loci, currentStructureGroup, apply)
-            }
-            if (changed) {
-                ValueCell.update(tMarker, tMarker.ref.value)
-            }
-            return changed
+            return Visual.mark(renderObject, loci, action, lociApply)
         },
         setVisibility(visible: boolean) {
             Visual.setVisibility(renderObject, visible)
@@ -254,32 +242,8 @@ export function UnitsVisual<G extends Geometry, P extends UnitsParams & Geometry
         setTransform(matrix?: Mat4, instanceMatrices?: Float32Array | null) {
             Visual.setTransform(renderObject, matrix, instanceMatrices)
         },
-        setOverpaint(layers: Overpaint.Layers, clear = false) {
-            if (!renderObject) return false
-            const { tOverpaint } = renderObject.values
-            const count = locationIt.groupCount * locationIt.instanceCount
-
-            // ensure texture has right size
-            createOverpaint(layers.list.length ? count : 0, renderObject.values)
-
-            // clear if requested
-            if (clear) clearOverpaint(tOverpaint.ref.value.array, 0, count)
-
-            for (let i = 0, il = layers.list.length; i < il; ++i) {
-                const { loci, color } = layers.list[i]
-                const apply = (interval: Interval) => {
-                    const start = Interval.start(interval)
-                    const end = Interval.end(interval)
-                    return applyOverpaintColor(tOverpaint.ref.value.array, start, end, color, layers.alpha)
-                }
-
-                if (isEveryLoci(loci) || (Structure.isLoci(loci) && Structure.areEquivalent(loci.structure, currentStructureGroup.structure))) {
-                    apply(Interval.ofBounds(0, count))
-                } else {
-                    eachLocation(loci, currentStructureGroup, apply)
-                }
-            }
-            ValueCell.update(tOverpaint, tOverpaint.ref.value)
+        setOverpaint(overpaint: Overpaint) {
+            return Visual.setOverpaint(renderObject, overpaint, lociApply, true)
         },
         destroy() {
             // TODO

+ 62 - 18
src/mol-repr/visual.ts

@@ -8,7 +8,7 @@ import { RuntimeContext } from 'mol-task'
 import { GraphicsRenderObject } from 'mol-gl/render-object'
 import { PickingId } from '../mol-geo/geometry/picking';
 import { Loci } from 'mol-model/loci';
-import { MarkerAction } from '../mol-geo/geometry/marker-data';
+import { MarkerAction, applyMarkerAction } from '../mol-geo/geometry/marker-data';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { WebGLContext } from 'mol-gl/webgl/context';
 import { Theme } from 'mol-theme/theme';
@@ -17,6 +17,8 @@ import { updateTransformData, fillIdentityTransform } from 'mol-geo/geometry/tra
 import { calculateTransformBoundingSphere } from 'mol-gl/renderable/util';
 import { ValueCell } from 'mol-util';
 import { Overpaint } from 'mol-theme/overpaint';
+import { createOverpaint, clearOverpaint, applyOverpaintColor } from 'mol-geo/geometry/overpaint-data';
+import { Interval } from 'mol-data/int';
 
 export interface VisualContext {
     readonly runtime: RuntimeContext
@@ -36,10 +38,12 @@ interface Visual<D, P extends PD.Params> {
     setAlphaFactor: (alphaFactor: number) => void
     setPickable: (pickable: boolean) => void
     setTransform: (matrix?: Mat4, instanceMatrices?: Float32Array | null) => void
-    setOverpaint: (layers: Overpaint.Layers, clear?: boolean) => void
+    setOverpaint: (overpaint: Overpaint) => void
     destroy: () => void
 }
 namespace Visual {
+    export type LociApply = (loci: Loci, apply: (interval: Interval) => boolean) => boolean
+
     export function setVisibility(renderObject: GraphicsRenderObject | undefined, visible: boolean) {
         if (renderObject) renderObject.state.visible = visible
     }
@@ -52,23 +56,63 @@ namespace Visual {
         if (renderObject) renderObject.state.pickable = pickable
     }
 
-    export function setTransform(renderObject: GraphicsRenderObject | undefined, transform?: Mat4, instanceTransforms?: Float32Array | null) {
-        if (renderObject && (transform || instanceTransforms)) {
-            const { values } = renderObject
-            if (transform) {
-                Mat4.copy(values.matrix.ref.value, transform)
-                ValueCell.update(values.matrix, values.matrix.ref.value)
-            }
-            if (instanceTransforms) {
-                values.extraTransform.ref.value.set(instanceTransforms)
-                ValueCell.update(values.extraTransform, values.extraTransform.ref.value)
-            } else if (instanceTransforms === null) {
-                fillIdentityTransform(values.extraTransform.ref.value, values.instanceCount.ref.value)
-                ValueCell.update(values.extraTransform, values.extraTransform.ref.value)
+    export function mark(renderObject: GraphicsRenderObject | undefined, loci: Loci, action: MarkerAction, lociApply: LociApply) {
+        if (!renderObject) return false
+
+        const { tMarker } = renderObject.values
+
+        function apply(interval: Interval) {
+            const start = Interval.start(interval)
+            const end = Interval.end(interval)
+            return applyMarkerAction(tMarker.ref.value.array, start, end, action)
+        }
+
+        const changed = lociApply(loci, apply)
+        if (changed) ValueCell.update(tMarker, tMarker.ref.value)
+        return changed
+    }
+
+    export function setOverpaint(renderObject: GraphicsRenderObject | undefined, overpaint: Overpaint, lociApply: LociApply, clear: boolean) {
+        if (!renderObject) return
+
+        const { tOverpaint, uGroupCount, instanceCount } = renderObject.values
+        const count = uGroupCount.ref.value * instanceCount.ref.value
+
+        // ensure texture has right size
+        createOverpaint(overpaint.layers.length ? count : 0, renderObject.values)
+
+        // clear if requested
+        if (clear) clearOverpaint(tOverpaint.ref.value.array, 0, count)
+
+        for (let i = 0, il = overpaint.layers.length; i < il; ++i) {
+            const { loci, color } = overpaint.layers[i]
+            const apply = (interval: Interval) => {
+                const start = Interval.start(interval)
+                const end = Interval.end(interval)
+                return applyOverpaintColor(tOverpaint.ref.value.array, start, end, color, overpaint.alpha)
             }
-            updateTransformData(values)
-            const boundingSphere = calculateTransformBoundingSphere(values.invariantBoundingSphere.ref.value, values.aTransform.ref.value, values.instanceCount.ref.value)
-            ValueCell.update(values.boundingSphere, boundingSphere)
+            lociApply(loci, apply)
+        }
+        ValueCell.update(tOverpaint, tOverpaint.ref.value)
+    }
+
+    export function setTransform(renderObject: GraphicsRenderObject | undefined, transform?: Mat4, instanceTransforms?: Float32Array | null) {
+        if (!renderObject || (!transform && !instanceTransforms)) return
+
+        const { values } = renderObject
+        if (transform) {
+            Mat4.copy(values.matrix.ref.value, transform)
+            ValueCell.update(values.matrix, values.matrix.ref.value)
+        }
+        if (instanceTransforms) {
+            values.extraTransform.ref.value.set(instanceTransforms)
+            ValueCell.update(values.extraTransform, values.extraTransform.ref.value)
+        } else if (instanceTransforms === null) {
+            fillIdentityTransform(values.extraTransform.ref.value, values.instanceCount.ref.value)
+            ValueCell.update(values.extraTransform, values.extraTransform.ref.value)
         }
+        updateTransformData(values)
+        const boundingSphere = calculateTransformBoundingSphere(values.invariantBoundingSphere.ref.value, values.aTransform.ref.value, values.instanceCount.ref.value)
+        ValueCell.update(values.boundingSphere, boundingSphere)
     }
 }

+ 1 - 1
src/mol-repr/volume/direct-volume.ts

@@ -169,7 +169,7 @@ export function DirectVolumeVisual(): VolumeVisual<DirectVolumeParams> {
         createGeometry: createDirectVolume,
         createLocationIterator: (volume: VolumeData) => LocationIterator(1, 1, () => NullLocation),
         getLoci: () => EmptyLoci,
-        mark: () => false,
+        eachLocation: () => false,
         setUpdateState: (state: VisualUpdateState, newProps: PD.Values<DirectVolumeParams>, currentProps: PD.Values<DirectVolumeParams>) => {
         },
         geometryUtils: DirectVolume.Utils

+ 2 - 2
src/mol-repr/volume/isosurface.ts

@@ -95,7 +95,7 @@ export function IsosurfaceMeshVisual(): VolumeVisual<IsosurfaceMeshParams> {
         createGeometry: createVolumeIsosurfaceMesh,
         createLocationIterator: (volume: VolumeData) => LocationIterator(1, 1, () => NullLocation),
         getLoci: () => EmptyLoci,
-        mark: () => false,
+        eachLocation: () => false,
         setUpdateState: (state: VisualUpdateState, newProps: PD.Values<IsosurfaceMeshParams>, currentProps: PD.Values<IsosurfaceMeshParams>) => {
             if (newProps.isoValue !== currentProps.isoValue) state.createGeometry = true
         },
@@ -131,7 +131,7 @@ export function IsosurfaceWireframeVisual(): VolumeVisual<IsosurfaceWireframePar
         createGeometry: createVolumeIsosurfaceWireframe,
         createLocationIterator: (volume: VolumeData) => LocationIterator(1, 1, () => NullLocation),
         getLoci: () => EmptyLoci,
-        mark: () => false,
+        eachLocation: () => false,
         setUpdateState: (state: VisualUpdateState, newProps: PD.Values<IsosurfaceWireframeParams>, currentProps: PD.Values<IsosurfaceWireframeParams>) => {
             if (newProps.isoValue !== currentProps.isoValue) state.createGeometry = true
         },

+ 16 - 30
src/mol-repr/volume/representation.ts

@@ -12,7 +12,7 @@ import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci';
 import { Geometry, GeometryUtils } from 'mol-geo/geometry/geometry';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { PickingId } from 'mol-geo/geometry/picking';
-import { MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data';
+import { MarkerAction } from 'mol-geo/geometry/marker-data';
 import { GraphicsRenderObject, createRenderObject } from 'mol-gl/render-object';
 import { Interval } from 'mol-data/int';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
@@ -43,7 +43,7 @@ interface VolumeVisualBuilder<P extends VolumeParams, G extends Geometry> {
     createGeometry(ctx: VisualContext, volume: VolumeData, theme: Theme, props: PD.Values<P>, geometry?: G): Promise<G> | G
     createLocationIterator(volume: VolumeData): LocationIterator
     getLoci(pickingId: PickingId, id: number): Loci
-    mark(loci: Loci, apply: (interval: Interval) => boolean): boolean
+    eachLocation(loci: Loci, apply: (interval: Interval) => boolean): boolean
     setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme): void
 }
 
@@ -52,7 +52,7 @@ interface VolumeVisualGeometryBuilder<P extends VolumeParams, G extends Geometry
 }
 
 export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geometry.Params<G>>(builder: VolumeVisualGeometryBuilder<P, G>): VolumeVisual<P> {
-    const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder
+    const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState } = builder
     const { updateValues, updateBoundingSphere, updateRenderableState } = builder.geometryUtils
     const updateState = VisualUpdateState.create()
 
@@ -145,6 +145,14 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
         if (newGeometry) geometry = newGeometry
     }
 
+    function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
+        if (isEveryLoci(loci)) {
+            return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount))
+        } else {
+            return eachLocation(loci, apply)
+        }
+    }
+
     return {
         get groupCount() { return locationIt ? locationIt.count : 0 },
         get renderObject () { return renderObject },
@@ -161,26 +169,7 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
             return renderObject ? getLoci(pickingId, renderObject.id) : EmptyLoci
         },
         mark(loci: Loci, action: MarkerAction) {
-            if (!renderObject) return false
-            const { tMarker } = renderObject.values
-            const { groupCount, instanceCount } = locationIt
-
-            function apply(interval: Interval) {
-                const start = Interval.start(interval)
-                const end = Interval.end(interval)
-                return applyMarkerAction(tMarker.ref.value.array, start, end, action)
-            }
-
-            let changed = false
-            if (isEveryLoci(loci)) {
-                changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
-            } else {
-                changed = mark(loci, apply)
-            }
-            if (changed) {
-                ValueCell.update(tMarker, tMarker.ref.value)
-            }
-            return changed
+            return Visual.mark(renderObject, loci, action, lociApply)
         },
         setVisibility(visible: boolean) {
             Visual.setVisibility(renderObject, visible)
@@ -194,8 +183,8 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
         setTransform(matrix?: Mat4, instanceMatrices?: Float32Array | null) {
             Visual.setTransform(renderObject, matrix, instanceMatrices)
         },
-        setOverpaint(layers: Overpaint.Layers, clear?: boolean) {
-            return false // TODO
+        setOverpaint(overpaint: Overpaint) {
+            return Visual.setOverpaint(renderObject, overpaint, lociApply, true)
         },
         destroy() {
             // TODO
@@ -257,7 +246,9 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
 
     function setState(state: Partial<Representation.State>) {
         if (state.visible !== undefined && visual) visual.setVisibility(state.visible)
+        if (state.alphaFactor !== undefined && visual) visual.setAlphaFactor(state.alphaFactor)
         if (state.pickable !== undefined && visual) visual.setPickable(state.pickable)
+        if (state.overpaint !== undefined && visual) visual.setOverpaint(state.overpaint)
         if (state.transform !== undefined && visual) visual.setTransform(state.transform)
 
         Representation.updateState(_state, state)
@@ -267,10 +258,6 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
         _theme = theme
     }
 
-    function setOverpaint(layers: Overpaint.Layers) {
-        return visual ? visual.setOverpaint(layers) : false
-    }
-
     function destroy() {
         if (visual) visual.destroy()
     }
@@ -289,7 +276,6 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
         createOrUpdate,
         setState,
         setTheme,
-        setOverpaint,
         getLoci,
         mark,
         destroy

+ 11 - 9
src/mol-theme/overpaint.ts

@@ -8,18 +8,20 @@ import { Loci } from 'mol-model/loci';
 import { Color } from 'mol-util/color';
 
 export { Overpaint }
+
+type Overpaint = { layers: ReadonlyArray<Overpaint.Layer>, readonly alpha: number }
+
 namespace Overpaint {
     export type Layer = { readonly loci: Loci, readonly color: Color }
-    export type Layers = { list: ReadonlyArray<Layer>, readonly alpha: number }
-    export const EmptyLayers: Layers = { list: [], alpha: 1 }
+    export const Empty: Overpaint = { layers: [], alpha: 1 }
 
-    export function areEqual(layersA: Layers, layersB: Layers) {
-        if (layersA.list.length === 0 && layersB.list.length === 0) return true
-        if (layersA.list.length !== layersB.list.length) return false
-        if (layersA.alpha !== layersB.alpha) return false
-        for (let i = 0, il = layersA.list.length; i < il; ++i) {
-            if (layersA.list[i].color !== layersB.list[i].color) return false
-            if (!Loci.areEqual(layersA.list[i].loci, layersB.list[i].loci)) return false
+    export function areEqual(oA: Overpaint, oB: Overpaint) {
+        if (oA.layers.length === 0 && oB.layers.length === 0) return true
+        if (oA.layers.length !== oB.layers.length) return false
+        if (oA.alpha !== oB.alpha) return false
+        for (let i = 0, il = oA.layers.length; i < il; ++i) {
+            if (oA.layers[i].color !== oB.layers[i].color) return false
+            if (!Loci.areEqual(oA.layers[i].loci, oB.layers[i].loci)) return false
         }
         return true
     }