Browse Source

handle polymer ends in visuals properly

Alexander Rose 5 years ago
parent
commit
defbadf4d7

+ 5 - 5
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 {

+ 2 - 1
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;

+ 2 - 2
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) {

+ 37 - 12
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 ||

+ 38 - 10
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
             )

+ 1 - 1
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

+ 7 - 1
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

+ 5 - 0
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]