Browse Source

more fine-grained model/structure/unit updates

Alexander Rose 4 years ago
parent
commit
b9d4501dcc

+ 14 - 0
src/mol-model/structure/model/model.ts

@@ -27,6 +27,7 @@ import { ModelSymmetry } from '../../../mol-model-formats/structure/property/sym
 import { Column } from '../../../mol-data/db';
 import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
 import { Trajectory, ArrayTrajectory } from '../trajectory';
+import { Unit } from '../structure';
 
 /**
  * Interface to the "source data" of the molecule.
@@ -203,6 +204,19 @@ export namespace Model {
         return model.parent || model;
     }
 
+    const CoordinatesHistoryProp = '__CoordinatesHistory__';
+    export type CoordinatesHistory = {
+        areEqual(unit: Unit, model: Model): boolean
+    }
+    export const CoordinatesHistory = {
+        get(model: Model): CoordinatesHistory | undefined {
+            return model._staticPropertyData[CoordinatesHistoryProp];
+        },
+        set(model: Model, coordinatesHistory: CoordinatesHistory) {
+            return model._staticPropertyData[CoordinatesHistoryProp] = coordinatesHistory;
+        }
+    };
+
     //
 
     export function hasCarbohydrate(model: Model): boolean {

+ 9 - 1
src/mol-model/structure/structure/structure.ts

@@ -360,7 +360,15 @@ class Structure {
     }
 
     remapModel(m: Model) {
-        const units = this.units.map(u => u.remapModel(m));
+        const units: Unit[] = [];
+        for (const ug of this.unitSymmetryGroups) {
+            const unit = ug.units[0].remapModel(m);
+            units.push(unit);
+            for (let i = 1, il = ug.units.length; i < il; ++i) {
+                const u = ug.units[i];
+                units.push(u.remapModel(m, unit.props));
+            }
+        }
         return Structure.create(units, {
             label: this.label,
             interUnitBonds: this._props.interUnitBonds,

+ 44 - 28
src/mol-model/structure/structure/unit.ts

@@ -7,7 +7,7 @@
 
 import { SymmetryOperator } from '../../../mol-math/geometry/symmetry-operator';
 import { Model } from '../model';
-import { GridLookup3D, Lookup3D } from '../../../mol-math/geometry';
+import { GridLookup3D, Lookup3D, Spacegroup } from '../../../mol-math/geometry';
 import { IntraUnitBonds, computeIntraUnitBonds } from './unit/bonds';
 import { CoarseElements, CoarseSphereConformation, CoarseGaussianConformation } from '../model/properties/coarse';
 import { BitFlags } from '../../../mol-util';
@@ -21,9 +21,10 @@ import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
 import { PrincipalAxes } from '../../../mol-math/linear-algebra/matrix/principal-axes';
 import { getPrincipalAxes } from './util/principal-axes';
 import { Boundary, getBoundary } from '../../../mol-math/geometry/boundary';
-import { Mat4 } from '../../../mol-math/linear-algebra';
+import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
 import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair';
 import { ElementSetIntraBondCache } from './unit/bonds/element-set-intra-bond-cache';
+import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 
 /**
  * A building block of a structure that corresponds to an atomic or
@@ -136,6 +137,7 @@ namespace Unit {
         readonly elements: StructureElement.Set,
         readonly model: Model,
         readonly conformation: SymmetryOperator.ArrayMapping<ElementIndex>,
+        readonly props: BaseProperties,
 
         getChild(elements: StructureElement.Set): Unit,
         applyOperator(id: number, operator: SymmetryOperator, dontCompose?: boolean /* = false */): Unit,
