Ver Fonte

Merge pull request #385 from molstar/traj-anim

Coordinate trajectory related improvements
Alexander Rose há 3 anos atrás
pai
commit
cd194cca65

+ 8 - 0
CHANGELOG.md

@@ -15,6 +15,14 @@ Note that since we don't clearly distinguish between a public and private interf
 - Add ``UnitResonance`` property with info about delocalized triplets
 - Resolve marking in main renderer loop to improve overall performance
 - Use ``throttleTime`` instead of ``debounceTime`` in sequence viewer for better responsiveness
+- Change line geometry default ``scaleFactor`` to 2 (3 is too big after fixing line rendering)
+- Trajectory animation performance improvements
+    - Reuse ``Model.CoarseGrained`` for coordinate trajectories
+    - Avoid calculating ``InterUnitBonds`` when ``Structure.parent`` ones are empty
+    - Reuse unit boundary if sphere has not changed too much
+    - Don't show 'inter-bond' and 'element-cross' visuals in line representations of polymerAndLigand preset
+- Fix additional mononucleotides detected as polymer components
+- Fix and improve ``canRemap`` handling in ``IntraUnitBonds``
 - Reuse occlusion for secondary passes during multi-sampling
 - Check if marking passes are needed before doing them
 - Add ``scaleFactor`` parameter to adjust resolution of occlusion calculation

