diff --git a/src/mol-model/structure/model/properties/utils/atomic-derived.ts b/src/mol-model/structure/model/properties/utils/atomic-derived.ts index 255c144473aeaa52d149d5d973bc25905b99f018..8c1a1bb429545a4bf29672b96a49615f286c2e30 100644 --- a/src/mol-model/structure/model/properties/utils/atomic-derived.ts +++ b/src/mol-model/structure/model/properties/utils/atomic-derived.ts @@ -21,7 +21,7 @@ export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemi const moleculeTypeMap = new Map<string, MoleculeType>() - for (let i = 0; i < n; ++i) { + for (let i = 0 as ResidueIndex; i < n; ++i) { const compId = label_comp_id.value(i) const chemCompMap = chemicalComponentMap let molType: MoleculeType @@ -39,18 +39,18 @@ export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemi moleculeType[i] = molType const traceAtomId = getAtomIdForAtomRole(molType, 'trace') - let traceIndex = index.findAtomsOnResidue(i as ResidueIndex, traceAtomId) + let traceIndex = index.findAtomsOnResidue(i, traceAtomId) if (traceIndex === -1) { const coarseAtomId = getAtomIdForAtomRole(molType, 'coarseBackbone') - traceIndex = index.findAtomsOnResidue(i as ResidueIndex, coarseAtomId) + traceIndex = index.findAtomsOnResidue(i, coarseAtomId) } traceElementIndex[i] = traceIndex const directionFromAtomId = getAtomIdForAtomRole(molType, 'directionFrom') - directionFromElementIndex[i] = index.findAtomsOnResidue(i as ResidueIndex, directionFromAtomId) + directionFromElementIndex[i] = index.findAtomsOnResidue(i, directionFromAtomId) const directionToAtomId = getAtomIdForAtomRole(molType, 'directionTo') - directionToElementIndex[i] = index.findAtomsOnResidue(i as ResidueIndex, directionToAtomId) + directionToElementIndex[i] = index.findAtomsOnResidue(i, directionToAtomId) } return { diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index d50179acd7784097e4e6d6d3d66d1b232df8c7a4..e36aba74fc910266f3aaf50d04a88f0b40b7b08c 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/src/mol-model/structure/structure/unit.ts @@ -143,8 +143,9 @@ namespace Unit { readonly model: Model; readonly conformation: SymmetryOperator.ArrayMapping<ElementIndex>; - /** Reference some commonly accessed things for faster access. */ + /** Reference `residueIndex` from `model` for faster access. */ readonly residueIndex: ArrayLike<ResidueIndex>; + /** Reference `chainIndex` from `model` for faster access. */ readonly chainIndex: ArrayLike<ChainIndex>; private props: AtomicProperties; diff --git a/src/mol-repr/structure/visual/nucleotide-block-mesh.ts b/src/mol-repr/structure/visual/nucleotide-block-mesh.ts index c7b4b2d9c21b6c6282890d83e485a6051785ed7a..255001edbe5f4f32d766b2506c72e33d317e7fa5 100644 --- a/src/mol-repr/structure/visual/nucleotide-block-mesh.ts +++ b/src/mol-repr/structure/visual/nucleotide-block-mesh.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -62,7 +62,7 @@ function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structure: St const chainIt = Segmentation.transientSegments(chainAtomSegments, elements) const residueIt = Segmentation.transientSegments(residueAtomSegments, elements) - const cylinderProps: CylinderProps = { radiusTop: 1 * sizeFactor, radiusBottom: 1 * sizeFactor, radialSegments } + const cylinderProps: CylinderProps = { radiusTop: 1 * sizeFactor, radiusBottom: 1 * sizeFactor, radialSegments, bottomCap: true } let i = 0 while (chainIt.hasNext) { diff --git a/src/mol-repr/structure/visual/polymer-trace-mesh.ts b/src/mol-repr/structure/visual/polymer-trace-mesh.ts index d7a5e39e5750156b45080691c9694589161be0c9..4f47a53f97e5ee7c70d5d144980b617780e455db 100644 --- a/src/mol-repr/structure/visual/polymer-trace-mesh.ts +++ b/src/mol-repr/structure/visual/polymer-trace-mesh.ts @@ -10,7 +10,7 @@ import { Unit, Structure } 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 { createCurveSegmentState, PolymerTraceIterator, interpolateCurveSegment, interpolateSizes, PolymerLocationIterator, getPolymerElementLoci, eachPolymerElement } from './util/polymer'; +import { createCurveSegmentState, PolymerTraceIterator, interpolateCurveSegment, interpolateSizes, PolymerLocationIterator, getPolymerElementLoci, eachPolymerElement, HelixTension, NucleicShift, StandardShift, StandardTension, OverhangFactor } from './util/polymer'; import { isNucleic, SecondaryStructureType } from '../../../mol-model/structure/model/types'; import { addSheet } from '../../../mol-geo/geometry/mesh/builder/sheet'; import { addTube } from '../../../mol-geo/geometry/mesh/builder/tube'; @@ -18,9 +18,12 @@ import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '. import { VisualUpdateState } from '../../util'; import { ComputedSecondaryStructure } from '../../../mol-model-props/computed/secondary-structure'; import { addRibbon } from '../../../mol-geo/geometry/mesh/builder/ribbon'; +import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere'; +import { Vec3 } from '../../../mol-math/linear-algebra'; export const PolymerTraceMeshParams = { sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }), + detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }), linearSegments: PD.Numeric(8, { min: 1, max: 48, step: 1 }), radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }), aspectRatio: PD.Numeric(5, { min: 0.1, max: 10, step: 0.1 }), @@ -29,13 +32,13 @@ export const PolymerTraceMeshParams = { export const DefaultPolymerTraceMeshProps = PD.getDefaultValues(PolymerTraceMeshParams) export type PolymerTraceMeshProps = typeof DefaultPolymerTraceMeshProps -// TODO handle polymer ends properly +const tmpV1 = Vec3() function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerTraceMeshProps, mesh?: Mesh) { const polymerElementCount = unit.polymerElements.length if (!polymerElementCount) return Mesh.createEmpty(mesh) - const { sizeFactor, linearSegments, radialSegments, aspectRatio, arrowFactor } = props + const { sizeFactor, detail, linearSegments, radialSegments, aspectRatio, arrowFactor } = props const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2 const builderState = MeshBuilder.createState(vertexCount, vertexCount / 10, mesh) @@ -53,8 +56,8 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc const isNucleicType = isNucleic(v.moleculeType) const isSheet = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Beta) const isHelix = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Helix) - const tension = isHelix ? 0.9 : 0.5 - const shift = isNucleicType ? 0.3 : 0.5 + const tension = isHelix ? HelixTension : StandardTension + const shift = isNucleicType ? NucleicShift : StandardShift interpolateCurveSegment(state, v, tension, shift) @@ -70,7 +73,28 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc const startCap = v.secStrucFirst || v.coarseBackboneFirst || v.first const endCap = v.secStrucLast || v.coarseBackboneLast || v.last - if (isSheet) { + let segmentCount = linearSegments + if (v.initial) { + segmentCount = Math.max(Math.round(linearSegments * shift), 1) + const offset = linearSegments - segmentCount + curvePoints.copyWithin(0, offset * 3) + binormalVectors.copyWithin(0, offset * 3) + normalVectors.copyWithin(0, offset * 3) + Vec3.fromArray(tmpV1, curvePoints, 3) + Vec3.normalize(tmpV1, Vec3.sub(tmpV1, v.p2, tmpV1)) + Vec3.scaleAndAdd(tmpV1, v.p2, tmpV1, w1 * OverhangFactor) + Vec3.toArray(tmpV1, curvePoints, 0) + } else if (v.final) { + segmentCount = Math.max(Math.round(linearSegments * (1 - shift)), 1) + Vec3.fromArray(tmpV1, curvePoints, segmentCount * 3 - 3) + Vec3.normalize(tmpV1, Vec3.sub(tmpV1, v.p2, tmpV1)) + Vec3.scaleAndAdd(tmpV1, v.p2, tmpV1, w1 * OverhangFactor) + Vec3.toArray(tmpV1, curvePoints, segmentCount * 3) + } + + if (v.initial === true && v.final === true) { + addSphere(builderState, v.p2, w1 * 2, detail) + } else if (isSheet) { const h0 = w0 * aspectRatio const h1 = w1 * aspectRatio const h2 = w2 * aspectRatio @@ -79,9 +103,9 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc interpolateSizes(state, w0, w1, w2, h0, h1, h2, shift) if (radialSegments === 2) { - addRibbon(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, widthValues, heightValues, arrowHeight) + addRibbon(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, arrowHeight) } else { - addSheet(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, widthValues, heightValues, arrowHeight, startCap, endCap) + addSheet(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, arrowHeight, startCap, endCap) } } else { let h0: number, h1: number, h2: number @@ -108,14 +132,14 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc if (isNucleicType && !v.isCoarseBackbone) { // TODO find a cleaner way to swap normal and binormal for nucleic types for (let i = 0, il = binormalVectors.length; i < il; i++) binormalVectors[i] *= -1 - addRibbon(builderState, curvePoints, binormalVectors, normalVectors, linearSegments, heightValues, widthValues, 0) + addRibbon(builderState, curvePoints, binormalVectors, normalVectors, segmentCount, heightValues, widthValues, 0) } else { - addRibbon(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, widthValues, heightValues, 0) + addRibbon(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0) } } else if (radialSegments === 4) { - addSheet(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, widthValues, heightValues, 0, startCap, endCap) + addSheet(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0, startCap, endCap) } else { - addTube(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, radialSegments, widthValues, heightValues, 1, startCap, endCap) + addTube(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, radialSegments, widthValues, heightValues, 1, startCap, endCap) } } @@ -141,6 +165,7 @@ export function PolymerTraceVisual(materialId: number): UnitsVisual<PolymerTrace setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerTraceParams>, currentProps: PD.Values<PolymerTraceParams>, newTheme: Theme, currentTheme: Theme, newStructureGroup: StructureGroup, currentStructureGroup: StructureGroup) => { state.createGeometry = ( newProps.sizeFactor !== currentProps.sizeFactor || + newProps.detail !== currentProps.detail || newProps.linearSegments !== currentProps.linearSegments || newProps.radialSegments !== currentProps.radialSegments || newProps.aspectRatio !== currentProps.aspectRatio || diff --git a/src/mol-repr/structure/visual/polymer-tube-mesh.ts b/src/mol-repr/structure/visual/polymer-tube-mesh.ts index c015f2053cbf19dd1e914debe75e95909d5eee86..09e337366801f949b4732d77cbc953ba481d3fb6 100644 --- a/src/mol-repr/structure/visual/polymer-tube-mesh.ts +++ b/src/mol-repr/structure/visual/polymer-tube-mesh.ts @@ -10,29 +10,32 @@ import { Unit, Structure } 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 { createCurveSegmentState, PolymerTraceIterator, interpolateCurveSegment, interpolateSizes, PolymerLocationIterator, getPolymerElementLoci, eachPolymerElement } from './util/polymer'; -import { isNucleic } from '../../../mol-model/structure/model/types'; +import { createCurveSegmentState, PolymerTraceIterator, interpolateCurveSegment, interpolateSizes, PolymerLocationIterator, getPolymerElementLoci, eachPolymerElement, HelixTension, StandardTension, StandardShift, NucleicShift, OverhangFactor } from './util/polymer'; +import { isNucleic, SecondaryStructureType } from '../../../mol-model/structure/model/types'; import { addTube } from '../../../mol-geo/geometry/mesh/builder/tube'; import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual'; import { VisualUpdateState } from '../../util'; import { addSheet } from '../../../mol-geo/geometry/mesh/builder/sheet'; import { addRibbon } from '../../../mol-geo/geometry/mesh/builder/ribbon'; +import { Vec3 } from '../../../mol-math/linear-algebra'; +import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere'; export const PolymerTubeMeshParams = { sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }), + detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }), linearSegments: PD.Numeric(8, { min: 1, max: 48, step: 1 }), radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }), } export const DefaultPolymerTubeMeshProps = PD.getDefaultValues(PolymerTubeMeshParams) export type PolymerTubeMeshProps = typeof DefaultPolymerTubeMeshProps -// TODO handle polymer ends properly +const tmpV1 = Vec3() function createPolymerTubeMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerTubeMeshProps, mesh?: Mesh) { const polymerElementCount = unit.polymerElements.length if (!polymerElementCount) return Mesh.createEmpty(mesh) - const { sizeFactor, linearSegments, radialSegments } = props + const { sizeFactor, detail, linearSegments, radialSegments } = props const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2 const builderState = MeshBuilder.createState(vertexCount, vertexCount / 10, mesh) @@ -47,8 +50,9 @@ function createPolymerTubeMesh(ctx: VisualContext, unit: Unit, structure: Struct builderState.currentGroup = i const isNucleicType = isNucleic(v.moleculeType) - const tension = isNucleicType ? 0.5 : 0.9 - const shift = isNucleicType ? 0.3 : 0.5 + const isHelix = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Helix) + const tension = isHelix ? HelixTension : StandardTension + const shift = isNucleicType ? NucleicShift : StandardShift interpolateCurveSegment(state, v, tension, shift) @@ -61,12 +65,35 @@ function createPolymerTubeMesh(ctx: VisualContext, unit: Unit, structure: Struct interpolateSizes(state, s0, s1, s2, s0, s1, s2, shift) - if (radialSegments === 2) { - addRibbon(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, widthValues, heightValues, 0) + let segmentCount = linearSegments + if (v.initial) { + segmentCount = Math.max(Math.round(linearSegments * shift), 1) + const offset = linearSegments - segmentCount + curvePoints.copyWithin(0, offset * 3) + binormalVectors.copyWithin(0, offset * 3) + normalVectors.copyWithin(0, offset * 3) + widthValues.copyWithin(0, offset * 3) + heightValues.copyWithin(0, offset * 3) + Vec3.fromArray(tmpV1, curvePoints, 3) + Vec3.normalize(tmpV1, Vec3.sub(tmpV1, v.p2, tmpV1)) + Vec3.scaleAndAdd(tmpV1, v.p2, tmpV1, s1 * OverhangFactor) + Vec3.toArray(tmpV1, curvePoints, 0) + } else if (v.final) { + segmentCount = Math.max(Math.round(linearSegments * (1 - shift)), 1) + Vec3.fromArray(tmpV1, curvePoints, segmentCount * 3 - 3) + Vec3.normalize(tmpV1, Vec3.sub(tmpV1, v.p2, tmpV1)) + Vec3.scaleAndAdd(tmpV1, v.p2, tmpV1, s1 * OverhangFactor) + Vec3.toArray(tmpV1, curvePoints, segmentCount * 3) + } + + if (v.initial === true && v.final === true) { + addSphere(builderState, v.p2, s1 * 2, detail) + } else if (radialSegments === 2) { + addRibbon(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0) } else if (radialSegments === 4) { - addSheet(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, widthValues, heightValues, 0, startCap, endCap) + addSheet(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0, startCap, endCap) } else { - addTube(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, radialSegments, widthValues, heightValues, 1, startCap, endCap) + addTube(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, radialSegments, widthValues, heightValues, 1, startCap, endCap) } ++i @@ -91,6 +118,7 @@ export function PolymerTubeVisual(materialId: number): UnitsVisual<PolymerTubePa setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerTubeParams>, currentProps: PD.Values<PolymerTubeParams>) => { state.createGeometry = ( newProps.sizeFactor !== currentProps.sizeFactor || + newProps.detail !== currentProps.detail || newProps.linearSegments !== currentProps.linearSegments || newProps.radialSegments !== currentProps.radialSegments ) diff --git a/src/mol-repr/structure/visual/util/nucleotide.ts b/src/mol-repr/structure/visual/util/nucleotide.ts index 7bc2400ddb4609887f8e4b789cd9216d0484dcec..32762da65a417d56d80136d5c42aaecc127cce33 100644 --- a/src/mol-repr/structure/visual/util/nucleotide.ts +++ b/src/mol-repr/structure/visual/util/nucleotide.ts @@ -35,7 +35,7 @@ export function getNucleotideElementLoci(pickingId: PickingId, structureGroup: S const { structure, group } = structureGroup const unit = group.units[instanceId] if (Unit.isAtomic(unit)) { - return getResidueLoci(structure, unit, unit.polymerElements[groupId]) + return getResidueLoci(structure, unit, unit.nucleotideElements[groupId]) } } return EmptyLoci diff --git a/src/mol-repr/structure/visual/util/polymer.ts b/src/mol-repr/structure/visual/util/polymer.ts index 927bb2dc199f4a519265941ec3ad7fba6add65a8..5fac3e83b765cb38e0a351b4ebcb70c707ba4a14 100644 --- a/src/mol-repr/structure/visual/util/polymer.ts +++ b/src/mol-repr/structure/visual/util/polymer.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -18,6 +18,12 @@ export * from './polymer/gap-iterator' export * from './polymer/trace-iterator' export * from './polymer/curve-segment' +export const StandardTension = 0.5 +export const HelixTension = 0.9 +export const StandardShift = 0.5 +export const NucleicShift = 0.3 +export const OverhangFactor = 2 + export function getPolymerRanges(unit: Unit): SortedRanges<ElementIndex> { switch (unit.kind) { case Unit.Kind.Atomic: return unit.model.atomicHierarchy.polymerRanges diff --git a/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts b/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts index 390b63bad83e32b208c315f3f3dbac1f80d1f96b..889c57ab7f373a439e97868d032c1c3f5ab6fa1c 100644 --- a/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts +++ b/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts @@ -34,6 +34,7 @@ interface PolymerTraceElement { centerPrev: StructureElement.Location centerNext: StructureElement.Location first: boolean, last: boolean + initial: boolean, final: boolean secStrucFirst: boolean, secStrucLast: boolean secStrucType: SecondaryStructureType moleculeType: MoleculeType @@ -52,6 +53,7 @@ function createPolymerTraceElement (unit: Unit): PolymerTraceElement { centerPrev: StructureElement.Location.create(unit), centerNext: StructureElement.Location.create(unit), first: false, last: false, + initial: false, final: false, secStrucFirst: false, secStrucLast: false, secStrucType: SecStrucTypeNA, moleculeType: MoleculeType.unknown, @@ -223,6 +225,9 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement> const residueIndexNext2 = this.getResidueIndex(residueIndex + 2) const residueIndexNext3 = this.getResidueIndex(residueIndex + 3) + value.initial = residueIndex === residueIndexPrev1 + value.final = residueIndex === residueIndexNext1 + value.centerPrev.element = this.traceElementIndex[residueIndexPrev1] value.center.element = this.traceElementIndex[residueIndex] value.centerNext.element = this.traceElementIndex[residueIndexNext1]