Compare commits

...

4 Commits

Author SHA1 Message Date
Alexander Rose
d5ed3aa674 3.3.1 2022-02-27 18:45:45 -08:00
Alexander Rose
cfb9c9acfe changelog 2022-02-27 18:40:48 -08:00
Alexander Rose
67feef0b1d add option to ignore ions for inter-unit bonds 2022-02-27 18:40:27 -08:00
Alexander Rose
e0192ab5aa fix issue with unit boundary reuse
- do at visual level instead
2022-02-27 18:03:41 -08:00
20 changed files with 257 additions and 78 deletions

View File

@@ -6,6 +6,11 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v3.3.1] - 2022-02-27
- Fix issue with unit boundary reuse (do at visual level instead)
- Add option to ignore ions for inter-unit bond computation
## [v3.3.0] - 2022-02-27
- Fix parsing contour-level from emdb v3 header files

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "molstar",
"version": "3.3.0",
"version": "3.3.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "molstar",
"version": "3.3.0",
"version": "3.3.1",
"license": "MIT",
"dependencies": {
"@types/argparse": "^2.0.10",

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "3.3.0",
"version": "3.3.1",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {

View File

@@ -55,7 +55,16 @@ function createIntraUnitClashCylinderMesh(ctx: VisualContext, unit: Unit, struct
radius: (edgeIndex: number) => magnitude[edgeIndex] * sizeFactor,
};
return createLinkCylinderMesh(ctx, builderProps, props, mesh);
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
if (boundingSphere) {
m.setBoundingSphere(boundingSphere);
} else if (m.triangleCount > 0) {
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * sizeFactor);
m.setBoundingSphere(sphere);
}
return m;
}
export const IntraUnitClashParams = {
@@ -169,7 +178,16 @@ function createInterUnitClashCylinderMesh(ctx: VisualContext, structure: Structu
radius: (edgeIndex: number) => edges[edgeIndex].props.magnitude * sizeFactor
};
return createLinkCylinderMesh(ctx, builderProps, props, mesh);
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
if (boundingSphere) {
m.setBoundingSphere(boundingSphere);
} else {
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * sizeFactor);
m.setBoundingSphere(sphere);
}
return m;
}
export const InterUnitClashParams = {

View File

@@ -85,10 +85,15 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
}
};
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * sizeFactor);
m.setBoundingSphere(sphere);
if (boundingSphere) {
m.setBoundingSphere(boundingSphere);
} else if (m.triangleCount > 0) {
const { child } = structure;
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * sizeFactor);
m.setBoundingSphere(sphere);
}
return m;
}

View File

@@ -71,10 +71,14 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
}
};
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * sizeFactor);
m.setBoundingSphere(sphere);
if (boundingSphere) {
m.setBoundingSphere(boundingSphere);
} else if (m.triangleCount > 0) {
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * sizeFactor);
m.setBoundingSphere(sphere);
}
return m;
}

View File

@@ -22,6 +22,7 @@ import { VisualUpdateState } from '../../../mol-repr/util';
import { ComplexRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider } from '../../../mol-repr/structure/representation';
import { CustomProperty } from '../../common/custom-property';
import { CrossLinkRestraintProvider, CrossLinkRestraint } from './property';
import { Sphere3D } from '../../../mol-math/geometry';
function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<CrossLinkRestraintCylinderParams>, mesh?: Mesh) {
@@ -47,7 +48,16 @@ function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Str
},
};
return createLinkCylinderMesh(ctx, builderProps, props, mesh);
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
if (boundingSphere) {
m.setBoundingSphere(boundingSphere);
} else if (m.triangleCount > 0) {
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * sizeFactor);
m.setBoundingSphere(sphere);
}
return m;
}
export const CrossLinkRestraintCylinderParams = {

View File

@@ -238,7 +238,10 @@ class Structure {
// 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 });
this.state.interUnitBonds = computeInterUnitBonds(this, {
ignoreWater: !this.dynamicBonds,
ignoreIon: !this.dynamicBonds,
});
}
return this.state.interUnitBonds;
}

View File

@@ -27,9 +27,6 @@ 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'.
@@ -231,25 +228,7 @@ namespace Unit {
: tryRemapBonds(this, this.props.bonds, model, dynamicBonds)
};
if (!Unit.isSameConformation(this, model)) {
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.boundary = undefined;
props.lookup3d = undefined;
props.principalAxes = undefined;
}