+ 2 - 2
src/mol-geo/geometry/lines/lines.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -165,7 +165,7 @@ export namespace Lines {
 
     export const Params = {
         ...BaseGeometry.Params,
-        sizeFactor: PD.Numeric(3, { min: 0, max: 10, step: 0.1 }),
+        sizeFactor: PD.Numeric(2, { min: 0, max: 10, step: 0.1 }),
         lineSizeAttenuation: PD.Boolean(false),
     };
     export type Params = typeof Params

+ 3 - 3
src/mol-model-formats/structure/common/component.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -30,7 +30,7 @@ const DnaAtomIdsList = [
 
 /** Used to reduce false positives for atom name-based type guessing */
 const NonPolymerNames = new Set([
-    'FMN', 'NCN', 'FNS', 'FMA' // Mononucleotides
+    'FMN', 'NCN', 'FNS', 'FMA', 'ATP', 'ADP', 'AMP', 'GTP', 'GDP', 'GMP' // Mononucleotides
 ]);
 
 const StandardComponents = (function () {
@@ -156,7 +156,7 @@ export class ComponentBuilder {
                 this.set(StandardComponents.get(compId)!);
             } else if (WaterNames.has(compId)) {
                 this.set({ id: compId, name: 'WATER', type: 'non-polymer' });
-            } else if (NonPolymerNames.has(compId)) {
+            } else if (NonPolymerNames.has(compId.toUpperCase())) {
                 this.set({ id: compId, name: this.namesMap.get(compId) || compId, type: 'non-polymer' });
             } else {
                 const atomIds = this.getAtomIds(index);

+ 35 - 22
src/mol-model/structure/model/model.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -98,6 +98,7 @@ export namespace Model {
         const srcIndex = model.atomicHierarchy.atomSourceIndex;
         const isIdentity = Column.isIdentity(srcIndex);
         const srcIndexArray = isIdentity ? void 0 : srcIndex.toArray({ array: Int32Array });
+        const coarseGrained = isCoarseGrained(model);
 
         for (let i = 0, il = frames.length; i < il; ++i) {
             const f = frames[i];
@@ -119,6 +120,7 @@ export namespace Model {
             }
 
             TrajectoryInfo.set(m, { index: i, size: frames.length });
+            CoarseGrained.set(m, coarseGrained);
 
             trajectory.push(m);
         }
@@ -138,11 +140,13 @@ export namespace Model {
 
             const bondData = { pairs: topology.bonds, count: model.atomicHierarchy.atoms._rowCount };
             const indexPairBonds = IndexPairBonds.fromData(bondData);
+            const coarseGrained = isCoarseGrained(model);
 
             let index = 0;
             for (const m of trajectory) {
                 IndexPairBonds.Provider.set(m, indexPairBonds);
                 TrajectoryInfo.set(m, { index: index++, size: trajectory.length });
+                CoarseGrained.set(m, coarseGrained);
             }
             return new ArrayTrajectory(trajectory);
         });
@@ -225,35 +229,44 @@ export namespace Model {
     };
 
     const CoarseGrainedProp = '__CoarseGrained__';
+    export const CoarseGrained = {
+        get(model: Model): boolean | undefined {
+            return model._staticPropertyData[CoarseGrainedProp];
+        },
+        set(model: Model, coarseGrained: boolean) {
+            return model._staticPropertyData[CoarseGrainedProp] = coarseGrained;
+        }
+    };
     /**
      * Has typical coarse grained atom names (BB, SC1) or less than three times as many
      * atoms as polymer residues (C-alpha only models).
      */
     export function isCoarseGrained(model: Model): boolean {
-        if (model._staticPropertyData[CoarseGrainedProp] !== undefined) return model._staticPropertyData[CoarseGrainedProp];
+        let coarseGrained = CoarseGrained.get(model);
+        if (coarseGrained === undefined) {
+            let polymerResidueCount = 0;
+            const { polymerType } = model.atomicHierarchy.derived.residue;
+            for (let i = 0; i < polymerType.length; ++i) {
+                if (polymerType[i] !== PolymerType.NA) polymerResidueCount += 1;
+            }
 
-        let polymerResidueCount = 0;
-        const { polymerType } = model.atomicHierarchy.derived.residue;
-        for (let i = 0; i < polymerType.length; ++i) {
-            if (polymerType[i] !== PolymerType.NA) polymerResidueCount += 1;
-        }
+            // check for coarse grained atom names
+            let hasBB = false, hasSC1 = false;
+            const { label_atom_id, _rowCount: atomCount } = model.atomicHierarchy.atoms;
+            for (let i = 0; i < atomCount; ++i) {
+                const atomName = label_atom_id.value(i);
+                if (!hasBB && atomName === 'BB') hasBB = true;
+                if (!hasSC1 && atomName === 'SC1') hasSC1 = true;
+                if (hasBB && hasSC1) break;
+            }
 
-        // check for coarse grained atom names
-        let hasBB = false, hasSC1 = false;
-        const { label_atom_id, _rowCount: atomCount } = model.atomicHierarchy.atoms;
-        for (let i = 0; i < atomCount; ++i) {
-            const atomName = label_atom_id.value(i);
-            if (!hasBB && atomName === 'BB') hasBB = true;
-            if (!hasSC1 && atomName === 'SC1') hasSC1 = true;
-            if (hasBB && hasSC1) break;
+            coarseGrained = (hasBB && hasSC1) || (
+                polymerResidueCount && atomCount
+                    ? atomCount / polymerResidueCount < 3
+                    : false
+            );
+            CoarseGrained.set(model, coarseGrained);
         }
-
-        const coarseGrained = (hasBB && hasSC1) || (
-            polymerResidueCount && atomCount
-                ? atomCount / polymerResidueCount < 3
-                : false
-        );
-        model._staticPropertyData[CoarseGrainedProp] = coarseGrained;
         return coarseGrained;
     }
 

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

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -232,7 +232,14 @@ class Structure {
 
     get interUnitBonds() {
         if (this.state.interUnitBonds) return this.state.interUnitBonds;
-        this.state.interUnitBonds = computeInterUnitBonds(this, { ignoreWater: !this.dynamicBonds });
+        if (this.parent && this.state.dynamicBonds === this.parent.state.dynamicBonds &&
+            this.parent.state.interUnitBonds && this.parent.state.interUnitBonds.edgeCount === 0
+        ) {
+            // no need to compute InterUnitBonds if parent's ones are empty
+            this.state.interUnitBonds = new InterUnitBonds(new Map());
+        } else {
+            this.state.interUnitBonds = computeInterUnitBonds(this, { ignoreWater: !this.dynamicBonds });
+        }
         return this.state.interUnitBonds;
     }
 

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

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -27,6 +27,9 @@ import { ElementSetIntraBondCache } from './unit/bonds/element-set-intra-bond-ca
 import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 import { getResonance, UnitResonance } from './unit/resonance';
 
+// avoiding namespace lookup improved performance in Chrome (Aug 2020)
+const v3add = Vec3.add;
+
 /**
  * A building block of a structure that corresponds to an atomic or
  * a coarse grained representation 'conveniently grouped together'.
@@ -221,9 +224,32 @@ namespace Unit {
 
         remapModel(model: Model, dynamicBonds: boolean, props?: AtomicProperties) {
             if (!props) {
-                props = { ...this.props, bonds: dynamicBonds ? undefined : tryRemapBonds(this, this.props.bonds, model) };
+                props = {
+                    ...this.props,
+                    bonds: dynamicBonds && !this.props.bonds?.props?.canRemap
+                        ? undefined
+                        : tryRemapBonds(this, this.props.bonds, model, dynamicBonds)
+                };
                 if (!Unit.isSameConformation(this, model)) {
-                    props.boundary = undefined;
+                    const b = props.boundary;
+                    if (b) {
+                        const { elements } = this;
+                        const pos = this.conformation.invariantPosition;
+                        const v = Vec3();
+                        const center = Vec3();
+
+                        for (let i = 0, il = elements.length; i < il; i++) {
+                            pos(elements[i], v);
+                            v3add(center, center, v);
+                        }
+                        Vec3.scale(center, center, 1 / elements.length);
+
+                        // only invalidate boundary if sphere has changed too much
+                        if (Vec3.distance(center, b.sphere.center) / b.sphere.radius >= 1.0) {
+                            props.boundary = undefined;
+                        }
+                    }
+
                     props.lookup3d = undefined;
                     props.principalAxes = undefined;
                 }
@@ -489,7 +515,7 @@ namespace Unit {
         return isSameConformation(a, b.model);
     }
 
-    function tryRemapBonds(a: Atomic, old: IntraUnitBonds | undefined, model: Model) {
+    function tryRemapBonds(a: Atomic, old: IntraUnitBonds | undefined, model: Model, dynamicBonds: boolean) {
         // TODO: should include additional checks?
 
         if (!old) return void 0;
@@ -503,7 +529,7 @@ namespace Unit {
             return void 0;
         }
 
-        if (old.props?.canRemap) {
+        if (old.props?.canRemap || !dynamicBonds) {
             return old;
         }
         return isSameConformation(a, model) ? old : void 0;

+ 8 - 2
src/mol-model/structure/structure/unit/bonds/data.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -12,7 +12,13 @@ import { StructureElement } from '../../element';
 import { Bond } from '../bonds';
 import { InterUnitGraph } from '../../../../../mol-math/graph/inter-unit-graph';
 
-type IntraUnitBonds = IntAdjacencyGraph<StructureElement.UnitIndex, { readonly order: ArrayLike<number>, readonly flags: ArrayLike<BondType.Flag> }, { readonly canRemap?: boolean }>
+type IntraUnitBonds = IntAdjacencyGraph<StructureElement.UnitIndex, {
+    readonly order: ArrayLike<number>,
+    readonly flags: ArrayLike<BondType.Flag>
+}, {
+    /** can remap even with dynamicBonds on, e.g., for water molecules */
+    readonly canRemap?: boolean
+}>
 
 namespace IntraUnitBonds {
     export const Empty: IntraUnitBonds = IntAdjacencyGraph.create([], [], [], 0, { flags: [], order: [] });

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

@@ -141,7 +141,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
         const aI = atoms[_aI];
 
         const elemA = type_symbol.value(aI);
-        if (isWatery && (elemA !== 'H' || elemA !== 'O')) isWatery = false;
+        if (isWatery && (elemA !== 'H' && elemA !== 'O')) isWatery = false;
 
         const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.byAtomIndex.get(aI);
         let hasStructConn = false;

+ 2 - 2
src/mol-plugin-state/builder/structure/representation-preset.ts

@@ -185,9 +185,9 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
             nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'non-standard' }),
             branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.3 }, color, colorParams: ballAndStickColor }, { tag: 'branched-ball-and-stick' }),
             branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }, { tag: 'branched-snfg-3d' }),
