// 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(); }