View File

@@ -193,11 +193,13 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
export interface InterBondComputationProps extends BondComputationProps {
validUnitPair: (structure: Structure, unitA: Unit, unitB: Unit) => boolean
ignoreWater: boolean
ignoreIon: boolean
}
const DefaultInterBondComputationProps = {
...DefaultBondComputationProps,
ignoreWater: true
ignoreWater: true,
ignoreIon: true,
};
function findBonds(structure: Structure, props: InterBondComputationProps) {
@@ -233,7 +235,11 @@ function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondCo
(!Unit.isAtomic(a) || mtA[a.residueIndex[a.elements[0]]] !== MoleculeType.Water) &&
(!Unit.isAtomic(b) || mtB[b.residueIndex[b.elements[0]]] !== MoleculeType.Water)
);
return Structure.validUnitPair(s, a, b) && (notWater || !p.ignoreWater);
const notIon = (
(!Unit.isAtomic(a) || mtA[a.residueIndex[a.elements[0]]] !== MoleculeType.Ion) &&
(!Unit.isAtomic(b) || mtB[b.residueIndex[b.elements[0]]] !== MoleculeType.Ion)
);
return Structure.validUnitPair(s, a, b) && (notWater || !p.ignoreWater) && (notIon || !p.ignoreIon);
}),
});
}

View File

@@ -17,7 +17,7 @@ import { PluginStateObject as SO } from '../objects';
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
const CommonStructureParams = {
dynamicBonds: PD.Optional(PD.Boolean(false, { description: 'Ensure bonds are recalculated upon model changes. Also enables calculation of inter-unit bonds in water molecules.' })),
dynamicBonds: PD.Optional(PD.Boolean(false, { description: 'Ensure bonds are recalculated upon model changes. Also enables calculation of inter-unit bonds in water molecules and ions.' })),
};
type CommonStructureProps = PD.ValuesFor<typeof CommonStructureParams>

View File

@@ -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>
*/
@@ -162,24 +162,32 @@ function createInterUnitBondCylinderImpostors(ctx: VisualContext, structure: Str
if (!structure.interUnitBonds.edgeCount) return Cylinders.createEmpty(cylinders);
const builderProps = getInterUnitBondCylinderBuilderProps(structure, theme, props);
const m = createLinkCylinderImpostors(ctx, builderProps, props, cylinders);
const { cylinders: c, boundingSphere } = createLinkCylinderImpostors(ctx, builderProps, props, cylinders);
const { child } = structure;
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * props.sizeFactor);
m.setBoundingSphere(sphere);
if (boundingSphere) {
c.setBoundingSphere(boundingSphere);
} else if (c.cylinderCount > 0) {
const { child } = structure;
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * props.sizeFactor);
c.setBoundingSphere(sphere);
}
return m;
return c;
}
function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitBondCylinderParams>, mesh?: Mesh) {
if (!structure.interUnitBonds.edgeCount) return Mesh.createEmpty(mesh);
const builderProps = getInterUnitBondCylinderBuilderProps(structure, theme, props);
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
const { child } = structure;
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * props.sizeFactor);
m.setBoundingSphere(sphere);
if (boundingSphere) {
m.setBoundingSphere(boundingSphere);
} else if (m.triangleCount > 0) {
const { child } = structure;
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * props.sizeFactor);
m.setBoundingSphere(sphere);
}
return m;
}

View File

@@ -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>
*/
@@ -103,11 +103,15 @@ function createInterUnitBondLines(ctx: VisualContext, structure: Structure, them
ignore: makeInterBondIgnoreTest(structure, props)
};
const l = createLinkLines(ctx, builderProps, props, lines);
const { lines: l, boundingSphere } = createLinkLines(ctx, builderProps, props, lines);
const { child } = structure;
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * props.sizeFactor);
l.setBoundingSphere(sphere);
if (boundingSphere) {
l.setBoundingSphere(boundingSphere);
} else if (l.lineCount > 0) {
const { child } = structure;
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * sizeFactor);
l.setBoundingSphere(sphere);
}
return l;
}

View File

