diff --git a/CHANGELOG.md b/CHANGELOG.md index f9dfd4beaa3ee2555fdf37ae0ea88529bddb638c..a0b3c1390c5c8aa6eeb43ba74a3e40a66a5bcb10 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/src/mol-geo/geometry/lines/lines.ts b/src/mol-geo/geometry/lines/lines.ts index d8e4051fe11931dffcbea168227d021f70471d84..5452ac7611d4698522c1cf08c31ccddd1272f2c3 100644 --- a/src/mol-geo/geometry/lines/lines.ts +++ b/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 diff --git a/src/mol-model-formats/structure/common/component.ts b/src/mol-model-formats/structure/common/component.ts index cdacb28722b658ff42e32c6e50d737b31a540868..11c5249757daa3fa1ed98acd53e4f81b791bd435 100644 --- a/src/mol-model-formats/structure/common/component.ts +++ b/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); diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index a78df048811350138596ef5a2be48d6189118d0c..1e8b9fc4306313c64827770f5b0cf1908b5b5e46 100644 --- a/src/mol-model/structure/model/model.ts +++ b/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; } diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index f962ee68207d5ea93d2b7fa605c9a78aae1b6ec1..a2d8e86805c87cf1552709c0ca217902c42c2770 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/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; } diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index 79cb163989a99cde662b300efba80b25fedc0f23..4c4f0e30975ce1a23fe2c4f97504621d079d814c 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/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; diff --git a/src/mol-model/structure/structure/unit/bonds/data.ts b/src/mol-model/structure/structure/unit/bonds/data.ts index 3f4692f2a4d1a6151fe213bae8fee51ab0389215..b05df8ed1d0e27a6a5e1c4720741bcc6ae11d356 100644 --- a/src/mol-model/structure/structure/unit/bonds/data.ts +++ b/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: [] }); diff --git a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts index f9eae7c0568aadfbfb37498123cd57b8ac0ebaa6..4792ca42fae30872316fab6846bacc7c0535b55b 100644 --- a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts +++ b/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; diff --git a/src/mol-plugin-state/builder/structure/representation-preset.ts b/src/mol-plugin-state/builder/structure/representation-preset.ts index bcccf0de5e0b9d2b590f5d63e3a75d6534927631..b581666d5548291d6082590eca1fadae460ccd94 100644 --- a/src/mol-plugin-state/builder/structure/representation-preset.ts +++ b/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' }) }; diff --git a/src/mol-repr/structure/representation/line.ts b/src/mol-repr/structure/representation/line.ts index c0936561151e29bc9222e7ac2c755c058d9344c7..65bed46df5ad924cac3592670349ded599e93492 100644 --- a/src/mol-repr/structure/representation/line.ts +++ b/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)) }; diff --git a/src/mol-repr/structure/visual/util/element.ts b/src/mol-repr/structure/visual/util/element.ts index 34ca57ffaf8d02daa31d2c1200c8cce4d513f6e5..a15721d5042997c7e62afc3062818020be3af39f 100644 --- a/src/mol-repr/structure/visual/util/element.ts +++ b/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; }