Browse Source

cylinder impostors for bonds

- inter/intra bonds
- ball & stick, ellipsoids
- new link visual helper
Alexander Rose 4 years ago
parent
commit
befa5174f8

+ 21 - 2
src/mol-repr/structure/complex-visual.ts

@@ -25,13 +25,14 @@ import { Mat4 } from '../../mol-math/linear-algebra';
 import { Overpaint } from '../../mol-theme/overpaint';
 import { Transparency } from '../../mol-theme/transparency';
 import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
+import { Cylinders } from '../../mol-geo/geometry/cylinders/cylinders';
+import { Lines } from '../../mol-geo/geometry/lines/lines';
 import { Text } from '../../mol-geo/geometry/text/text';
 import { SizeTheme } from '../../mol-theme/size';
 import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume';
 import { createMarkers } from '../../mol-geo/geometry/marker-data';
-import { StructureParams, StructureMeshParams, StructureTextParams, StructureDirectVolumeParams, StructureLinesParams } from './params';
+import { StructureParams, StructureMeshParams, StructureTextParams, StructureDirectVolumeParams, StructureLinesParams, StructureCylindersParams } from './params';
 import { Clipping } from '../../mol-theme/clipping';
-import { Lines } from '../../mol-geo/geometry/lines/lines';
 
 export interface  ComplexVisual<P extends StructureParams> extends Visual<Structure, P> { }
 
@@ -261,6 +262,24 @@ export function ComplexMeshVisual<P extends ComplexMeshParams>(builder: ComplexM
     }, materialId);
 }
 
+// cylinders
+
+export const ComplexCylindersParams = { ...StructureCylindersParams, ...StructureParams };
+export type ComplexCylindersParams = typeof ComplexCylindersParams
+
+export interface ComplexCylindersVisualBuilder<P extends ComplexCylindersParams> extends ComplexVisualBuilder<P, Cylinders> { }
+
+export function ComplexCylindersVisual<P extends ComplexCylindersParams>(builder: ComplexCylindersVisualBuilder<P>, materialId: number): ComplexVisual<P> {
+    return ComplexVisual<Cylinders, P>({
+        ...builder,
+        setUpdateState: (state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure) => {
+            builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme, newStructure, currentStructure);
+            if (!SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.updateSize = true;
+        },
+        geometryUtils: Cylinders.Utils
+    }, materialId);
+}
+
 // lines
 
 export const ComplexLinesParams = { ...StructureLinesParams, ...StructureParams };

+ 4 - 1
src/mol-repr/structure/params.ts

@@ -10,6 +10,7 @@ import { Lines } from '../../mol-geo/geometry/lines/lines';
 import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
 import { Points } from '../../mol-geo/geometry/points/points';
 import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
+import { Cylinders } from '../../mol-geo/geometry/cylinders/cylinders';
 import { Text } from '../../mol-geo/geometry/text/text';
 import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
@@ -30,10 +31,12 @@ export type StructureMeshParams = typeof StructureMeshParams
 export const StructureSpheresParams = { ...Spheres.Params };
 export type StructureSpheresParams = typeof StructureSpheresParams
 
+export const StructureCylindersParams = { ...Cylinders.Params };
+export type StructureCylindersParams = typeof StructureCylindersParams
+
 export const StructurePointsParams = { ...Points.Params };
 export type StructurePointsParams = typeof StructurePointsParams
 
-
 export const StructureLinesParams = { ...Lines.Params };
 export type StructureLinesParams = typeof StructureLinesParams
 

+ 5 - 5
src/mol-repr/structure/representation/ball-and-stick.ts

@@ -1,12 +1,12 @@
 /**
- * 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>
  */
 
 import { getElementSphereVisual, ElementSphereParams } from '../visual/element-sphere';
-import { IntraUnitBondCylinderVisual, IntraUnitBondCylinderParams } from '../visual/bond-intra-unit-cylinder';
-import { InterUnitBondCylinderVisual, InterUnitBondCylinderParams } from '../visual/bond-inter-unit-cylinder';
+import { getIntraUnitBondCylinderVisual, IntraUnitBondCylinderParams } from '../visual/bond-intra-unit-cylinder';
+import { InterUnitBondCylinderParams, getInterUnitBondCylinderVisual } from '../visual/bond-inter-unit-cylinder';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { UnitsRepresentation } from '../units-representation';
 import { ComplexRepresentation } from '../complex-representation';
