Sfoglia il codice sorgente

refactored link/bond utils for visuals

Alexander Rose 5 anni fa
parent
commit
93bfa7a575

+ 22 - 9
src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -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 { createBondCylinderMesh, BondCylinderParams, BondIterator, ignoreBondType } from './util/bond';
+import { createLinkCylinderMesh, LinkCylinderStyle } from './util/link';
 import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../complex-visual';
 import { VisualUpdateState } from '../../util';
 import { PickingId } from '../../../mol-geo/geometry/picking';
@@ -19,6 +19,7 @@ import { EmptyLoci, Loci } from '../../../mol-model/loci';
 import { Interval, OrderedSet } from '../../../mol-data/int';
 import { isHydrogen } from './util/common';
 import { BondType } from '../../../mol-model/structure/model/types';
+import { ignoreBondType, BondCylinderParams, BondIterator } from './util/bond';
 
 const tmpRefPosBondIt = new Bond.ElementBondIterator()
 function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, index: StructureElement.UnitIndex) {
@@ -51,7 +52,7 @@ function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structur
     if (!edgeCount) return Mesh.createEmpty(mesh)
 
     const builderProps = {
-        bondCount: edgeCount,
+        linkCount: edgeCount,
         referencePosition: (edgeIndex: number) => {
             const b = edges[edgeIndex]
             let unitA: Unit, unitB: Unit
@@ -73,8 +74,20 @@ function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structur
             uA.conformation.position(uA.elements[b.indexA], posA)
             uB.conformation.position(uB.elements[b.indexB], posB)
         },
-        order: (edgeIndex: number) => edges[edgeIndex].props.order,
-        flags: (edgeIndex: number) => BitFlags.create(edges[edgeIndex].props.flag),
+        style: (edgeIndex: number) => {
+            const o = edges[edgeIndex].props.order
+            const f = BitFlags.create(edges[edgeIndex].props.flag)
+            if (BondType.is(f, BondType.Flag.MetallicCoordination) || BondType.is(f, BondType.Flag.HydrogenBond)) {
+                // show metall coordinations and hydrogen bonds with dashed cylinders
+                return LinkCylinderStyle.Dashed
+            } else if (o === 2) {
+                return LinkCylinderStyle.Double
+            } else if (o === 3) {
+                return LinkCylinderStyle.Triple
+            } else {
+                return LinkCylinderStyle.Solid
+            }
+        },
         radius: (edgeIndex: number) => {
             const b = edges[edgeIndex]
             tmpLoc.unit = b.unitA
@@ -88,7 +101,7 @@ function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structur
         ignore: (edgeIndex: number) => ignoreHydrogen(edgeIndex) || ignoreBondType(include, exclude, edges[edgeIndex].props.flag)
     }
 
-    return createBondCylinderMesh(ctx, builderProps, props, mesh)
+    return createLinkCylinderMesh(ctx, builderProps, props, mesh)
 }
 
 export const InterUnitBondParams = {
@@ -112,10 +125,10 @@ export function InterUnitBondVisual(materialId: number): ComplexVisual<InterUnit
                 newProps.sizeFactor !== currentProps.sizeFactor ||
                 newProps.sizeAspectRatio !== currentProps.sizeAspectRatio ||
                 newProps.radialSegments !== currentProps.radialSegments ||
-                newProps.bondScale !== currentProps.bondScale ||
-                newProps.bondSpacing !== currentProps.bondSpacing ||
+                newProps.linkScale !== currentProps.linkScale ||
+                newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
-                newProps.bondCap !== currentProps.bondCap ||
+                newProps.linkCap !== currentProps.linkCap ||
                 !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
                 !arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
             )

+ 22 - 9
src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
@@ -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 { BitFlags, arrayEqual } from '../../../mol-util';
-import { createBondCylinderMesh, BondCylinderParams, BondIterator, ignoreBondType } from './util/bond';
+import { createLinkCylinderMesh, LinkCylinderStyle } from './util/link';
 import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../units-visual';
 import { VisualUpdateState } from '../../util';
 import { PickingId } from '../../../mol-geo/geometry/picking';
@@ -20,6 +20,7 @@ import { EmptyLoci, Loci } from '../../../mol-model/loci';
 import { Interval, OrderedSet } from '../../../mol-data/int';
 import { isHydrogen } from './util/common';
 import { BondType } from '../../../mol-model/structure/model/types';
+import { ignoreBondType, BondCylinderParams, BondIterator } from './util/bond';
 
 function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondParams>, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
@@ -45,7 +46,7 @@ function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structu
     const pos = unit.conformation.invariantPosition
 
     const builderProps = {
-        bondCount: edgeCount * 2,
+        linkCount: edgeCount * 2,
         referencePosition: (edgeIndex: number) => {
             let aI = a[edgeIndex], bI = b[edgeIndex];
 
@@ -67,8 +68,20 @@ function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structu
             pos(elements[a[edgeIndex]], posA)
             pos(elements[b[edgeIndex]], posB)
         },
-        order: (edgeIndex: number) => _order[edgeIndex],
-        flags: (edgeIndex: number) => BitFlags.create(_flags[edgeIndex]),
+        style: (edgeIndex: number) => {
+            const o = _order[edgeIndex]
+            const f = BitFlags.create(_flags[edgeIndex])
+            if (BondType.is(f, BondType.Flag.MetallicCoordination) || BondType.is(f, BondType.Flag.HydrogenBond)) {
+                // show metall coordinations and hydrogen bonds with dashed cylinders
+                return LinkCylinderStyle.Dashed
+            } else if (o === 2) {
+                return LinkCylinderStyle.Double
+            } else if (o === 3) {
+                return LinkCylinderStyle.Triple
+            } else {
+                return LinkCylinderStyle.Solid
+            }
+        },
         radius: (edgeIndex: number) => {
             location.element = elements[a[edgeIndex]]
             const sizeA = theme.size.size(location)
@@ -79,7 +92,7 @@ function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structu
         ignore: (edgeIndex: number) => ignoreHydrogen(edgeIndex) || ignoreBondType(include, exclude, _flags[edgeIndex])
     }
 
-    return createBondCylinderMesh(ctx, builderProps, props, mesh)
+    return createLinkCylinderMesh(ctx, builderProps, props, mesh)
 }
 
 export const IntraUnitBondParams = {
@@ -103,10 +116,10 @@ export function IntraUnitBondVisual(materialId: number): UnitsVisual<IntraUnitBo
                 newProps.sizeFactor !== currentProps.sizeFactor ||
                 newProps.sizeAspectRatio !== currentProps.sizeAspectRatio ||
                 newProps.radialSegments !== currentProps.radialSegments ||
-                newProps.bondScale !== currentProps.bondScale ||
-                newProps.bondSpacing !== currentProps.bondSpacing ||
+                newProps.linkScale !== currentProps.linkScale ||
+                newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
-                newProps.bondCap !== currentProps.bondCap ||
+                newProps.linkCap !== currentProps.linkCap ||
                 !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
                 !arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
             )

+ 6 - 12
src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -7,11 +7,9 @@
 import { Structure, Bond, StructureElement } from '../../../mol-model/structure';
 import { Loci, EmptyLoci } from '../../../mol-model/loci';
 import { Vec3 } from '../../../mol-math/linear-algebra';
-import { createBondCylinderMesh, BondCylinderParams } from './util/bond';
+import { createLinkCylinderMesh, LinkCylinderParams } from './util/link';
 import { OrderedSet, Interval } from '../../../mol-data/int';
 import { ComplexMeshVisual, ComplexVisual } from '../complex-visual';
-import { BondType } from '../../../mol-model/structure/model/types';
-import { BitFlags } from '../../../mol-util';
 import { UnitsMeshParams } from '../units-visual';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
@@ -28,30 +26,26 @@ function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure: Struc
     const location = StructureElement.Location.create()
 
     const builderProps = {
-        bondCount: links.length,
-        referencePosition: (edgeIndex: number) => null,
+        linkCount: links.length,
         position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
             const l = links[edgeIndex]
             Vec3.copy(posA, elements[l.carbohydrateIndexA].geometry.center)
             Vec3.copy(posB, elements[l.carbohydrateIndexB].geometry.center)
         },
-        order: (edgeIndex: number) => 1,
-        flags: (edgeIndex: number) => BitFlags.create(BondType.Flag.None),
         radius: (edgeIndex: number) => {
             const l = links[edgeIndex]
             location.unit = elements[l.carbohydrateIndexA].unit
             location.element = elements[l.carbohydrateIndexA].anomericCarbon
             return theme.size.size(location) * linkSizeFactor
         },
-        ignore: (edgeIndex: number) => false
     }
 