@@ -201,7 +203,7 @@ namespace Unit {
         /** Reference `chainIndex` from `model` for faster access. */
         readonly chainIndex: ArrayLike<ChainIndex>;
 
-        private props: AtomicProperties;
+        readonly props: AtomicProperties;
 
         getChild(elements: StructureElement.Set): Unit {
             if (elements.length === this.elements.length) return this;
@@ -213,11 +215,27 @@ namespace Unit {
             return new Atomic(id, this.invariantId, this.chainGroupId, this.traits, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation, this.conformation.r), this.props);
         }
 
-        remapModel(model: Model) {
-            const boundary = Unit.isSameConformation(this, model) ? this.props.boundary : undefined;
-            const props = { ...this.props, bonds: tryRemapBonds(this, this.props.bonds, model), boundary, lookup3d: undefined, principalAxes: undefined };
-            const conformation = this.model.atomicConformation !== model.atomicConformation
-                ? SymmetryOperator.createMapping(this.conformation.operator, model.atomicConformation)
+        remapModel(model: Model, props?: AtomicProperties) {
+            if (!props) {
+                props = { ...this.props, bonds: tryRemapBonds(this, this.props.bonds, model) };
+                if (!Unit.isSameConformation(this, model)) {
+                    props.boundary = undefined;
+                    props.lookup3d = undefined;
+                    props.principalAxes = undefined;
+                }
+            }
+
+            let operator = this.conformation.operator;
+            const symmetry = ModelSymmetry.Provider.get(model);
+            if (operator.spgrOp !== -1 && symmetry && symmetry !== ModelSymmetry.Provider.get(this.model)) {
+                const [i, j, k] = operator.hkl;
+                const { toFractional } = symmetry.spacegroup.cell;
+                const ref = Vec3.transformMat4(Vec3(), Model.getCenter(model), toFractional);
+                operator = Spacegroup.getSymmetryOperatorRef(symmetry.spacegroup, operator.spgrOp, i, j, k, ref);
+            }
+
+            const conformation = (this.model.atomicConformation !== model.atomicConformation || operator !== this.conformation.operator)
+                ? SymmetryOperator.createMapping(operator, model.atomicConformation)
                 : this.conformation;
             return new Atomic(this.id, this.invariantId, this.chainGroupId, this.traits, model, this.elements, conformation, props);
         }
@@ -345,7 +363,7 @@ namespace Unit {
         readonly coarseElements: CoarseElements;
         readonly coarseConformation: C;
 
-        private props: CoarseProperties;
+        readonly props: CoarseProperties;
 
         getChild(elements: StructureElement.Set): Unit {
             if (elements.length === this.elements.length) return this as any as Unit /** lets call this an ugly temporary hack */;
@@ -357,11 +375,15 @@ namespace Unit {
             return createCoarse(id, this.invariantId, this.chainGroupId, this.traits, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseConformation(), this.conformation.r), this.props);
         }
 
-        remapModel(model: Model): Unit.Spheres | Unit.Gaussians {
+        remapModel(model: Model, props?: CoarseProperties): Unit.Spheres | Unit.Gaussians {
             const coarseConformation = this.getCoarseConformation();
             const modelCoarseConformation = getCoarseConformation(this.kind, model);
-            const boundary = Unit.isSameConformation(this as Unit.Spheres | Unit.Gaussians, model) ? this.props.boundary : undefined; // TODO get rid of casting
-            const props = { ...this.props, boundary, lookup3d: undefined, principalAxes: undefined };
+
+            if (!props) {
+                const boundary = Unit.isSameConformation(this as Unit.Spheres | Unit.Gaussians, model) ? this.props.boundary : undefined; // TODO get rid of casting
+                props = { ...this.props, boundary, lookup3d: undefined, principalAxes: undefined };
+            }
+
             const conformation = coarseConformation !== modelCoarseConformation
                 ? SymmetryOperator.createMapping(this.conformation.operator, modelCoarseConformation)
                 : this.conformation;
@@ -444,19 +466,10 @@ namespace Unit {
     }
 
     export function areAreConformationsEquivalent(a: Unit, b: Unit) {
-        if (a.elements.length !== b.elements.length) return false;
+        if (!SortedArray.areEqual(a.elements, b.elements)) return false;
         if (!Mat4.areEqual(a.conformation.operator.matrix, b.conformation.operator.matrix, 1e-6)) return false;
 
-        const xs = a.elements, ys = b.elements;
-        const { x: xa, y: ya, z: za } = a.conformation.coordinates;
-        const { x: xb, y: yb, z: zb } = b.conformation.coordinates;
-
-        for (let i = 0, _i = xs.length; i < _i; i++) {
-            const u = xs[i], v = ys[i];
-            if (xa[u] !== xb[v] || ya[u] !== yb[v] || za[u] !== zb[v]) return false;
-        }
-
-        return true;
+        return isSameConformation(a, b.model);
     }
 
     function tryRemapBonds(a: Atomic, old: IntraUnitBonds | undefined, model: Model) {
@@ -480,9 +493,12 @@ namespace Unit {
     }
 
     export function isSameConformation(u: Unit, model: Model) {
+        const coordsHistory = Model.CoordinatesHistory.get(Model.getRoot(model));
+        if (coordsHistory?.areEqual(u, model)) return true;
+
         const xs = u.elements;
         const { x: xa, y: ya, z: za } = u.conformation.coordinates;
-        const { x: xb, y: yb, z: zb } = model.atomicConformation;
+        const { x: xb, y: yb, z: zb } = getConformation(u.kind, model);
 
         for (let i = 0, _i = xs.length; i < _i; i++) {
             const u = xs[i];
@@ -492,10 +508,10 @@ namespace Unit {
         return true;
     }
 
-    export function getConformation(u: Unit) {
-        return u.kind === Kind.Atomic ? u.model.atomicConformation :
-            u.kind === Kind.Spheres ? u.model.coarseConformation.spheres :
-                u.model.coarseConformation.gaussians;
+    export function getConformation(kind: Unit.Kind, model: Model) {
+        return kind === Kind.Atomic ? model.atomicConformation :
+            kind === Kind.Spheres ? model.coarseConformation.spheres :
+                model.coarseConformation.gaussians;
     }
 }
 

+ 15 - 1
src/mol-model/structure/structure/unit/bonds/inter-compute.ts

@@ -76,7 +76,8 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
                 if (type_symbolA.value(aI) === 'H' && type_symbolB.value(bI) === 'H') continue;
 
                 const d = distance[i];
-                if (d === -1 || equalEps(getDistance(unitA, aI, unitB, bI), d, 0.5)) {
+                // only allow inter-unit index-pair bonds when a distance is given
+                if (d !== -1 && equalEps(getDistance(unitA, aI, unitB, bI), d, 0.5)) {
                     builder.add(_aI, _bI, { order: order[i], flag: flag[i] });
                 }
             }
@@ -179,6 +180,19 @@ function findBonds(structure: Structure, props: InterBondComputationProps) {
         return new InterUnitBonds(builder.getMap());
     }
 
+    const indexPairs = structure.models.length === 1 && IndexPairBonds.Provider.get(structure.model);
+    if (indexPairs) {
+        const { distance } = indexPairs.edgeProps;
+        let hasDistance = false;
+        for (let i = 0, il = distance.length; i < il; ++i) {
+            if (distance[i] !== -1) {
+                hasDistance = true;
+                break;
+            }
+        }
+        if (!hasDistance) return new InterUnitBonds(builder.getMap());
+    }
+
     Structure.eachUnitPair(structure, (unitA: Unit, unitB: Unit) => {
         findPairBonds(unitA as Unit.Atomic, unitB as Unit.Atomic, props, builder);
     }, {

+ 14 - 3
src/mol-plugin-state/helpers/structure-component.ts

@@ -7,7 +7,7 @@
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import Expression from '../../mol-script/language/expression';
 import { MolScriptBuilder } from '../../mol-script/language/builder';
-import { StructureElement, Structure, StructureSelection as Sel, StructureQuery, Queries, QueryContext } from '../../mol-model/structure';
+import { StructureElement, Structure, StructureSelection as Sel, StructureQuery, Queries, QueryContext, Model } from '../../mol-model/structure';
 import { StructureQueryHelper } from './structure-query';
 import { PluginStateObject as SO } from '../objects';
 import { StructureSelectionQueries } from './structure-selection-query';
@@ -105,10 +105,21 @@ export function updateStructureComponent(a: Structure, b: SO.Molecule.Structure,
 
     switch (newParams.type.name) {
         case 'static': {
-            if (a !== cache.source || oldParams.type.params !== newParams.type.params) {
+            if (oldParams.type.params !== newParams.type.params) {
                 return StateTransformer.UpdateResult.Recreate;
             }
-            break;
+            if (a.hashCode !== cache.source.hashCode) {
+                return StateTransformer.UpdateResult.Recreate;
+            }
+            if (b.data.model === a.model) return StateTransformer.UpdateResult.Unchanged;
+            if (Model.getRoot(b.data.model) !== Model.getRoot(a.model)
+                && (a.model.atomicHierarchy !== b.data.model.atomicHierarchy
+                    || a.model.coarseHierarchy !== b.data.model.coarseHierarchy)) {
+                return StateTransformer.UpdateResult.Recreate;
+            }
+
+            b.data = b.data.remapModel(a.model);
+            return StateTransformer.UpdateResult.Updated;
         }
         case 'script':
             if (!Script.areEqual(oldParams.type.params as Script, newParams.type.params)) {

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

@@ -65,7 +65,8 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
                     visuals.set(group.hashCode, { visual, group });
                     if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length });
                 }
-            } else if (structure && !Structure.areEquivalent(structure, _structure)) {
+            } else if (structure && !Structure.areUnitAndIndicesEqual(structure, _structure)) {
+                // console.log(label, 'structures 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;
@@ -118,7 +119,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
                 //     visuals.set(group.hashCode, { visual, group })
                 // })
                 // unusedVisuals.forEach(visual => visual.destroy())
-            } else if (structure && structure !== _structure && Structure.areEquivalent(structure, _structure)) {
+            } else if (structure && structure !== _structure && Structure.areUnitAndIndicesEqual(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.

+ 5 - 6
src/mol-repr/structure/units-visual.ts

@@ -93,8 +93,10 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
         VisualUpdateState.reset(updateState);
 
         if (!renderObject) {
+            // console.log('create new - no renderObject');
             updateState.createNew = true;
         } else if (!currentStructureGroup || !Unit.SymmetryGroup.areInvariantElementsEqual(newStructureGroup.group, currentStructureGroup.group)) {
+            // console.log('create new - elements not equal');
             updateState.createNew = true;
         }
 
@@ -127,13 +129,9 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
         // check if the conformation of unit.model has changed
         const newUnit = newStructureGroup.group.units[0];
         const currentUnit = currentStructureGroup.group.units[0];
-        if (Unit.conformationId(newUnit) !== Unit.conformationId(currentUnit)) {
+        if (!Unit.areAreConformationsEquivalent(newUnit, currentUnit)) {
             // console.log('new conformation');
-            updateState.updateTransform = true;
-            if (!updateState.createGeometry && !Unit.areAreConformationsEquivalent(newUnit, currentUnit)) {
-                // console.log('new position');
-                updateState.createGeometry = true;
-            }
+            updateState.createGeometry = true;
         }
 
         if (updateState.updateTransform) {
@@ -142,6 +140,7 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
 
         if (updateState.createGeometry || updateState.updateTransform) {
             if (currentStructureGroup.structure.hashCode !== newStructureGroup.structure.hashCode) {
+                // console.log('new hashCode');
                 updateState.updateColor = true;
                 updateState.updateSize = true;
             }