@@ -178,10 +178,14 @@ function createIntraUnitBondCylinderImpostors(ctx: VisualContext, unit: Unit, st
if (child && !childUnit) return Cylinders.createEmpty(cylinders);
const builderProps = getIntraUnitBondCylinderBuilderProps(unit, structure, theme, props);
const c = createLinkCylinderImpostors(ctx, builderProps, props, cylinders);
const { cylinders: c, boundingSphere } = createLinkCylinderImpostors(ctx, builderProps, props, cylinders);
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * props.sizeFactor);
c.setBoundingSphere(sphere);
if (boundingSphere) {
c.setBoundingSphere(boundingSphere);
} else if (c.cylinderCount > 0) {
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * props.sizeFactor);
c.setBoundingSphere(sphere);
}
return c;
}
@@ -195,10 +199,14 @@ function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structu
if (child && !childUnit) return Mesh.createEmpty(mesh);
const builderProps = getIntraUnitBondCylinderBuilderProps(unit, structure, theme, props);
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * props.sizeFactor);
m.setBoundingSphere(sphere);
if (boundingSphere) {
m.setBoundingSphere(boundingSphere);
} else if (m.triangleCount > 0) {
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * props.sizeFactor);
m.setBoundingSphere(sphere);
}
return m;
}

View File

@@ -132,10 +132,14 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
ignore: makeIntraBondIgnoreTest(structure, unit, props)
};
const l = createLinkLines(ctx, builderProps, props, lines);
const { lines: l, boundingSphere } = createLinkLines(ctx, builderProps, props, lines);
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * sizeFactor);
l.setBoundingSphere(sphere);
if (boundingSphere) {
l.setBoundingSphere(boundingSphere);
} else if (l.lineCount > 0) {
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * sizeFactor);
l.setBoundingSphere(sphere);
}
return l;
}

View File

@@ -19,6 +19,7 @@ import { VisualUpdateState } from '../../util';
import { VisualContext } from '../../../mol-repr/visual';
import { Theme } from '../../../mol-theme/theme';
import { getAltResidueLociFromId } from './util/common';
import { Sphere3D } from '../../../mol-math/geometry';
function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<CarbohydrateLinkParams>, mesh?: Mesh) {
const { links, elements } = structure.carbohydrates;
@@ -43,7 +44,16 @@ function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure: Struc
},
};
return createLinkCylinderMesh(ctx, builderProps, props, mesh);
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
if (boundingSphere) {
m.setBoundingSphere(boundingSphere);
} else if (m.triangleCount > 0) {
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * linkSizeFactor);
m.setBoundingSphere(sphere);
}
return m;
}
export const CarbohydrateLinkParams = {

View File

@@ -20,6 +20,7 @@ import { PickingId } from '../../../mol-geo/geometry/picking';
import { EmptyLoci, Loci } from '../../../mol-model/loci';
import { getElementIdx, MetalsSet } from '../../../mol-model/structure/structure/unit/bonds/common';
import { getAltResidueLociFromId, getAltResidueLoci } from './util/common';
import { Sphere3D } from '../../../mol-math/geometry';
function createCarbohydrateTerminalLinkCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<CarbohydrateTerminalLinkParams>, mesh?: Mesh) {
const { terminalLinks, elements } = structure.carbohydrates;
@@ -60,7 +61,16 @@ function createCarbohydrateTerminalLinkCylinderMesh(ctx: VisualContext, structur
}
};
return createLinkCylinderMesh(ctx, builderProps, props, mesh);
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
if (boundingSphere) {
m.setBoundingSphere(boundingSphere);
} else if (m.triangleCount > 0) {
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * terminalLinkSizeFactor);
m.setBoundingSphere(sphere);
}
return m;
}
export const CarbohydrateTerminalLinkParams = {

View File

@@ -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>
*/
@@ -16,6 +16,9 @@ import { ElementIterator, getElementLoci, eachElement, makeElementIgnoreTest } f
import { VisualUpdateState } from '../../util';
import { Sphere3D } from '../../../mol-math/geometry';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3add = Vec3.add;
export const ElementPointParams = {
...UnitsPointsParams,
pointSizeAttenuation: PD.Boolean(false),
@@ -39,24 +42,39 @@ export function createElementPoint(ctx: VisualContext, unit: Unit, structure: St
const p = Vec3();
const pos = unit.conformation.invariantPosition;
const ignore = makeElementIgnoreTest(structure, unit, props);
const center = Vec3();
let count = 0;
if (ignore) {
for (let i = 0; i < n; ++i) {
if (ignore(elements[i])) continue;
pos(elements[i], p);
v3add(center, center, p);
count += 1;
builder.add(p[0], p[1], p[2], i);
}
} else {
for (let i = 0; i < n; ++i) {
pos(elements[i], p);
v3add(center, center, p);
count += 1;
builder.add(p[0], p[1], p[2], i);
}
}
const oldBoundingSphere = points ? Sphere3D.clone(points.boundingSphere) : undefined;
const pt = builder.getPoints();
if (count === 0) return pt;
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor);
pt.setBoundingSphere(sphere);
// re-use boundingSphere if it has not changed much
let boundingSphere: Sphere3D;
Vec3.scale(center, center, 1 / count);
if (oldBoundingSphere && Vec3.distance(center, oldBoundingSphere.center) / oldBoundingSphere.radius < 1.0) {
boundingSphere = oldBoundingSphere;
} else {
boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor);
}
pt.setBoundingSphere(boundingSphere);
return pt;
}