-    return createBondCylinderMesh(ctx, builderProps, props, mesh)
+    return createLinkCylinderMesh(ctx, builderProps, props, mesh)
 }
 
 export const CarbohydrateLinkParams = {
     ...UnitsMeshParams,
-    ...BondCylinderParams,
+    ...LinkCylinderParams,
     linkSizeFactor: PD.Numeric(0.3, { min: 0, max: 3, step: 0.01 }),
 }
 export type CarbohydrateLinkParams = typeof CarbohydrateLinkParams
@@ -67,7 +61,7 @@ export function CarbohydrateLinkVisual(materialId: number): ComplexVisual<Carboh
             state.createGeometry = (
                 newProps.linkSizeFactor !== currentProps.linkSizeFactor ||
                 newProps.radialSegments !== currentProps.radialSegments ||
-                newProps.bondCap !== currentProps.bondCap
+                newProps.linkCap !== currentProps.linkCap
             )
         }
     }, materialId)

+ 12 - 17
src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -10,9 +10,7 @@ import { Structure, StructureElement, Bond } from '../../../mol-model/structure'
 import { Theme } from '../../../mol-theme/theme';
 import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
 import { Vec3 } from '../../../mol-math/linear-algebra';
-import { BitFlags } from '../../../mol-util';
-import { BondType } from '../../../mol-model/structure/model/types';
-import { createBondCylinderMesh, BondCylinderParams } from './util/bond';
+import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from './util/link';
 import { UnitsMeshParams } from '../units-visual';
 import { ComplexVisual, ComplexMeshVisual } from '../complex-visual';
 import { VisualUpdateState } from '../../util';
