// Three.js renderer for zigzag-engine half_braid geometry // Renders VISIBLE elements only from explosion poset // Visibility: point at geom_dim d is visible iff coords[d..] are all singular const GEOMETRY_DATA = { "metadata": { "source": "half_braid.json", "dimension": 3, "total_points": 23, "visible_points": 7, "total_covers": 35 }, "vertices": [ { "id": 21, "label": "vertex_0", "point": "s0,s0,s0", "coords": [0.0, 0.0, -0.5] }, { "id": 22, "label": "vertex_1", "point": "s1,s0,s0", "coords": [0.0, 0.0, 0.5] } ], "wires": [ { "id": 18, "label": "wire_0", "point": "r0,s0,s0", "coords": [0.0, 0.0, -1.0], "endpoints": [21, 22], "endpoint_coords": [[0.0, 0.0, -0.5], [0.0, 0.0, 0.5]] }, { "id": 19, "label": "wire_1", "point": "r1,s0,s0", "coords": [0.0, 0.0, 0.0], "endpoints": [21, 22], "endpoint_coords": [[0.0, 0.0, -0.5], [0.0, 0.0, 0.5]] }, { "id": 20, "label": "wire_2", "point": "r2,s0,s0", "coords": [0.0, 0.0, 1.0], "endpoints": [21, 22], "endpoint_coords": [[0.0, 0.0, -0.5], [0.0, 0.0, 0.5]] } ], "surfaces": [ { "id": 16, "label": "surface_0", "point": "r0,r0,s0", "coords": [0.0, -1.0, -1.0], "boundary_wires": [18] }, { "id": 17, "label": "surface_1", "point": "r0,r1,s0", "coords": [0.0, 0.0, -1.0], "boundary_wires": [18] } ] }; // Coordinate mapping: coords are already [x, y, z] layout coordinates // Scale for better visualization const SCALE = 2.0; function mapCoords(coords) { return [coords[0] * SCALE, coords[1] * SCALE, coords[2] * SCALE]; } // Wire colors by group const WIRE_COLORS = { input: 0x4fc3f7, // blue - r0 in third coord crossing: 0xe040fb, // purple - s0 in third coord output: 0x66bb6a, // green - r1 in third coord selfloop: 0xff7043 // orange - self-loops }; function getWireGroup(wire) { if (wire.endpoints[0] === wire.endpoints[1]) return 'selfloop'; const point = wire.point; const thirdCoord = point.split(',')[2]; if (thirdCoord === 'r0') return 'input'; if (thirdCoord === 's0') return 'crossing'; if (thirdCoord === 'r1') return 'output'; return 'crossing'; } // Variational elastic curve solver // Minimizes E = τ·Σ|Δp|² + β·Σ|Δ²p|² + κ·|p_mid - waypoint|² function solveElasticCurve(p0, p1, waypoint, tau, beta, kappa, resolution) { const n = resolution; // interior points const total = n + 2; // including endpoints // Build matrix A = τ·DᵀD + β·D²ᵀD² + κ·(waypoint spring at midpoint) // DᵀD tridiagonal: main=2, off=-1 // D²ᵀD² pentadiagonal: [1, -4, 6, -4, 1] // We solve for interior points only (indices 1..n) // Matrix is n×n const A = []; const bx = new Array(n).fill(0); const by = new Array(n).fill(0); const bz = new Array(n).fill(0); for (let i = 0; i < n; i++) { A[i] = new Array(n).fill(0); } // Build DᵀD (tension term) for (let i = 0; i < n; i++) { A[i][i] += 2 * tau; if (i > 0) A[i][i-1] += -tau; if (i < n-1) A[i][i+1] += -tau; } // Boundary contributions to RHS for tension term bx[0] += tau * p0[0]; by[0] += tau * p0[1]; bz[0] += tau * p0[2]; bx[n-1] += tau * p1[0]; by[n-1] += tau * p1[1]; bz[n-1] += tau * p1[2]; // Build D²ᵀD² (bending term) - pentadiagonal // For interior point i, D²p[i] = p[i-2] - 4p[i-1] + 6p[i] - 4p[i+1] + p[i+2] for (let i = 0; i < n; i++) { A[i][i] += 6 * beta; if (i > 0) A[i][i-1] += -4 * beta; if (i > 1) A[i][i-2] += beta; if (i < n-1) A[i][i+1] += -4 * beta; if (i < n-2) A[i][i+2] += beta; } // Boundary contributions for bending term // At i=0: needs p[-1]=p0, p[-2] (extrapolate as p0) // At i=1: needs p[-1]=p0 // At i=n-2: needs p[n]=p1 // At i=n-1: needs p[n]=p1, p[n+1] (extrapolate as p1) // i=0 contributions from p0 (at index -1) and extrapolated p0 (at index -2) bx[0] += beta * (4 * p0[0] - p0[0]); // -4*p[-1] + p[-2], but these go to RHS with sign flip by[0] += beta * (4 * p0[1] - p0[1]); bz[0] += beta * (4 * p0[2] - p0[2]); if (n > 1) { bx[1] += beta * p0[0]; by[1] += beta * p0[1]; bz[1] += beta * p0[2]; } // i=n-1 contributions from p1 (at index n) and extrapolated p1 (at index n+1) bx[n-1] += beta * (4 * p1[0] - p1[0]); by[n-1] += beta * (4 * p1[1] - p1[1]); bz[n-1] += beta * (4 * p1[2] - p1[2]); if (n > 1) { bx[n-2] += beta * p1[0]; by[n-2] += beta * p1[1]; bz[n-2] += beta * p1[2]; } // Waypoint spring at midpoint const midIdx = Math.floor(n / 2); A[midIdx][midIdx] += kappa; bx[midIdx] += kappa * waypoint[0]; by[midIdx] += kappa * waypoint[1]; bz[midIdx] += kappa * waypoint[2]; // Solve using Gaussian elimination with partial pivoting function solveTridiagonal(A, b) { const n = b.length; const x = new Array(n); const Ac = A.map(row => [...row]); const bc = [...b]; // Forward elimination for (let k = 0; k < n-1; k++) { // Find pivot let maxIdx = k; for (let i = k+1; i < n; i++) { if (Math.abs(Ac[i][k]) > Math.abs(Ac[maxIdx][k])) maxIdx = i; } // Swap rows [Ac[k], Ac[maxIdx]] = [Ac[maxIdx], Ac[k]]; [bc[k], bc[maxIdx]] = [bc[maxIdx], bc[k]]; if (Math.abs(Ac[k][k]) < 1e-10) continue; for (let i = k+1; i < n; i++) { const factor = Ac[i][k] / Ac[k][k]; for (let j = k; j < n; j++) { Ac[i][j] -= factor * Ac[k][j]; } bc[i] -= factor * bc[k]; } } // Back substitution for (let i = n-1; i >= 0; i--) { let sum = bc[i]; for (let j = i+1; j < n; j++) { sum -= Ac[i][j] * x[j]; } x[i] = Math.abs(Ac[i][i]) > 1e-10 ? sum / Ac[i][i] : 0; } return x; } const solX = solveTridiagonal(A, bx); const solY = solveTridiagonal(A, by); const solZ = solveTridiagonal(A, bz); // Build full curve with endpoints const curve = [p0]; for (let i = 0; i < n; i++) { curve.push([solX[i], solY[i], solZ[i]]); } curve.push(p1); return curve; } // Generate self-loop curve (parametric loop offset toward waypoint) function generateSelfLoop(center, waypoint, resolution) { const points = []; const offset = [ waypoint[0] - center[0], waypoint[1] - center[1], waypoint[2] - center[2] ]; const offsetMag = Math.sqrt(offset[0]**2 + offset[1]**2 + offset[2]**2); const norm = offsetMag > 0.01 ? offset.map(x => x / offsetMag) : [0, 1, 0]; // Create perpendicular vectors for the loop plane let perp1, perp2; if (Math.abs(norm[1]) < 0.9) { perp1 = [norm[2], 0, -norm[0]]; } else { perp1 = [0, norm[2], -norm[1]]; } const mag1 = Math.sqrt(perp1[0]**2 + perp1[1]**2 + perp1[2]**2); perp1 = perp1.map(x => x / mag1); perp2 = [ norm[1]*perp1[2] - norm[2]*perp1[1], norm[2]*perp1[0] - norm[0]*perp1[2], norm[0]*perp1[1] - norm[1]*perp1[0] ]; const loopRadius = Math.min(offsetMag * 0.6, 0.8); const loopCenter = [ center[0] + norm[0] * offsetMag * 0.4, center[1] + norm[1] * offsetMag * 0.4, center[2] + norm[2] * offsetMag * 0.4 ]; for (let i = 0; i <= resolution; i++) { const t = (i / resolution) * 2 * Math.PI; const x = loopCenter[0] + loopRadius * (Math.cos(t) * perp1[0] + Math.sin(t) * perp2[0]); const y = loopCenter[1] + loopRadius * (Math.cos(t) * perp1[1] + Math.sin(t) * perp2[1]); const z = loopCenter[2] + loopRadius * (Math.cos(t) * perp1[2] + Math.sin(t) * perp2[2]); points.push([x, y, z]); } return points; } function ZigzagRenderer() { const containerRef = React.useRef(null); const sceneRef = React.useRef(null); const cameraRef = React.useRef(null); const rendererRef = React.useRef(null); const wireObjectsRef = React.useRef([]); const waypointObjectsRef = React.useRef([]); const surfaceObjectsRef = React.useRef([]); const labelObjectsRef = React.useRef([]); const [tension, setTension] = React.useState(1.0); const [bending, setBending] = React.useState(0.5); const [kappa, setKappa] = React.useState(2.0); const [resolution, setResolution] = React.useState(20); const [showWaypoints, setShowWaypoints] = React.useState(true); const [showSurfaces, setShowSurfaces] = React.useState(true); const [showLabels, setShowLabels] = React.useState(true); // Orbit controls state const orbitRef = React.useRef({ theta: Math.PI / 4, phi: Math.PI / 4, radius: 12, target: [2, 2, 2], isDragging: false, lastX: 0, lastY: 0 }); // Initialize Three.js scene React.useEffect(() => { if (!containerRef.current || !window.THREE) return; const THREE = window.THREE; const width = containerRef.current.clientWidth; const height = containerRef.current.clientHeight; // Scene const scene = new THREE.Scene(); scene.background = new THREE.Color(0x0a0a0f); sceneRef.current = scene; // Camera const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 100); cameraRef.current = camera; // Renderer const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(width, height); renderer.setPixelRatio(window.devicePixelRatio); containerRef.current.appendChild(renderer.domElement); rendererRef.current = renderer; // Lights const ambient = new THREE.AmbientLight(0xffffff, 0.4); scene.add(ambient); const directional = new THREE.DirectionalLight(0xffffff, 0.8); directional.position.set(5, 10, 5); scene.add(directional); // Add vertices as glowing spheres const vertexGeom = new THREE.SphereGeometry(0.15, 32, 32); const vertexMat = new THREE.MeshStandardMaterial({ color: 0xf0c040, emissive: 0xf0c040, emissiveIntensity: 0.5 }); GEOMETRY_DATA.vertices.forEach(v => { const pos = mapCoords(v.coords); const mesh = new THREE.Mesh(vertexGeom, vertexMat); mesh.position.set(pos[0], pos[1], pos[2]); scene.add(mesh); }); // Grid helper const gridHelper = new THREE.GridHelper(10, 10, 0x333344, 0x222233); gridHelper.position.y = -0.5; scene.add(gridHelper); // Axes helper const axesHelper = new THREE.AxesHelper(3); scene.add(axesHelper); // Mouse controls const canvas = renderer.domElement; canvas.addEventListener('mousedown', (e) => { orbitRef.current.isDragging = true; orbitRef.current.lastX = e.clientX; orbitRef.current.lastY = e.clientY; }); canvas.addEventListener('mousemove', (e) => { if (!orbitRef.current.isDragging) return; const dx = e.clientX - orbitRef.current.lastX; const dy = e.clientY - orbitRef.current.lastY; orbitRef.current.theta -= dx * 0.01; orbitRef.current.phi = Math.max(0.1, Math.min(Math.PI - 0.1, orbitRef.current.phi + dy * 0.01)); orbitRef.current.lastX = e.clientX; orbitRef.current.lastY = e.clientY; }); canvas.addEventListener('mouseup', () => { orbitRef.current.isDragging = false; }); canvas.addEventListener('mouseleave', () => { orbitRef.current.isDragging = false; }); canvas.addEventListener('wheel', (e) => { e.preventDefault(); orbitRef.current.radius = Math.max(3, Math.min(30, orbitRef.current.radius + e.deltaY * 0.01)); }); // Animation loop function animate() { requestAnimationFrame(animate); const orbit = orbitRef.current; const x = orbit.target[0] + orbit.radius * Math.sin(orbit.phi) * Math.cos(orbit.theta); const y = orbit.target[1] + orbit.radius * Math.cos(orbit.phi); const z = orbit.target[2] + orbit.radius * Math.sin(orbit.phi) * Math.sin(orbit.theta); camera.position.set(x, y, z); camera.lookAt(orbit.target[0], orbit.target[1], orbit.target[2]); renderer.render(scene, camera); } animate(); // Resize handler const handleResize = () => { const w = containerRef.current.clientWidth; const h = containerRef.current.clientHeight; camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h); }; window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); renderer.dispose(); if (containerRef.current && renderer.domElement) { containerRef.current.removeChild(renderer.domElement); } }; }, []); // Update wires when parameters change React.useEffect(() => { if (!sceneRef.current || !window.THREE) return; const THREE = window.THREE; const scene = sceneRef.current; // Remove old wire objects wireObjectsRef.current.forEach(obj => scene.remove(obj)); wireObjectsRef.current = []; // Create wire lookup for surfaces const wireById = {}; // Add wires GEOMETRY_DATA.wires.forEach(wire => { const p0 = mapCoords(wire.endpoint_coords[0]); const p1 = mapCoords(wire.endpoint_coords[1]); const waypoint = mapCoords(wire.coords); const group = getWireGroup(wire); const color = WIRE_COLORS[group]; let curvePoints; if (wire.endpoints[0] === wire.endpoints[1]) { // Self-loop curvePoints = generateSelfLoop(p0, waypoint, resolution); } else { // Elastic curve curvePoints = solveElasticCurve(p0, p1, waypoint, tension, bending, kappa, resolution); } wireById[wire.id] = curvePoints; // Create tube geometry const points = curvePoints.map(p => new THREE.Vector3(p[0], p[1], p[2])); const curve = new THREE.CatmullRomCurve3(points); const tubeGeom = new THREE.TubeGeometry(curve, resolution * 2, 0.04, 8, false); const tubeMat = new THREE.MeshStandardMaterial({ color: color, emissive: color, emissiveIntensity: 0.3 }); const tube = new THREE.Mesh(tubeGeom, tubeMat); scene.add(tube); wireObjectsRef.current.push(tube); }); // Store wire paths for surface rendering sceneRef.current.userData.wireById = wireById; }, [tension, bending, kappa, resolution]); // Update waypoint visibility React.useEffect(() => { if (!sceneRef.current || !window.THREE) return; const THREE = window.THREE; const scene = sceneRef.current; // Remove old waypoint objects waypointObjectsRef.current.forEach(obj => scene.remove(obj)); waypointObjectsRef.current = []; if (showWaypoints) { const waypointGeom = new THREE.SphereGeometry(0.08, 16, 16); const waypointMat = new THREE.MeshStandardMaterial({ color: 0xf0c040, emissive: 0xf0c040, emissiveIntensity: 0.6, transparent: true, opacity: 0.8 }); GEOMETRY_DATA.wires.forEach(wire => { const pos = mapCoords(wire.coords); const mesh = new THREE.Mesh(waypointGeom, waypointMat); mesh.position.set(pos[0], pos[1], pos[2]); scene.add(mesh); waypointObjectsRef.current.push(mesh); }); } }, [showWaypoints]); // Update surface visibility React.useEffect(() => { if (!sceneRef.current || !window.THREE) return; const THREE = window.THREE; const scene = sceneRef.current; // Remove old surface objects surfaceObjectsRef.current.forEach(obj => scene.remove(obj)); surfaceObjectsRef.current = []; if (showSurfaces) { const surfaceMat = new THREE.MeshStandardMaterial({ color: 0xff6b6b, emissive: 0xff6b6b, emissiveIntensity: 0.4, transparent: true, opacity: 0.6 }); const lineMat = new THREE.LineBasicMaterial({ color: 0xff6b6b, transparent: true, opacity: 0.4 }); GEOMETRY_DATA.surfaces.forEach(surface => { const pos = mapCoords(surface.coords); // Surface center point const sphereGeom = new THREE.SphereGeometry(0.06, 12, 12); const sphere = new THREE.Mesh(sphereGeom, surfaceMat); sphere.position.set(pos[0], pos[1], pos[2]); scene.add(sphere); surfaceObjectsRef.current.push(sphere); // Lines to boundary wire midpoints const wireById = scene.userData.wireById || {}; surface.boundary_wires.forEach(wireId => { const wire = GEOMETRY_DATA.wires.find(w => w.id === wireId); if (wire) { const wirePos = mapCoords(wire.coords); const geometry = new THREE.BufferGeometry().setFromPoints([ new THREE.Vector3(pos[0], pos[1], pos[2]), new THREE.Vector3(wirePos[0], wirePos[1], wirePos[2]) ]); const line = new THREE.Line(geometry, lineMat); scene.add(line); surfaceObjectsRef.current.push(line); } }); }); } }, [showSurfaces, tension, bending, kappa, resolution]); // Update label visibility React.useEffect(() => { if (!sceneRef.current || !window.THREE) return; const THREE = window.THREE; const scene = sceneRef.current; // Remove old label objects labelObjectsRef.current.forEach(obj => scene.remove(obj)); labelObjectsRef.current = []; if (showLabels) { // Create canvas-based sprite labels GEOMETRY_DATA.vertices.forEach(v => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = 128; canvas.height = 64; ctx.fillStyle = 'rgba(10, 10, 15, 0.8)'; ctx.fillRect(0, 0, 128, 64); ctx.strokeStyle = '#f0c040'; ctx.lineWidth = 2; ctx.strokeRect(2, 2, 124, 60); ctx.font = 'bold 20px monospace'; ctx.fillStyle = '#f0c040'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(v.label, 64, 32); const texture = new THREE.CanvasTexture(canvas); const spriteMat = new THREE.SpriteMaterial({ map: texture, transparent: true }); const sprite = new THREE.Sprite(spriteMat); const pos = mapCoords(v.coords); sprite.position.set(pos[0], pos[1] + 0.4, pos[2]); sprite.scale.set(1, 0.5, 1); scene.add(sprite); labelObjectsRef.current.push(sprite); }); } }, [showLabels]); const panelStyle = { position: 'absolute', top: '20px', left: '20px', background: 'rgba(10, 10, 20, 0.85)', backdropFilter: 'blur(10px)', padding: '20px', borderRadius: '12px', color: '#e0e0e0', fontFamily: 'monospace', fontSize: '14px', border: '1px solid rgba(240, 192, 64, 0.3)', minWidth: '220px' }; const sliderStyle = { width: '100%', marginTop: '4px', accentColor: '#f0c040' }; const checkboxStyle = { accentColor: '#f0c040', marginRight: '8px' }; return React.createElement('div', { style: { width: '100vw', height: '100vh', position: 'relative' } }, React.createElement('div', { ref: containerRef, style: { width: '100%', height: '100%' } }), React.createElement('div', { style: panelStyle }, React.createElement('h3', { style: { margin: '0 0 15px 0', color: '#f0c040' } }, 'Zigzag Renderer'), React.createElement('div', { style: { marginBottom: '12px' } }, React.createElement('label', null, `Tension τ: ${tension.toFixed(2)}`), React.createElement('input', { type: 'range', min: '0.1', max: '5', step: '0.1', value: tension, onChange: (e) => setTension(parseFloat(e.target.value)), style: sliderStyle }) ), React.createElement('div', { style: { marginBottom: '12px' } }, React.createElement('label', null, `Bending β: ${bending.toFixed(2)}`), React.createElement('input', { type: 'range', min: '0', max: '2', step: '0.05', value: bending, onChange: (e) => setBending(parseFloat(e.target.value)), style: sliderStyle }) ), React.createElement('div', { style: { marginBottom: '12px' } }, React.createElement('label', null, `Waypoint κ: ${kappa.toFixed(2)}`), React.createElement('input', { type: 'range', min: '0', max: '10', step: '0.1', value: kappa, onChange: (e) => setKappa(parseFloat(e.target.value)), style: sliderStyle }) ), React.createElement('div', { style: { marginBottom: '12px' } }, React.createElement('label', null, `Resolution: ${resolution}`), React.createElement('input', { type: 'range', min: '5', max: '50', step: '1', value: resolution, onChange: (e) => setResolution(parseInt(e.target.value)), style: sliderStyle }) ), React.createElement('hr', { style: { border: 'none', borderTop: '1px solid rgba(240, 192, 64, 0.3)', margin: '15px 0' } }), React.createElement('div', { style: { marginBottom: '8px' } }, React.createElement('label', null, React.createElement('input', { type: 'checkbox', checked: showWaypoints, onChange: (e) => setShowWaypoints(e.target.checked), style: checkboxStyle }), 'Waypoints' ) ), React.createElement('div', { style: { marginBottom: '8px' } }, React.createElement('label', null, React.createElement('input', { type: 'checkbox', checked: showSurfaces, onChange: (e) => setShowSurfaces(e.target.checked), style: checkboxStyle }), 'Surfaces' ) ), React.createElement('div', { style: { marginBottom: '8px' } }, React.createElement('label', null, React.createElement('input', { type: 'checkbox', checked: showLabels, onChange: (e) => setShowLabels(e.target.checked), style: checkboxStyle }), 'Labels' ) ), React.createElement('hr', { style: { border: 'none', borderTop: '1px solid rgba(240, 192, 64, 0.3)', margin: '15px 0' } }), React.createElement('div', { style: { fontSize: '11px', color: '#888' } }, React.createElement('div', null, '🔵 Input (r0)'), React.createElement('div', null, '🟣 Crossing (s0)'), React.createElement('div', null, '🟢 Output (r1)'), React.createElement('div', null, '🟠 Self-loop') ) ) ); } // Export for use window.ZigzagRenderer = ZigzagRenderer;