@@ -18,8 +18,8 @@ import { getUnitKindsParam } from '../params';
 
 const BallAndStickVisuals = {
     'element-sphere': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, ElementSphereParams>) => UnitsRepresentation('Element sphere mesh', ctx, getParams, getElementSphereVisual(ctx.webgl)),
-    'intra-bond': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, IntraUnitBondCylinderParams>) => UnitsRepresentation('Intra-unit bond cylinder', ctx, getParams, IntraUnitBondCylinderVisual),
-    'inter-bond': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, InterUnitBondCylinderParams>) => ComplexRepresentation('Inter-unit bond cylinder', ctx, getParams, InterUnitBondCylinderVisual),
+    'intra-bond': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, IntraUnitBondCylinderParams>) => UnitsRepresentation('Intra-unit bond cylinder', ctx, getParams, getIntraUnitBondCylinderVisual(ctx.webgl)),
+    'inter-bond': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, InterUnitBondCylinderParams>) => ComplexRepresentation('Inter-unit bond cylinder', ctx, getParams, getInterUnitBondCylinderVisual(ctx.webgl)),
 };
 
 export const BallAndStickParams = {

+ 5 - 5
src/mol-repr/structure/representation/ellipsoid.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>
  */
@@ -11,14 +11,14 @@ import { Structure } from '../../../mol-model/structure';
 import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
 import { EllipsoidMeshParams, EllipsoidMeshVisual } from '../visual/ellipsoid-mesh';
 import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/property/anisotropic';
-import { IntraUnitBondCylinderParams, IntraUnitBondCylinderVisual } from '../visual/bond-intra-unit-cylinder';
-import { InterUnitBondCylinderParams, InterUnitBondCylinderVisual } from '../visual/bond-inter-unit-cylinder';
+import { IntraUnitBondCylinderParams, getIntraUnitBondCylinderVisual } from '../visual/bond-intra-unit-cylinder';
+import { getInterUnitBondCylinderVisual, InterUnitBondCylinderParams } from '../visual/bond-inter-unit-cylinder';
 import { getUnitKindsParam } from '../params';
 
 const EllipsoidVisuals = {
     'ellipsoid-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, EllipsoidMeshParams>) => UnitsRepresentation('Ellipsoid Mesh', ctx, getParams, EllipsoidMeshVisual),
-    'intra-bond': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, IntraUnitBondCylinderParams>) => UnitsRepresentation('Intra-unit bond cylinder', ctx, getParams, IntraUnitBondCylinderVisual),
-    'inter-bond': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, InterUnitBondCylinderParams>) => ComplexRepresentation('Inter-unit bond cylinder', ctx, getParams, InterUnitBondCylinderVisual),
+    'intra-bond': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, IntraUnitBondCylinderParams>) => UnitsRepresentation('Intra-unit bond cylinder', ctx, getParams, getIntraUnitBondCylinderVisual(ctx.webgl)),
+    'inter-bond': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, InterUnitBondCylinderParams>) => ComplexRepresentation('Inter-unit bond cylinder', ctx, getParams, getInterUnitBondCylinderVisual(ctx.webgl)),
 };
 
 export const EllipsoidParams = {

+ 19 - 1
src/mol-repr/structure/units-visual.ts

@@ -29,13 +29,14 @@ import { Transparency } from '../../mol-theme/transparency';
 import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
 import { SizeTheme } from '../../mol-theme/size';
 import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
+import { Cylinders } from '../../mol-geo/geometry/cylinders/cylinders';
 import { Points } from '../../mol-geo/geometry/points/points';
 import { Lines } from '../../mol-geo/geometry/lines/lines';
 import { Text } from '../../mol-geo/geometry/text/text';
 import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume';
 import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
 import { SizeValues } from '../../mol-gl/renderable/schema';
-import { StructureParams, StructureMeshParams, StructureSpheresParams, StructurePointsParams, StructureLinesParams, StructureTextParams, StructureDirectVolumeParams, StructureTextureMeshParams } from './params';
+import { StructureParams, StructureMeshParams, StructureSpheresParams, StructurePointsParams, StructureLinesParams, StructureTextParams, StructureDirectVolumeParams, StructureTextureMeshParams, StructureCylindersParams } from './params';
 import { Clipping } from '../../mol-theme/clipping';
 
 export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
@@ -330,6 +331,23 @@ export function UnitsSpheresVisual<P extends UnitsSpheresParams>(builder: UnitsS
     }, materialId);
 }
 