@@ -29,8 +27,7 @@ function createCarbohydrateTerminalLinkCylinderMesh(ctx: VisualContext, structur
     const location = StructureElement.Location.create()
 
     const builderProps = {
-        bondCount: terminalLinks.length,
-        referencePosition: (edgeIndex: number) => null,
+        linkCount: terminalLinks.length,
         position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
             const l = terminalLinks[edgeIndex]
             if (l.fromCarbohydrate) {
@@ -41,13 +38,6 @@ function createCarbohydrateTerminalLinkCylinderMesh(ctx: VisualContext, structur
                 Vec3.copy(posB, elements[l.carbohydrateIndex].geometry.center)
             }
         },
-        order: (edgeIndex: number) => 1,
-        flags: (edgeIndex: number) => {
-            const l = terminalLinks[edgeIndex]
-            const eI = l.elementUnit.elements[l.elementIndex]
-            const beI = getElementIdx(l.elementUnit.model.atomicHierarchy.atoms.type_symbol.value(eI));
-            return BitFlags.create(MetalsSet.has(beI) ? BondType.Flag.MetallicCoordination : BondType.Flag.None);
-        },
         radius: (edgeIndex: number) => {
             const l = terminalLinks[edgeIndex]
             if (l.fromCarbohydrate) {
@@ -59,15 +49,20 @@ function createCarbohydrateTerminalLinkCylinderMesh(ctx: VisualContext, structur
             }
             return theme.size.size(location) * terminalLinkSizeFactor
         },
-        ignore: (edgeIndex: number) => false
+        style: (edgeIndex: number) => {
+            const l = terminalLinks[edgeIndex]
+            const eI = l.elementUnit.elements[l.elementIndex]
+            const beI = getElementIdx(l.elementUnit.model.atomicHierarchy.atoms.type_symbol.value(eI));
+            return MetalsSet.has(beI) ? LinkCylinderStyle.Dashed : LinkCylinderStyle.Solid
+        }
     }
 
-    return createBondCylinderMesh(ctx, builderProps, props, mesh)
+    return createLinkCylinderMesh(ctx, builderProps, props, mesh)
 }
 
 export const CarbohydrateTerminalLinkParams = {
     ...UnitsMeshParams,
-    ...BondCylinderParams,
+    ...LinkCylinderParams,
     terminalLinkSizeFactor: PD.Numeric(0.2, { min: 0, max: 3, step: 0.01 }),
 }
 export type CarbohydrateTerminalLinkParams = typeof CarbohydrateTerminalLinkParams
@@ -83,7 +78,7 @@ export function CarbohydrateTerminalLinkVisual(materialId: number): ComplexVisua
             state.createGeometry = (
                 newProps.terminalLinkSizeFactor !== currentProps.terminalLinkSizeFactor ||
                 newProps.radialSegments !== currentProps.radialSegments ||
-                newProps.bondCap !== currentProps.bondCap
+                newProps.linkCap !== currentProps.linkCap
             )
         }
     }, materialId)