-            water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6 }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'water' }),
+            water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6, visuals: waterType === 'line' ? ['intra-bond', 'element-point'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'water' }),
             ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ion' }),
-            lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6 }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'lipid' }),
+            lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6, visuals: lipidType === 'line' ? ['intra-bond'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'lipid' }),
             coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id' }, { tag: 'coarse' })
         };
 

+ 2 - 2
src/mol-repr/structure/representation/line.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -31,7 +31,7 @@ export const LineParams = {
     ...ElementCrossParams,
     multipleBonds: PD.Select('offset', PD.arrayToOptions(['off', 'symmetric', 'offset'] as const)),
     includeParent: PD.Boolean(false),
-    sizeFactor: PD.Numeric(3, { min: 0.01, max: 10, step: 0.01 }),
+    sizeFactor: PD.Numeric(2, { min: 0.01, max: 10, step: 0.01 }),
     unitKinds: getUnitKindsParam(['atomic']),
     visuals: PD.MultiSelect(['intra-bond', 'inter-bond', 'element-point', 'element-cross'], PD.objectToOptions(LineVisuals))
 };

+ 3 - 32
src/mol-repr/structure/visual/util/element.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
@@ -22,9 +22,6 @@ import { SpheresBuilder } from '../../../../mol-geo/geometry/spheres/spheres-bui
 import { isTrace, isH, StructureGroup } from './common';
 import { Sphere3D } from '../../../../mol-math/geometry';
 
