Skip to content
Snippets Groups Projects
Commit f7821b7b authored by Alexander Rose's avatar Alexander Rose
Browse files

fixed visual update when only unit transformations have changed

parent b31e58d8
No related branches found
No related tags found
No related merge requests found
...@@ -8,7 +8,7 @@ import { IntMap, SortedArray, Iterator, Segmentation } from 'mol-data/int' ...@@ -8,7 +8,7 @@ import { IntMap, SortedArray, Iterator, Segmentation } from 'mol-data/int'
import { UniqueArray } from 'mol-data/generic' import { UniqueArray } from 'mol-data/generic'
import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator' import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
import { Model, ElementIndex } from '../model' 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 StructureElement from './element'
import Unit from './unit' import Unit from './unit'
import { StructureLookup3D } from './util/lookup3d'; import { StructureLookup3D } from './util/lookup3d';
...@@ -44,9 +44,11 @@ class Structure { ...@@ -44,9 +44,11 @@ class Structure {
entityIndices?: ReadonlyArray<EntityIndex>, entityIndices?: ReadonlyArray<EntityIndex>,
uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>, uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
hashCode: number, hashCode: number,
/** Hash based on all unit.id values in the structure, reflecting the units transformation */
transformHash: number,
elementCount: number, elementCount: number,
polymerResidueCount: number, polymerResidueCount: number,
} = { hashCode: -1, elementCount: 0, polymerResidueCount: 0 }; } = { hashCode: -1, transformHash: -1, elementCount: 0, polymerResidueCount: 0 };
subsetBuilder(isSorted: boolean) { subsetBuilder(isSorted: boolean) {
return new StructureSubsetBuilder(this, isSorted); return new StructureSubsetBuilder(this, isSorted);
...@@ -74,6 +76,12 @@ class Structure { ...@@ -74,6 +76,12 @@ class Structure {
return this.computeHash(); 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() { private computeHash() {
let hash = 23; let hash = 23;
for (let i = 0, _i = this.units.length; i < _i; i++) { for (let i = 0, _i = this.units.length; i < _i; i++) {
...@@ -389,6 +397,7 @@ namespace Structure { ...@@ -389,6 +397,7 @@ namespace Structure {
return s.hashCode; return s.hashCode;
} }
/** Hash based on all unit.model conformation values in the structure */
export function conformationHash(s: Structure) { export function conformationHash(s: Structure) {
return hashString(s.units.map(u => Unit.conformationId(u)).join('|')) return hashString(s.units.map(u => Unit.conformationId(u)).join('|'))
} }
...@@ -409,6 +418,13 @@ namespace Structure { ...@@ -409,6 +418,13 @@ namespace Structure {
return true; 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> { export class ElementLocationIterator implements Iterator<StructureElement> {
private current = StructureElement.create(); private current = StructureElement.create();
private unitIndex = 0; private unitIndex = 0;
......
...@@ -78,6 +78,15 @@ namespace StructureSymmetry { ...@@ -78,6 +78,15 @@ namespace StructureSymmetry {
return ret; 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) { function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3) {
......
...@@ -15,7 +15,7 @@ import { UnitRings } from './unit/rings'; ...@@ -15,7 +15,7 @@ import { UnitRings } from './unit/rings';
import StructureElement from './element' import StructureElement from './element'
import { ChainIndex, ResidueIndex, ElementIndex } from '../model/indexing'; import { ChainIndex, ResidueIndex, ElementIndex } from '../model/indexing';
import { IntMap, SortedArray } from 'mol-data/int'; 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 { getAtomicPolymerElements, getCoarsePolymerElements, getAtomicGapElements, getCoarseGapElements } from './util/polymer';
import { getNucleotideElements } from './util/nucleotide'; import { getNucleotideElements } from './util/nucleotide';
import { GaussianDensityProps, computeUnitGaussianDensityCached } from './unit/gaussian-density'; import { GaussianDensityProps, computeUnitGaussianDensityCached } from './unit/gaussian-density';
...@@ -48,7 +48,10 @@ namespace Unit { ...@@ -48,7 +48,10 @@ namespace Unit {
readonly units: ReadonlyArray<Unit> readonly units: ReadonlyArray<Unit>
/** Maps unit.id to index of unit in units array */ /** Maps unit.id to index of unit in units array */
readonly unitIndexMap: IntMap<number> readonly unitIndexMap: IntMap<number>
/** Hash based on unit.invariantId which is the same for all units in the group */
readonly hashCode: number readonly hashCode: number
/** Hash based on all unit.id values in the group, reflecting the units transformation*/
readonly transformHash: number
} }
function getUnitIndexMap(units: Unit[]) { function getUnitIndexMap(units: Unit[]) {
...@@ -72,7 +75,8 @@ namespace Unit { ...@@ -72,7 +75,8 @@ namespace Unit {
props.unitIndexMap = getUnitIndexMap(units) props.unitIndexMap = getUnitIndexMap(units)
return props.unitIndexMap return props.unitIndexMap
}, },
hashCode: hashUnit(units[0]) hashCode: hashUnit(units[0]),
transformHash: hashFnv32a(units.map(u => u.id))
} }
} }
......
...@@ -85,15 +85,15 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo ...@@ -85,15 +85,15 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo
VisualUpdateState.reset(updateState) VisualUpdateState.reset(updateState)
setUpdateState(updateState, newProps, currentProps, theme, currentTheme) 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) const newConformationHash = Structure.conformationHash(currentStructure)
if (newConformationHash !== conformationHash) { if (newConformationHash !== conformationHash) {
conformationHash = newConformationHash conformationHash = newConformationHash
updateState.createGeometry = true 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) { if (updateState.createGeometry) {
...@@ -129,7 +129,7 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo ...@@ -129,7 +129,7 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo
throw new Error('missing structure') throw new Error('missing structure')
} else if (structure && (!currentStructure || !renderObject)) { } else if (structure && (!currentStructure || !renderObject)) {
await create(ctx, structure, theme, props) 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) await create(ctx, structure, theme, props)
} else { } else {
if (structure && Structure.conformationHash(structure) !== Structure.conformationHash(currentStructure)) { if (structure && Structure.conformationHash(structure) !== Structure.conformationHash(currentStructure)) {
......
...@@ -50,7 +50,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar ...@@ -50,7 +50,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
if (!_structure && !structure) { if (!_structure && !structure) {
throw new Error('missing structure') throw new Error('missing structure')
} else if (structure && !_structure) { } else if (structure && !_structure) {
// console.log('initial structure') // console.log(label, 'initial structure')
// First call with a structure, create visuals for each group. // First call with a structure, create visuals for each group.
_groups = structure.unitSymmetryGroups; _groups = structure.unitSymmetryGroups;
for (let i = 0; i < _groups.length; i++) { for (let i = 0; i < _groups.length; i++) {
...@@ -59,8 +59,8 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar ...@@ -59,8 +59,8 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure }) await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
visuals.set(group.hashCode, { visual, group }) visuals.set(group.hashCode, { visual, group })
} }
} else if (structure && _structure.hashCode !== structure.hashCode) { } else if (structure && !Structure.areEquivalent(structure, _structure)) {
// console.log('_structure.hashCode !== structure.hashCode') // console.log(label, 'structure not equivalent')
// Tries to re-use existing visuals for the groups of the new structure. // Tries to re-use existing visuals for the groups of the new structure.
// Creates additional visuals if needed, destroys left-over visuals. // Creates additional visuals if needed, destroys left-over visuals.
_groups = structure.unitSymmetryGroups; _groups = structure.unitSymmetryGroups;
...@@ -71,18 +71,25 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar ...@@ -71,18 +71,25 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
const group = _groups[i]; const group = _groups[i];
const visualGroup = oldVisuals.get(group.hashCode) const visualGroup = oldVisuals.get(group.hashCode)
if (visualGroup) { if (visualGroup) {
// console.log(label, 'found visualGroup to reuse')
// console.log('old', visualGroup.group)
// console.log('new', group)
const { visual } = visualGroup const { visual } = visualGroup
await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure }) await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
visuals.set(group.hashCode, { visual, group }) visuals.set(group.hashCode, { visual, group })
oldVisuals.delete(group.hashCode) oldVisuals.delete(group.hashCode)
} else { } else {
// console.log(label, 'not found visualGroup to reuse, creating new')
// newGroups.push(group) // newGroups.push(group)
const visual = visualCtor() const visual = visualCtor()
await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure }) await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
visuals.set(group.hashCode, { visual, group }) 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 // TODO review logic
// For new groups, re-use left-over visuals // For new groups, re-use left-over visuals
...@@ -94,12 +101,14 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar ...@@ -94,12 +101,14 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
// visuals.set(group.hashCode, { visual, group }) // visuals.set(group.hashCode, { visual, group })
// }) // })
// unusedVisuals.forEach(visual => visual.destroy()) // unusedVisuals.forEach(visual => visual.destroy())
} else if (structure && structure !== _structure && _structure.hashCode === structure.hashCode) { } else if (structure && structure !== _structure && Structure.areEquivalent(structure, _structure)) {
// console.log('_structure.hashCode === structure.hashCode') console.log(label, 'structures equivalent but not identical')
// Expects that for structures with the same hashCode, // Expects that for structures with the same hashCode,
// the unitSymmetryGroups are the same as well. // the unitSymmetryGroups are the same as well.
// Re-uses existing visuals for the groups of the new structure. // Re-uses existing visuals for the groups of the new structure.
_groups = structure.unitSymmetryGroups; _groups = structure.unitSymmetryGroups;
// console.log('new', structure.unitSymmetryGroups)
// console.log('old', _structure.unitSymmetryGroups)
for (let i = 0; i < _groups.length; i++) { for (let i = 0; i < _groups.length; i++) {
const group = _groups[i]; const group = _groups[i];
const visualGroup = visuals.get(group.hashCode) const visualGroup = visuals.get(group.hashCode)
...@@ -111,7 +120,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar ...@@ -111,7 +120,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
} }
} }
} else { } else {
// console.log('no new structure') // console.log(label, 'no new structure')
// No new structure given, just update all visuals with new props. // No new structure given, just update all visuals with new props.
const visualsList: [ UnitsVisual<P>, Unit.SymmetryGroup ][] = [] // TODO avoid allocation const visualsList: [ UnitsVisual<P>, Unit.SymmetryGroup ][] = [] // TODO avoid allocation
visuals.forEach(({ visual, group }) => visualsList.push([ visual, group ])) visuals.forEach(({ visual, group }) => visualsList.push([ visual, group ]))
......
...@@ -34,13 +34,6 @@ export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup } ...@@ -34,13 +34,6 @@ export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { } 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 type UnitsRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject
interface UnitsVisualBuilder<P extends UnitsParams, G extends Geometry> { interface UnitsVisualBuilder<P extends UnitsParams, G extends Geometry> {
...@@ -88,35 +81,46 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB ...@@ -88,35 +81,46 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB
renderObject = await createRenderObject(ctx, group, geometry, locationIt, theme, currentProps) 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 if (!renderObject) return
const newProps = Object.assign({}, currentProps, props, { structure: currentStructure }) const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
const unit = currentGroup.units[0] const unit = group.units[0]
locationIt.reset() locationIt.reset()
VisualUpdateState.reset(updateState) VisualUpdateState.reset(updateState)
setUpdateState(updateState, newProps, currentProps, theme, currentTheme) 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) const newConformationId = Unit.conformationId(unit)
if (newConformationId !== currentConformationId) { if (newConformationId !== currentConformationId) {
currentConformationId = newConformationId currentConformationId = newConformationId
updateState.createGeometry = true 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) { if (updateState.updateTransform) {
locationIt = createLocationIterator(currentGroup) locationIt = createLocationIterator(group)
const { instanceCount, groupCount } = locationIt const { instanceCount, groupCount } = locationIt
createUnitsTransform(currentGroup, renderObject.values)
createMarkers(instanceCount * groupCount, renderObject.values) createMarkers(instanceCount * groupCount, renderObject.values)
updateState.updateColor = true updateState.updateColor = true
updateState.updateMatrix = true
}
if (updateState.updateMatrix) {
createUnitsTransform(group, renderObject.values)
} }
if (updateState.createGeometry) { if (updateState.createGeometry) {
...@@ -143,6 +147,7 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB ...@@ -143,6 +147,7 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB
currentProps = newProps currentProps = newProps
currentTheme = theme currentTheme = theme
currentGroup = group
} }
return { return {
...@@ -161,11 +166,7 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB ...@@ -161,11 +166,7 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB
await create(ctx, group, theme, props) await create(ctx, group, theme, props)
} else { } else {
// console.log('unit-visual update') // console.log('unit-visual update')
if (group && !sameGroupConformation(group, currentGroup)) { await update(ctx, group || currentGroup, theme, props)
// console.log('unit-visual new conformation')
currentGroup = group
}
await update(ctx, theme, props)
} }
}, },
getLoci(pickingId: PickingId) { getLoci(pickingId: PickingId) {
......
...@@ -10,6 +10,7 @@ import { VisualQuality } from 'mol-geo/geometry/geometry'; ...@@ -10,6 +10,7 @@ import { VisualQuality } from 'mol-geo/geometry/geometry';
export interface VisualUpdateState { export interface VisualUpdateState {
updateTransform: boolean updateTransform: boolean
updateMatrix: boolean
updateColor: boolean updateColor: boolean
updateSize: boolean updateSize: boolean
createGeometry: boolean createGeometry: boolean
...@@ -18,6 +19,7 @@ export namespace VisualUpdateState { ...@@ -18,6 +19,7 @@ export namespace VisualUpdateState {
export function create(): VisualUpdateState { export function create(): VisualUpdateState {
return { return {
updateTransform: false, updateTransform: false,
updateMatrix: false,
updateColor: false, updateColor: false,
updateSize: false, updateSize: false,
createGeometry: false createGeometry: false
...@@ -25,6 +27,7 @@ export namespace VisualUpdateState { ...@@ -25,6 +27,7 @@ export namespace VisualUpdateState {
} }
export function reset(state: VisualUpdateState) { export function reset(state: VisualUpdateState) {
state.updateTransform = false state.updateTransform = false
state.updateMatrix = false
state.updateColor = false state.updateColor = false
state.updateSize = false state.updateSize = false
state.createGeometry = false state.createGeometry = false
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment