Compare commits

..

21 Commits

Author SHA1 Message Date
Alexander Rose
1eb351369e 2.1.0 2021-07-05 16:11:46 -07:00
Alexander Rose
701d782485 changelog 2021-07-05 16:07:30 -07:00
Alexander Rose
dc8457c4dc smoother trace normals 2021-07-05 15:59:28 -07:00
Alexander Rose
f104cd4d11 add missing import 2021-07-05 13:47:39 -07:00
Alexander Rose
9b56a6ae65 Merge pull request #221 from molstar/aromatic
Aromatic bond display option
2021-07-05 13:42:11 -07:00
Alexander Rose
2485ad5a2f Merge branch 'master' into aromatic 2021-07-05 13:38:05 -07:00
Alexander Rose
a56716ab6a Merge pull request #222 from molstar/backbone
Backbone representation
2021-07-05 13:37:24 -07:00
Alexander Rose
e0aaaa989e Merge branch 'master' into backbone 2021-07-05 13:34:40 -07:00
Alexander Rose
9ec0f9e736 outline fixes and improvements
- better handle outlines in orthographic mode
- remove unused code
- increase default outline scale to 2
2021-07-05 13:31:00 -07:00
Alexander Rose
47968eeeec fix traceOnly handling in makeElementIgnoreTest 2021-07-04 14:54:52 -07:00
Alexander Rose
9c157b70e1 warning for arrayAreIntersecting/arrayIntersectionSize 2021-07-04 14:50:58 -07:00
Alexander Rose
6d7e4ca227 remove unused import 2021-07-04 14:48:06 -07:00
Alexander Rose
fccd08d2ec add backbone repr
- atomistic and coarse units
2021-07-04 14:39:56 -07:00
Alexander Rose
19bae202d0 handle Vec3.angle edge case 2021-07-04 14:35:46 -07:00
Alexander Rose
4ba0ae24e4 support aromatic bond display as dashes
- add arrayAreIntersecting/arrayIntersectionSize helpers
- prefer reference atoms within rings (also for double/triple bonds)
2021-07-03 23:12:06 -07:00
Alexander Rose
fcf3718d75 better parsing of mol2 bond types 2021-07-03 22:38:29 -07:00
Alexander Rose
df1dd94f1c fix BondType.Names 2021-07-03 22:35:35 -07:00
Alexander Rose
65ba401850 fix repr update for Structure.asParent objects 2021-07-03 22:26:20 -07:00
Alexander Rose
a98f5e1047 fix fxaa antialiasing
- was broken when used with other postprocessing effects
- expose texture.filter
2021-07-03 22:19:51 -07:00
David Sehnal
e5cf97d1ea Merge pull request #217 from sukolsak/fix-cylinder
Fix cylinder orientation
2021-06-28 14:45:38 +02:00
Sukolsak Sakshuwong
1844fc14b2 fix cylinder orientation 2021-06-27 13:14:30 -07:00
33 changed files with 749 additions and 306 deletions

View File

@@ -7,6 +7,12 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v2.1.0] - 2021-07-05
- Add parameter for to display aromatic bonds as dashes next to solid cylinder/line.
- Add backbone representation
- Fix outline in orthographic mode and set default scale to 2.
## [v2.0.7] - 2021-06-23
- Add ability to specify ``volumeIndex`` in ``Viewer.loadVolumeFromUrl`` to better support Volume Server inputs.

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "2.0.7",
"version": "2.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

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

View File

@@ -364,8 +364,9 @@ function updateClip(camera: Camera) {
near = Math.max(Math.min(radiusMax, 5), near);
far = Math.max(5, far);
} else {
near = Math.max(0, near);
far = Math.max(0, far);
// not too close to 0 as it causes issues with outline rendering
near = Math.max(Math.min(radiusMax, 5), near);
far = Math.max(5, far);
}
if (near === far) {

View File

@@ -250,7 +250,7 @@ export const PostprocessingParams = {
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
outline: PD.MappedStatic('off', {
on: PD.Group({
scale: PD.Numeric(1, { min: 1, max: 5, step: 1 }),
scale: PD.Numeric(2, { min: 1, max: 5, step: 1 }),
threshold: PD.Numeric(0.33, { min: 0.01, max: 1, step: 0.01 }),
}),
off: PD.Group({})
@@ -306,7 +306,8 @@ export class PostprocessingPass {
this.nSamples = 1;
this.blurKernelSize = 1;
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'nearest');
// needs to be linear for anti-aliasing pass
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
this.outlinesTarget = webgl.createRenderTarget(width, height, false);
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTexture);
@@ -433,8 +434,12 @@ export class PostprocessingPass {
}
if (props.outline.name === 'on') {
const factor = Math.pow(1000, props.outline.params.threshold) / 1000;
const maxPossibleViewZDiff = factor * (camera.far - camera.near);
let { threshold } = props.outline.params;
// orthographic needs lower threshold
if (camera.state.mode === 'orthographic') threshold /= 5;
const factor = Math.pow(1000, threshold) / 1000;
// use radiusMax for stable outlines when zooming
const maxPossibleViewZDiff = factor * camera.state.radiusMax;
const outlineScale = props.outline.params.scale - 1;
ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
@@ -442,7 +447,6 @@ export class PostprocessingPass {
ValueCell.updateIfChanged(this.outlinesRenderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
ValueCell.updateIfChanged(this.renderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
ValueCell.updateIfChanged(this.renderable.values.uOutlineThreshold, props.outline.params.threshold);
if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; }
ValueCell.updateIfChanged(this.renderable.values.dOutlineScale, outlineScale);
}

View File

@@ -31,6 +31,7 @@ function setCylinderMat(m: Mat4, start: Vec3, dir: Vec3, length: number, matchDi
// ensure the direction used to create the rotation is always pointing in the same
// direction so the triangles of adjacent cylinder will line up
if (matchDir) Vec3.matchDirection(tmpUp, up, tmpCylinderMatDir);
else Vec3.copy(tmpUp, up);
Mat4.fromScaling(tmpCylinderMatScale, Vec3.set(tmpCylinderScale, 1, length, 1));
Vec3.makeRotation(tmpCylinderMatRot, tmpUp, tmpCylinderMatDir);
Mat4.mul(m, tmpCylinderMatRot, tmpCylinderMatScale);

View File

@@ -26,9 +26,6 @@ uniform bool uTransparentBackground;
uniform float uOcclusionBias;
uniform float uOcclusionRadius;
uniform float uOutlineScale;
uniform float uOutlineThreshold;
uniform float uMaxPossibleViewZDiff;
const vec3 occlusionColor = vec3(0.0);

View File

@@ -186,6 +186,7 @@ export interface Texture {
readonly format: number
readonly internalFormat: number
readonly type: number
readonly filter: number
getWidth: () => number
getHeight: () => number
@@ -326,6 +327,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
format,
internalFormat,
type,
filter,
getWidth: () => width,
getHeight: () => height,
@@ -415,6 +417,7 @@ export function createNullTexture(gl?: GLRenderingContext): Texture {
format: 0,
internalFormat: 0,
type: 0,
filter: 0,
getWidth: () => 0,
getHeight: () => 0,

View File

@@ -474,7 +474,9 @@ namespace Vec3 {
/** Computes the angle between 2 vectors, reports in radians. */
export function angle(a: Vec3, b: Vec3) {
const theta = dot(a, b) / Math.sqrt(squaredMagnitude(a) * squaredMagnitude(b));
const denominator = Math.sqrt(squaredMagnitude(a) * squaredMagnitude(b));
if (denominator === 0) return Math.PI / 2;
const theta = dot(a, b) / denominator;
return Math.acos(clamp(theta, -1, 1)); // clamp to avoid numerical problems
}

View File

@@ -6,7 +6,7 @@
import { Column, Table } from '../../mol-data/db';
import { Model } from '../../mol-model/structure/model';
import { MoleculeType } from '../../mol-model/structure/model/types';
import { BondType, MoleculeType } from '../../mol-model/structure/model/types';
import { RuntimeContext, Task } from '../../mol-task';
import { createModels } from './basic/parser';
import { BasicSchema, createBasic } from './basic/schema';
@@ -74,8 +74,33 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
if (_models.frameCount > 0) {
const indexA = Column.ofIntArray(Column.mapToArray(bonds.origin_atom_id, x => x - 1, Int32Array));
const indexB = Column.ofIntArray(Column.mapToArray(bonds.target_atom_id, x => x - 1, Int32Array));
const order = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => x === 'ar' ? 1 : parseInt(x), Int8Array));
const pairBonds = IndexPairBonds.fromData({ pairs: { indexA, indexB, order }, count: atoms.count });
const order = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => {
switch (x) {
case 'ar': // aromatic
case 'am': // amide
case 'un': // unknown
return 1;
case 'du': // dummy
case 'nc': // not connected
return 0;
default:
return parseInt(x);
}
}, Int8Array));
const flag = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => {
switch (x) {
case 'ar': // aromatic
return BondType.Flag.Aromatic | BondType.Flag.Covalent;
case 'du': // dummy
case 'nc': // not connected
return BondType.Flag.None;
case 'am': // amide
case 'un': // unknown
default:
return BondType.Flag.Covalent;
}
}, Int8Array));
const pairBonds = IndexPairBonds.fromData({ pairs: { indexA, indexB, order, flag }, count: atoms.count });
const first = _models.representative;
IndexPairBonds.Provider.set(first, pairBonds);

View File

@@ -620,9 +620,9 @@ export namespace BondType {
'covalent': Flag.Covalent,
'metal-coordination': Flag.MetallicCoordination,
'hydrogen-bond': Flag.HydrogenBond,
'disulfide': Flag.HydrogenBond,
'aromatic': Flag.HydrogenBond,
'computed': Flag.HydrogenBond,
'disulfide': Flag.Disulfide,
'aromatic': Flag.Aromatic,
'computed': Flag.Computed,
};
export type Names = keyof typeof Names

View File

@@ -368,13 +368,16 @@ class Structure {
private _child: Structure | undefined;
private _target: Structure | undefined;
private _proxy: Structure | undefined;
/**
* For `structure` with `parent` this returns a proxy that
* targets `parent` and has `structure` attached as a child.
*/
asParent(): Structure {
return this.parent ? new Structure(this.parent.units, this.parent.unitMap, this.parent.unitIndexMap, this.parent.state, { child: this, target: this.parent }) : this;
if (this._proxy) return this._proxy;
this._proxy = this.parent ? new Structure(this.parent.units, this.parent.unitMap, this.parent.unitIndexMap, this.parent.state, { child: this, target: this.parent }) : this;
return this._proxy;
}
get child(): Structure | undefined {

View File

@@ -117,7 +117,6 @@ namespace UnitRing {
// comes e.g. from `chem_comp_bond.pdbx_aromatic_flag`
if (BondType.is(BondType.Flag.Aromatic, flags[j])) {
if (SortedArray.has(ring, b[j])) aromaticBondCount += 1;
}
}
}

View File

@@ -118,6 +118,11 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
updateState.createGeometry = true;
}
if (currentStructure.child !== newStructure.child) {
// console.log('new child');
updateState.createGeometry = true;
}
if (updateState.updateSize && !('uSize' in renderObject.values)) {
updateState.createGeometry = true;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -21,6 +21,7 @@ import { PuttyRepresentationProvider } from './representation/putty';
import { SpacefillRepresentationProvider } from './representation/spacefill';
import { LineRepresentationProvider } from './representation/line';
import { GaussianVolumeRepresentationProvider } from './representation/gaussian-volume';
import { BackboneRepresentationProvider } from './representation/backbone';
export class StructureRepresentationRegistry extends RepresentationRegistry<Structure, StructureRepresentationState> {
constructor() {
@@ -35,6 +36,7 @@ export class StructureRepresentationRegistry extends RepresentationRegistry<Stru
export namespace StructureRepresentationRegistry {
export const BuiltIn = {
'cartoon': CartoonRepresentationProvider,
'backbone': BackboneRepresentationProvider,
'ball-and-stick': BallAndStickRepresentationProvider,
'carbohydrate': CarbohydrateRepresentationProvider,
'ellipsoid': EllipsoidRepresentationProvider,

View File

@@ -1,29 +1,57 @@
// /**
// * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
// *
// * @author Alexander Rose <alexander.rose@weirdbyte.de>
// */
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
// import { PolymerBackboneVisual, PolymerBackboneParams } from '../visual/polymer-backbone-cylinder';
// import { ParamDefinition as PD } from 'mol-util/param-definition';
// import { UnitsRepresentation } from '../units-representation';
// import { StructureRepresentation } from '../representation';
// import { Representation } from 'mol-repr/representation';
// import { ThemeRegistryContext } from 'mol-theme/theme';
// import { Structure } from 'mol-model/structure';
import { PolymerBackboneCylinderVisual, PolymerBackboneCylinderParams } from '../visual/polymer-backbone-cylinder';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { UnitsRepresentation } from '../units-representation';
import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder } from '../representation';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
import { ThemeRegistryContext } from '../../../mol-theme/theme';
import { Structure } from '../../../mol-model/structure';
import { PolymerBackboneSphereParams, PolymerBackboneSphereVisual } from '../visual/polymer-backbone-sphere';
import { PolymerGapParams, PolymerGapVisual } from '../visual/polymer-gap-cylinder';
// export const BackboneParams = {
// ...PolymerBackboneParams,
// }
// export function getBackboneParams(ctx: ThemeRegistryContext, structure: Structure) {
// return BackboneParams // TODO return copy
// }
// export type BackboneProps = PD.DefaultValues<typeof BackboneParams>
const BackboneVisuals = {
'polymer-backbone-cylinder': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerBackboneCylinderParams>) => UnitsRepresentation('Polymer backbone cylinder', ctx, getParams, PolymerBackboneCylinderVisual),
'polymer-backbone-sphere': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerBackboneSphereParams>) => UnitsRepresentation('Polymer backbone sphere', ctx, getParams, PolymerBackboneSphereVisual),
'polymer-gap': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerGapParams>) => UnitsRepresentation('Polymer gap cylinder', ctx, getParams, PolymerGapVisual),
};
// export type BackboneRepresentation = StructureRepresentation<BackboneProps>
export const BackboneParams = {
...PolymerBackboneSphereParams,
...PolymerBackboneCylinderParams,
...PolymerGapParams,
sizeAspectRatio: PD.Numeric(1, { min: 0.1, max: 3, step: 0.1 }),
visuals: PD.MultiSelect(['polymer-backbone-cylinder', 'polymer-backbone-sphere', 'polymer-gap'], PD.objectToOptions(BackboneVisuals))
};
export type BackboneParams = typeof BackboneParams
export function getBackboneParams(ctx: ThemeRegistryContext, structure: Structure) {
const params = PD.clone(BackboneParams);
let hasGaps = false;
structure.units.forEach(u => {
if (!hasGaps && u.gapElements.length) hasGaps = true;
});
params.visuals.defaultValue = ['polymer-backbone-cylinder', 'polymer-backbone-sphere'];
if (hasGaps) params.visuals.defaultValue.push('polymer-gap');
return params;
}
// export function BackboneRepresentation(defaultProps: BackboneProps): BackboneRepresentation {
// return Representation.createMulti('Backbone', defaultProps, [
// UnitsRepresentation('Polymer backbone cylinder', defaultProps, PolymerBackboneVisual)
// ])
// }
export type BackboneRepresentation = StructureRepresentation<BackboneParams>
export function BackboneRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, BackboneParams>): BackboneRepresentation {
return Representation.createMulti('Backbone', ctx, getParams, StructureRepresentationStateBuilder, BackboneVisuals as unknown as Representation.Def<Structure, BackboneParams>);
}
export const BackboneRepresentationProvider = StructureRepresentationProvider({
name: 'backbone',
label: 'Backbone',
description: 'Displays polymer backbone with cylinders and spheres.',
factory: BackboneRepresentation,
getParams: getBackboneParams,
defaultValues: PD.getDefaultValues(BackboneParams),
defaultColorTheme: { name: 'chain-id' },
defaultSizeTheme: { name: 'uniform' },
isApplicable: (structure: Structure) => structure.polymerResidueCount > 0,
});

View File

@@ -66,7 +66,7 @@ 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.areUnitIdsAndIndicesEqual(structure, _structure)) {
} else if (structure && (!Structure.areUnitIdsAndIndicesEqual(structure, _structure) || structure.child !== _structure.child)) {
// 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.

View File

@@ -120,6 +120,11 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
updateState.updateColor = true;
}
if (currentStructureGroup.structure.child !== newStructureGroup.structure.child) {
// console.log('new child');
updateState.createGeometry = true;
}
if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) {
// console.log('new unitKinds');
updateState.createGeometry = true;

View File

@@ -1 +0,0 @@
// TODO

View File

@@ -11,7 +11,7 @@ import { Theme } from '../../../mol-theme/theme';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { BitFlags, arrayEqual } from '../../../mol-util';
import { createLinkCylinderImpostors, createLinkCylinderMesh, LinkStyle } from './util/link';
import { createLinkCylinderImpostors, createLinkCylinderMesh, LinkBuilderProps, LinkStyle } from './util/link';
import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual, ComplexCylindersParams, ComplexCylindersVisual } from '../complex-visual';
import { VisualUpdateState } from '../../util';
import { BondType } from '../../../mol-model/structure/model/types';
@@ -34,22 +34,20 @@ function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, inde
const tmpRef = Vec3();
function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme, props: PD.Values<InterUnitBondCylinderParams>) {
function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme, props: PD.Values<InterUnitBondCylinderParams>): LinkBuilderProps {
const locE = StructureElement.Location.create(structure);
const locB = Bond.Location(structure, undefined, undefined, structure, undefined, undefined);
const bonds = structure.interUnitBonds;
const { edgeCount, edges } = bonds;
const { sizeFactor, sizeAspectRatio, adjustCylinderLength } = props;
const { sizeFactor, sizeAspectRatio, adjustCylinderLength, aromaticBonds } = props;
const delta = Vec3();
let stub: undefined | ((edgeIndex: number) => boolean);
if (props.includeParent) {
const { child } = structure;
if (!child) throw new Error('expected child to exist');
const { child } = structure;
if (props.includeParent && child) {
stub = (edgeIndex: number) => {
const b = edges[edgeIndex];
const childUnitA = child.unitMap.get(b.unitA);
@@ -137,13 +135,15 @@ function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme
if (BondType.is(f, BondType.Flag.MetallicCoordination) || BondType.is(f, BondType.Flag.HydrogenBond)) {
// show metall coordinations and hydrogen bonds with dashed cylinders
return LinkStyle.Dashed;
} else if (o === 2) {
return LinkStyle.Double;
} else if (o === 3) {
return LinkStyle.Triple;
} else {
return LinkStyle.Solid;
} else if (aromaticBonds && BondType.is(f, BondType.Flag.Aromatic)) {
return LinkStyle.Aromatic;
} else if (o === 2) {
return LinkStyle.Double;
}
return LinkStyle.Solid;
},
radius: (edgeIndex: number) => {
return radius(edgeIndex) * sizeAspectRatio;

View File

@@ -10,7 +10,7 @@ import { Structure, StructureElement, Bond, Unit } from '../../../mol-model/stru
import { Theme } from '../../../mol-theme/theme';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { BitFlags, arrayEqual } from '../../../mol-util';
import { LinkStyle, createLinkLines } from './util/link';
import { LinkStyle, createLinkLines, LinkBuilderProps } from './util/link';
import { ComplexVisual, ComplexLinesVisual, ComplexLinesParams } from '../complex-visual';
import { VisualUpdateState } from '../../util';
import { BondType } from '../../../mol-model/structure/model/types';
@@ -32,14 +32,14 @@ function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, inde
function createInterUnitBondLines(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitBondLineParams>, lines?: Lines) {
const bonds = structure.interUnitBonds;
const { edgeCount, edges } = bonds;
const { sizeFactor } = props;
const { sizeFactor, aromaticBonds } = props;
if (!edgeCount) return Lines.createEmpty(lines);
const ref = Vec3();
const loc = StructureElement.Location.create();
const builderProps = {
const builderProps: LinkBuilderProps = {
linkCount: edgeCount,
referencePosition: (edgeIndex: number) => {
const b = edges[edgeIndex];
@@ -73,13 +73,15 @@ function createInterUnitBondLines(ctx: VisualContext, structure: Structure, them
if (BondType.is(f, BondType.Flag.MetallicCoordination) || BondType.is(f, BondType.Flag.HydrogenBond)) {
// show metall coordinations and hydrogen bonds with dashed cylinders
return LinkStyle.Dashed;
} else if (o === 2) {
return LinkStyle.Double;
} else if (o === 3) {
return LinkStyle.Triple;
} else {
return LinkStyle.Solid;
}else if (aromaticBonds && BondType.is(f, BondType.Flag.Aromatic)) {
return LinkStyle.Aromatic;
} else if (o === 2) {
return LinkStyle.Double;
}
return LinkStyle.Solid;
},
radius: (edgeIndex: number) => {
const b = edges[edgeIndex];

View File

@@ -12,7 +12,7 @@ import { Theme } from '../../../mol-theme/theme';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { arrayEqual } from '../../../mol-util';
import { createLinkCylinderImpostors, createLinkCylinderMesh, LinkStyle } from './util/link';
import { createLinkCylinderImpostors, createLinkCylinderMesh, LinkBuilderProps, LinkStyle } from './util/link';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup, UnitsCylindersParams, UnitsCylindersVisual } from '../units-visual';
import { VisualUpdateState } from '../../util';
import { BondType } from '../../../mol-model/structure/model/types';
@@ -22,16 +22,17 @@ import { IntAdjacencyGraph } from '../../../mol-math/graph';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { Cylinders } from '../../../mol-geo/geometry/cylinders/cylinders';
import { SortedArray } from '../../../mol-data/int';
import { arrayIntersectionSize } from '../../../mol-util/array';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const isBondType = BondType.is;
function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondCylinderParams>) {
function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondCylinderParams>): LinkBuilderProps {
const elements = unit.elements;
const bonds = unit.bonds;
const { edgeCount, a, b, edgeProps, offset } = bonds;
const { order: _order, flags: _flags } = edgeProps;
const { sizeFactor, sizeAspectRatio, adjustCylinderLength } = props;
const { sizeFactor, sizeAspectRatio, adjustCylinderLength, aromaticBonds } = props;
const vRef = Vec3(), delta = Vec3();
const pos = unit.conformation.invariantPosition;
@@ -41,9 +42,8 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
const locE = StructureElement.Location.create(structure, unit);
const locB = Bond.Location(structure, unit, undefined, structure, unit, undefined);
if (props.includeParent) {
const { child } = structure;
if (!child) throw new Error('expected child to exist');
const { child } = structure;
if (props.includeParent && child) {
const childUnit = child.unitMap.get(unit.id);
if (!childUnit) throw new Error('expected childUnit to exist');
@@ -70,6 +70,8 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
return theme.size.size(locE) * sizeFactor;
};
const { elementRingIndices, elementAromaticRingIndices } = unit.rings;
return {
linkCount: edgeCount * 2,
referencePosition: (edgeIndex: number) => {
@@ -77,17 +79,28 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
if (aI > bI) [aI, bI] = [bI, aI];
if (offset[aI + 1] - offset[aI] === 1) [aI, bI] = [bI, aI];
// TODO prefer reference atoms within rings
const aR = elementRingIndices.get(aI);
let maxSize = 0;
for (let i = offset[aI], il = offset[aI + 1]; i < il; ++i) {
const _bI = b[i];
if (_bI !== bI && _bI !== aI) return pos(elements[_bI], vRef);
if (_bI !== bI && _bI !== aI) {
if (aR) {
const _bR = elementRingIndices.get(_bI);
if (!_bR) continue;
const size = arrayIntersectionSize(aR, _bR);
if (size > maxSize) {
maxSize = size;
pos(elements[_bI], vRef);
}
} else {
return pos(elements[_bI], vRef);
}
}
}
for (let i = offset[bI], il = offset[bI + 1]; i < il; ++i) {
const _aI = a[i];
if (_aI !== aI && _aI !== bI) return pos(elements[_aI], vRef);
}
return null;
return maxSize > 0 ? vRef : null;
},
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
pos(elements[a[edgeIndex]], posA);
@@ -111,13 +124,24 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
if (isBondType(f, BondType.Flag.MetallicCoordination) || isBondType(f, BondType.Flag.HydrogenBond)) {
// show metall coordinations and hydrogen bonds with dashed cylinders
return LinkStyle.Dashed;
} else if (o === 2) {
return LinkStyle.Double;
} else if (o === 3) {
return LinkStyle.Triple;
} else {
return LinkStyle.Solid;
} else if (aromaticBonds) {
const aI = a[edgeIndex], bI = b[edgeIndex];
const aR = elementAromaticRingIndices.get(aI);
const bR = elementAromaticRingIndices.get(bI);
const arCount = (aR && bR) ? arrayIntersectionSize(aR, bR) : 0;
if (arCount || isBondType(f, BondType.Flag.Aromatic)) {
if (arCount === 2) {
return LinkStyle.MirroredAromatic;
} else {
return LinkStyle.Aromatic;
}
}
}
return o === 2 ? LinkStyle.Double : LinkStyle.Solid;
},
radius: (edgeIndex: number) => {
return radius(edgeIndex) * sizeAspectRatio;
@@ -198,7 +222,8 @@ export function IntraUnitBondCylinderImpostorVisual(materialId: number): UnitsVi
newProps.stubCap !== currentProps.stubCap ||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength ||
newProps.aromaticBonds !== currentProps.aromaticBonds
);
const newUnit = newStructureGroup.group.units[0];
@@ -240,7 +265,8 @@ export function IntraUnitBondCylinderMeshVisual(materialId: number): UnitsVisual
newProps.stubCap !== currentProps.stubCap ||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength ||
newProps.aromaticBonds !== currentProps.aromaticBonds
);
const newUnit = newStructureGroup.group.units[0];

View File

@@ -10,7 +10,7 @@ import { Unit, Structure, StructureElement } from '../../../mol-model/structure'
import { Theme } from '../../../mol-theme/theme';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { arrayEqual } from '../../../mol-util';
import { LinkStyle, createLinkLines } from './util/link';
import { LinkStyle, createLinkLines, LinkBuilderProps } from './util/link';
import { UnitsVisual, UnitsLinesParams, UnitsLinesVisual, StructureGroup } from '../units-visual';
import { VisualUpdateState } from '../../util';
import { BondType } from '../../../mol-model/structure/model/types';
@@ -18,6 +18,7 @@ import { BondIterator, BondLineParams, getIntraBondLoci, eachIntraBond, makeIntr
import { Sphere3D } from '../../../mol-math/geometry';
import { Lines } from '../../../mol-geo/geometry/lines/lines';
import { IntAdjacencyGraph } from '../../../mol-math/graph';
import { arrayIntersectionSize } from '../../../mol-util/array';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const isBondType = BondType.is;
@@ -29,41 +30,50 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
const childUnit = child?.unitMap.get(unit.id);
if (child && !childUnit) return Lines.createEmpty(lines);
if (props.includeParent) {
if (!child) throw new Error('expected child to exist');
}
const location = StructureElement.Location.create(structure, unit);
const elements = unit.elements;
const bonds = unit.bonds;
const { edgeCount, a, b, edgeProps, offset } = bonds;
const { order: _order, flags: _flags } = edgeProps;
const { sizeFactor } = props;
const { sizeFactor, aromaticBonds } = props;
if (!edgeCount) return Lines.createEmpty(lines);
const vRef = Vec3();
const pos = unit.conformation.invariantPosition;
const builderProps = {
const { elementRingIndices, elementAromaticRingIndices } = unit.rings;
const builderProps: LinkBuilderProps = {
linkCount: edgeCount * 2,
referencePosition: (edgeIndex: number) => {
let aI = a[edgeIndex], bI = b[edgeIndex];
if (aI > bI) [aI, bI] = [bI, aI];
if (offset[aI + 1] - offset[aI] === 1) [aI, bI] = [bI, aI];
// TODO prefer reference atoms within rings
const aR = elementRingIndices.get(aI);
let maxSize = 0;
for (let i = offset[aI], il = offset[aI + 1]; i < il; ++i) {
const _bI = b[i];
if (_bI !== bI && _bI !== aI) return pos(elements[_bI], vRef);
if (_bI !== bI && _bI !== aI) {
if (aR) {
const _bR = elementRingIndices.get(_bI);
if (!_bR) continue;
const size = arrayIntersectionSize(aR, _bR);
if (size > maxSize) {
maxSize = size;
pos(elements[_bI], vRef);
}
} else {
return pos(elements[_bI], vRef);
}
}
}
for (let i = offset[bI], il = offset[bI + 1]; i < il; ++i) {
const _aI = a[i];
if (_aI !== aI && _aI !== bI) return pos(elements[_aI], vRef);
}
return null;
return maxSize > 0 ? vRef : null;
},
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
pos(elements[a[edgeIndex]], posA);
@@ -75,13 +85,24 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
if (isBondType(f, BondType.Flag.MetallicCoordination) || isBondType(f, BondType.Flag.HydrogenBond)) {
// show metall coordinations and hydrogen bonds with dashed cylinders
return LinkStyle.Dashed;
} else if (o === 2) {
return LinkStyle.Double;
} else if (o === 3) {
return LinkStyle.Triple;
} else {
return LinkStyle.Solid;
} else if (aromaticBonds) {
const aI = a[edgeIndex], bI = b[edgeIndex];
const aR = elementAromaticRingIndices.get(aI);
const bR = elementAromaticRingIndices.get(bI);
const arCount = (aR && bR) ? arrayIntersectionSize(aR, bR) : 0;
if (arCount || isBondType(f, BondType.Flag.Aromatic)) {
if (arCount === 2) {
return LinkStyle.MirroredAromatic;
} else {
return LinkStyle.Aromatic;
}
}
}
return o === 2 ? LinkStyle.Double : LinkStyle.Solid;
},
radius: (edgeIndex: number) => {
location.element = elements[a[edgeIndex]];
@@ -123,7 +144,8 @@ export function IntraUnitBondLineVisual(materialId: number): UnitsVisual<IntraUn
newProps.dashCount !== currentProps.dashCount ||
newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
newProps.aromaticBonds !== currentProps.aromaticBonds
);
const newUnit = newStructureGroup.group.units[0];

View File

@@ -1,34 +1,102 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { VisualContext } from '../../visual';
import { Unit, Structure } from '../../../mol-model/structure';
import { Unit, Structure, StructureElement, ElementIndex } from '../../../mol-model/structure';
import { Theme } from '../../../mol-theme/theme';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { CylinderProps } from '../../../mol-geo/primitive/cylinder';
import { PolymerBackboneIterator } from './util/polymer';
import { OrderedSet } from '../../../mol-data/int';
import { eachPolymerElement, getPolymerElementLoci, NucleicShift, PolymerLocationIterator, StandardShift } from './util/polymer';
import { addCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
import { ElementIterator, getElementLoci, eachElement } from './util/element';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, UnitsCylindersVisual, UnitsCylindersParams, StructureGroup } from '../units-visual';
import { VisualUpdateState } from '../../util';
import { BaseGeometry } from '../../../mol-geo/geometry/base';
import { Sphere3D } from '../../../mol-math/geometry';
import { isNucleic, MoleculeType } from '../../../mol-model/structure/model/types';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { Cylinders } from '../../../mol-geo/geometry/cylinders/cylinders';
import { CylindersBuilder } from '../../../mol-geo/geometry/cylinders/cylinders-builder';
import { eachPolymerBackboneLink } from './util/polymer/backbone';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3scale = Vec3.scale;
const v3add = Vec3.add;
const v3sub = Vec3.sub;
export const PolymerBackboneCylinderParams = {
...UnitsMeshParams,
...UnitsCylindersParams,
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo),
tryUseImpostor: PD.Boolean(true),
};
export const DefaultPolymerBackboneCylinderProps = PD.getDefaultValues(PolymerBackboneCylinderParams);
export type PolymerBackboneCylinderProps = typeof DefaultPolymerBackboneCylinderProps
export type PolymerBackboneCylinderParams = typeof PolymerBackboneCylinderParams
export function PolymerBackboneCylinderVisual(materialId: number, structure: Structure, props: PD.Values<PolymerBackboneCylinderParams>, webgl?: WebGLContext) {
return props.tryUseImpostor && webgl && webgl.extensions.fragDepth
? PolymerBackboneCylinderImpostorVisual(materialId)
: PolymerBackboneCylinderMeshVisual(materialId);
}
interface PolymerBackboneCylinderProps {
radialSegments: number,
sizeFactor: number,
}
function createPolymerBackboneCylinderImpostor(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerBackboneCylinderProps, cylinders?: Cylinders) {
const polymerElementCount = unit.polymerElements.length;
if (!polymerElementCount) return Cylinders.createEmpty(cylinders);
const cylindersCountEstimate = polymerElementCount * 2;
const builder = CylindersBuilder.create(cylindersCountEstimate, cylindersCountEstimate / 4, cylinders);
const pos = unit.conformation.invariantPosition;
const pA = Vec3();
const pB = Vec3();
const pM = Vec3();
const add = function(indexA: ElementIndex, indexB: ElementIndex, groupA: number, groupB: number, moleculeType: MoleculeType) {
pos(indexA, pA);
pos(indexB, pB);
const isNucleicType = isNucleic(moleculeType);
const shift = isNucleicType ? NucleicShift : StandardShift;
v3add(pM, pA, v3scale(pM, v3sub(pM, pB, pA), shift));
builder.add(pA[0], pA[1], pA[2], pM[0], pM[1], pM[2], 1, false, false, groupA);
builder.add(pM[0], pM[1], pM[2], pB[0], pB[1], pB[2], 1, false, false, groupB);
};
eachPolymerBackboneLink(unit, add);
const c = builder.getCylinders();
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor);
c.setBoundingSphere(sphere);
return c;
}
export function PolymerBackboneCylinderImpostorVisual(materialId: number): UnitsVisual<PolymerBackboneCylinderParams> {
return UnitsCylindersVisual<PolymerBackboneCylinderParams>({
defaultProps: PD.getDefaultValues(PolymerBackboneCylinderParams),
createGeometry: createPolymerBackboneCylinderImpostor,
createLocationIterator: PolymerLocationIterator.fromGroup,
getLoci: getPolymerElementLoci,
eachLocation: eachPolymerElement,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerBackboneCylinderParams>, currentProps: PD.Values<PolymerBackboneCylinderParams>) => { },
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<PolymerBackboneCylinderParams>, webgl?: WebGLContext) => {
return !props.tryUseImpostor || !webgl;
}
}, materialId);
}
// TODO do group id based on polymer index not element index
function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerBackboneCylinderProps, mesh?: Mesh) {
const polymerElementCount = unit.polymerElements.length;
if (!polymerElementCount) return Mesh.createEmpty(mesh);
@@ -38,26 +106,34 @@ function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, struc
const vertexCountEstimate = radialSegments * 2 * polymerElementCount * 2;
const builderState = MeshBuilder.createState(vertexCountEstimate, vertexCountEstimate / 10, mesh);
const { elements } = unit;
const pos = unit.conformation.invariantPosition;
const pA = Vec3.zero();
const pB = Vec3.zero();
const pA = Vec3();
const pB = Vec3();
const cylinderProps: CylinderProps = { radiusTop: 1, radiusBottom: 1, radialSegments };
const polymerBackboneIt = PolymerBackboneIterator(structure, unit);
while (polymerBackboneIt.hasNext) {
const { centerA, centerB } = polymerBackboneIt.move();
const centerA = StructureElement.Location.create(structure, unit);
const centerB = StructureElement.Location.create(structure, unit);
const add = function(indexA: ElementIndex, indexB: ElementIndex, groupA: number, groupB: number, moleculeType: MoleculeType) {
centerA.element = indexA;
centerB.element = indexB;
pos(centerA.element, pA);
pos(centerB.element, pB);
const isNucleicType = isNucleic(moleculeType);
const shift = isNucleicType ? NucleicShift : StandardShift;
cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerA) * sizeFactor;
builderState.currentGroup = OrderedSet.indexOf(elements, centerA.element);
addCylinder(builderState, pA, pB, 0.5, cylinderProps);
builderState.currentGroup = groupA;
addCylinder(builderState, pA, pB, shift, cylinderProps);
cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerB) * sizeFactor;
builderState.currentGroup = OrderedSet.indexOf(elements, centerB.element);
addCylinder(builderState, pB, pA, 0.5, cylinderProps);
}
builderState.currentGroup = groupB;
addCylinder(builderState, pB, pA, 1 - shift, cylinderProps);
};
eachPolymerBackboneLink(unit, add);
const m = MeshBuilder.getMesh(builderState);
@@ -67,25 +143,21 @@ function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, struc
return m;
}
export const PolymerBackboneParams = {
...UnitsMeshParams,
...PolymerBackboneCylinderParams,
};
export type PolymerBackboneParams = typeof PolymerBackboneParams
export function PolymerBackboneVisual(materialId: number): UnitsVisual<PolymerBackboneParams> {
return UnitsMeshVisual<PolymerBackboneParams>({
defaultProps: PD.getDefaultValues(PolymerBackboneParams),
export function PolymerBackboneCylinderMeshVisual(materialId: number): UnitsVisual<PolymerBackboneCylinderParams> {
return UnitsMeshVisual<PolymerBackboneCylinderParams>({
defaultProps: PD.getDefaultValues(PolymerBackboneCylinderParams),
createGeometry: createPolymerBackboneCylinderMesh,
// TODO create a specialized location iterator
createLocationIterator: ElementIterator.fromGroup,
getLoci: getElementLoci,
eachLocation: eachElement,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerBackboneParams>, currentProps: PD.Values<PolymerBackboneParams>) => {
createLocationIterator: PolymerLocationIterator.fromGroup,
getLoci: getPolymerElementLoci,
eachLocation: eachPolymerElement,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerBackboneCylinderParams>, currentProps: PD.Values<PolymerBackboneCylinderParams>) => {
state.createGeometry = (
newProps.sizeFactor !== currentProps.sizeFactor ||
newProps.radialSegments !== currentProps.radialSegments
);
},
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<PolymerBackboneCylinderParams>, webgl?: WebGLContext) => {
return props.tryUseImpostor && !!webgl;
}
}, materialId);
}

View File

@@ -1 +1,131 @@
// TODO
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { VisualContext } from '../../visual';
import { Unit, Structure, ElementIndex, StructureElement } from '../../../mol-model/structure';
import { Theme } from '../../../mol-theme/theme';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { eachPolymerElement, getPolymerElementLoci, PolymerLocationIterator } from './util/polymer';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, UnitsSpheresVisual, UnitsSpheresParams, StructureGroup } from '../units-visual';
import { VisualUpdateState } from '../../util';
import { BaseGeometry } from '../../../mol-geo/geometry/base';
import { Sphere3D } from '../../../mol-math/geometry';
import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
import { sphereVertexCount } from '../../../mol-geo/primitive/sphere';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { Spheres } from '../../../mol-geo/geometry/spheres/spheres';
import { SpheresBuilder } from '../../../mol-geo/geometry/spheres/spheres-builder';
import { eachPolymerBackboneElement } from './util/polymer/backbone';
export const PolymerBackboneSphereParams = {
...UnitsMeshParams,
...UnitsSpheresParams,
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
tryUseImpostor: PD.Boolean(true),
};
export type PolymerBackboneSphereParams = typeof PolymerBackboneSphereParams
export function PolymerBackboneSphereVisual(materialId: number, structure: Structure, props: PD.Values<PolymerBackboneSphereParams>, webgl?: WebGLContext) {
return props.tryUseImpostor && webgl && webgl.extensions.fragDepth
? PolymerBackboneSphereImpostorVisual(materialId)
: PolymerBackboneSphereMeshVisual(materialId);
}
interface PolymerBackboneSphereProps {
detail: number,
sizeFactor: number,
}
function createPolymerBackboneSphereImpostor(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerBackboneSphereProps, spheres?: Spheres) {
const polymerElementCount = unit.polymerElements.length;
if (!polymerElementCount) return Spheres.createEmpty(spheres);
const builder = SpheresBuilder.create(polymerElementCount, polymerElementCount / 2, spheres);
const pos = unit.conformation.invariantPosition;
const p = Vec3();
const add = (index: ElementIndex, group: number) => {
pos(index, p);
builder.add(p[0], p[1], p[2], group);
};
eachPolymerBackboneElement(unit, add);
const s = builder.getSpheres();
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor);
s.setBoundingSphere(sphere);
return s;
}
export function PolymerBackboneSphereImpostorVisual(materialId: number): UnitsVisual<PolymerBackboneSphereParams> {
return UnitsSpheresVisual<PolymerBackboneSphereParams>({
defaultProps: PD.getDefaultValues(PolymerBackboneSphereParams),
createGeometry: createPolymerBackboneSphereImpostor,
createLocationIterator: PolymerLocationIterator.fromGroup,
getLoci: getPolymerElementLoci,
eachLocation: eachPolymerElement,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerBackboneSphereParams>, currentProps: PD.Values<PolymerBackboneSphereParams>) => { },
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<PolymerBackboneSphereParams>, webgl?: WebGLContext) => {
return !props.tryUseImpostor || !webgl;
}
}, materialId);
}
function createPolymerBackboneSphereMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerBackboneSphereProps, mesh?: Mesh) {
const polymerElementCount = unit.polymerElements.length;
if (!polymerElementCount) return Mesh.createEmpty(mesh);
const { detail, sizeFactor } = props;
const vertexCount = polymerElementCount * sphereVertexCount(detail);
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh);
const pos = unit.conformation.invariantPosition;
const p = Vec3();
const center = StructureElement.Location.create(structure, unit);
const add = (index: ElementIndex, group: number) => {
center.element = index;
pos(center.element, p);
builderState.currentGroup = group;
addSphere(builderState, p, theme.size.size(center) * sizeFactor, detail);
};
eachPolymerBackboneElement(unit, add);
const m = MeshBuilder.getMesh(builderState);
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor);
m.setBoundingSphere(sphere);
return m;
}
export function PolymerBackboneSphereMeshVisual(materialId: number): UnitsVisual<PolymerBackboneSphereParams> {
return UnitsMeshVisual<PolymerBackboneSphereParams>({
defaultProps: PD.getDefaultValues(PolymerBackboneSphereParams),
createGeometry: createPolymerBackboneSphereMesh,
createLocationIterator: PolymerLocationIterator.fromGroup,
getLoci: getPolymerElementLoci,
eachLocation: eachPolymerElement,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerBackboneSphereParams>, currentProps: PD.Values<PolymerBackboneSphereParams>) => {
state.createGeometry = (
newProps.sizeFactor !== currentProps.sizeFactor ||
newProps.detail !== currentProps.detail
);
},
mustRecreate: (structureGroup: StructureGroup, props: PD.Values<PolymerBackboneSphereParams>, webgl?: WebGLContext) => {
return props.tryUseImpostor && !!webgl;
}
}, materialId);
}

