Browse Source

wip, repr

Alexander Rose 6 years ago
parent
commit
d87387125c

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

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Task } from 'mol-task'
+import { Task, RuntimeContext } from 'mol-task'
 import { RenderObject } from 'mol-gl/render-object'
 import { PickingId } from '../util/picking';
 import { Loci } from 'mol-model/loci';
@@ -13,9 +13,19 @@ import { MarkerAction } from '../util/marker-data';
 export interface RepresentationProps {}
 
 export interface Representation<D, P extends RepresentationProps = {}> {
-    renderObjects: ReadonlyArray<RenderObject>
+    readonly renderObjects: ReadonlyArray<RenderObject>
     create: (data: D, props?: P) => Task<void>
     update: (props: P) => Task<void>
     getLoci: (pickingId: PickingId) => Loci
     mark: (loci: Loci, action: MarkerAction) => void
+    destroy: () => void
+}
+
+export interface Visual<D, P extends RepresentationProps = {}> {
+    readonly renderObjects: ReadonlyArray<RenderObject>
+    create: (ctx: RuntimeContext, data: D, props: P) => Promise<void>
+    update: (ctx: RuntimeContext, props: P) => Promise<boolean>
+    getLoci: (pickingId: PickingId) => Loci
+    mark: (loci: Loci, action: MarkerAction) => void
+    destroy: () => void
 }

+ 4 - 1
src/mol-geo/representation/structure/bond.ts

@@ -78,7 +78,7 @@ export const DefaultBondProps = {
 }
 export type BondProps = Partial<typeof DefaultBondProps>
 
-export default function IntraUnitBonds(): UnitsVisual<BondProps> {
+export default function IntraUnitBondVisual(): UnitsVisual<BondProps> {
     const renderObjects: RenderObject[] = []
     let cylinders: MeshRenderObject
     let currentProps: typeof DefaultBondProps
@@ -203,6 +203,9 @@ export default function IntraUnitBonds(): UnitsVisual<BondProps> {
             if (changed) {
                 ValueCell.update(tMarker, tMarker.ref.value)
             }
+        },
+        destroy() {
+            // TODO
         }
     }
 }

+ 113 - 68
src/mol-geo/representation/structure/index.ts

@@ -6,29 +6,19 @@
  */
 
 import { Structure, StructureSymmetry, Unit } from 'mol-model/structure';
-import { Task, RuntimeContext } from 'mol-task'
+import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/render-object';
-import { Representation, RepresentationProps } from '..';
+import { Representation, RepresentationProps, Visual } from '..';
 import { ColorTheme } from '../../theme';
 import { PickingId } from '../../util/picking';
 import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
 import { MarkerAction } from '../../util/marker-data';
 
-export interface UnitsVisual<P> {
-    readonly renderObjects: ReadonlyArray<RenderObject>
-    create: (ctx: RuntimeContext, group: Unit.SymmetryGroup, props: P) => Promise<void>
-    update: (ctx: RuntimeContext, props: P) => Promise<boolean>
-    getLoci: (pickingId: PickingId) => Loci
-    mark: (loci: Loci, action: MarkerAction) => void
-}
+export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<Unit.SymmetryGroup, P> { }
+export interface  StructureVisual<P extends RepresentationProps = {}> extends Visual<Structure, P> { }
 
 export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { }
 
