Jelajahi Sumber

add additional aromatic bond visual params

Alexander Rose 3 tahun lalu
induk
melakukan
340f8f1af3

+ 9 - 13
CHANGELOG.md

@@ -6,6 +6,9 @@ Note that since we don't clearly distinguish between a public and private interf
 
 
 ## [Unreleased]
 ## [Unreleased]
 
 
+- Improve aromatic bond visuals (add ``aromaticScale``, ``aromaticSpacing``, ``aromaticDashCount`` params)
+- [Breaking] Change ``adjustCylinderLength`` default to ``false`` (set to true for focus representation)
+
 ## [v2.3.5] - 2021-10-19
 ## [v2.3.5] - 2021-10-19
 
 
 - Fix sequence viewer for PDB files with COMPND record and multichain entities.
 - Fix sequence viewer for PDB files with COMPND record and multichain entities.
@@ -153,29 +156,22 @@ Note that since we don't clearly distinguish between a public and private interf
 - Fixed Measurements UI labels (#166)
 - Fixed Measurements UI labels (#166)
 
 
 ## [v2.0.3] - 2021-04-09
 ## [v2.0.3] - 2021-04-09
-### Added
-- Support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
 
 
-### Changed
+- Add support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
 - [Breaking] The ``zip`` function is now asynchronous and expects a ``RuntimeContext``. Also added ``Zip()`` returning a ``Task``.
 - [Breaking] The ``zip`` function is now asynchronous and expects a ``RuntimeContext``. Also added ``Zip()`` returning a ``Task``.
 - [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
 - [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
 
 
 ## [v2.0.2] - 2021-03-29
 ## [v2.0.2] - 2021-03-29
-### Added
-- ``Canvas3D.getRenderObjects``.
-- [WIP] Animate state interpolating, including model trajectories
 
 
-### Changed
+- Add ``Canvas3D.getRenderObjects``.
+- [WIP] Animate state interpolating, including model trajectories
 - Recognise MSE, SEP, TPO, PTR and PCA as non-standard amino-acids.
 - Recognise MSE, SEP, TPO, PTR and PCA as non-standard amino-acids.
-
-### Fixed
-- VolumeFromDensityServerCif transform label
-
+- Fix VolumeFromDensityServerCif transform label
 
 
 ## [v2.0.1] - 2021-03-23
 ## [v2.0.1] - 2021-03-23
-### Fixed
-- Exclude tsconfig.commonjs.tsbuildinfo from npm bundle
 
 
+- Exclude tsconfig.commonjs.tsbuildinfo from npm bundle
 
 
 ## [v2.0.0] - 2021-03-23
 ## [v2.0.0] - 2021-03-23
+
 Too many changes to list as this is the start of the changelog... Notably, default exports are now forbidden.
 Too many changes to list as this is the start of the changelog... Notably, default exports are now forbidden.

+ 1 - 1
src/mol-plugin/behavior/dynamic/selection/structure-focus-representation.ts

@@ -25,7 +25,7 @@ const StructureFocusRepresentationParams = (plugin: PluginContext) => {
         expandRadius: PD.Numeric(5, { min: 1, max: 10, step: 1 }),
         expandRadius: PD.Numeric(5, { min: 1, max: 10, step: 1 }),
         targetParams: PD.Group(reprParams, {
         targetParams: PD.Group(reprParams, {
             label: 'Target',
             label: 'Target',
-            customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', size: 'physical', typeParams: { sizeFactor: 0.26, alpha: 0.51 } })
+            customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', size: 'physical', typeParams: { sizeFactor: 0.26, alpha: 0.51, adjustCylinderLength: true } })
         }),
         }),
         surroundingsParams: PD.Group(reprParams, {
         surroundingsParams: PD.Group(reprParams, {
             label: 'Surroundings',
             label: 'Surroundings',

+ 6 - 0
src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts

@@ -215,6 +215,9 @@ export function InterUnitBondCylinderImpostorVisual(materialId: number): Complex
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.linkCap !== currentProps.linkCap ||
                 newProps.linkCap !== currentProps.linkCap ||
+                newProps.aromaticScale !== currentProps.aromaticScale ||
+                newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||
                 newProps.dashCap !== currentProps.dashCap ||
@@ -254,6 +257,9 @@ export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisu
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.linkCap !== currentProps.linkCap ||
                 newProps.linkCap !== currentProps.linkCap ||
+                newProps.aromaticScale !== currentProps.aromaticScale ||
+                newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||
                 newProps.dashCap !== currentProps.dashCap ||

+ 1 - 0
src/mol-repr/structure/visual/bond-inter-unit-line.ts

@@ -131,6 +131,7 @@ export function InterUnitBondLineVisual(materialId: number): ComplexVisual<Inter
                 newProps.sizeFactor !== currentProps.sizeFactor ||
                 newProps.sizeFactor !== currentProps.sizeFactor ||
                 newProps.linkScale !== currentProps.linkScale ||
                 newProps.linkScale !== currentProps.linkScale ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
                 !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||

+ 6 - 0
src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts

@@ -228,6 +228,9 @@ export function IntraUnitBondCylinderImpostorVisual(materialId: number): UnitsVi
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.linkCap !== currentProps.linkCap ||
                 newProps.linkCap !== currentProps.linkCap ||
+                newProps.aromaticScale !== currentProps.aromaticScale ||
+                newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||
                 newProps.dashCap !== currentProps.dashCap ||
@@ -272,6 +275,9 @@ export function IntraUnitBondCylinderMeshVisual(materialId: number): UnitsVisual
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.linkCap !== currentProps.linkCap ||
                 newProps.linkCap !== currentProps.linkCap ||
+                newProps.aromaticScale !== currentProps.aromaticScale ||
+                newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||
                 newProps.dashCap !== currentProps.dashCap ||

+ 1 - 0
src/mol-repr/structure/visual/bond-intra-unit-line.ts

@@ -153,6 +153,7 @@ export function IntraUnitBondLineVisual(materialId: number): UnitsVisual<IntraUn
                 newProps.sizeFactor !== currentProps.sizeFactor ||
                 newProps.sizeFactor !== currentProps.sizeFactor ||
                 newProps.linkScale !== currentProps.linkScale ||
                 newProps.linkScale !== currentProps.linkScale ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
                 !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||

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

@@ -28,7 +28,7 @@ export type BondProps = typeof DefaultBondProps
 export const BondCylinderParams = {
 export const BondCylinderParams = {
     ...LinkCylinderParams,
     ...LinkCylinderParams,
     ...BondParams,
     ...BondParams,
-    adjustCylinderLength: PD.Boolean(true, { description: 'Shorten cylinders to reduce overlap with spheres.' })
+    adjustCylinderLength: PD.Boolean(false, { description: 'Shorten cylinders to reduce overlap with spheres. Useful for for transparent bonds. Not working well with aromatic bonds.' })
 };
 };
 export const DefaultBondCylinderProps = PD.getDefaultValues(BondCylinderParams);
 export const DefaultBondCylinderProps = PD.getDefaultValues(BondCylinderParams);
 export type BondCylinderProps = typeof DefaultBondCylinderProps
 export type BondCylinderProps = typeof DefaultBondCylinderProps

+ 41 - 27
src/mol-repr/structure/visual/util/link.ts

@@ -21,6 +21,9 @@ export const LinkCylinderParams = {
     linkScale: PD.Numeric(0.45, { min: 0, max: 1, step: 0.01 }),
     linkScale: PD.Numeric(0.45, { min: 0, max: 1, step: 0.01 }),
     linkSpacing: PD.Numeric(1, { min: 0, max: 2, step: 0.01 }),
     linkSpacing: PD.Numeric(1, { min: 0, max: 2, step: 0.01 }),
     linkCap: PD.Boolean(false),
     linkCap: PD.Boolean(false),
+    aromaticScale: PD.Numeric(0.3, { min: 0, max: 1, step: 0.01 }),
+    aromaticSpacing: PD.Numeric(1.5, { min: 0, max: 3, step: 0.01 }),
+    aromaticDashCount: PD.Numeric(2, { min: 2, max: 6, step: 2 }),
     dashCount: PD.Numeric(4, { min: 2, max: 10, step: 2 }),
     dashCount: PD.Numeric(4, { min: 2, max: 10, step: 2 }),
     dashScale: PD.Numeric(0.8, { min: 0, max: 2, step: 0.1 }),
     dashScale: PD.Numeric(0.8, { min: 0, max: 2, step: 0.1 }),
     dashCap: PD.Boolean(true),
     dashCap: PD.Boolean(true),
@@ -33,6 +36,7 @@ export type LinkCylinderProps = typeof DefaultLinkCylinderProps
 export const LinkLineParams = {
 export const LinkLineParams = {
     linkScale: PD.Numeric(0.5, { min: 0, max: 1, step: 0.1 }),
     linkScale: PD.Numeric(0.5, { min: 0, max: 1, step: 0.1 }),
     linkSpacing: PD.Numeric(0.1, { min: 0, max: 2, step: 0.01 }),
     linkSpacing: PD.Numeric(0.1, { min: 0, max: 2, step: 0.01 }),
+    aromaticDashCount: PD.Numeric(2, { min: 2, max: 6, step: 2 }),
     dashCount: PD.Numeric(4, { min: 2, max: 10, step: 2 }),
     dashCount: PD.Numeric(4, { min: 2, max: 10, step: 2 }),
 };
 };
 export const DefaultLinkLineProps = PD.getDefaultValues(LinkLineParams);
 export const DefaultLinkLineProps = PD.getDefaultValues(LinkLineParams);
@@ -107,7 +111,7 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
 
 
     if (!linkCount) return Mesh.createEmpty(mesh);
     if (!linkCount) return Mesh.createEmpty(mesh);
 
 
-    const { linkScale, linkSpacing, radialSegments, linkCap, dashCount, dashScale, dashCap, stubCap } = props;
+    const { linkScale, linkSpacing, radialSegments, linkCap, aromaticScale, aromaticSpacing, aromaticDashCount, dashCount, dashScale, dashCap, stubCap } = props;
 
 
     const vertexCountEstimate = radialSegments * 2 * linkCount * 2;
     const vertexCountEstimate = radialSegments * 2 * linkCount * 2;
     const builderState = MeshBuilder.createState(vertexCountEstimate, vertexCountEstimate / 4, mesh);
     const builderState = MeshBuilder.createState(vertexCountEstimate, vertexCountEstimate / 4, mesh);
@@ -138,8 +142,7 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
         const [topCap, bottomCap] = dirFlag ? [linkStub, linkCap] : [linkCap, linkStub];
         const [topCap, bottomCap] = dirFlag ? [linkStub, linkCap] : [linkCap, linkStub];
         builderState.currentGroup = edgeIndex;
         builderState.currentGroup = edgeIndex;
 
 
-        const aromaticOffsetFactor = 5.5;
-        const multipleOffsetFactor = 4;
+        const aromaticSegmentCount = aromaticDashCount + 1;
 
 
         if (linkStyle === LinkStyle.Solid) {
         if (linkStyle === LinkStyle.Solid) {
             cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
             cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
@@ -153,8 +156,8 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
 
 
             addFixedCountDashedCylinder(builderState, va, vb, 0.5, segmentCount, cylinderProps);
             addFixedCountDashedCylinder(builderState, va, vb, 0.5, segmentCount, cylinderProps);
         } else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
         } else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
-            const order = linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble ? 2 :
-                linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple ? 3 : 1.5;
+            const order = (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble) ? 2 :
+                (linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple) ? 3 : 1.5;
             const multiRadius = linkRadius * (linkScale / (0.5 * order));
             const multiRadius = linkRadius * (linkScale / (0.5 * order));
             const absOffset = (linkRadius - multiRadius) * linkSpacing;
             const absOffset = (linkRadius - multiRadius) * linkSpacing;
 
 
@@ -167,21 +170,28 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
 
 
-                cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius * linkScale;
+                const aromaticOffset = linkRadius + aromaticScale * linkRadius + aromaticScale * linkRadius * aromaticSpacing;
+
+                v3setMagnitude(tmpV12, v3sub(tmpV12, vb, va), linkRadius * 0.5);
+                v3add(va, va, tmpV12);
+                v3sub(vb, vb, tmpV12);
+
+                cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius * aromaticScale;
                 cylinderProps.topCap = cylinderProps.bottomCap = dashCap;
                 cylinderProps.topCap = cylinderProps.bottomCap = dashCap;
-                v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
+                v3setMagnitude(vShift, vShift, aromaticOffset);
                 v3sub(va, va, vShift);
                 v3sub(va, va, vShift);
                 v3sub(vb, vb, vShift);
                 v3sub(vb, vb, vShift);
-                addFixedCountDashedCylinder(builderState, va, vb, 0.5, 3, cylinderProps);
+                addFixedCountDashedCylinder(builderState, va, vb, 0.5, aromaticSegmentCount, cylinderProps);
 
 
                 if (linkStyle === LinkStyle.MirroredAromatic) {
                 if (linkStyle === LinkStyle.MirroredAromatic) {
-                    v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
+                    v3setMagnitude(vShift, vShift, aromaticOffset * 2);
                     v3add(va, va, vShift);
                     v3add(va, va, vShift);
                     v3add(vb, vb, vShift);
                     v3add(vb, vb, vShift);
-                    addFixedCountDashedCylinder(builderState, va, vb, 0.5, 3, cylinderProps);
+                    addFixedCountDashedCylinder(builderState, va, vb, 0.5, aromaticSegmentCount, cylinderProps);
                 }
                 }
             } else if (linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.OffsetTriple) {
             } else if (linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.OffsetTriple) {
-                v3setMagnitude(vShift, vShift, absOffset);
+                const multipleOffset = linkRadius + multiRadius + linkScale * linkRadius * linkSpacing;
+                v3setMagnitude(vShift, vShift, multipleOffset);
 
 
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
@@ -193,13 +203,13 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius;
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius;
                 cylinderProps.topCap = dirFlag ? linkStub : dashCap;
                 cylinderProps.topCap = dirFlag ? linkStub : dashCap;
                 cylinderProps.bottomCap = dirFlag ? dashCap : linkStub;
                 cylinderProps.bottomCap = dirFlag ? dashCap : linkStub;
-                v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor);
+                v3setMagnitude(vShift, vShift, multipleOffset);
                 v3sub(va, va, vShift);
                 v3sub(va, va, vShift);
                 v3sub(vb, vb, vShift);
                 v3sub(vb, vb, vShift);
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
 
 
                 if (order === 3) {
                 if (order === 3) {
-                    v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor * 2);
+                    v3setMagnitude(vShift, vShift, multipleOffset * 2);
                     v3add(va, va, vShift);
                     v3add(va, va, vShift);
                     v3add(vb, vb, vShift);
                     v3add(vb, vb, vShift);
                     addCylinder(builderState, va, vb, 0.5, cylinderProps);
                     addCylinder(builderState, va, vb, 0.5, cylinderProps);
@@ -236,7 +246,7 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
 
 
     if (!linkCount) return Cylinders.createEmpty(cylinders);
     if (!linkCount) return Cylinders.createEmpty(cylinders);
 
 
-    const { linkScale, linkSpacing, linkCap, dashCount, dashScale, dashCap, stubCap } = props;
+    const { linkScale, linkSpacing, linkCap, aromaticScale, aromaticSpacing, aromaticDashCount, dashCount, dashScale, dashCap, stubCap } = props;
 
 
     const cylindersCountEstimate = linkCount * 2;
     const cylindersCountEstimate = linkCount * 2;
     const builder = CylindersBuilder.create(cylindersCountEstimate, cylindersCountEstimate / 4, cylinders);
     const builder = CylindersBuilder.create(cylindersCountEstimate, cylindersCountEstimate / 4, cylinders);
@@ -250,10 +260,8 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
     const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
     const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
     const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
     const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
 
 
-    const aromaticSegmentCount = 3;
+    const aromaticSegmentCount = aromaticDashCount + 1;
     const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
     const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
-    const aromaticOffsetFactor = 5.5;
-    const multipleOffsetFactor = 4;
 
 
     for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
     for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
         if (ignore && ignore(edgeIndex)) continue;
         if (ignore && ignore(edgeIndex)) continue;
@@ -272,8 +280,8 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
             v3sub(vb, vb, tmpV12);
             v3sub(vb, vb, tmpV12);
             builder.addFixedCountDashes(va, vb, segmentCount, dashScale, dashCap, dashCap, edgeIndex);
             builder.addFixedCountDashes(va, vb, segmentCount, dashScale, dashCap, dashCap, edgeIndex);
         } else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
         } else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
-            const order = linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble ? 2 :
-                linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple ? 3 : 1.5;
+            const order = (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble) ? 2 :
+                (linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple) ? 3 : 1.5;
             const multiScale = linkScale / (0.5 * order);
             const multiScale = linkScale / (0.5 * order);
             const absOffset = (linkRadius - multiScale * linkRadius) * linkSpacing;
             const absOffset = (linkRadius - multiScale * linkRadius) * linkSpacing;
 
 
@@ -283,26 +291,32 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
             if (linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
             if (linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
                 builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], 1, linkCap, linkStub, edgeIndex);
                 builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], 1, linkCap, linkStub, edgeIndex);
 
 
+                const aromaticOffset = linkRadius + aromaticScale * linkRadius + aromaticScale * linkRadius * aromaticSpacing;
+
                 v3scale(tmpV12, v3sub(tmpV12, vb, va), aromaticLengthScale);
                 v3scale(tmpV12, v3sub(tmpV12, vb, va), aromaticLengthScale);
                 v3sub(vb, vb, tmpV12);
                 v3sub(vb, vb, tmpV12);
 
 
-                v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
+                v3setMagnitude(tmpV12, v3sub(tmpV12, vb, va), linkRadius * 0.5);
+                v3add(va, va, tmpV12);
+
+                v3setMagnitude(vShift, vShift, aromaticOffset);
                 v3sub(va, va, vShift);
                 v3sub(va, va, vShift);
                 v3sub(vb, vb, vShift);
                 v3sub(vb, vb, vShift);
-                builder.addFixedCountDashes(va, vb, aromaticSegmentCount, linkScale, dashCap, dashCap, edgeIndex);
+                builder.addFixedCountDashes(va, vb, aromaticSegmentCount, aromaticScale, dashCap, dashCap, edgeIndex);
 
 
                 if (linkStyle === LinkStyle.MirroredAromatic) {
                 if (linkStyle === LinkStyle.MirroredAromatic) {
-                    v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
+                    v3setMagnitude(vShift, vShift, aromaticOffset * 2);
                     v3add(va, va, vShift);
                     v3add(va, va, vShift);
                     v3add(vb, vb, vShift);
                     v3add(vb, vb, vShift);
-                    builder.addFixedCountDashes(va, vb, aromaticSegmentCount, linkScale, dashCap, dashCap, edgeIndex);
+                    builder.addFixedCountDashes(va, vb, aromaticSegmentCount, aromaticScale, dashCap, dashCap, edgeIndex);
                 }
                 }
             } else if (linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.OffsetTriple) {
             } else if (linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.OffsetTriple) {
-                v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor);
+                const multipleOffset = linkRadius + multiScale * linkRadius + linkScale * linkRadius * linkSpacing;
+                v3setMagnitude(vShift, vShift, multipleOffset);
 
 
                 builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], 1, linkCap, linkStub, edgeIndex);
                 builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], 1, linkCap, linkStub, edgeIndex);
 
 
-                v3scale(tmpV12, v3sub(tmpV12, va, vb), linkSpacing * linkScale * 0.2);
+                v3setMagnitude(tmpV12, v3sub(tmpV12, va, vb), linkRadius / 1.5);
                 v3sub(va, va, tmpV12);
                 v3sub(va, va, tmpV12);
 
 
                 if (order === 3) builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vm[0] + vShift[0], vm[1] + vShift[1], vm[2] + vShift[2], multiScale, linkCap, linkStub, edgeIndex);
                 if (order === 3) builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vm[0] + vShift[0], vm[1] + vShift[1], vm[2] + vShift[2], multiScale, linkCap, linkStub, edgeIndex);
@@ -335,7 +349,7 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
 
 
     if (!linkCount) return Lines.createEmpty(lines);
     if (!linkCount) return Lines.createEmpty(lines);
 
 
-    const { linkScale, linkSpacing, dashCount } = props;
+    const { linkScale, linkSpacing, aromaticDashCount, dashCount } = props;
 
 
     const linesCountEstimate = linkCount * 2;
     const linesCountEstimate = linkCount * 2;
     const builder = LinesBuilder.create(linesCountEstimate, linesCountEstimate / 4, lines);
     const builder = LinesBuilder.create(linesCountEstimate, linesCountEstimate / 4, lines);
@@ -349,7 +363,7 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
     const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
     const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
     const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
     const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
 
 
-    const aromaticSegmentCount = 3;
+    const aromaticSegmentCount = aromaticDashCount + 1;
     const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
     const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
     const aromaticOffsetFactor = 4.5;
     const aromaticOffsetFactor = 4.5;
     const multipleOffsetFactor = 3;
     const multipleOffsetFactor = 3;