+// cylinders
+
+export const UnitsCylindersParams = { ...StructureCylindersParams, ...StructureParams };
+export type UnitsCylindersParams = typeof UnitsCylindersParams
+export interface UnitsCylindersVisualBuilder<P extends UnitsCylindersParams> extends UnitsVisualBuilder<P, Cylinders> { }
+
+export function UnitsCylindersVisual<P extends UnitsCylindersParams>(builder: UnitsCylindersVisualBuilder<P>, materialId: number): UnitsVisual<P> {
+    return UnitsVisual<Cylinders, P>({
+        ...builder,
+        setUpdateState: (state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructureGroup: StructureGroup, currentStructureGroup: StructureGroup) => {
+            builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme, newStructureGroup, currentStructureGroup);
+            if (!SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.updateSize = true;
+        },
+        geometryUtils: Cylinders.Utils
+    }, materialId);
+}
+
 // points
 
 export const UnitsPointsParams = { ...StructurePointsParams, ...StructureParams };

+ 72 - 18
src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts

@@ -11,12 +11,14 @@ 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 { createLinkCylinderMesh, LinkStyle } from './util/link';
-import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../complex-visual';
+import { createLinkCylinderImpostors, createLinkCylinderMesh, 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';
 import { BondCylinderParams, BondIterator, getInterBondLoci, eachInterBond, makeInterBondIgnoreTest } from './util/bond';
 import { Sphere3D } from '../../../mol-math/geometry';
+import { Cylinders } from '../../../mol-geo/geometry/cylinders/cylinders';
+import { WebGLContext } from '../../../mol-gl/webgl/context';
 
 const tmpRefPosBondIt = new Bond.ElementBondIterator();
 function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, index: StructureElement.UnitIndex) {
@@ -30,34 +32,41 @@ function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, inde
 }
 
 const tmpRef = Vec3();
-const tmpLoc = StructureElement.Location.create(void 0);
 
-function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitBondCylinderParams>, mesh?: Mesh) {
+function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme, props: PD.Values<InterUnitBondCylinderParams>) {
+    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 } = props;
 
-    if (!edgeCount) return Mesh.createEmpty(mesh);
-
     const delta = Vec3();
 
+    const radius = (edgeIndex: number) => {
+        const b = edges[edgeIndex];
+        locB.aUnit = structure.unitMap.get(b.unitA);
+        locB.aIndex = b.indexA;
+        locB.bUnit = structure.unitMap.get(b.unitB);
+        locB.bIndex = b.indexB;
+        return theme.size.size(locB) * sizeFactor;
+    };
+
     const radiusA = (edgeIndex: number) => {
         const b = edges[edgeIndex];
-        tmpLoc.structure = structure;
-        tmpLoc.unit = structure.unitMap.get(b.unitA);
-        tmpLoc.element = tmpLoc.unit.elements[b.indexA];
-        return theme.size.size(tmpLoc) * sizeFactor;
+        locE.unit = structure.unitMap.get(b.unitA);
+        locE.element = locE.unit.elements[b.indexA];
+        return theme.size.size(locE) * sizeFactor;
     };
 
     const radiusB = (edgeIndex: number) => {
         const b = edges[edgeIndex];
-        tmpLoc.structure = structure;
-        tmpLoc.unit = structure.unitMap.get(b.unitB);
-        tmpLoc.element = tmpLoc.unit.elements[b.indexB];
-        return theme.size.size(tmpLoc) * sizeFactor;
+        locE.unit = structure.unitMap.get(b.unitB);
+        locE.element = locE.unit.elements[b.indexB];
+        return theme.size.size(locE) * sizeFactor;
     };
 
-    const builderProps = {
+    return {
         linkCount: edgeCount,
         referencePosition: (edgeIndex: number) => {
             const b = edges[edgeIndex];
@@ -112,14 +121,31 @@ function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structur
             }
         },
         radius: (edgeIndex: number) => {
-            return Math.min(radiusA(edgeIndex), radiusB(edgeIndex)) * sizeAspectRatio;
+            return radius(edgeIndex) * sizeAspectRatio;
         },
         ignore: makeInterBondIgnoreTest(structure, props)
     };
