|
@@ -21,6 +21,9 @@ export const LinkCylinderParams = {
|
|
|
linkScale: PD.Numeric(0.45, { min: 0, max: 1, step: 0.01 }),
|
|
|
linkSpacing: PD.Numeric(1, { min: 0, max: 2, step: 0.01 }),
|
|
|
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 }),
|
|
|
dashScale: PD.Numeric(0.8, { min: 0, max: 2, step: 0.1 }),
|
|
|
dashCap: PD.Boolean(true),
|
|
@@ -33,6 +36,7 @@ export type LinkCylinderProps = typeof DefaultLinkCylinderProps
|
|
|
export const LinkLineParams = {
|
|
|
linkScale: PD.Numeric(0.5, { min: 0, max: 1, step: 0.1 }),
|
|
|
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 }),
|
|
|
};
|
|
|
export const DefaultLinkLineProps = PD.getDefaultValues(LinkLineParams);
|
|
@@ -83,10 +87,12 @@ export const enum LinkStyle {
|
|
|
Solid = 0,
|
|
|
Dashed = 1,
|
|
|
Double = 2,
|
|
|
- Triple = 3,
|
|
|
- Disk = 4,
|
|
|
- Aromatic = 5,
|
|
|
- MirroredAromatic = 6,
|
|
|
+ OffsetDouble = 3,
|
|
|
+ Triple = 4,
|
|
|
+ OffsetTriple = 5,
|
|
|
+ Disk = 6,
|
|
|
+ Aromatic = 7,
|
|
|
+ MirroredAromatic = 8,
|
|
|
}
|
|
|
|
|
|
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
|
@@ -105,7 +111,7 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
|
|
|
|
|
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 builderState = MeshBuilder.createState(vertexCountEstimate, vertexCountEstimate / 4, mesh);
|
|
@@ -128,14 +134,15 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
|
|
|
|
|
position(va, vb, edgeIndex);
|
|
|
v3sub(tmpV12, vb, va);
|
|
|
+ const dirFlag = v3dot(tmpV12, up) > 0;
|
|
|
|
|
|
const linkRadius = radius(edgeIndex);
|
|
|
const linkStyle = style ? style(edgeIndex) : LinkStyle.Solid;
|
|
|
const linkStub = stubCap && (stub ? stub(edgeIndex) : false);
|
|
|
- const [topCap, bottomCap] = (v3dot(tmpV12, up) > 0) ? [linkStub, linkCap] : [linkCap, linkStub];
|
|
|
+ const [topCap, bottomCap] = dirFlag ? [linkStub, linkCap] : [linkCap, linkStub];
|
|
|
builderState.currentGroup = edgeIndex;
|
|
|
|
|
|
- const aromaticOffsetFactor = 4.5;
|
|
|
+ const aromaticSegmentCount = aromaticDashCount + 1;
|
|
|
|
|
|
if (linkStyle === LinkStyle.Solid) {
|
|
|
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
|
|
@@ -148,9 +155,9 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
|
|
cylinderProps.topCap = cylinderProps.bottomCap = dashCap;
|
|
|
|
|
|
addFixedCountDashedCylinder(builderState, va, vb, 0.5, segmentCount, cylinderProps);
|
|
|
- } else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
|
|
- const order = linkStyle === LinkStyle.Double ? 2 :
|
|
|
- linkStyle === LinkStyle.Triple ? 3 : 1.5;
|
|
|
+ } 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 multiRadius = linkRadius * (linkScale / (0.5 * order));
|
|
|
const absOffset = (linkRadius - multiRadius) * linkSpacing;
|
|
|
|
|
@@ -163,18 +170,49 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
|
|
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
|
|
|
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;
|
|
|
- v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
|
|
|
+ v3setMagnitude(vShift, vShift, aromaticOffset);
|
|
|
v3sub(va, va, 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) {
|
|
|
- v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
|
|
|
+ v3setMagnitude(vShift, vShift, aromaticOffset * 2);
|
|
|
+ v3add(va, va, vShift);
|
|
|
+ v3add(vb, vb, vShift);
|
|
|
+ addFixedCountDashedCylinder(builderState, va, vb, 0.5, aromaticSegmentCount, cylinderProps);
|
|
|
+ }
|
|
|
+ } else if (linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.OffsetTriple) {
|
|
|
+ const multipleOffset = linkRadius + multiRadius + linkScale * linkRadius * linkSpacing;
|
|
|
+ v3setMagnitude(vShift, vShift, multipleOffset);
|
|
|
+
|
|
|
+ cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
|
|
|
+ addCylinder(builderState, va, vb, 0.5, cylinderProps);
|
|
|
+
|
|
|
+ v3scale(tmpV12, tmpV12, linkSpacing * linkScale * 0.2);
|
|
|
+ v3add(va, va, tmpV12);
|
|
|
+ v3sub(vb, vb, tmpV12);
|
|
|
+
|
|
|
+ cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius;
|
|
|
+ cylinderProps.topCap = dirFlag ? linkStub : dashCap;
|
|
|
+ cylinderProps.bottomCap = dirFlag ? dashCap : linkStub;
|
|
|
+ v3setMagnitude(vShift, vShift, multipleOffset);
|
|
|
+ v3sub(va, va, vShift);
|
|
|
+ v3sub(vb, vb, vShift);
|
|
|
+ addCylinder(builderState, va, vb, 0.5, cylinderProps);
|
|
|
+
|
|
|
+ if (order === 3) {
|
|
|
+ v3setMagnitude(vShift, vShift, multipleOffset * 2);
|
|
|
v3add(va, va, vShift);
|
|
|
v3add(vb, vb, vShift);
|
|
|
- addFixedCountDashedCylinder(builderState, va, vb, 0.5, 3, cylinderProps);
|
|
|
+ addCylinder(builderState, va, vb, 0.5, cylinderProps);
|
|
|
}
|
|
|
} else {
|
|
|
v3setMagnitude(vShift, vShift, absOffset);
|
|
@@ -208,7 +246,7 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
|
|
|
|
|
|
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 builder = CylindersBuilder.create(cylindersCountEstimate, cylindersCountEstimate / 4, cylinders);
|
|
@@ -222,9 +260,8 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
|
|
|
const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
|
|
|
const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
|
|
|
|
|
|
- const aromaticSegmentCount = 3;
|
|
|
+ const aromaticSegmentCount = aromaticDashCount + 1;
|
|
|
const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
|
|
|
- const aromaticOffsetFactor = 4.5;
|
|
|
|
|
|
for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
|
|
|
if (ignore && ignore(edgeIndex)) continue;
|
|
@@ -242,9 +279,9 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
|
|
|
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 || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
|
|
- const order = linkStyle === LinkStyle.Double ? 2 :
|
|
|
- linkStyle === LinkStyle.Triple ? 3 : 1.5;
|
|
|
+ } 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 multiScale = linkScale / (0.5 * order);
|
|
|
const absOffset = (linkRadius - multiScale * linkRadius) * linkSpacing;
|
|
|
|
|
@@ -254,24 +291,40 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
|
|
|
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);
|
|
|
|
|
|
+ const aromaticOffset = linkRadius + aromaticScale * linkRadius + aromaticScale * linkRadius * aromaticSpacing;
|
|
|
+
|
|
|
v3scale(tmpV12, v3sub(tmpV12, vb, va), aromaticLengthScale);
|
|
|
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(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) {
|
|
|
- v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
|
|
|
+ v3setMagnitude(vShift, vShift, aromaticOffset * 2);
|
|
|
v3add(va, va, 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) {
|
|
|
+ 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);
|
|
|
+
|
|
|
+ v3setMagnitude(tmpV12, v3sub(tmpV12, va, vb), linkRadius / 1.5);
|
|
|
+ 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);
|
|
|
+ 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, dashCap, linkStub, edgeIndex);
|
|
|
} else {
|
|
|
v3setMagnitude(vShift, vShift, absOffset);
|
|
|
|
|
|
- if (order === 3) builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], multiScale, linkCap, false, edgeIndex);
|
|
|
+ if (order === 3) builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], multiScale, linkCap, linkStub, edgeIndex);
|
|
|
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);
|
|
|
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);
|
|
|
}
|
|
@@ -296,7 +349,7 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
|
|
|
|
|
|
if (!linkCount) return Lines.createEmpty(lines);
|
|
|
|
|
|
- const { linkScale, linkSpacing, dashCount } = props;
|
|
|
+ const { linkScale, linkSpacing, aromaticDashCount, dashCount } = props;
|
|
|
|
|
|
const linesCountEstimate = linkCount * 2;
|
|
|
const builder = LinesBuilder.create(linesCountEstimate, linesCountEstimate / 4, lines);
|
|
@@ -310,9 +363,10 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
|
|
|
const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
|
|
|
const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
|
|
|
|
|
|
- const aromaticSegmentCount = 3;
|
|
|
+ const aromaticSegmentCount = aromaticDashCount + 1;
|
|
|
const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
|
|
|
const aromaticOffsetFactor = 4.5;
|
|
|
+ const multipleOffsetFactor = 3;
|
|
|
|
|
|
for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
|
|
|
if (ignore && ignore(edgeIndex)) continue;
|
|
@@ -328,9 +382,9 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
|
|
|
v3scale(tmpV12, v3sub(tmpV12, vb, va), lengthScale);
|
|
|
v3sub(vb, vb, tmpV12);
|
|
|
builder.addFixedCountDashes(va, vb, segmentCount, edgeIndex);
|
|
|
- } else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
|
|
- const order = linkStyle === LinkStyle.Double ? 2 :
|
|
|
- linkStyle === LinkStyle.Triple ? 3 : 1.5;
|
|
|
+ } 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 multiRadius = 1 * (linkScale / (0.5 * order));
|
|
|
const absOffset = (1 - multiRadius) * linkSpacing;
|
|
|
|
|
@@ -354,8 +408,18 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
|
|
|
v3add(vb, vb, vShift);
|
|
|
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, edgeIndex);
|
|
|
}
|
|
|
+ } else if (linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.OffsetTriple) {
|
|
|
+ v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor);
|
|
|
+
|
|
|
+ builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], edgeIndex);
|
|
|
+
|
|
|
+ v3scale(tmpV12, v3sub(tmpV12, va, vb), linkSpacing * linkScale);
|
|
|
+ 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], edgeIndex);
|
|
|
+ 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], edgeIndex);
|
|
|
} else {
|
|
|
- v3setMagnitude(vShift, vShift, absOffset);
|
|
|
+ v3setMagnitude(vShift, vShift, absOffset * 1.5);
|
|
|
|
|
|
if (order === 3) builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], edgeIndex);
|
|
|
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], edgeIndex);
|