+ 6 - 12
src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -10,9 +10,7 @@ import { Structure, StructureElement, Bond } from '../../../mol-model/structure'
 import { Theme } from '../../../mol-theme/theme';
 import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
 import { Vec3 } from '../../../mol-math/linear-algebra';
-import { BitFlags } from '../../../mol-util';
-import { BondType } from '../../../mol-model/structure/model/types';
-import { createBondCylinderMesh, BondCylinderParams } from './util/bond';
+import { createLinkCylinderMesh, LinkCylinderParams } from './util/link';
 import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../complex-visual';
 import { VisualUpdateState } from '../../util';
 import { LocationIterator } from '../../../mol-geo/util/location-iterator';
@@ -29,31 +27,27 @@ function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Str
     const location = StructureElement.Location.create()
 
     const builderProps = {
-        bondCount: crossLinks.count,
-        referencePosition: () => null,
+        linkCount: crossLinks.count,
         position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
             const b = crossLinks.pairs[edgeIndex]
             const uA = b.unitA, uB = b.unitB
             uA.conformation.position(uA.elements[b.indexA], posA)
             uB.conformation.position(uB.elements[b.indexB], posB)
         },
-        order: () => 1,
-        flags: () => BitFlags.create(BondType.Flag.None),
         radius: (edgeIndex: number) => {
             const b = crossLinks.pairs[edgeIndex]
             location.unit = b.unitA
             location.element = b.unitA.elements[b.indexA]
             return theme.size.size(location) * sizeFactor
         },
-        ignore: () => false
     }
 
-    return createBondCylinderMesh(ctx, builderProps, props, mesh)
+    return createLinkCylinderMesh(ctx, builderProps, props, mesh)
 }
 
 export const CrossLinkRestraintParams = {
     ...ComplexMeshParams,
-    ...BondCylinderParams,
+    ...LinkCylinderParams,
     sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
 }
 export type CrossLinkRestraintParams = typeof CrossLinkRestraintParams
@@ -69,7 +63,7 @@ export function CrossLinkRestraintVisual(materialId: number): ComplexVisual<Cros
             state.createGeometry = (
                 newProps.sizeFactor !== currentProps.sizeFactor ||
                 newProps.radialSegments !== currentProps.radialSegments ||
-                newProps.bondCap !== currentProps.bondCap
+                newProps.linkCap !== currentProps.linkCap
             )
         }
     }, materialId)

+ 6 - 9
src/mol-repr/structure/visual/interactions-inter-unit-cylinder.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -10,13 +10,12 @@ import { Structure, StructureElement } from '../../../mol-model/structure';
 import { Theme } from '../../../mol-theme/theme';
 import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
 import { Vec3 } from '../../../mol-math/linear-algebra';
-import { createBondCylinderMesh, BondCylinderParams } from './util/bond';
+import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from './util/link';
 import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../complex-visual';
 import { VisualUpdateState } from '../../util';
 import { PickingId } from '../../../mol-geo/geometry/picking';
 import { EmptyLoci, Loci } from '../../../mol-model/loci';
 import { Interval } from '../../../mol-data/int';