+}
+
+function createInterUnitBondCylinderImpostors(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitBondCylinderParams>, cylinders?: Cylinders) {
+    if (!structure.interUnitBonds.edgeCount) return Cylinders.createEmpty(cylinders);
 
+    const builderProps = getInterUnitBondCylinderBuilderProps(structure, theme, props);
+    const m = createLinkCylinderImpostors(ctx, builderProps, props, cylinders);
+
+    const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * props.sizeFactor);
+    m.setBoundingSphere(sphere);
+
+    return m;
+}
+
+function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitBondCylinderParams>, mesh?: Mesh) {
+    if (!structure.interUnitBonds.edgeCount) return Mesh.createEmpty(mesh);
+
+    const builderProps = getInterUnitBondCylinderBuilderProps(structure, theme, props);
     const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
 
-    const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * sizeFactor);
+    const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * props.sizeFactor);
     m.setBoundingSphere(sphere);
 
     return m;
@@ -127,13 +153,41 @@ function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structur
 
 export const InterUnitBondCylinderParams = {
     ...ComplexMeshParams,
+    ...ComplexCylindersParams,
     ...BondCylinderParams,
     sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
     sizeAspectRatio: PD.Numeric(2 / 3, { min: 0, max: 3, step: 0.01 }),
 };
 export type InterUnitBondCylinderParams = typeof InterUnitBondCylinderParams
 