-interface GroupVisual<T> {
-    readonly visual: UnitsVisual<T>
-    readonly group: Unit.SymmetryGroup
-}
-
 export const DefaultStructureProps = {
     colorTheme: { name: 'instance-index' } as ColorTheme,
     alpha: 1,
@@ -39,68 +29,123 @@ export const DefaultStructureProps = {
 }
 export type StructureProps = Partial<typeof DefaultStructureProps>
 
-export function StructureRepresentation<P extends StructureProps>(visualCtor: () => UnitsVisual<P>): StructureRepresentation<P> {
-    const renderObjects: RenderObject[] = []
-    const groupVisuals: GroupVisual<P>[] = []
-    // let currentProps: typeof DefaultStructureProps
+export function StructureRepresentation<P extends StructureProps>(unitsVisualCtor: () => UnitsVisual<P>, structureVisualCtor?: () => StructureVisual<P>): StructureRepresentation<P> {
+    let unitsVisuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>()
+    let structureVisual: StructureVisual<P> | undefined
 
-    return {
-        renderObjects,
-        create(structure: Structure, props: P = {} as P) {
-            // currentProps = Object.assign({}, DefaultStructureProps, props)
-
-            return Task.create('StructureRepresentation.create', async ctx => {
-                // const { query } = currentProps
-                // const qs = await query(structure).runAsChild(ctx)
-                // const subStructure = Selection.unionStructure(qs)
-
-                const groups = StructureSymmetry.getTransformGroups(structure);
-                for (let i = 0; i < groups.length; i++) {
-                    if(ctx.shouldUpdate) await ctx.update({
-                        message: 'Building structure unit visuals...',
-                        current: i,
-                        max: groups.length
-                    })
-                    const group = groups[i];
-                    const visual = visualCtor()
-                    groupVisuals.push({ visual, group })
-                    await visual.create(ctx, group, props)
-                    renderObjects.push(...visual.renderObjects)
+    let _props: Required<P>
+    let _structure: Structure
+    let _groups: ReadonlyArray<Unit.SymmetryGroup>
+
+    function create(structure: Structure, props: P = {} as P) {
+        _props = Object.assign({}, DefaultStructureProps, _props, props)
+
+        return Task.create('Creating StructureRepresentation', async ctx => {
+            if (!_structure) {
+                _groups = StructureSymmetry.getTransformGroups(structure);
+                for (let i = 0; i < _groups.length; i++) {
+                    const group = _groups[i];
+                    const visual = unitsVisualCtor()
+                    await visual.create(ctx, group, _props)
+                    unitsVisuals.set(group.hashCode, { visual, group })
                 }
-            });
-        },
-        update(props: P) {
-            return Task.create('StructureRepresentation.update', async ctx => {
-                renderObjects.length = 0 // clear
-
-                for (let i = 0, il = groupVisuals.length; i < il; ++i) {
-                    if(ctx.shouldUpdate) await ctx.update({
-                        message: 'Updating structure unit visuals...',
-                        current: i,
-                        max: il
+
+                if (structureVisualCtor) {
+                    structureVisual = structureVisualCtor()
+                    await structureVisual.create(ctx, structure, _props)
+                }
+            } else {
+                if (_structure.hashCode === structure.hashCode) {
+                    await update(_props)
+                } else {
+                    _groups = StructureSymmetry.getTransformGroups(structure);
+                    const newGroups: Unit.SymmetryGroup[] = []
+                    const oldUnitsVisuals = unitsVisuals
+                    unitsVisuals = new Map()
+                    for (let i = 0; i < _groups.length; i++) {
+                        const group = _groups[i];
+                        const visualGroup = oldUnitsVisuals.get(group.hashCode)
+                        if (visualGroup) {
+                            const { visual, group } = visualGroup
+                            if (!await visual.update(ctx, _props)) {
+                                await visual.create(ctx, group, _props)
+                            }
+                            oldUnitsVisuals.delete(group.hashCode)
+                        } else {
+                            newGroups.push(group)
+                            const visual = unitsVisualCtor()
+                            await visual.create(ctx, group, _props)
+                            unitsVisuals.set(group.hashCode, { visual, group })
+                        }
+                    }
+    
+                    // for new groups, reuse leftover visuals
+                    const unusedVisuals: UnitsVisual<P>[] = []
+                    oldUnitsVisuals.forEach(({ visual }) => unusedVisuals.push(visual))
+                    newGroups.forEach(async group => {
+                        const visual = unusedVisuals.pop() || unitsVisualCtor()
+                        await visual.create(ctx, group, _props)
+                        unitsVisuals.set(group.hashCode, { visual, group })
                     })
-                    const groupVisual = groupVisuals[i]
-                    const { visual, group } = groupVisual
+                    unusedVisuals.forEach(visual => visual.destroy())
 
-                    if (!await visual.update(ctx, props)) {
-                        console.log('update failed, need to rebuild')
-                        visual.create(ctx, group, props)
+                    if (structureVisual) {
+                        if (!await structureVisual.update(ctx, _props)) {
+                            await structureVisual.create(ctx, _structure, _props)
+                        }
                     }
-                    renderObjects.push(...visual.renderObjects)
+                }
+            }
+            _structure = structure
+        });
+    }
+
+    function update(props: P) {
+        return Task.create('Updating StructureRepresentation', async ctx => {
+            _props = Object.assign({}, DefaultStructureProps, _props, props)
+
+            unitsVisuals.forEach(async ({ visual, group }) => {
+                if (!await visual.update(ctx, _props)) {
+                    await visual.create(ctx, group, _props)
                 }
             })
-        },
-        getLoci(pickingId: PickingId) {
-            for (let i = 0, il = groupVisuals.length; i < il; ++i) {
-                const loc = groupVisuals[i].visual.getLoci(pickingId)
-                if (!isEmptyLoci(loc)) return loc
+
+            if (structureVisual) {
+                if (!await structureVisual.update(ctx, _props)) {
+                    await structureVisual.create(ctx, _structure, _props)
+                }
             }
-            return EmptyLoci
+        })
+    }
+
+    function getLoci(pickingId: PickingId) {
+        let loci: Loci = EmptyLoci
+        unitsVisuals.forEach(({ visual }) => {
+            const _loci = visual.getLoci(pickingId)
+            if (!isEmptyLoci(_loci)) loci = _loci
+        })
+        return loci
+    }
+
+    function mark(loci: Loci, action: MarkerAction) {
+        unitsVisuals.forEach(({ visual }) => visual.mark(loci, action))
+    }
+
+    function destroy() {
+        unitsVisuals.forEach(({ visual }) => visual.destroy())
+        unitsVisuals.clear()
+    }
+
+    return {
+        get renderObjects() {
+            const renderObjects: RenderObject[] = []
+            unitsVisuals.forEach(({ visual }) => renderObjects.push(...visual.renderObjects))
+            return renderObjects
         },
-        mark(loci: Loci, action: MarkerAction) {
-            for (let i = 0, il = groupVisuals.length; i < il; ++i) {
-                groupVisuals[i].visual.mark(loci, action)
-            }
-        }
+        create,
+        update,
+        getLoci,
+        mark,
+        destroy
     }
 }

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

@@ -47,7 +47,7 @@ export function createPointVertices(unit: Unit) {
     return vertices
 }
 
-export default function PointUnitsRepresentation(): UnitsVisual<PointProps> {
+export default function PointVisual(): UnitsVisual<PointProps> {
     const renderObjects: RenderObject[] = []
     let points: PointRenderObject
     let currentProps = DefaultPointProps
@@ -164,6 +164,9 @@ export default function PointUnitsRepresentation(): UnitsVisual<PointProps> {
         },
         mark(loci: Loci, action: MarkerAction) {
             markElement(points.values.tMarker, currentGroup, loci, action)
+        },
+        destroy() {
+            // TODO
         }
     }
 }

+ 5 - 2
src/mol-geo/representation/structure/spacefill.ts

@@ -9,7 +9,7 @@ import { ValueCell } from 'mol-util/value-cell'
 
 import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
 import { Unit, Element, Queries } from 'mol-model/structure';
-import { UnitsVisual, DefaultStructureProps } from './index';
+import { DefaultStructureProps, UnitsVisual } from './index';
 import { RuntimeContext } from 'mol-task'
 import { createTransforms, createColors, createSphereMesh, markElement } from './utils';
 import VertexMap from '../../shape/vertex-map';
@@ -44,7 +44,7 @@ export const DefaultSpacefillProps = {
 }
 export type SpacefillProps = Partial<typeof DefaultSpacefillProps>
 
-export default function SpacefillUnitsRepresentation(): UnitsVisual<SpacefillProps> {
+export default function SpacefillVisual(): UnitsVisual<SpacefillProps> {
     const renderObjects: RenderObject[] = []
     let spheres: MeshRenderObject
     let currentProps: typeof DefaultSpacefillProps
@@ -152,6 +152,9 @@ export default function SpacefillUnitsRepresentation(): UnitsVisual<SpacefillPro
         },
         mark(loci: Loci, action: MarkerAction) {
             markElement(spheres.values.tMarker, currentGroup, loci, action)
+        },
+        destroy() {
+            // TODO
         }
     }
 }

+ 23 - 20
src/mol-geo/representation/volume/index.ts

@@ -4,45 +4,48 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Task, RuntimeContext } from 'mol-task'
+import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/render-object';
-import { RepresentationProps, Representation } from '..';
+import { RepresentationProps, Representation, Visual } from '..';
 import { VolumeData } from 'mol-model/volume';
 import { PickingId } from '../../util/picking';
 import { Loci, EmptyLoci } from 'mol-model/loci';
 import { MarkerAction } from '../../util/marker-data';
 
-export interface VolumeVisual<P> {
-    readonly renderObjects: ReadonlyArray<RenderObject>
-    create: (ctx: RuntimeContext, volumeData: VolumeData, props: P) => Promise<void>
-    update: (ctx: RuntimeContext, props: P) => Promise<boolean>
-    getLoci: (pickingId: PickingId) => Loci
-    mark: (loci: Loci, action: MarkerAction) => void
-}
+export interface VolumeVisual<P extends RepresentationProps = {}> extends Visual<VolumeData, P> { }
 
 export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> { }
 
-export function VolumeRepresentation<P>(visualCtor: () => VolumeVisual<P>): VolumeRepresentation<P> {
+export function VolumeRepresentation<P>(visualCtor: (volumeData: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> {
     const renderObjects: RenderObject[] = []
+    let _volumeData: VolumeData
+
+    function create(volumeData: VolumeData, props: P = {} as P) {
+        return Task.create('VolumeRepresentation.create', async ctx => {
+            _volumeData = volumeData
+            const visual = visualCtor(_volumeData)
+            await visual.create(ctx, _volumeData, props)
+            renderObjects.push(...visual.renderObjects)
+        });
+    }
+    
+    function update(props: P) {
+        return Task.create('VolumeRepresentation.update', async ctx => {})
+    }
 
     return {
         renderObjects,
-        create(volumeData: VolumeData, props: P = {} as P) {
-            return Task.create('VolumeRepresentation.create', async ctx => {
-                const visual = visualCtor()
-                await visual.create(ctx, volumeData, props)
-                renderObjects.push(...visual.renderObjects)
-            });
-        },
-        update(props: P) {
-            return Task.create('VolumeRepresentation.update', async ctx => {})
-        },
+        create,
+        update,
         getLoci(pickingId: PickingId) {
             // TODO
             return EmptyLoci
         },
         mark(loci: Loci, action: MarkerAction) {
             // TODO
+        },
+        destroy() {
+            // TODO
         }
     }
 }

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

@@ -50,7 +50,7 @@ export const DefaultSurfaceProps = {
 }
 export type SurfaceProps = Partial<typeof DefaultSurfaceProps>
 
-export default function Surface(): VolumeVisual<SurfaceProps> {
+export default function SurfaceVisual(): VolumeVisual<SurfaceProps> {
     const renderObjects: RenderObject[] = []
     let surface: MeshRenderObject
     let curProps = DefaultSurfaceProps
@@ -109,6 +109,9 @@ export default function Surface(): VolumeVisual<SurfaceProps> {
         },
         mark(loci: Loci, action: MarkerAction) {
             // TODO
+        },
+        destroy() {
+            // TODO
         }
     }
 }

+ 6 - 2
src/mol-model/structure/structure/symmetry.ts

@@ -60,7 +60,7 @@ namespace StructureSymmetry {
         return hash2(u.invariantId, SortedArray.hashCode(u.elements));
     }
 
-    function areUnitsEquivalent(a: Unit, b: Unit) {
+    export function areUnitsEquivalent(a: Unit, b: Unit) {
         return a.invariantId === b.invariantId && a.model.id === b.model.id && SortedArray.areEqual(a.elements, b.elements);
     }
 
@@ -75,7 +75,11 @@ namespace StructureSymmetry {
         const ret: Unit.SymmetryGroup[] = [];
         for (const eqUnits of groups.groups) {
             const first = s.unitMap.get(eqUnits[0]);
-            ret.push({ elements: first.elements, units: eqUnits.map(id => s.unitMap.get(id)) });
+            ret.push({
+                elements: first.elements,
+                units: eqUnits.map(id => s.unitMap.get(id)),
+                hashCode: hashUnit(first)
+            });
         }
 
         return ret;

+ 5 - 1
src/mol-model/structure/structure/unit.ts

@@ -35,7 +35,11 @@ namespace Unit {
     }
 
     /** A group of units that differ only by symmetry operators. */
-    export type SymmetryGroup = { readonly elements: Element.Set, readonly units: ReadonlyArray<Unit> }
+    export type SymmetryGroup = {
+        readonly elements: Element.Set,
+        readonly units: ReadonlyArray<Unit>
+        readonly hashCode: number
+    }
 
     /** Find index of unit with given id, returns -1 if not found */
     export function findUnitById(id: number, units: ReadonlyArray<Unit>) {