From f7821b7bad6f3d87e939321db894703221533572 Mon Sep 17 00:00:00 2001 From: Alexander Rose <alex.rose@rcsb.org> Date: Tue, 20 Nov 2018 16:31:59 -0800 Subject: [PATCH] fixed visual update when only unit transformations have changed --- .../structure/structure/structure.ts | 20 ++++++++- src/mol-model/structure/structure/symmetry.ts | 9 ++++ src/mol-model/structure/structure/unit.ts | 8 +++- src/mol-repr/structure/complex-visual.ts | 8 ++-- .../structure/units-representation.ts | 23 +++++++--- src/mol-repr/structure/units-visual.ts | 43 ++++++++++--------- src/mol-repr/util.ts | 3 ++ 7 files changed, 78 insertions(+), 36 deletions(-) diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index d4e76255b..6eaa79bb0 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -8,7 +8,7 @@ import { IntMap, SortedArray, Iterator, Segmentation } from 'mol-data/int' import { UniqueArray } from 'mol-data/generic' import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator' import { Model, ElementIndex } from '../model' -import { sort, arraySwap, hash1, sortArray, hashString } from 'mol-data/util'; +import { sort, arraySwap, hash1, sortArray, hashString, hashFnv32a } from 'mol-data/util'; import StructureElement from './element' import Unit from './unit' import { StructureLookup3D } from './util/lookup3d'; @@ -44,9 +44,11 @@ class Structure { entityIndices?: ReadonlyArray<EntityIndex>, uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>, hashCode: number, + /** Hash based on all unit.id values in the structure, reflecting the units transformation */ + transformHash: number, elementCount: number, polymerResidueCount: number, - } = { hashCode: -1, elementCount: 0, polymerResidueCount: 0 }; + } = { hashCode: -1, transformHash: -1, elementCount: 0, polymerResidueCount: 0 }; subsetBuilder(isSorted: boolean) { return new StructureSubsetBuilder(this, isSorted); @@ -74,6 +76,12 @@ class Structure { return this.computeHash(); } + get transformHash() { + if (this._props.transformHash !== -1) return this._props.transformHash; + this._props.transformHash = hashFnv32a(this.units.map(u => u.id)) + return this._props.transformHash; + } + private computeHash() { let hash = 23; for (let i = 0, _i = this.units.length; i < _i; i++) { @@ -389,6 +397,7 @@ namespace Structure { return s.hashCode; } + /** Hash based on all unit.model conformation values in the structure */ export function conformationHash(s: Structure) { return hashString(s.units.map(u => Unit.conformationId(u)).join('|')) } @@ -409,6 +418,13 @@ namespace Structure { return true; } + export function areEquivalent(a: Structure, b: Structure) { + return a === b || ( + a.hashCode === b.hashCode && + StructureSymmetry.areTransformGroupsEquivalent(a.unitSymmetryGroups, b.unitSymmetryGroups) + ) + } + export class ElementLocationIterator implements Iterator<StructureElement> { private current = StructureElement.create(); private unitIndex = 0; diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts index 1b35f8341..5af14696a 100644 --- a/src/mol-model/structure/structure/symmetry.ts +++ b/src/mol-model/structure/structure/symmetry.ts @@ -78,6 +78,15 @@ namespace StructureSymmetry { return ret; } + + /** Checks if transform groups are equal up to their unit's transformations */ + export function areTransformGroupsEquivalent(a: ReadonlyArray<Unit.SymmetryGroup>, b: ReadonlyArray<Unit.SymmetryGroup>) { + if (a.length !== b.length) return false + for (let i = 0, il = a.length; i < il; ++i) { + if (a[i].hashCode !== b[i].hashCode) return false + } + return true + } } function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3) { diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index fd41f3db1..a1cd1097e 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/src/mol-model/structure/structure/unit.ts @@ -15,7 +15,7 @@ import { UnitRings } from './unit/rings'; import StructureElement from './element' import { ChainIndex, ResidueIndex, ElementIndex } from '../model/indexing'; import { IntMap, SortedArray } from 'mol-data/int'; -import { hash2 } from 'mol-data/util'; +import { hash2, hashFnv32a } from 'mol-data/util'; import { getAtomicPolymerElements, getCoarsePolymerElements, getAtomicGapElements, getCoarseGapElements } from './util/polymer'; import { getNucleotideElements } from './util/nucleotide'; import { GaussianDensityProps, computeUnitGaussianDensityCached } from './unit/gaussian-density'; @@ -48,7 +48,10 @@ namespace Unit { readonly units: ReadonlyArray<Unit> /** Maps unit.id to index of unit in units array */ readonly unitIndexMap: IntMap<number> + /** Hash based on unit.invariantId which is the same for all units in the group */ readonly hashCode: number + /** Hash based on all unit.id values in the group, reflecting the units transformation*/ + readonly transformHash: number } function getUnitIndexMap(units: Unit[]) { @@ -72,7 +75,8 @@ namespace Unit { props.unitIndexMap = getUnitIndexMap(units) return props.unitIndexMap }, - hashCode: hashUnit(units[0]) + hashCode: hashUnit(units[0]), + transformHash: hashFnv32a(units.map(u => u.id)) } } diff --git a/src/mol-repr/structure/complex-visual.ts b/src/mol-repr/structure/complex-visual.ts index 1574648d2..a5adc76f9 100644 --- a/src/mol-repr/structure/complex-visual.ts +++ b/src/mol-repr/structure/complex-visual.ts @@ -85,15 +85,15 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo VisualUpdateState.reset(updateState) setUpdateState(updateState, newProps, currentProps, theme, currentTheme) + if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true + if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true + const newConformationHash = Structure.conformationHash(currentStructure) if (newConformationHash !== conformationHash) { conformationHash = newConformationHash updateState.createGeometry = true } - if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true - if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true - // if (updateState.createGeometry) { @@ -129,7 +129,7 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo throw new Error('missing structure') } else if (structure && (!currentStructure || !renderObject)) { await create(ctx, structure, theme, props) - } else if (structure && structure.hashCode !== currentStructure.hashCode) { + } else if (structure && !Structure.areEquivalent(structure, currentStructure)) { await create(ctx, structure, theme, props) } else { if (structure && Structure.conformationHash(structure) !== Structure.conformationHash(currentStructure)) { diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts index abd56451a..ac47dae9d 100644 --- a/src/mol-repr/structure/units-representation.ts +++ b/src/mol-repr/structure/units-representation.ts @@ -50,7 +50,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar if (!_structure && !structure) { throw new Error('missing structure') } else if (structure && !_structure) { - // console.log('initial structure') + // console.log(label, 'initial structure') // First call with a structure, create visuals for each group. _groups = structure.unitSymmetryGroups; for (let i = 0; i < _groups.length; i++) { @@ -59,8 +59,8 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure }) visuals.set(group.hashCode, { visual, group }) } - } else if (structure && _structure.hashCode !== structure.hashCode) { - // console.log('_structure.hashCode !== structure.hashCode') + } else if (structure && !Structure.areEquivalent(structure, _structure)) { + // console.log(label, 'structure not equivalent') // Tries to re-use existing visuals for the groups of the new structure. // Creates additional visuals if needed, destroys left-over visuals. _groups = structure.unitSymmetryGroups; @@ -71,18 +71,25 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar const group = _groups[i]; const visualGroup = oldVisuals.get(group.hashCode) if (visualGroup) { + // console.log(label, 'found visualGroup to reuse') + // console.log('old', visualGroup.group) + // console.log('new', group) const { visual } = visualGroup await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure }) visuals.set(group.hashCode, { visual, group }) oldVisuals.delete(group.hashCode) } else { + // console.log(label, 'not found visualGroup to reuse, creating new') // newGroups.push(group) const visual = visualCtor() await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure }) visuals.set(group.hashCode, { visual, group }) } } - oldVisuals.forEach(({ visual }) => visual.destroy()) + oldVisuals.forEach(({ visual }) => { + // console.log(label, 'removed unused visual') + visual.destroy() + }) // TODO review logic // For new groups, re-use left-over visuals @@ -94,12 +101,14 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar // visuals.set(group.hashCode, { visual, group }) // }) // unusedVisuals.forEach(visual => visual.destroy()) - } else if (structure && structure !== _structure && _structure.hashCode === structure.hashCode) { - // console.log('_structure.hashCode === structure.hashCode') + } else if (structure && structure !== _structure && Structure.areEquivalent(structure, _structure)) { + console.log(label, 'structures equivalent but not identical') // Expects that for structures with the same hashCode, // the unitSymmetryGroups are the same as well. // Re-uses existing visuals for the groups of the new structure. _groups = structure.unitSymmetryGroups; + // console.log('new', structure.unitSymmetryGroups) + // console.log('old', _structure.unitSymmetryGroups) for (let i = 0; i < _groups.length; i++) { const group = _groups[i]; const visualGroup = visuals.get(group.hashCode) @@ -111,7 +120,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar } } } else { - // console.log('no new structure') + // console.log(label, 'no new structure') // No new structure given, just update all visuals with new props. const visualsList: [ UnitsVisual<P>, Unit.SymmetryGroup ][] = [] // TODO avoid allocation visuals.forEach(({ visual, group }) => visualsList.push([ visual, group ])) diff --git a/src/mol-repr/structure/units-visual.ts b/src/mol-repr/structure/units-visual.ts index f89364eb9..a80885a74 100644 --- a/src/mol-repr/structure/units-visual.ts +++ b/src/mol-repr/structure/units-visual.ts @@ -34,13 +34,6 @@ export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup } export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { } -function sameGroupConformation(groupA: Unit.SymmetryGroup, groupB: Unit.SymmetryGroup) { - return ( - groupA.units.length === groupB.units.length && - Unit.conformationId(groupA.units[0]) === Unit.conformationId(groupB.units[0]) - ) -} - type UnitsRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject interface UnitsVisualBuilder<P extends UnitsParams, G extends Geometry> { @@ -88,35 +81,46 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB renderObject = await createRenderObject(ctx, group, geometry, locationIt, theme, currentProps) } - async function update(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}) { + async function update(ctx: VisualContext, group: Unit.SymmetryGroup, theme: Theme, props: Partial<PD.Values<P>> = {}) { if (!renderObject) return const newProps = Object.assign({}, currentProps, props, { structure: currentStructure }) - const unit = currentGroup.units[0] + const unit = group.units[0] locationIt.reset() VisualUpdateState.reset(updateState) setUpdateState(updateState, newProps, currentProps, theme, currentTheme) + if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true + if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true + + if (group.transformHash !== currentGroup.transformHash) { + if (group.units.length !== currentGroup.units.length || updateState.updateColor) { + updateState.updateTransform = true + } else { + updateState.updateMatrix = true + } + } + + // check if the conformation of unit.model has changed const newConformationId = Unit.conformationId(unit) if (newConformationId !== currentConformationId) { currentConformationId = newConformationId updateState.createGeometry = true } - if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true - - if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true - if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true - // if (updateState.updateTransform) { - locationIt = createLocationIterator(currentGroup) + locationIt = createLocationIterator(group) const { instanceCount, groupCount } = locationIt - createUnitsTransform(currentGroup, renderObject.values) createMarkers(instanceCount * groupCount, renderObject.values) updateState.updateColor = true + updateState.updateMatrix = true + } + + if (updateState.updateMatrix) { + createUnitsTransform(group, renderObject.values) } if (updateState.createGeometry) { @@ -143,6 +147,7 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB currentProps = newProps currentTheme = theme + currentGroup = group } return { @@ -161,11 +166,7 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB await create(ctx, group, theme, props) } else { // console.log('unit-visual update') - if (group && !sameGroupConformation(group, currentGroup)) { - // console.log('unit-visual new conformation') - currentGroup = group - } - await update(ctx, theme, props) + await update(ctx, group || currentGroup, theme, props) } }, getLoci(pickingId: PickingId) { diff --git a/src/mol-repr/util.ts b/src/mol-repr/util.ts index ad7d10164..dd3acb447 100644 --- a/src/mol-repr/util.ts +++ b/src/mol-repr/util.ts @@ -10,6 +10,7 @@ import { VisualQuality } from 'mol-geo/geometry/geometry'; export interface VisualUpdateState { updateTransform: boolean + updateMatrix: boolean updateColor: boolean updateSize: boolean createGeometry: boolean @@ -18,6 +19,7 @@ export namespace VisualUpdateState { export function create(): VisualUpdateState { return { updateTransform: false, + updateMatrix: false, updateColor: false, updateSize: false, createGeometry: false @@ -25,6 +27,7 @@ export namespace VisualUpdateState { } export function reset(state: VisualUpdateState) { state.updateTransform = false + state.updateMatrix = false state.updateColor = false state.updateSize = false state.createGeometry = false -- GitLab