-import { BondType } from '../../../mol-model/structure/model/types';
 import { Interactions } from '../../../mol-model-props/computed/interactions/interactions';
 import { InteractionsProvider } from '../../../mol-model-props/computed/interactions';
 import { LocationIterator } from '../../../mol-geo/util/location-iterator';
@@ -36,8 +35,7 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
     if (!edgeCount) return Mesh.createEmpty(mesh)
 
     const builderProps = {
-        bondCount: edgeCount,
-        referencePosition: () => null,
+        linkCount: edgeCount,
         position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
             const { unitA, indexA, unitB, indexB } = edges[edgeIndex]
             const fA = unitsFeatures.get(unitA.id)
@@ -47,8 +45,7 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
             Vec3.set(posB, fB.x[indexB], fB.y[indexB], fB.z[indexB])
             Vec3.transformMat4(posB, posB, unitB.conformation.operator.matrix)
         },
-        order: (edgeIndex: number) => 1,
-        flags: (edgeIndex: number) => BondType.Flag.MetallicCoordination, // TODO
+        style: (edgeIndex: number) => LinkCylinderStyle.Dashed,
         radius: (edgeIndex: number) => {
             const b = edges[edgeIndex]
             const fA = unitsFeatures.get(b.unitA.id)
@@ -64,12 +61,12 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
         ignore: (edgeIndex: number) => edges[edgeIndex].props.flag === InteractionFlag.Filtered
     }
 
-    return createBondCylinderMesh(ctx, builderProps, props, mesh)
+    return createLinkCylinderMesh(ctx, builderProps, props, mesh)
 }
 
 export const InteractionsInterUnitParams = {
     ...ComplexMeshParams,
-    ...BondCylinderParams,
+    ...LinkCylinderParams,
     sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
 }
 export type InteractionsInterUnitParams = typeof InteractionsInterUnitParams

+ 6 - 9
src/mol-repr/structure/visual/interactions-intra-unit-cylinder.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -13,9 +13,8 @@ import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
 import { PickingId } from '../../../mol-geo/geometry/picking';
 import { VisualContext } from '../../visual';
 import { Theme } from '../../../mol-theme/theme';
-import { BondType } from '../../../mol-model/structure/model/types';
 import { InteractionsProvider } from '../../../mol-model-props/computed/interactions';
-import { createBondCylinderMesh, BondCylinderParams } from './util/bond';
+import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from './util/link';
 import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../units-visual';
 import { VisualUpdateState } from '../../util';
 import { LocationIterator } from '../../../mol-geo/util/location-iterator';
@@ -39,16 +38,14 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
     if (!edgeCount) return Mesh.createEmpty(mesh)
 
     const builderProps = {
-        bondCount: edgeCount * 2,
-        referencePosition: () => null,
+        linkCount: edgeCount * 2,
         position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
             Vec3.set(posA, x[a[edgeIndex]], y[a[edgeIndex]], z[a[edgeIndex]])
             Vec3.transformMat4(posA, posA, matrix)
             Vec3.set(posB, x[b[edgeIndex]], y[b[edgeIndex]], z[b[edgeIndex]])
             Vec3.transformMat4(posB, posB, matrix)
         },
-        order: (edgeIndex: number) => 1,
-        flags: (edgeIndex: number) => BondType.Flag.MetallicCoordination, // TODO
+        style: (edgeIndex: number) => LinkCylinderStyle.Dashed,
         radius: (edgeIndex: number) => {
             location.element = unit.elements[members[offsets[a[edgeIndex]]]]
             const sizeA = theme.size.size(location)
@@ -59,12 +56,12 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
         ignore: (edgeIndex: number) => flag[edgeIndex] === InteractionFlag.Filtered
     }
 
-    return createBondCylinderMesh(ctx, builderProps, props, mesh)
+    return createLinkCylinderMesh(ctx, builderProps, props, mesh)
 }
 
 export const InteractionsIntraUnitParams = {
     ...UnitsMeshParams,
-    ...BondCylinderParams,
+    ...LinkCylinderParams,
     sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
 }
 export type InteractionsIntraUnitParams = typeof InteractionsIntraUnitParams

