Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Right-angle grid-aligned wires in the node graph #2182

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 142 additions & 50 deletions frontend/src/components/views/Graph.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@
const pathString = buildWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn);
const dataType = (outputPort.getAttribute("data-datatype") as FrontendGraphDataType) || "General";
const thick = verticalIn && verticalOut;

return { pathString, dataType, thick, dashed };
}

Expand Down Expand Up @@ -136,68 +135,161 @@
function buildWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
if (!nodesContainer) return [];

const scale = $nodeGraph.transform.scale; // Get the current scale
const lineWidth = 2;
const halfLineWidth = lineWidth / 2;
const VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP = 1;

const containerBounds = nodesContainer.getBoundingClientRect();

const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
const outY = verticalOut ? outputBounds.y + VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : outputBounds.y + outputBounds.height / 2;
const outConnectorX = (outX - containerBounds.x) / $nodeGraph.transform.scale;
const outConnectorY = (outY - containerBounds.y) / $nodeGraph.transform.scale;
const outConnectorX = Math.round((outX - containerBounds.x) / scale); // Adjust for scale
const outConnectorY = Math.round((outY - containerBounds.y) / scale); // Adjust for scale

const inX = verticalIn ? inputBounds.x + inputBounds.width / 2 : inputBounds.x + 1;
const inY = verticalIn ? inputBounds.y + inputBounds.height - VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : inputBounds.y + inputBounds.height / 2;
const inConnectorX = (inX - containerBounds.x) / $nodeGraph.transform.scale;
const inConnectorY = (inY - containerBounds.y) / $nodeGraph.transform.scale;
const horizontalGap = Math.abs(outConnectorX - inConnectorX);
const verticalGap = Math.abs(outConnectorY - inConnectorY);

// TODO: Finish this commented out code replacement for the code below it based on this diagram: <https://files.keavon.com/-/InsubstantialElegantQueenant/capture.png>
// // Straight: stacking lines which are always straight, or a straight horizontal wire between two aligned nodes
// if ((verticalOut && verticalIn) || (!verticalOut && !verticalIn && verticalGap === 0)) {
// return [
// { x: outConnectorX, y: outConnectorY },
// { x: inConnectorX, y: inConnectorY },
// ];
// }

// // L-shape bend
// if (verticalOut !== verticalIn) {
// }

const curveLength = 24;
const curveFalloffRate = curveLength * Math.PI * 2;

const horizontalCurveAmount = -(2 ** ((-10 * horizontalGap) / curveFalloffRate)) + 1;
const verticalCurveAmount = -(2 ** ((-10 * verticalGap) / curveFalloffRate)) + 1;
const horizontalCurve = horizontalCurveAmount * curveLength;
const verticalCurve = verticalCurveAmount * curveLength;

return [
{ x: outConnectorX, y: outConnectorY },
{ x: verticalOut ? outConnectorX : outConnectorX + horizontalCurve, y: verticalOut ? outConnectorY - verticalCurve : outConnectorY },
{ x: verticalIn ? inConnectorX : inConnectorX - horizontalCurve, y: verticalIn ? inConnectorY + verticalCurve : inConnectorY },
{ x: inConnectorX, y: inConnectorY },
];
const inConnectorX = Math.round((inX - containerBounds.x) / scale); // Adjust for scale
const inConnectorY = Math.round((inY - containerBounds.y) / scale); // Adjust for scale

// Handle straight lines
if ((verticalOut && verticalIn && outConnectorX === inConnectorX) || (!verticalOut && !verticalIn && outConnectorY === inConnectorY)) {
return [
{ x: outConnectorX, y: outConnectorY },
{ x: inConnectorX, y: inConnectorY },
];
}

// Handle L-shaped paths
if ((verticalOut && !verticalIn && outConnectorX === inConnectorX) || (!verticalOut && verticalIn && outConnectorY === inConnectorY)) {
return [
{ x: outConnectorX, y: outConnectorY },
{ x: inConnectorX, y: inConnectorY },
];
}

