Browse Source

wip, improved marking and picking

Alexander Rose 6 years ago
parent
commit
73631fcf63

+ 1 - 1
src/apps/canvas/component/structure-representation.tsx

@@ -52,7 +52,7 @@ export class StructureRepresentationComponent extends React.Component<StructureR
 
         await repr.createOrUpdate(props).run()
         this.props.viewer.add(repr)
-        this.props.viewer.requestDraw()
+        this.props.viewer.requestDraw(true)
         console.log(this.props.viewer.stats)
 
         const newState = {

+ 15 - 9
src/apps/canvas/component/viewport.tsx

@@ -7,7 +7,7 @@
 import * as React from 'react'
 import { App } from '../app';
 import { MarkerAction } from 'mol-geo/util/marker-data';
-import { EveryLoci } from 'mol-model/loci';
+import { EveryLoci, EmptyLoci, Loci, areLociEqual } from 'mol-model/loci';
 import { labelFirst } from 'mol-view/label';
 
 interface ViewportProps {
@@ -42,17 +42,23 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> {
 
         viewer.input.resize.subscribe(() => this.handleResize())
 
+        let prevLoci: Loci = EmptyLoci
         viewer.input.move.subscribe(({x, y, inside, buttons}) => {
             if (!inside || buttons) return
             const p = viewer.identify(x, y)
-            const loci = viewer.getLoci(p)
-
-            viewer.mark(EveryLoci, MarkerAction.RemoveHighlight)
-            viewer.mark(loci, MarkerAction.Highlight)
-
-            const label = labelFirst(loci)
-            const info = `${label}`
-            this.setState({ info })
+            if (p) {
+                const loci = viewer.getLoci(p)
+
+                if (!areLociEqual(loci, prevLoci)) {
+                    viewer.mark(prevLoci, MarkerAction.RemoveHighlight)
+                    viewer.mark(loci, MarkerAction.Highlight)
+                    prevLoci = loci
+
+                    const label = labelFirst(loci)
+                    const info = `${label}`
+                    this.setState({ info })
+                }
+            }
         })
     }
 

+ 3 - 3
src/apps/canvas/structure-view.ts

@@ -243,7 +243,7 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>
         viewer.add(polymerSphere)
 
         updated.next(null)
-        viewer.requestDraw()
+        viewer.requestDraw(true)
         console.log(viewer.stats)
     }
 
@@ -276,7 +276,7 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>
             viewer.remove(symmetryAxes)
         }
         updated.next(null)
-        viewer.requestDraw()
+        viewer.requestDraw(true)
     }
 
     await setModel(0, props.assemblyId, props.symmetryFeatureId)
@@ -315,7 +315,7 @@ export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>
             }
             viewer.remove(polymerSphere)
             viewer.remove(symmetryAxes)
-            viewer.requestDraw()
+            viewer.requestDraw(true)
 
             polymerSphere.destroy()
             symmetryAxes.destroy()

+ 0 - 6
src/apps/viewer/index.tsx

@@ -104,10 +104,4 @@ ctx.dispatcher.getStream(InteractivityEvents.HighlightLoci).subscribe(event => {
     }
 })
 
-ctx.dispatcher.getStream(InteractivityEvents.SelectLoci).subscribe(event => {
-    if (event && event.data) {
-        ctx.stage.viewer.mark(event.data, MarkerAction.ToggleSelect)
-    }
-})
-
 ReactDOM.render(React.createElement(Layout, { controller: ctx.layout }), elm);

+ 14 - 9
src/mol-app/ui/visualization/viewport.tsx

