Files
logic-gates/js/bus.js
Jose Luis 99f0fefe5c feat: shift+drag to cut wires and create bus connectors
Hold Shift and drag across wires to create a BUS gate that groups
them together. The cut line shows a live preview with wire count.
BUS gates are pass-through (each input maps to its output) and
render as a thin cyan bar with ports on each side.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 04:39:00 +01:00

162 lines
5.0 KiB
JavaScript

// Bus system — shift+drag to cut wires and create bus connectors
import { state } from './state.js';
import { getOutputPorts, getInputPorts, getComponentWidth, getComponentHeight, evaluateAll } from './gates.js';
/**
* Sample a cubic bezier curve into discrete line segments.
* Returns array of {x, y} points.
*/
function sampleBezier(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, steps = 24) {
const points = [];
for (let i = 0; i <= steps; i++) {
const t = i / steps;
const mt = 1 - t;
const mt2 = mt * mt, mt3 = mt2 * mt;
const t2 = t * t, t3 = t2 * t;
points.push({
x: mt3 * p0x + 3 * mt2 * t * p1x + 3 * mt * t2 * p2x + t3 * p3x,
y: mt3 * p0y + 3 * mt2 * t * p1y + 3 * mt * t2 * p2y + t3 * p3y
});
}
return points;
}
/**
* Test intersection between two line segments.
* Returns { x, y, t } if they intersect, null otherwise.
* t is the parameter along the first segment (0..1).
*/
function segmentIntersect(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) {
const dx1 = ax2 - ax1, dy1 = ay2 - ay1;
const dx2 = bx2 - bx1, dy2 = by2 - by1;
const d = dx1 * dy2 - dy1 * dx2;
if (Math.abs(d) < 1e-10) return null;
const t = ((bx1 - ax1) * dy2 - (by1 - ay1) * dx2) / d;
const u = ((bx1 - ax1) * dy1 - (by1 - ay1) * dx1) / d;
if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
return {
x: ax1 + t * dx1,
y: ay1 + t * dy1,
t // position along the CUT line (for sorting)
};
}
return null;
}
/**
* Get the bezier control points for a connection, matching drawConnection in renderer.js.
*/
function getConnectionBezier(conn) {
const fromGate = state.gates.find(g => g.id === conn.from);
const toGate = state.gates.find(g => g.id === conn.to);
if (!fromGate || !toGate) return null;
const fromPort = getOutputPorts(fromGate)[conn.fromPort];
const toPort = getInputPorts(toGate)[conn.toPort];
if (!fromPort || !toPort) return null;
const midX = (fromPort.x + toPort.x) / 2;
return {
p0x: fromPort.x, p0y: fromPort.y,
p1x: midX, p1y: fromPort.y,
p2x: midX, p2y: toPort.y,
p3x: toPort.x, p3y: toPort.y
};
}
/**
* Find all connections that intersect a cut line.
* Returns array of { conn, hitPoint } sorted by position along the cut line.
*/
export function findIntersectingConnections(startX, startY, endX, endY) {
const results = [];
for (const conn of state.connections) {
const bez = getConnectionBezier(conn);
if (!bez) continue;
const points = sampleBezier(
bez.p0x, bez.p0y, bez.p1x, bez.p1y,
bez.p2x, bez.p2y, bez.p3x, bez.p3y
);
// Test each bezier segment against the cut line
for (let i = 0; i < points.length - 1; i++) {
const hit = segmentIntersect(
startX, startY, endX, endY,
points[i].x, points[i].y, points[i + 1].x, points[i + 1].y
);
if (hit) {
results.push({ conn, hitPoint: hit });
break; // one hit per connection is enough
}
}
}
// Sort by position along the cut line (t parameter) so bus ports are ordered visually
results.sort((a, b) => a.hitPoint.t - b.hitPoint.t);
return results;
}
/**
* Create a BUS gate from a cut line and rewire intersecting connections through it.
*/
export function createBusFromCut() {
const cut = state.busCutting;
if (!cut) return;
const hits = findIntersectingConnections(cut.startX, cut.startY, cut.endX, cut.endY);
if (hits.length === 0) {
console.log('[bus] no connections intersected');
return;
}
const n = hits.length;
console.log(`[bus] cut intersected ${n} connection(s)`);
// Calculate bus position: centroid of all hit points
const avgX = hits.reduce((s, h) => s + h.hitPoint.x, 0) / n;
const avgY = hits.reduce((s, h) => s + h.hitPoint.y, 0) / n;
// Create BUS gate
const busType = `BUS:${n}`;
const busGate = {
id: state.nextId++,
type: busType,
x: avgX - 15, // half of bus width (30)
y: avgY - Math.max(40, (n + 1) * 22) / 2,
value: 0,
outputValues: new Array(n).fill(0)
};
state.gates.push(busGate);
// Rewire: for each intersected connection, split through the bus
hits.forEach((hit, i) => {
const origConn = hit.conn;
// Remove the original connection
state.connections = state.connections.filter(c => c !== origConn);
// Source → BUS input port i
state.connections.push({
from: origConn.from,
fromPort: origConn.fromPort,
to: busGate.id,
toPort: i
});
// BUS output port i → original destination
state.connections.push({
from: busGate.id,
fromPort: i,
to: origConn.to,
toPort: origConn.toPort
});
});
console.log(`[bus] created BUS#${busGate.id} with ${n} channels`);
evaluateAll();
}