View File

@@ -20,6 +20,7 @@ export const BondParams = {
includeTypes: PD.MultiSelect(ObjectKeys(BondType.Names), PD.objectToOptions(BondType.Names)),
excludeTypes: PD.MultiSelect([] as BondType.Names[], PD.objectToOptions(BondType.Names)),
ignoreHydrogens: PD.Boolean(false),
aromaticBonds: PD.Boolean(false, { description: 'Display aromatic bonds with dashes' }),
};
export const DefaultBondProps = PD.getDefaultValues(BondParams);
export type BondProps = typeof DefaultBondProps

View File

@@ -46,7 +46,7 @@ export function makeElementIgnoreTest(structure: Structure, unit: Unit, props: E
const childUnit = child?.unitMap.get(unit.id);
if (child && !childUnit) throw new Error('expected childUnit to exist if child exists');
if (!child && ((!ignoreHydrogens && !traceOnly) || traceOnly)) return;
if (!child && !ignoreHydrogens && !traceOnly) return;
return (element: ElementIndex) => {
return (

View File

@@ -18,7 +18,7 @@ import { Cylinders } from '../../../../mol-geo/geometry/cylinders/cylinders';
import { CylindersBuilder } from '../../../../mol-geo/geometry/cylinders/cylinders-builder';
export const LinkCylinderParams = {
linkScale: PD.Numeric(0.4, { min: 0, max: 1, step: 0.1 }),
linkScale: PD.Numeric(0.45, { min: 0, max: 1, step: 0.01 }),
linkSpacing: PD.Numeric(1, { min: 0, max: 2, step: 0.01 }),
linkCap: PD.Boolean(false),
dashCount: PD.Numeric(4, { min: 2, max: 10, step: 2 }),
@@ -84,7 +84,9 @@ export const enum LinkStyle {
Dashed = 1,
Double = 2,
Triple = 3,
Disk = 4
Disk = 4,
Aromatic = 5,
MirroredAromatic = 6,
}
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
@@ -133,6 +135,8 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
const [topCap, bottomCap] = (v3dot(tmpV12, up) > 0) ? [linkStub, linkCap] : [linkCap, linkStub];
builderState.currentGroup = edgeIndex;
const aromaticOffsetFactor = 4.5;
if (linkStyle === LinkStyle.Solid) {
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
cylinderProps.topCap = topCap;
@@ -144,20 +148,41 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
cylinderProps.topCap = cylinderProps.bottomCap = dashCap;
addFixedCountDashedCylinder(builderState, va, vb, 0.5, segmentCount, cylinderProps);
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple) {
const order = linkStyle === LinkStyle.Double ? 2 : 3;
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
const order = linkStyle === LinkStyle.Double ? 2 :
linkStyle === LinkStyle.Triple ? 3 : 1.5;
const multiRadius = linkRadius * (linkScale / (0.5 * order));
const absOffset = (linkRadius - multiRadius) * linkSpacing;
calculateShiftDir(vShift, va, vb, referencePosition ? referencePosition(edgeIndex) : null);
v3setMagnitude(vShift, vShift, absOffset);
cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius;
cylinderProps.topCap = topCap;
cylinderProps.bottomCap = bottomCap;
if (order === 3) addCylinder(builderState, va, vb, 0.5, cylinderProps);
addDoubleCylinder(builderState, va, vb, 0.5, vShift, cylinderProps);
if (linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
addCylinder(builderState, va, vb, 0.5, cylinderProps);
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius * linkScale;
cylinderProps.topCap = cylinderProps.bottomCap = dashCap;
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
v3sub(va, va, vShift);
v3sub(vb, vb, vShift);
addFixedCountDashedCylinder(builderState, va, vb, 0.5, 3, cylinderProps);
if (linkStyle === LinkStyle.MirroredAromatic) {
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
v3add(va, va, vShift);
v3add(vb, vb, vShift);
addFixedCountDashedCylinder(builderState, va, vb, 0.5, 3, cylinderProps);
}
} else {
v3setMagnitude(vShift, vShift, absOffset);
cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius;
if (order === 3) addCylinder(builderState, va, vb, 0.5, cylinderProps);
addDoubleCylinder(builderState, va, vb, 0.5, vShift, cylinderProps);
}
} else if (linkStyle === LinkStyle.Disk) {
v3scale(tmpV12, tmpV12, 0.475);
v3add(va, va, tmpV12);
@@ -190,12 +215,17 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
const va = Vec3();
const vb = Vec3();
const vm = Vec3();
const vShift = Vec3();
// automatically adjust length for evenly spaced dashed cylinders
const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
const aromaticSegmentCount = 3;
const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
const aromaticOffsetFactor = 4.5;
for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
if (ignore && ignore(edgeIndex)) continue;
@@ -206,24 +236,45 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
const linkStub = stubCap && (stub ? stub(edgeIndex) : false);
if (linkStyle === LinkStyle.Solid) {
v3scale(vb, v3add(vb, va, vb), 0.5);
builder.add(va[0], va[1], va[2], vb[0], vb[1], vb[2], 1, linkCap, linkStub, edgeIndex);
v3scale(vm, v3add(vm, va, vb), 0.5);
builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], 1, linkCap, linkStub, edgeIndex);
} else if (linkStyle === LinkStyle.Dashed) {
v3scale(tmpV12, v3sub(tmpV12, vb, va), lengthScale);
v3sub(vb, vb, tmpV12);
builder.addFixedCountDashes(va, vb, segmentCount, dashScale, dashCap, dashCap, edgeIndex);
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple) {
v3scale(vb, v3add(vb, va, vb), 0.5);
const order = linkStyle === LinkStyle.Double ? 2 : 3;
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
const order = linkStyle === LinkStyle.Double ? 2 :
linkStyle === LinkStyle.Triple ? 3 : 1.5;
const multiScale = linkScale / (0.5 * order);
const absOffset = (linkRadius - multiScale * linkRadius) * linkSpacing;
v3scale(vm, v3add(vm, va, vb), 0.5);
calculateShiftDir(vShift, va, vb, referencePosition ? referencePosition(edgeIndex) : null);
v3setMagnitude(vShift, vShift, absOffset);
if (order === 3) builder.add(va[0], va[1], va[2], vb[0], vb[1], vb[2], multiScale, linkCap, false, edgeIndex);
builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vb[0] + vShift[0], vb[1] + vShift[1], vb[2] + vShift[2], multiScale, linkCap, linkStub, edgeIndex);
builder.add(va[0] - vShift[0], va[1] - vShift[1], va[2] - vShift[2], vb[0] - vShift[0], vb[1] - vShift[1], vb[2] - vShift[2], multiScale, linkCap, linkStub, edgeIndex);
if (linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], 1, linkCap, linkStub, edgeIndex);
v3scale(tmpV12, v3sub(tmpV12, vb, va), aromaticLengthScale);
v3sub(vb, vb, tmpV12);
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
v3sub(va, va, vShift);
v3sub(vb, vb, vShift);
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, linkScale, dashCap, dashCap, edgeIndex);
if (linkStyle === LinkStyle.MirroredAromatic) {
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
v3add(va, va, vShift);
v3add(vb, vb, vShift);
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, linkScale, dashCap, dashCap, edgeIndex);
}
} else {
v3setMagnitude(vShift, vShift, absOffset);
if (order === 3) builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], multiScale, linkCap, false, edgeIndex);
builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vm[0] + vShift[0], vm[1] + vShift[1], vm[2] + vShift[2], multiScale, linkCap, linkStub, edgeIndex);
builder.add(va[0] - vShift[0], va[1] - vShift[1], va[2] - vShift[2], vm[0] - vShift[0], vm[1] - vShift[1], vm[2] - vShift[2], multiScale, linkCap, linkStub, edgeIndex);
}
} else if (linkStyle === LinkStyle.Disk) {
v3scale(tmpV12, v3sub(tmpV12, vb, va), 0.475);
v3add(va, va, tmpV12);
@@ -252,12 +303,17 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
const va = Vec3();
const vb = Vec3();
const vm = Vec3();
const vShift = Vec3();
// automatically adjust length for evenly spaced dashed lines
const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
const aromaticSegmentCount = 3;
const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
const aromaticOffsetFactor = 4.5;
for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
if (ignore && ignore(edgeIndex)) continue;
@@ -266,24 +322,45 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
const linkStyle = style ? style(edgeIndex) : LinkStyle.Solid;
if (linkStyle === LinkStyle.Solid) {
v3scale(vb, v3add(vb, va, vb), 0.5);
builder.add(va[0], va[1], va[2], vb[0], vb[1], vb[2], edgeIndex);
v3scale(vm, v3add(vm, va, vb), 0.5);
builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], edgeIndex);
} else if (linkStyle === LinkStyle.Dashed) {
v3scale(tmpV12, v3sub(tmpV12, vb, va), lengthScale);
v3sub(vb, vb, tmpV12);
builder.addFixedCountDashes(va, vb, segmentCount, edgeIndex);
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple) {
v3scale(vb, v3add(vb, va, vb), 0.5);
const order = linkStyle === LinkStyle.Double ? 2 : 3;
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
const order = linkStyle === LinkStyle.Double ? 2 :
linkStyle === LinkStyle.Triple ? 3 : 1.5;
const multiRadius = 1 * (linkScale / (0.5 * order));
const absOffset = (1 - multiRadius) * linkSpacing;
v3scale(vm, v3add(vm, va, vb), 0.5);
calculateShiftDir(vShift, va, vb, referencePosition ? referencePosition(edgeIndex) : null);
v3setMagnitude(vShift, vShift, absOffset);
if (order === 3) builder.add(va[0], va[1], va[2], vb[0], vb[1], vb[2], edgeIndex);
builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vb[0] + vShift[0], vb[1] + vShift[1], vb[2] + vShift[2], edgeIndex);
builder.add(va[0] - vShift[0], va[1] - vShift[1], va[2] - vShift[2], vb[0] - vShift[0], vb[1] - vShift[1], vb[2] - vShift[2], edgeIndex);
if (linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], edgeIndex);
v3scale(tmpV12, v3sub(tmpV12, vb, va), aromaticLengthScale);
v3sub(vb, vb, tmpV12);
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
v3sub(va, va, vShift);
v3sub(vb, vb, vShift);
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, edgeIndex);
if (linkStyle === LinkStyle.MirroredAromatic) {
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
v3add(va, va, vShift);
v3add(vb, vb, vShift);
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, edgeIndex);
}
} else {
v3setMagnitude(vShift, vShift, absOffset);
if (order === 3) builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], edgeIndex);
builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vm[0] + vShift[0], vm[1] + vShift[1], vm[2] + vShift[2], edgeIndex);
builder.add(va[0] - vShift[0], va[1] - vShift[1], va[2] - vShift[2], vm[0] - vShift[0], vm[1] - vShift[1], vm[2] - vShift[2], edgeIndex);
}
} else if (linkStyle === LinkStyle.Disk) {
v3scale(tmpV12, v3sub(tmpV12, vb, va), 0.475);
v3add(va, va, tmpV12);

View File

@@ -14,7 +14,7 @@ import { PickingId } from '../../../../mol-geo/geometry/picking';
import { StructureGroup } from '../../../structure/units-visual';
import { getResidueLoci } from './common';
export * from './polymer/backbone-iterator';
export * from './polymer/backbone';
export * from './polymer/gap-iterator';
export * from './polymer/trace-iterator';
export * from './polymer/curve-segment';

View File

@@ -1,134 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Unit, Structure, StructureElement, ElementIndex, ResidueIndex } from '../../../../../mol-model/structure';
import { Segmentation } from '../../../../../mol-data/int';
import { Iterator } from '../../../../../mol-data/iterator';
import { SortedRanges } from '../../../../../mol-data/int/sorted-ranges';
import { getPolymerRanges } from '../polymer';
/** Iterates over consecutive pairs of residues/coarse elements in polymers */
export function PolymerBackboneIterator(structure: Structure, unit: Unit): Iterator<PolymerBackbonePair> {
switch (unit.kind) {
case Unit.Kind.Atomic: return new AtomicPolymerBackboneIterator(structure, unit);
case Unit.Kind.Spheres:
case Unit.Kind.Gaussians:
return new CoarsePolymerBackboneIterator(structure, unit);
}
}
interface PolymerBackbonePair {
centerA: StructureElement.Location
centerB: StructureElement.Location
}
function createPolymerBackbonePair (structure: Structure, unit: Unit) {
return {
centerA: StructureElement.Location.create(structure, unit),
centerB: StructureElement.Location.create(structure, unit),
};
}
const enum AtomicPolymerBackboneIteratorState { nextPolymer, firstResidue, nextResidue, cycle }
export class AtomicPolymerBackboneIterator implements Iterator<PolymerBackbonePair> {
private traceElementIndex: ArrayLike<ElementIndex>
private value: PolymerBackbonePair
private polymerIt: SortedRanges.Iterator<ElementIndex, ResidueIndex>
private residueIt: Segmentation.SegmentIterator<ResidueIndex>
private state: AtomicPolymerBackboneIteratorState = AtomicPolymerBackboneIteratorState.nextPolymer
private residueSegment: Segmentation.Segment<ResidueIndex>
hasNext: boolean = false;
move() {
if (this.state === AtomicPolymerBackboneIteratorState.nextPolymer) {
while (this.polymerIt.hasNext) {
this.residueIt.setSegment(this.polymerIt.move());
if (this.residueIt.hasNext) {
this.residueSegment = this.residueIt.move();
this.value.centerB.element = this.traceElementIndex[this.residueSegment.index];
this.state = AtomicPolymerBackboneIteratorState.nextResidue;
break;
}
}
}
if (this.state === AtomicPolymerBackboneIteratorState.nextResidue) {
this.residueSegment = this.residueIt.move();
this.value.centerA.element = this.value.centerB.element;
this.value.centerB.element = this.traceElementIndex[this.residueSegment.index];
if (!this.residueIt.hasNext) {
if (this.unit.model.atomicRanges.cyclicPolymerMap.has(this.residueSegment.index)) {
this.state = AtomicPolymerBackboneIteratorState.cycle;
} else {
// TODO need to advance to a polymer that has two or more residues (can't assume it has)
this.state = AtomicPolymerBackboneIteratorState.nextPolymer;
}
}
} else if (this.state === AtomicPolymerBackboneIteratorState.cycle) {
const { cyclicPolymerMap } = this.unit.model.atomicRanges;
this.value.centerA.element = this.value.centerB.element;
this.value.centerB.element = this.traceElementIndex[cyclicPolymerMap.get(this.residueSegment.index)!];
// TODO need to advance to a polymer that has two or more residues (can't assume it has)
this.state = AtomicPolymerBackboneIteratorState.nextPolymer;
}
this.hasNext = this.residueIt.hasNext || this.polymerIt.hasNext || this.state === AtomicPolymerBackboneIteratorState.cycle;
return this.value;
}
constructor(structure: Structure, private unit: Unit.Atomic) {
this.traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex as ArrayLike<ElementIndex>; // can assume it won't be -1 for polymer residues
this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
this.value = createPolymerBackbonePair(structure, unit);
this.hasNext = this.residueIt.hasNext && this.polymerIt.hasNext;
}
}
const enum CoarsePolymerBackboneIteratorState { nextPolymer, nextElement }
export class CoarsePolymerBackboneIterator implements Iterator<PolymerBackbonePair> {
private value: PolymerBackbonePair
private polymerIt: SortedRanges.Iterator<ElementIndex, ResidueIndex>
private polymerSegment: Segmentation.Segment<ResidueIndex>
private state: CoarsePolymerBackboneIteratorState = CoarsePolymerBackboneIteratorState.nextPolymer
private elementIndex: number
hasNext: boolean = false;
move() {
if (this.state === CoarsePolymerBackboneIteratorState.nextPolymer) {
if (this.polymerIt.hasNext) {
this.polymerSegment = this.polymerIt.move();
this.elementIndex = this.polymerSegment.start;
if (this.elementIndex + 1 < this.polymerSegment.end) {
this.value.centerB.element = this.unit.elements[this.elementIndex];
this.state = CoarsePolymerBackboneIteratorState.nextElement;
} else {
this.state = CoarsePolymerBackboneIteratorState.nextPolymer;
}
}
}
if (this.state === CoarsePolymerBackboneIteratorState.nextElement) {
this.elementIndex += 1;
this.value.centerA.element = this.value.centerB.element;
this.value.centerB.element = this.unit.elements[this.elementIndex];
if (this.elementIndex + 1 >= this.polymerSegment.end) {
this.state = CoarsePolymerBackboneIteratorState.nextPolymer;
}
}
this.hasNext = this.elementIndex + 1 < this.polymerSegment.end || this.polymerIt.hasNext;
return this.value;
}
constructor(structure: Structure, private unit: Unit.Spheres | Unit.Gaussians) {
this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
this.value = createPolymerBackbonePair(structure, unit);
this.hasNext = this.polymerIt.hasNext;
}
}