-export function InterUnitBondCylinderVisual(materialId: number): ComplexVisual<InterUnitBondCylinderParams> {
+export function getInterUnitBondCylinderVisual(webgl?: WebGLContext) {
+    return webgl && webgl.extensions.fragDepth ? InterUnitBondCylinderImpostorVisual : InterUnitBondCylinderMeshVisual;
+}
+
+export function InterUnitBondCylinderImpostorVisual(materialId: number): ComplexVisual<InterUnitBondCylinderParams> {
+    return ComplexCylindersVisual<InterUnitBondCylinderParams>({
+        defaultProps: PD.getDefaultValues(InterUnitBondCylinderParams),
+        createGeometry: createInterUnitBondCylinderImpostors,
+        createLocationIterator: BondIterator.fromStructure,
+        getLoci: getInterBondLoci,
+        eachLocation: eachInterBond,
+        setUpdateState: (state: VisualUpdateState, newProps: PD.Values<InterUnitBondCylinderParams>, currentProps: PD.Values<InterUnitBondCylinderParams>) => {
+            state.createGeometry = (
+                newProps.linkScale !== currentProps.linkScale ||
+                newProps.linkSpacing !== currentProps.linkSpacing ||
+                newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
+                newProps.linkCap !== currentProps.linkCap ||
+                newProps.dashCount !== currentProps.dashCount ||
+                newProps.dashScale !== currentProps.dashScale ||
+                newProps.dashCap !== currentProps.dashCap ||
+                !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
+                !arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
+            );
+        }
+    }, materialId);
+}
+
+export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisual<InterUnitBondCylinderParams> {
     return ComplexMeshVisual<InterUnitBondCylinderParams>({
         defaultProps: PD.getDefaultValues(InterUnitBondCylinderParams),
         createGeometry: createInterUnitBondCylinderMesh,

+ 81 - 18
src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts

@@ -7,26 +7,27 @@
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { VisualContext } from '../../visual';
-import { Unit, Structure, StructureElement } from '../../../mol-model/structure';
+import { Unit, 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 { arrayEqual } from '../../../mol-util';
-import { createLinkCylinderMesh, LinkStyle } from './util/link';
-import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../units-visual';
+import { createLinkCylinderImpostors, createLinkCylinderMesh, 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';
 import { BondCylinderParams, BondIterator, eachIntraBond, getIntraBondLoci, makeIntraBondIgnoreTest } from './util/bond';
 import { Sphere3D } from '../../../mol-math/geometry';
 import { IntAdjacencyGraph } from '../../../mol-math/graph';
+import { WebGLContext } from '../../../mol-gl/webgl/context';
+import { Cylinders } from '../../../mol-geo/geometry/cylinders/cylinders';
 
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 const isBondType = BondType.is;
 
-function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondCylinderParams>, mesh?: Mesh) {
-    if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
-
-    const location = StructureElement.Location.create(structure, unit);
+function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondCylinderParams>) {
+    const locE = StructureElement.Location.create(structure, unit);
+    const locB = Bond.Location(structure, unit, undefined, structure, unit, undefined);
 
     const elements = unit.elements;
     const bonds = unit.bonds;
@@ -34,22 +35,26 @@ function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structu
     const { order: _order, flags: _flags } = edgeProps;
     const { sizeFactor, sizeAspectRatio } = props;
 
-    if (!edgeCount) return Mesh.createEmpty(mesh);
-
     const vRef = Vec3(), delta = Vec3();
     const pos = unit.conformation.invariantPosition;
 
+    const radius = (edgeIndex: number) => {
+        locB.aIndex = a[edgeIndex];
+        locB.bIndex = b[edgeIndex];
+        return theme.size.size(locB) * sizeFactor;
+    };
+
     const radiusA = (edgeIndex: number) => {
-        location.element = elements[a[edgeIndex]];
-        return theme.size.size(location) * sizeFactor;
+        locE.element = elements[a[edgeIndex]];
+        return theme.size.size(locE) * sizeFactor;
     };
 
     const radiusB = (edgeIndex: number) => {
-        location.element = elements[b[edgeIndex]];
-        return theme.size.size(location) * sizeFactor;
+        locE.element = elements[b[edgeIndex]];
+        return theme.size.size(locE) * sizeFactor;
     };
 
-    const builderProps = {
+    return {
         linkCount: edgeCount * 2,
         referencePosition: (edgeIndex: number) => {
             let aI = a[edgeIndex], bI = b[edgeIndex];
@@ -98,14 +103,33 @@ function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structu
             }
         },
         radius: (edgeIndex: number) => {
-            return Math.min(radiusA(edgeIndex), radiusB(edgeIndex)) * sizeAspectRatio;
+            return radius(edgeIndex) * sizeAspectRatio;
         },
         ignore: makeIntraBondIgnoreTest(unit, props)
     };
+}
+
+function createIntraUnitBondCylinderImpostors(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondCylinderParams>, cylinders?: Cylinders): Cylinders {
+    if (!Unit.isAtomic(unit)) return Cylinders.createEmpty(cylinders);
+    if (!unit.bonds.edgeCount) return Cylinders.createEmpty(cylinders);
+
+    const builderProps = getIntraUnitBondCylinderBuilderProps(unit, structure, theme, props);
+    const c = createLinkCylinderImpostors(ctx, builderProps, props, cylinders);
+
+    const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor);
+    c.setBoundingSphere(sphere);
+
+    return c;
+}
+
+function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondCylinderParams>, mesh?: Mesh): Mesh {
+    if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
+    if (!unit.bonds.edgeCount) return Mesh.createEmpty(mesh);
 
+    const builderProps = getIntraUnitBondCylinderBuilderProps(unit, structure, theme, props);
     const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
 
-    const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * sizeFactor);
+    const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor);
     m.setBoundingSphere(sphere);
 
     return m;
@@ -113,13 +137,52 @@ function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structu
 
 export const IntraUnitBondCylinderParams = {
     ...UnitsMeshParams,
+    ...UnitsCylindersParams,
     ...BondCylinderParams,
     sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
     sizeAspectRatio: PD.Numeric(2 / 3, { min: 0, max: 3, step: 0.01 }),
 };
 export type IntraUnitBondCylinderParams = typeof IntraUnitBondCylinderParams
 
-export function IntraUnitBondCylinderVisual(materialId: number): UnitsVisual<IntraUnitBondCylinderParams> {
+export function getIntraUnitBondCylinderVisual(webgl?: WebGLContext) {
+    return webgl && webgl.extensions.fragDepth ? IntraUnitBondCylinderImpostorVisual : IntraUnitBondCylinderMeshVisual;
+}
+
+export function IntraUnitBondCylinderImpostorVisual(materialId: number): UnitsVisual<IntraUnitBondCylinderParams> {
+    return UnitsCylindersVisual<IntraUnitBondCylinderParams>({
+        defaultProps: PD.getDefaultValues(IntraUnitBondCylinderParams),
+        createGeometry: createIntraUnitBondCylinderImpostors,
+        createLocationIterator: BondIterator.fromGroup,
+        getLoci: getIntraBondLoci,
+        eachLocation: eachIntraBond,
+        setUpdateState: (state: VisualUpdateState, newProps: PD.Values<IntraUnitBondCylinderParams>, currentProps: PD.Values<IntraUnitBondCylinderParams>, newTheme: Theme, currentTheme: Theme, newStructureGroup: StructureGroup, currentStructureGroup: StructureGroup) => {
+            state.createGeometry = (
+                newProps.linkScale !== currentProps.linkScale ||
+                newProps.linkSpacing !== currentProps.linkSpacing ||
+                newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
+                newProps.linkCap !== currentProps.linkCap ||
+                newProps.dashCount !== currentProps.dashCount ||
+                newProps.dashScale !== currentProps.dashScale ||
+                newProps.dashCap !== currentProps.dashCap ||
+                !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
+                !arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
+            );
+
+            const newUnit = newStructureGroup.group.units[0];
+            const currentUnit = currentStructureGroup.group.units[0];
+            if (Unit.isAtomic(newUnit) && Unit.isAtomic(currentUnit)) {
+                if (!IntAdjacencyGraph.areEqual(newUnit.bonds, currentUnit.bonds)) {
+                    state.createGeometry = true;
+                    state.updateTransform = true;
+                    state.updateColor = true;
+                    state.updateSize = true;
+                }
+            }
+        }
+    }, materialId);
+}
+
+export function IntraUnitBondCylinderMeshVisual(materialId: number): UnitsVisual<IntraUnitBondCylinderParams> {
     return UnitsMeshVisual<IntraUnitBondCylinderParams>({
         defaultProps: PD.getDefaultValues(IntraUnitBondCylinderParams),
         createGeometry: createIntraUnitBondCylinderMesh,
@@ -154,4 +217,4 @@ export function IntraUnitBondCylinderVisual(materialId: number): UnitsVisual<Int
             }
         }
     }, materialId);
-}
+}

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