-// avoiding namespace lookup improved performance in Chrome (Aug 2020)
-const v3add = Vec3.add;
-
 type ElementProps = {
     ignoreHydrogens: boolean,
     traceOnly: boolean,
@@ -73,17 +70,13 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur
     const ignore = makeElementIgnoreTest(structure, unit, props);
     const l = StructureElement.Location.create(structure, unit);
     const themeSize = theme.size.size;
-    const center = Vec3();
     let maxSize = 0;
-    let count = 0;
 
     for (let i = 0; i < elementCount; i++) {
         if (ignore && ignore(elements[i])) continue;
 
         l.element = elements[i];
         pos(elements[i], v);
-        v3add(center, center, v);
-        count += 1;
 
         builderState.currentGroup = i;
         const size = themeSize(l);
@@ -92,17 +85,8 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur
         addSphere(builderState, v, size * sizeFactor, detail);
     }
 
-    // re-use boundingSphere if it has not changed much
-    let boundingSphere: Sphere3D;
-    Vec3.scale(center, center, 1 / count);
-    if (mesh && Vec3.distance(center, mesh.boundingSphere.center) / mesh.boundingSphere.radius < 1.0) {
-        boundingSphere = Sphere3D.clone(mesh.boundingSphere);
-    } else {
-        boundingSphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * sizeFactor + 0.05);
-    }
-
     const m = MeshBuilder.getMesh(builderState);
-    m.setBoundingSphere(boundingSphere);
+    m.setBoundingSphere(Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * sizeFactor + 0.05));
 
     return m;
 }
@@ -126,34 +110,21 @@ export function createElementSphereImpostor(ctx: VisualContext, unit: Unit, stru
 
     const l = StructureElement.Location.create(structure, unit);
     const themeSize = theme.size.size;
-    const center = Vec3();
     let maxSize = 0;
-    let count = 0;
 
     for (let i = 0; i < elementCount; i++) {
         if (ignore?.(elements[i])) continue;
 
         pos(elements[i], v);
         builder.add(v[0], v[1], v[2], i);
-        v3add(center, center, v);
-        count += 1;
 
         l.element = elements[i];
         const size = themeSize(l);
         if (size > maxSize) maxSize = size;
     }
 
-    // re-use boundingSphere if it has not changed much
-    let boundingSphere: Sphere3D;
-    Vec3.scale(center, center, 1 / count);
-    if (spheres && Vec3.distance(center, spheres.boundingSphere.center) / spheres.boundingSphere.radius < 1.0) {
-        boundingSphere = Sphere3D.clone(spheres.boundingSphere);
-    } else {
-        boundingSphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * props.sizeFactor + 0.05);
-    }
-
     const s = builder.getSpheres();
-    s.setBoundingSphere(boundingSphere);
+    s.setBoundingSphere(Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * props.sizeFactor + 0.05));
 
     return s;
 }