@@ -163,19 +163,24 @@ export class Viewport extends View<ViewportController, ViewportState, { noWebGl?
         viewer.input.move.subscribe(({x, y, inside, buttons}) => {
             if (!inside || buttons) return
             const p = viewer.identify(x, y)
-            const loci = viewer.getLoci(p)
-            InteractivityEvents.HighlightLoci.dispatch(this.controller.context, loci);
-
-            // TODO use LabelLoci event and make configurable
-            const label = labelFirst(loci)
-            const info = `Object: ${p.objectId}, Instance: ${p.instanceId}, Group: ${p.groupId}, Label: ${label}`
-            this.setState({ info })
+            if (p) {
+                const loci = viewer.getLoci(p)
+                InteractivityEvents.HighlightLoci.dispatch(this.controller.context, loci);
+
+                // TODO use LabelLoci event and make configurable
+                const label = labelFirst(loci)
+                const info = `Object: ${p.objectId}, Instance: ${p.instanceId}, Group: ${p.groupId}, Label: ${label}`
+                this.setState({ info })
+            }
         })
 
         // TODO filter only for left button/single finger touch?
         viewer.input.click.subscribe(({x, y}) => {
-            const loci = viewer.getLoci(viewer.identify(x, y))
-            InteractivityEvents.SelectLoci.dispatch(this.controller.context, loci);
+            const p = viewer.identify(x, y)
+            if (p) {
+                const loci = viewer.getLoci(p)
+                InteractivityEvents.SelectLoci.dispatch(this.controller.context, loci);
+            }
         })
     }
 

+ 2 - 2
src/mol-geo/representation/index.ts

@@ -18,7 +18,7 @@ export interface Representation<D, P extends RepresentationProps = {}> {
     readonly props: Readonly<P>
     createOrUpdate: (props?: Partial<P>, data?: D) => Task<void>
     getLoci: (pickingId: PickingId) => Loci
-    mark: (loci: Loci, action: MarkerAction) => void
+    mark: (loci: Loci, action: MarkerAction) => boolean
     destroy: () => void
 }
 
@@ -26,6 +26,6 @@ export interface Visual<D, P extends RepresentationProps = {}> {
     readonly renderObject: RenderObject | undefined
     createOrUpdate: (ctx: RuntimeContext, props?: Partial<P>, data?: D) => Promise<void>
     getLoci: (pickingId: PickingId) => Loci
-    mark: (loci: Loci, action: MarkerAction) => void
+    mark: (loci: Loci, action: MarkerAction) => boolean
     destroy: () => void
 }

+ 2 - 1
src/mol-geo/representation/shape/index.ts