@@ -14,6 +14,8 @@ import { VisualContext } from '../../../visual';
 import { BaseGeometry } from '../../../../mol-geo/geometry/base';
 import { Lines } from '../../../../mol-geo/geometry/lines/lines';
 import { LinesBuilder } from '../../../../mol-geo/geometry/lines/lines-builder';
+import { Cylinders } from '../../../../mol-geo/geometry/cylinders/cylinders';
+import { CylindersBuilder } from '../../../../mol-geo/geometry/cylinders/cylinders-builder';
 
 export const LinkCylinderParams = {
     linkScale: PD.Numeric(0.4, { min: 0, max: 1, step: 0.1 }),
@@ -169,6 +171,67 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
     return MeshBuilder.getMesh(builderState);
 }
 
+/**
+ * Each edge is included twice to allow for coloring/picking
+ * the half closer to the first vertex, i.e. vertex a.
+ */
+export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: LinkBuilderProps, props: LinkCylinderProps, cylinders?: Cylinders) {
+    const { linkCount, referencePosition, position, style, radius, ignore } = linkBuilder;
+
+    if (!linkCount) return Cylinders.createEmpty(cylinders);
+
+    const { linkScale, linkSpacing, linkCap, dashCount, dashScale, dashCap } = props;
+
+    const cylindersCountEstimate = linkCount * 2;
+    const builder = CylindersBuilder.create(cylindersCountEstimate, cylindersCountEstimate / 4, cylinders);
+
+    const va = Vec3();
+    const vb = 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);
+
+    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) : 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], 1, linkCap, false, 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;
+            const multiScale = linkScale / (0.5 * order);
+            const absOffset = (linkRadius - multiScale * linkRadius) * linkSpacing;
+
+            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, 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, false, edgeIndex);
+        } else if (linkStyle === LinkStyle.Disk) {
+            v3scale(tmpV12, v3sub(tmpV12, vb, va), 0.475);
+            v3add(va, va, tmpV12);
+            v3sub(vb, vb, tmpV12);
+
+            builder.add(va[0], va[1], va[2], vb[0], vb[1], vb[2], 1, linkCap, false, edgeIndex);
+        }
+    }
+
+    return builder.getCylinders();
+}
+
 /**
  * Each edge is included twice to allow for coloring/picking
  * the half closer to the first vertex, i.e. vertex a.