+ 3 - 113
src/mol-repr/structure/visual/util/bond.ts

@@ -1,26 +1,18 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 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 { BondType } from '../../../../mol-model/structure/model/types';
 import { Unit, StructureElement, Structure, Bond } from '../../../../mol-model/structure';
 import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
-import { Mesh } from '../../../../mol-geo/geometry/mesh/mesh';
-import { MeshBuilder } from '../../../../mol-geo/geometry/mesh/mesh-builder';
-import { CylinderProps } from '../../../../mol-geo/primitive/cylinder';
-import { addFixedCountDashedCylinder, addCylinder, addDoubleCylinder } from '../../../../mol-geo/geometry/mesh/builder/cylinder';
 import { LocationIterator } from '../../../../mol-geo/util/location-iterator';
-import { VisualContext } from '../../../visual';
 import { StructureGroup } from '../../units-visual';
+import { LinkCylinderParams } from './link';
 
 export const BondCylinderParams = {
-    bondScale: PD.Numeric(0.4, { min: 0, max: 1, step: 0.1 }),
-    bondSpacing: PD.Numeric(1, { min: 0, max: 2, step: 0.01 }),
-    bondCap: PD.Boolean(false),
-    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }),
+    ...LinkCylinderParams,
     includeTypes: PD.MultiSelect(Object.keys(BondType.Names) as BondType.Names[], PD.objectToOptions(BondType.Names)),
     excludeTypes: PD.MultiSelect([] as BondType.Names[], PD.objectToOptions(BondType.Names)),
 }
@@ -31,108 +23,6 @@ export function ignoreBondType(include: BondType.Flag, exclude: BondType.Flag, f
     return !BondType.is(include, f) || BondType.is(exclude, f)
 }
 