View File

@@ -22,6 +22,9 @@ 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,
@@ -70,13 +73,17 @@ 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);
@@ -85,8 +92,19 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur
addSphere(builderState, v, size * sizeFactor, detail);
}
const oldBoundingSphere = mesh ? Sphere3D.clone(mesh.boundingSphere) : undefined;
const m = MeshBuilder.getMesh(builderState);
m.setBoundingSphere(Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * sizeFactor + 0.05));
if (count === 0) return m;
// re-use boundingSphere if it has not changed much
let boundingSphere: Sphere3D;
Vec3.scale(center, center, 1 / count);
if (oldBoundingSphere && Vec3.distance(center, oldBoundingSphere.center) / oldBoundingSphere.radius < 1.0) {
boundingSphere = oldBoundingSphere;
} else {
boundingSphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * sizeFactor + 0.05);
}
m.setBoundingSphere(boundingSphere);
return m;
}
@@ -110,21 +128,36 @@ 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;
}
const oldBoundingSphere = spheres ? Sphere3D.clone(spheres.boundingSphere) : undefined;
const s = builder.getSpheres();
s.setBoundingSphere(Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * props.sizeFactor + 0.05));
if (count === 0) return s;
// re-use boundingSphere if it has not changed much
let boundingSphere: Sphere3D;
Vec3.scale(center, center, 1 / count);
if (oldBoundingSphere && Vec3.distance(center, oldBoundingSphere.center) / oldBoundingSphere.radius < 1.0) {
boundingSphere = oldBoundingSphere;
} else {
boundingSphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * props.sizeFactor + 0.05);
}
s.setBoundingSphere(boundingSphere);
return s;
}

View File