View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Segmentation } from '../../../../../mol-data/int';
import { SortedRanges } from '../../../../../mol-data/int/sorted-ranges';
import { ElementIndex, ResidueIndex, Unit } from '../../../../../mol-model/structure';
import { MoleculeType } from '../../../../../mol-model/structure/model/types';
import { getPolymerRanges } from '../polymer';
export type PolymerBackboneLinkCallback = (indexA: ElementIndex, indexB: ElementIndex, groupA: number, groupB: number, moleculeType: MoleculeType) => void
export function eachPolymerBackboneLink(unit: Unit, callback: PolymerBackboneLinkCallback) {
switch (unit.kind) {
case Unit.Kind.Atomic: return eachAtomicPolymerBackboneLink(unit, callback);
case Unit.Kind.Spheres:
case Unit.Kind.Gaussians:
return eachCoarsePolymerBackboneLink(unit, callback);
}
}
function eachAtomicPolymerBackboneLink(unit: Unit.Atomic, callback: PolymerBackboneLinkCallback) {
const cyclicPolymerMap = unit.model.atomicRanges.cyclicPolymerMap;
const polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
const residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
const traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex as ArrayLike<ElementIndex>; // can assume it won't be -1 for polymer residues
const { moleculeType } = unit.model.atomicHierarchy.derived.residue;
let indexA = -1 as ResidueIndex;
let indexB = -1 as ResidueIndex;
let isFirst = true;
let firstGroup = -1;
let i = 0;
while (polymerIt.hasNext) {
isFirst = true;
firstGroup = i;
residueIt.setSegment(polymerIt.move());
while (residueIt.hasNext) {
if (isFirst) {
const index_1 = residueIt.move().index;
++i;
if (!residueIt.hasNext)
continue;
isFirst = false;
indexB = index_1;
}
const index = residueIt.move().index;
indexA = indexB;
indexB = index;
callback(traceElementIndex[indexA], traceElementIndex[indexB], i - 1, i, moleculeType[indexA]);
++i;
}
if (cyclicPolymerMap.has(indexB)) {
indexA = indexB;
indexB = cyclicPolymerMap.get(indexA)!;
callback(traceElementIndex[indexA], traceElementIndex[indexB], i - 1, firstGroup, moleculeType[indexA]);
}
}
}
function eachCoarsePolymerBackboneLink(unit: Unit.Spheres | Unit.Gaussians, callback: PolymerBackboneLinkCallback) {
const polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
const { elements } = unit;
let isFirst = true;
let i = 0;
while (polymerIt.hasNext) {
isFirst = true;
const _a = polymerIt.move(), start = _a.start, end = _a.end;
for (let j = start, jl = end; j < jl; ++j) {
if (isFirst) {
++j;
++i;
if (j > jl)
continue;
isFirst = false;
}
callback(elements[j - 1], elements[j], i - 1, i, 0 /* Unknown */);
++i;
}
}
}
//
export type PolymerBackboneElementCallback = (index: ElementIndex, group: number) => void
export function eachPolymerBackboneElement(unit: Unit, callback: PolymerBackboneElementCallback) {
switch (unit.kind) {
case Unit.Kind.Atomic: return eachAtomicPolymerBackboneElement(unit, callback);
case Unit.Kind.Spheres:
case Unit.Kind.Gaussians:
return eachCoarsePolymerBackboneElement(unit, callback);
}
}
export function eachAtomicPolymerBackboneElement(unit: Unit.Atomic, callback: PolymerBackboneElementCallback) {
const polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
const residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
const traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex as ArrayLike<ElementIndex>; // can assume it won't be -1 for polymer residues
let i = 0;
while (polymerIt.hasNext) {
residueIt.setSegment(polymerIt.move());
while (residueIt.hasNext) {
const index = residueIt.move().index;
callback(traceElementIndex[index], i);
++i;
}
}
}
function eachCoarsePolymerBackboneElement(unit: Unit.Spheres | Unit.Gaussians, callback: PolymerBackboneElementCallback) {
const polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements);
const { elements } = unit;
let i = 0;
while (polymerIt.hasNext) {
const _a = polymerIt.move(), start = _a.start, end = _a.end;
for (let j = start, jl = end; j < jl; ++j) {
callback(elements[j], i);
++i;
}
}
}