-const tmpShiftV12 = Vec3.zero()
-const tmpShiftV13 = Vec3.zero()
-
-/** Calculate 'shift' direction that is perpendiculat to v1 - v2 and goes through v3 */
-export function calculateShiftDir (out: Vec3, v1: Vec3, v2: Vec3, v3: Vec3 | null) {
-    Vec3.normalize(tmpShiftV12, Vec3.sub(tmpShiftV12, v1, v2))
-    if (v3 !== null) {
-        Vec3.sub(tmpShiftV13, v1, v3)
-    } else {
-        Vec3.copy(tmpShiftV13, v1)  // no reference point, use v1
-    }
-    Vec3.normalize(tmpShiftV13, tmpShiftV13)
-
-    // ensure v13 and v12 are not colinear
-    let dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
-    if (1 - Math.abs(dp) < 1e-5) {
-        Vec3.set(tmpShiftV13, 1, 0, 0)
-        dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
-        if (1 - Math.abs(dp) < 1e-5) {
-            Vec3.set(tmpShiftV13, 0, 1, 0)
-            dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
-        }
-    }
-
-    Vec3.setMagnitude(tmpShiftV12, tmpShiftV12, dp)
-    Vec3.sub(tmpShiftV13, tmpShiftV13, tmpShiftV12)
-    return Vec3.normalize(out, tmpShiftV13)
-}
-
-export interface BondCylinderMeshBuilderProps {
-    bondCount: number
-    referencePosition(edgeIndex: number): Vec3 | null
-    position(posA: Vec3, posB: Vec3, edgeIndex: number): void
-    order(edgeIndex: number): number
-    flags(edgeIndex: number): BondType
-    radius(edgeIndex: number): number
-    ignore(edgeIndex: number): boolean
-}
-
-/**
- * Each edge is included twice to allow for coloring/picking
- * the half closer to the first vertex, i.e. vertex a.
- */
-export function createBondCylinderMesh(ctx: VisualContext, bondBuilder: BondCylinderMeshBuilderProps, props: BondCylinderProps, mesh?: Mesh) {
-    const { bondCount, referencePosition, position, order, flags, radius, ignore } = bondBuilder
-
-    if (!bondCount) return Mesh.createEmpty(mesh)
-
-    const { bondScale, bondSpacing, radialSegments, bondCap } = props
-
-    const vertexCountEstimate = radialSegments * 2 * bondCount * 2
-    const builderState = MeshBuilder.createState(vertexCountEstimate, vertexCountEstimate / 4, mesh)
-
-    const va = Vec3.zero()
-    const vb = Vec3.zero()
-    const vShift = Vec3.zero()
-    const cylinderProps: CylinderProps = {
-        radiusTop: 1,
-        radiusBottom: 1,
-        radialSegments,
-        topCap: bondCap,
-        bottomCap: bondCap
-    }
-
-    for (let edgeIndex = 0, _eI = bondCount; edgeIndex < _eI; ++edgeIndex) {
-        if (ignore(edgeIndex)) continue
-
-        position(va, vb, edgeIndex)
-
-        const linkRadius = radius(edgeIndex)
-        const o = order(edgeIndex)
-        const f = flags(edgeIndex)
-        builderState.currentGroup = edgeIndex
-
-        if (BondType.is(f, BondType.Flag.MetallicCoordination) || BondType.is(f, BondType.Flag.HydrogenBond)) {
-            // show metall coordinations and hydrogen bonds with dashed cylinders
-            cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius / 3
-            cylinderProps.topCap = cylinderProps.bottomCap = true
-            addFixedCountDashedCylinder(builderState, va, vb, 0.5, 7, cylinderProps)
-        } else if (o === 2 || o === 3) {
-            // show bonds with order 2 or 3 using 2 or 3 parallel cylinders
-            const multiRadius = linkRadius * (bondScale / (0.5 * o))
-            const absOffset = (linkRadius - multiRadius) * bondSpacing
-
-            calculateShiftDir(vShift, va, vb, referencePosition(edgeIndex))
-            Vec3.setMagnitude(vShift, vShift, absOffset)
-
-            cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius
-            cylinderProps.topCap = cylinderProps.bottomCap = bondCap
-
-            if (o === 3) addCylinder(builderState, va, vb, 0.5, cylinderProps)
-            addDoubleCylinder(builderState, va, vb, 0.5, vShift, cylinderProps)
-        } else {
-            cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius
-            cylinderProps.topCap = cylinderProps.bottomCap = bondCap
-            addCylinder(builderState, va, vb, 0.5, cylinderProps)
-        }
-    }
-
-    return MeshBuilder.getMesh(builderState)
-}
-
 export namespace BondIterator {
     export function fromGroup(structureGroup: StructureGroup): LocationIterator {
         const { group } = structureGroup

+ 128 - 0
src/mol-repr/structure/visual/util/link.ts

@@ -0,0 +1,128 @@
+/**
+ * Copyright (c) 2018-2020 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 { ParamDefinition as PD } from '../../../../mol-util/param-definition';
+import { Mesh } from '../../../../mol-geo/geometry/mesh/mesh';
+import { MeshBuilder } from '../../../../mol-geo/geometry/mesh/mesh-builder';
+import { CylinderProps } from '../../../../mol-geo/primitive/cylinder';
+import { addFixedCountDashedCylinder, addCylinder, addDoubleCylinder } from '../../../../mol-geo/geometry/mesh/builder/cylinder';
+import { VisualContext } from '../../../visual';
+
+export const LinkCylinderParams = {
+    linkScale: PD.Numeric(0.4, { min: 0, max: 1, step: 0.1 }),
+    linkSpacing: PD.Numeric(1, { min: 0, max: 2, step: 0.01 }),
+    linkCap: PD.Boolean(false),
+    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }),
+}
+export const DefaultLinkCylinderProps = PD.getDefaultValues(LinkCylinderParams)
+export type LinkCylinderProps = typeof DefaultLinkCylinderProps
+
+const tmpShiftV12 = Vec3.zero()
+const tmpShiftV13 = Vec3.zero()
+
+/** Calculate 'shift' direction that is perpendiculat to v1 - v2 and goes through v3 */
+export function calculateShiftDir (out: Vec3, v1: Vec3, v2: Vec3, v3: Vec3 | null) {
+    Vec3.normalize(tmpShiftV12, Vec3.sub(tmpShiftV12, v1, v2))
+    if (v3 !== null) {
+        Vec3.sub(tmpShiftV13, v1, v3)
+    } else {
+        Vec3.copy(tmpShiftV13, v1)  // no reference point, use v1
+    }
+    Vec3.normalize(tmpShiftV13, tmpShiftV13)
+
+    // ensure v13 and v12 are not colinear
+    let dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
+    if (1 - Math.abs(dp) < 1e-5) {
+        Vec3.set(tmpShiftV13, 1, 0, 0)
+        dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
+        if (1 - Math.abs(dp) < 1e-5) {
+            Vec3.set(tmpShiftV13, 0, 1, 0)
+            dp = Vec3.dot(tmpShiftV12, tmpShiftV13)
+        }
+    }
+
+    Vec3.setMagnitude(tmpShiftV12, tmpShiftV12, dp)
+    Vec3.sub(tmpShiftV13, tmpShiftV13, tmpShiftV12)
+    return Vec3.normalize(out, tmpShiftV13)
+}
+
+export interface LinkCylinderMeshBuilderProps {
+    linkCount: number
+    position: (posA: Vec3, posB: Vec3, edgeIndex: number) => void
+    radius: (edgeIndex: number) => number
+    referencePosition?: (edgeIndex: number) => Vec3 | null
+    style?: (edgeIndex: number) => LinkCylinderStyle
+    ignore?: (edgeIndex: number) => boolean
+}
+
+export enum LinkCylinderStyle {
+    Solid = 0,
+    Dashed = 1,
+    Double = 2,
+    Triple = 3
+}
+
+/**
+ * Each edge is included twice to allow for coloring/picking
+ * the half closer to the first vertex, i.e. vertex a.
+ */
+export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkCylinderMeshBuilderProps, props: LinkCylinderProps, mesh?: Mesh) {
+    const { linkCount, referencePosition, position, style, radius, ignore } = linkBuilder
+
+    if (!linkCount) return Mesh.createEmpty(mesh)
+
+    const { linkScale, linkSpacing, radialSegments, linkCap } = props
+
+    const vertexCountEstimate = radialSegments * 2 * linkCount * 2
+    const builderState = MeshBuilder.createState(vertexCountEstimate, vertexCountEstimate / 4, mesh)
+
+    const va = Vec3.zero()
+    const vb = Vec3.zero()
+    const vShift = Vec3.zero()
+    const cylinderProps: CylinderProps = {
+        radiusTop: 1,
+        radiusBottom: 1,
+        radialSegments,
+        topCap: linkCap,
+        bottomCap: linkCap
+    }
+
+    for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
+        if (ignore && ignore(edgeIndex)) continue
+
+        position(va, vb, edgeIndex)
+
+        const linkRadius = radius(edgeIndex)
+        const linkStyle = style ? style(edgeIndex) : LinkCylinderStyle.Solid
+        builderState.currentGroup = edgeIndex
+
+        if (linkStyle === LinkCylinderStyle.Dashed) {
+            cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius / 3
+            cylinderProps.topCap = cylinderProps.bottomCap = true
+            addFixedCountDashedCylinder(builderState, va, vb, 0.5, 7, cylinderProps)
+        } else if (linkStyle === LinkCylinderStyle.Double || linkStyle === LinkCylinderStyle.Triple) {
+            const order = LinkCylinderStyle.Double ? 2 : 3
+            const multiRadius = linkRadius * (linkScale / (0.5 * order))
+            const absOffset = (linkRadius - multiRadius) * linkSpacing
+
+            calculateShiftDir(vShift, va, vb, referencePosition ? referencePosition(edgeIndex) : null)
+            Vec3.setMagnitude(vShift, vShift, absOffset)
+
+            cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius
+            cylinderProps.topCap = cylinderProps.bottomCap = linkCap
+
+            if (order === 3) addCylinder(builderState, va, vb, 0.5, cylinderProps)
+            addDoubleCylinder(builderState, va, vb, 0.5, vShift, cylinderProps)
+        } else {
+            cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius
+            cylinderProps.topCap = cylinderProps.bottomCap = linkCap
+            addCylinder(builderState, va, vb, 0.5, cylinderProps)
+        }
+    }
+
+    return MeshBuilder.getMesh(builderState)
+}