@@ -16,6 +16,7 @@ import { Lines } from '../../../../mol-geo/geometry/lines/lines';
import { LinesBuilder } from '../../../../mol-geo/geometry/lines/lines-builder';
import { Cylinders } from '../../../../mol-geo/geometry/cylinders/cylinders';
import { CylindersBuilder } from '../../../../mol-geo/geometry/cylinders/cylinders-builder';
import { Sphere3D } from '../../../../mol-math/geometry/primitives/sphere3d';
export const LinkCylinderParams = {
linkScale: PD.Numeric(0.45, { min: 0, max: 1, step: 0.01 }),
@@ -106,10 +107,10 @@ const v3dot = Vec3.dot;
* Each edge is included twice to allow for coloring/picking
* the half closer to the first vertex, i.e. vertex a.
*/
export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuilderProps, props: LinkCylinderProps, mesh?: Mesh) {
export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuilderProps, props: LinkCylinderProps, mesh?: Mesh): { mesh: Mesh, boundingSphere?: Sphere3D } {
const { linkCount, referencePosition, position, style, radius, ignore, stub } = linkBuilder;
if (!linkCount) return Mesh.createEmpty(mesh);
if (!linkCount) return { mesh: Mesh.createEmpty(mesh) };
const { linkScale, linkSpacing, radialSegments, linkCap, aromaticScale, aromaticSpacing, aromaticDashCount, dashCount, dashScale, dashCap, stubCap } = props;
@@ -119,6 +120,10 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
const va = Vec3();
const vb = Vec3();
const vShift = Vec3();
const center = Vec3();
let count = 0;
const cylinderProps: CylinderProps = {
radiusTop: 1,
radiusBottom: 1,
@@ -133,6 +138,11 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
if (ignore && ignore(edgeIndex)) continue;
position(va, vb, edgeIndex);
v3add(center, center, va);
v3add(center, center, vb);
count += 2;
v3sub(tmpV12, vb, va);
const dirFlag = v3dot(tmpV12, up) > 0;
@@ -234,17 +244,27 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
}
}
return MeshBuilder.getMesh(builderState);
const oldBoundingSphere = mesh ? Sphere3D.clone(mesh.boundingSphere) : undefined;
const m = MeshBuilder.getMesh(builderState);
if (count === 0) return { mesh: m };
// re-use boundingSphere if it has not changed much
Vec3.scale(center, center, 1 / count);
if (oldBoundingSphere && Vec3.distance(center, oldBoundingSphere.center) / oldBoundingSphere.radius < 1.0) {
return { mesh: m, boundingSphere: oldBoundingSphere };
} else {
return { mesh: m };
}
}
/**
* Each edge is included twice to allow for coloring/picking
* the half closer to the first vertex, i.e. vertex a.
*/
export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: LinkBuilderProps, props: LinkCylinderProps, cylinders?: Cylinders) {
export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: LinkBuilderProps, props: LinkCylinderProps, cylinders?: Cylinders): { cylinders: Cylinders, boundingSphere?: Sphere3D } {
const { linkCount, referencePosition, position, style, radius, ignore, stub } = linkBuilder;
if (!linkCount) return Cylinders.createEmpty(cylinders);
if (!linkCount) return { cylinders: Cylinders.createEmpty(cylinders) };
const { linkScale, linkSpacing, linkCap, aromaticScale, aromaticSpacing, aromaticDashCount, dashCount, dashScale, dashCap, stubCap } = props;
@@ -256,6 +276,9 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
const vm = Vec3();
const vShift = Vec3();
const center = Vec3();
let count = 0;
// automatically adjust length for evenly spaced dashed cylinders
const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
@@ -268,6 +291,10 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
position(va, vb, edgeIndex);
v3add(center, center, va);
v3add(center, center, vb);
count += 2;
const linkRadius = radius(edgeIndex);
const linkStyle = style ? style(edgeIndex) : LinkStyle.Solid;
const linkStub = stubCap && (stub ? stub(edgeIndex) : false);
@@ -337,17 +364,27 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
}
}
return builder.getCylinders();
const oldBoundingSphere = cylinders ? Sphere3D.clone(cylinders.boundingSphere) : undefined;
const c = builder.getCylinders();
if (count === 0) return { cylinders: c };
// re-use boundingSphere if it has not changed much
Vec3.scale(center, center, 1 / count);
if (oldBoundingSphere && Vec3.distance(center, oldBoundingSphere.center) / oldBoundingSphere.radius < 1.0) {
return { cylinders: c, boundingSphere: oldBoundingSphere };
} else {
return { cylinders: c };
}
}
/**
* Each edge is included twice to allow for coloring/picking
* the half closer to the first vertex, i.e. vertex a.
*/
export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProps, props: LinkLineProps, lines?: Lines) {
export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProps, props: LinkLineProps, lines?: Lines): { lines: Lines, boundingSphere?: Sphere3D } {
const { linkCount, referencePosition, position, style, ignore } = linkBuilder;
if (!linkCount) return Lines.createEmpty(lines);
if (!linkCount) return { lines: Lines.createEmpty(lines) };
const { linkScale, linkSpacing, aromaticDashCount, dashCount } = props;
@@ -359,6 +396,9 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
const vm = Vec3();
const vShift = Vec3();
const center = Vec3();
let count = 0;
// automatically adjust length for evenly spaced dashed lines
const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
@@ -373,6 +413,10 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
position(va, vb, edgeIndex);
v3add(center, center, va);
v3add(center, center, vb);
count += 2;
const linkStyle = style ? style(edgeIndex) : LinkStyle.Solid;
if (linkStyle === LinkStyle.Solid) {
@@ -435,5 +479,15 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
}
}
return builder.getLines();
const oldBoundingSphere = lines ? Sphere3D.clone(lines.boundingSphere) : undefined;
const l = builder.getLines();
if (count === 0) return { lines: l };
// re-use boundingSphere if it has not changed much
Vec3.scale(center, center, 1 / count);
if (oldBoundingSphere && Vec3.distance(center, oldBoundingSphere.center) / oldBoundingSphere.radius < 1.0) {
return { lines: l, boundingSphere: oldBoundingSphere };
} else {
return { lines: l };
}
}