@@ -86,7 +86,7 @@ export function ShapeRepresentation<P extends ShapeProps>(): ShapeRepresentation
             return EmptyLoci
         },
         mark(loci: Loci, action: MarkerAction) {
-            if (!_renderObject) return
+            if (!_renderObject) return false
             const { tMarker } = _renderObject.values
             let changed = false
             if (isEveryLoci(loci)) {
@@ -108,6 +108,7 @@ export function ShapeRepresentation<P extends ShapeProps>(): ShapeRepresentation
             if (changed) {
                 ValueCell.update(tMarker, tMarker.ref.value)
             }
+            return changed
         },
         destroy() {
             // TODO

+ 1 - 1
src/mol-geo/representation/structure/complex-representation.ts

@@ -31,7 +31,7 @@ export function ComplexRepresentation<P extends StructureProps>(label: string, v
     }
 
     function mark(loci: Loci, action: MarkerAction) {
-        if (visual) visual.mark(loci, action)
+        return visual ? visual.mark(loci, action) : false
     }
 
     function destroy() {

+ 3 - 3
src/mol-geo/representation/structure/complex-visual.ts

@@ -117,7 +117,7 @@ export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMe
             return renderObject ? getLoci(pickingId, currentStructure, renderObject.id) : EmptyLoci
         },
         mark(loci: Loci, action: MarkerAction) {
-            if (!renderObject) return
+            if (!renderObject) return false
             const { tMarker } = renderObject.values
             const { groupCount, instanceCount } = locationIt
 
@@ -129,14 +129,14 @@ export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMe
 
             let changed = false
             if (isEveryLoci(loci)) {
-                apply(Interval.ofBounds(0, groupCount * instanceCount))
-                changed = true
+                changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
             } else {
                 changed = mark(loci, currentStructure, apply)
             }
             if (changed) {
                 ValueCell.update(tMarker, tMarker.ref.value)
             }
+            return changed
         },
         destroy() {
             // TODO

+ 1 - 1
src/mol-geo/representation/structure/representation/backbone.ts

@@ -43,7 +43,7 @@ export function BackboneRepresentation(): BackboneRepresentation {
             return traceRepr.getLoci(pickingId)
         },
         mark: (loci: Loci, action: MarkerAction) => {
-            traceRepr.mark(loci, action)
+            return traceRepr.mark(loci, action)
         },
         destroy() {
             traceRepr.destroy()

+ 4 - 3
src/mol-geo/representation/structure/representation/ball-and-stick.ts

@@ -65,9 +65,10 @@ export function BallAndStickRepresentation(): BallAndStickRepresentation {
             }
         },
         mark: (loci: Loci, action: MarkerAction) => {
-            elmementRepr.mark(loci, action)
-            intraLinkRepr.mark(loci, action)
-            interLinkRepr.mark(loci, action)
+            const markElement = elmementRepr.mark(loci, action)
+            const markIntraLink = intraLinkRepr.mark(loci, action)
+            const markInterLink = interLinkRepr.mark(loci, action)
+            return markElement || markIntraLink || markInterLink
         },
         destroy() {
             elmementRepr.destroy()

+ 3 - 2
src/mol-geo/representation/structure/representation/carbohydrate.ts

@@ -53,8 +53,9 @@ export function CarbohydrateRepresentation(): CarbohydrateRepresentation {
                 : carbohydrateLinkLoci
         },
         mark: (loci: Loci, action: MarkerAction) => {
-            carbohydrateSymbolRepr.mark(loci, action)
-            carbohydrateLinkRepr.mark(loci, action)
+            const markSymbol = carbohydrateSymbolRepr.mark(loci, action)
+            const markLink = carbohydrateLinkRepr.mark(loci, action)
+            return markSymbol || markLink
         },
         destroy() {
             carbohydrateSymbolRepr.destroy()

+ 5 - 4
src/mol-geo/representation/structure/representation/cartoon.ts

@@ -68,10 +68,11 @@ export function CartoonRepresentation(): CartoonRepresentation {
                 // : directionLoci
         },
         mark: (loci: Loci, action: MarkerAction) => {
-            traceRepr.mark(loci, action)
-            gapRepr.mark(loci, action)
-            blockRepr.mark(loci, action)
-            // directionRepr.mark(loci, action)
+            const markTrace = traceRepr.mark(loci, action)
+            const markGap = gapRepr.mark(loci, action)
+            const markBlock = blockRepr.mark(loci, action)
+            // const markDirection = directionRepr.mark(loci, action)
+            return markTrace || markGap || markBlock // \\ markDirection
         },
         destroy() {
             traceRepr.destroy()

+ 1 - 1
src/mol-geo/representation/structure/representation/distance-restraint.ts

@@ -45,7 +45,7 @@ export function DistanceRestraintRepresentation(): DistanceRestraintRepresentati
             return crossLinkRepr.getLoci(pickingId)
         },
         mark: (loci: Loci, action: MarkerAction) => {
-            crossLinkRepr.mark(loci, action)
+            return crossLinkRepr.mark(loci, action)
         },
         destroy() {
             crossLinkRepr.destroy()

+ 1 - 1
src/mol-geo/representation/structure/representation/point.ts

@@ -38,7 +38,7 @@ export function PointRepresentation(): PointRepresentation {
             return pointRepr.getLoci(pickingId)
         },
         mark: (loci: Loci, action: MarkerAction) => {
-            pointRepr.mark(loci, action)
+            return pointRepr.mark(loci, action)
         },
         destroy() {
             pointRepr.destroy()

+ 1 - 1
src/mol-geo/representation/structure/representation/spacefill.ts

@@ -40,7 +40,7 @@ export function SpacefillRepresentation(): SpacefillRepresentation {
             return sphereRepr.getLoci(pickingId)
         },
         mark: (loci: Loci, action: MarkerAction) => {
-            sphereRepr.mark(loci, action)
+            return sphereRepr.mark(loci, action)
         },
         destroy() {
             sphereRepr.destroy()

+ 5 - 1
src/mol-geo/representation/structure/units-representation.ts

@@ -115,7 +115,11 @@ export function UnitsRepresentation<P extends StructureProps>(label: string, vis
     }
 
     function mark(loci: Loci, action: MarkerAction) {
-        visuals.forEach(({ visual }) => visual.mark(loci, action))
+        let changed = false
+        visuals.forEach(({ visual }) => {
+            changed = visual.mark(loci, action) || changed
+        })
+        return changed
     }
 
     function destroy() {

+ 3 - 3
src/mol-geo/representation/structure/units-visual.ts

@@ -140,7 +140,7 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu
             return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
         },
         mark(loci: Loci, action: MarkerAction) {
-            if (!renderObject) return
+            if (!renderObject) return false
             const { tMarker } = renderObject.values
             const { groupCount, instanceCount } = locationIt
 
@@ -152,14 +152,14 @@ export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisu
 
             let changed = false
             if (isEveryLoci(loci)) {
-                apply(Interval.ofBounds(0, groupCount * instanceCount))
-                changed = true
+                changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
             } else {
                 changed = mark(loci, currentGroup, apply)
             }
             if (changed) {
                 ValueCell.update(tMarker, tMarker.ref.value)
             }
+            return changed
         },
         destroy() {
             // TODO

+ 2 - 1
src/mol-geo/representation/structure/visual/element-point.ts

@@ -139,7 +139,7 @@ export function ElementPointVisual(): UnitsVisual<ElementPointProps> {
             return renderObject ? getElementLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
         },
         mark(loci: Loci, action: MarkerAction) {
-            if (!renderObject) return
+            if (!renderObject) return false
             const { tMarker } = renderObject.values
             const { groupCount, instanceCount } = locationIt
 
@@ -159,6 +159,7 @@ export function ElementPointVisual(): UnitsVisual<ElementPointProps> {
             if (changed) {
                 ValueCell.update(tMarker, tMarker.ref.value)
             }
+            return changed
         },
         destroy() {
             // TODO

+ 2 - 2
src/mol-geo/representation/structure/visual/util/nucleotide.ts

@@ -52,8 +52,8 @@ export function markNucleotideElement(loci: Loci, group: Unit.SymmetryGroup, app
             const unitIdx = group.unitIndexMap.get(e.unit.id)
             if (unitIdx !== undefined && Unit.isAtomic(e.unit)) {
                 if (Interval.is(e.indices)) {
-                    const min = unitIdx * groupCount + OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[Interval.min(e.indices)])
-                    const max = unitIdx * groupCount + OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[Interval.max(e.indices)])
+                    const min = OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[Interval.min(e.indices)])
+                    const max = OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[Interval.max(e.indices)])
                     if (min !== -1 && max !== -1) {
                         if (apply(Interval.ofRange(unitIdx * groupCount + min, unitIdx * groupCount + max))) changed = true
                     }

+ 1 - 0
src/mol-geo/representation/volume/index.ts

@@ -52,6 +52,7 @@ export function VolumeRepresentation<P extends VolumeProps>(visualCtor: (volumeD
         },
         mark(loci: Loci, action: MarkerAction) {
             // TODO
+            return false
         },
         destroy() {
             // TODO

+ 1 - 0
src/mol-geo/representation/volume/surface.ts

@@ -105,6 +105,7 @@ export default function SurfaceVisual(): VolumeVisual<SurfaceProps> {
         },
         mark(loci: Loci, action: MarkerAction) {
             // TODO
+            return false
         },
         destroy() {
             // TODO

+ 1 - 18
src/mol-geo/util/marker-data.ts

@@ -18,7 +18,6 @@ export enum MarkerAction {
     RemoveHighlight,
     Select,
     Deselect,
-    ToggleSelect,
     Clear
 }
 
@@ -30,42 +29,26 @@ export function applyMarkerAction(array: Uint8Array, start: number, end: number,
             case MarkerAction.Highlight:
                 if (v % 2 === 0) {
                     v += 1
-                    changed = true
                 }
                 break
             case MarkerAction.RemoveHighlight:
                 if (v % 2 !== 0) {
                     v -= 1
-                    changed = true
                 }
                 break
             case MarkerAction.Select:
                 v += 2
-                changed = true
                 break
             case MarkerAction.Deselect:
                 if (v >= 2) {
                     v -= 2
-                    changed = true
                 }
                 break
-            case MarkerAction.ToggleSelect:
-                if (v === 0) {
-                    v = 2
-                } else if (v === 1) {
-                    v = 3
-                } else if (v === 2) {
-                    v = 0
-                } else {
-                    v -= 2
-                }
-                changed = true
-                break
             case MarkerAction.Clear:
                 v = 0
-                changed = true
                 break
         }
+        changed = array[i] !== v || changed
         array[i] = v
     }
     return changed

+ 19 - 12
src/mol-view/viewer.ts

@@ -38,10 +38,10 @@ interface Viewer {
     clear: () => void
 
     draw: (force?: boolean) => void
-    requestDraw: () => void
+    requestDraw: (force?: boolean) => void
     animate: () => void
     pick: () => void
-    identify: (x: number, y: number) => PickingId
+    identify: (x: number, y: number) => PickingId | undefined
     mark: (loci: Loci, action: MarkerAction) => void
     getLoci: (pickingId: PickingId) => Loci
 
@@ -128,9 +128,16 @@ namespace Viewer {
         }
 
         function mark(loci: Loci, action: MarkerAction) {
-            reprMap.forEach((roSet, repr) => repr.mark(loci, action))
-            scene.update()
-            requestDraw()
+            let changed = false
+            reprMap.forEach((roSet, repr) => {
+                changed = repr.mark(loci, action) || changed
+            })
+            if (changed) {
+                // console.log('changed')
+                scene.update()
+                draw(true)
+                pickDirty = false // picking buffers should not have changed
+            }
         }
 
         let nearPlaneDelta = 0
@@ -181,7 +188,6 @@ namespace Viewer {
                 if (variant === 'draw') {
                     lastRenderTime = performance.now()
                     pickDirty = true
-                    // pick()
                 }
                 didRender = true
             }
@@ -195,22 +201,21 @@ namespace Viewer {
             drawPending = false
         }
 
-        function requestDraw () {
+        function requestDraw(force?: boolean) {
             if (drawPending) return
             drawPending = true
-            window.requestAnimationFrame(() => draw(true))
+            window.requestAnimationFrame(() => draw(force))
         }
 
-        function animate () {
+        function animate() {
             draw(false)
-            if (performance.now() - lastRenderTime > 500) {
+            if (performance.now() - lastRenderTime > 200) {
                 if (pickDirty) pick()
             }
             window.requestAnimationFrame(() => animate())
         }
 
         function pick() {
-            console.log('pick')
             render('pickObject', pickDirty)
             render('pickInstance', pickDirty)
             render('pickGroup', pickDirty)
@@ -218,7 +223,9 @@ namespace Viewer {
             pickDirty = false
         }
 
-        function identify (x: number, y: number): PickingId {
+        function identify(x: number, y: number): PickingId | undefined {
+            if (pickDirty) return undefined
+
             x *= ctx.pixelRatio
             y *= ctx.pixelRatio
             y = canvas.height - y // flip y