View File

@@ -1,12 +1,12 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3 } from '../../../../../mol-math/linear-algebra';
import { NumberArray } from '../../../../../mol-util/type-helpers';
import { lerp } from '../../../../../mol-math/interpolate';
import { lerp, smoothstep } from '../../../../../mol-math/interpolate';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray;
@@ -19,6 +19,8 @@ const v3copy = Vec3.copy;
const v3cross = Vec3.cross;
const v3orthogonalize = Vec3.orthogonalize;
const v3matchDirection = Vec3.matchDirection;
const v3scale = Vec3.scale;
const v3add = Vec3.add;
export interface CurveSegmentState {
curvePoints: NumberArray,
@@ -92,6 +94,7 @@ const tangentVec = Vec3();
const normalVec = Vec3();
const binormalVec = Vec3();
const prevNormal = Vec3();
const nextNormal = Vec3();
const firstTangentVec = Vec3();
const lastTangentVec = Vec3();
const firstNormalVec = Vec3();
@@ -116,8 +119,10 @@ export function interpolateNormals(state: CurveSegmentState, controls: CurveSegm
v3copy(prevNormal, firstNormalVec);
const n1 = n - 1;
for (let i = 0; i < n; ++i) {
const t = i === 0 ? 0 : 1 / (n - i);
const j = smoothstep(0, n1, i) * n1;
const t = i === 0 ? 0 : 1 / (n - j);
v3fromArray(tangentVec, tangentVectors, i * 3);
@@ -129,6 +134,19 @@ export function interpolateNormals(state: CurveSegmentState, controls: CurveSegm
v3normalize(binormalVec, v3cross(binormalVec, tangentVec, normalVec));
v3toArray(binormalVec, binormalVectors, i * 3);
}
for (let i = 1; i < n1; ++i) {
v3fromArray(prevNormal, normalVectors, (i - 1) * 3);
v3fromArray(normalVec, normalVectors, i * 3);
v3fromArray(nextNormal, normalVectors, (i + 1) * 3);
v3scale(normalVec, v3add(normalVec, prevNormal, v3add(normalVec, nextNormal, normalVec)), 1 / 3);
v3toArray(normalVec, normalVectors, i * 3);
v3fromArray(tangentVec, tangentVectors, i * 3);
v3normalize(binormalVec, v3cross(binormalVec, tangentVec, normalVec));
v3toArray(binormalVec, binormalVectors, i * 3);
}
}
export function interpolateSizes(state: CurveSegmentState, w0: number, w1: number, w2: number, h0: number, h1: number, h2: number, shift: number) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -105,6 +105,29 @@ export function arraySetRemove<T>(xs: T[], x: T) {
return true;
}
/**
* Caution, O(n^2) complexity. Only use for small input sizes.
* For larger inputs consider using `SortedArray`.
*/
export function arrayAreIntersecting<T>(xs: T[], ys: T[]) {
for (let i = 0, il = xs.length; i < il; ++i) {
if (ys.includes(xs[i])) return true;
}
return false;
}
/**
* Caution, O(n^2) complexity. Only use for small input sizes.
* For larger inputs consider using `SortedArray`.
*/
export function arrayIntersectionSize<T>(xs: T[], ys: T[]) {
let count = 0;
for (let i = 0, il = xs.length; i < il; ++i) {
if (ys.includes(xs[i])) count += 1;
}
return count;
}
export function arrayEqual<T>(xs?: ArrayLike<T>, ys?: ArrayLike<T>) {
if (!xs || xs.length === 0) return !ys || ys.length === 0;
if (!ys) return false;