// Handle standard right-angle paths
if (verticalOut && inConnectorX > outConnectorX) {
// Start vertical, then horizontal

return [
{ x: outConnectorX, y: outConnectorY },
{ x: outConnectorX, y: inConnectorY },
{ x: inConnectorX, y: inConnectorY },
];
} else if (verticalOut && inConnectorX <= outConnectorX) {
const midY = (inConnectorY + outConnectorY) / 2 + (((inConnectorY + outConnectorY) / 2) % gridSpacing);
return [
{ x: outConnectorX, y: outConnectorY },
{ x: outConnectorX, y: midY + 5.5 * lineWidth },
{ x: inConnectorX - gridSpacing + lineWidth, y: midY + 5.5 * lineWidth },
{ x: inConnectorX - gridSpacing + lineWidth, y: inConnectorY },
{ x: inConnectorX, y: inConnectorY },
];
} else if (verticalIn) {
// Start horizontal, then vertical

return [
{ x: outConnectorX, y: outConnectorY },
{ x: inConnectorX, y: outConnectorY },
{ x: inConnectorX, y: inConnectorY },
];
} else {
// Both horizontal - use horizontal middle point
// Whwn outConnector point is one of the two closest diagonally opposite points.
if (0 <= inConnectorX - outConnectorX && inConnectorX - outConnectorX <= gridSpacing && inConnectorY - outConnectorY >= -1 * gridSpacing && inConnectorY - outConnectorY <= gridSpacing) {
return [
{ x: outConnectorX - 2 * lineWidth, y: outConnectorY },
{ x: outConnectorX - 2 * lineWidth, y: inConnectorY },
{ x: inConnectorX, y: inConnectorY },
];
}

// When inConnector point lies on the horizontal line above and below the outConnector point.
else if (-1 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= gridSpacing && outConnectorX > inConnectorX) {
if (inConnectorY < outConnectorY) {
return [
{ x: outConnectorX, y: outConnectorY },
{ x: outConnectorX, y: inConnectorY - gridSpacing },
{ x: inConnectorX - gridSpacing, y: inConnectorY - gridSpacing },
{ x: inConnectorX - gridSpacing, y: inConnectorY },
{ x: inConnectorX, y: inConnectorY },
];
} else {
return [
{ x: outConnectorX, y: outConnectorY },
{ x: outConnectorX, y: inConnectorY + gridSpacing },
{ x: inConnectorX - gridSpacing, y: inConnectorY + gridSpacing },
{ x: inConnectorX - gridSpacing, y: inConnectorY },
{ x: inConnectorX, y: inConnectorY },
];
}
} else if (outConnectorX > inConnectorX - gridSpacing) {
const midY = (inConnectorY + outConnectorY) / 2 + (((inConnectorY + outConnectorY) / 2) % gridSpacing);
return [
{ x: outConnectorX, y: outConnectorY },
{ x: outConnectorX + gridSpacing - 2 * lineWidth, y: outConnectorY },
{ x: outConnectorX + gridSpacing - 2 * lineWidth, y: midY },
{ x: inConnectorX - gridSpacing + lineWidth, y: midY },
{ x: inConnectorX - gridSpacing + lineWidth, y: inConnectorY },
{ x: inConnectorX, y: inConnectorY },
];
}
// when inConnector point lies on the vertical grid line two units to the right of outConnector point.
else if (gridSpacing <= inConnectorX - outConnectorX && inConnectorX - outConnectorX <= 2 * gridSpacing) {
return [
{ x: outConnectorX, y: outConnectorY },
{ x: outConnectorX + gridSpacing - 2 * lineWidth, y: outConnectorY },
{ x: outConnectorX + gridSpacing - 2 * lineWidth, y: inConnectorY },
{ x: inConnectorX, y: inConnectorY },
];
} else {
const midX = (outConnectorX + inConnectorX) / 2 + (((outConnectorX + inConnectorX) / 2) % gridSpacing);
return [
{ x: outConnectorX, y: outConnectorY },
{ x: midX - lineWidth, y: outConnectorY },
{ x: midX - lineWidth, y: inConnectorY },
{ x: inConnectorX, y: inConnectorY },
];
}
}
}

function buildWirePathString(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): string {
const locations = buildWirePathLocations(outputBounds, inputBounds, verticalOut, verticalIn);
if (locations.length === 0) return "[error]";
const SMOOTHING = 0.5;
const delta01 = { x: (locations[1].x - locations[0].x) * SMOOTHING, y: (locations[1].y - locations[0].y) * SMOOTHING };
const delta23 = { x: (locations[3].x - locations[2].x) * SMOOTHING, y: (locations[3].y - locations[2].y) * SMOOTHING };
return `
M${locations[0].x},${locations[0].y}
L${locations[1].x},${locations[1].y}
C${locations[1].x + delta01.x},${locations[1].y + delta01.y}
${locations[2].x - delta23.x},${locations[2].y - delta23.y}
${locations[2].x},${locations[2].y}
L${locations[3].x},${locations[3].y}
`
.split("\n")
.map((line) => line.trim())
.join(" ");

if (locations.length === 2) {
return `M${locations[0].x},${locations[0].y} L${locations[1].x},${locations[1].y}`;
}

const CORNER_RADIUS = 10;

// Create path with rounded corners
let path = `M${locations[0].x},${locations[0].y}`;

for (let i = 1; i < locations.length - 1; i++) {
const prev = locations[i - 1];
const curr = locations[i];
const next = locations[i + 1];

// Calculate corner points
const isVertical = curr.x === prev.x;
const cornerStart = {
x: curr.x + (isVertical ? 0 : prev.x < curr.x ? -CORNER_RADIUS : CORNER_RADIUS),
y: curr.y + (isVertical ? (prev.y < curr.y ? -CORNER_RADIUS : CORNER_RADIUS) : 0),
};
const cornerEnd = {
x: curr.x + (isVertical ? (next.x < curr.x ? -CORNER_RADIUS : CORNER_RADIUS) : 0),
y: curr.y + (isVertical ? 0 : next.y < curr.y ? -CORNER_RADIUS : CORNER_RADIUS),
};

// Add line to corner start, quadratic curve for corner, then continue to next point
path += ` L${cornerStart.x},${cornerStart.y}`;
path += ` Q${curr.x},${curr.y} ${cornerEnd.x},${cornerEnd.y}`;
}

path += ` L${locations[locations.length - 1].x},${locations[locations.length - 1].y}`;
return path;
}

function toggleLayerDisplay(displayAsLayer: boolean, toggleId: bigint) {
Expand Down