Download vortex_torus_diagram.html from EvanPi/NGVT: direct link, hf CLI and curl.
- Browser
- Download file 27.6 kB
-
https://huggingface.co/EvanPi/NGVT/resolve/main/vortex_torus_diagram.html
- Command line
-
hf download hf://EvanPi/NGVT/vortex_torus_diagram.html
-
curl -L -o vortex_torus_diagram.html https://huggingface.co/EvanPi/NGVT/resolve/main/vortex_torus_diagram.html
27.6 kB
| .control-group input[type="checkbox"] { | |
| width: auto; | |
| margin-right: 5px; | |
| } | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>NGVT: Vortex Torus - Compatible Version</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <style> | |
| body { | |
| margin: 0; | |
| background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%); | |
| font-family: 'Arial', sans-serif; | |
| overflow: hidden; | |
| color: white; | |
| } | |
| #container { | |
| position: relative; | |
| width: 100vw; | |
| height: 100vh; | |
| } | |
| #info { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| z-index: 100; | |
| background: rgba(0, 0, 0, 0.9); | |
| padding: 20px; | |
| border-radius: 10px; | |
| border: 1px solid #00ffff; | |
| box-shadow: 0 0 20px rgba(0, 255, 255, 0.3); | |
| max-width: 280px; | |
| } | |
| #info h1 { | |
| margin: 0 0 10px 0; | |
| color: #00ffff; | |
| font-size: 20px; | |
| text-shadow: 0 0 10px rgba(0, 255, 255, 0.5); | |
| } | |
| #info h2 { | |
| margin: 15px 0 5px 0; | |
| color: #ff6b6b; | |
| font-size: 14px; | |
| } | |
| #info p { | |
| margin: 3px 0; | |
| font-size: 12px; | |
| color: #cccccc; | |
| } | |
| #controls { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 20px; | |
| z-index: 100; | |
| background: rgba(0, 0, 0, 0.9); | |
| padding: 15px; | |
| border-radius: 10px; | |
| border: 1px solid #ff6b6b; | |
| } | |
| .control-group { | |
| margin: 10px 0; | |
| } | |
| .control-group label { | |
| display: block; | |
| color: #ff6b6b; | |
| font-size: 11px; | |
| margin-bottom: 5px; | |
| } | |
| .control-group input { | |
| width: 120px; | |
| } | |
| #legend { | |
| position: absolute; | |
| bottom: 20px; | |
| right: 20px; | |
| z-index: 100; | |
| background: rgba(0, 0, 0, 0.9); | |
| padding: 15px; | |
| border-radius: 10px; | |
| border: 1px solid #4ecdc4; | |
| } | |
| .legend-item { | |
| display: flex; | |
| align-items: center; | |
| margin: 6px 0; | |
| } | |
| .legend-color { | |
| width: 16px; | |
| height: 16px; | |
| margin-right: 8px; | |
| border-radius: 3px; | |
| } | |
| .legend-text { | |
| font-size: 11px; | |
| color: #cccccc; | |
| } | |
| #error-message { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(255, 0, 0, 0.1); | |
| border: 1px solid #ff6b6b; | |
| padding: 20px; | |
| border-radius: 10px; | |
| color: #ff6b6b; | |
| display: none; | |
| text-align: center; | |
| } | |
| #fallback-canvas { | |
| display: none; | |
| border: 1px solid #4ecdc4; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="container"> | |
| <div id="error-message"> | |
| <h3>WebGL Not Available</h3> | |
| <p>Your browser doesn't support WebGL or it's disabled.</p> | |
| <p>Falling back to 2D visualization...</p> | |
| </div> | |
| <div id="info"> | |
| <h1>NGVT Vortex Torus</h1> | |
| <p><strong>Directional Flow Visualization</strong></p> | |
| <h2>Torus Parameters:</h2> | |
| <p>Rings: 1-5 concentric</p> | |
| <p>Particles: ON SAME PLANE</p> | |
| <p>Drag to orbit manually</p> | |
| <h2>Code Flow:</h2> | |
| <p>6 colored spiral bands</p> | |
| <p>Tight/loose spiral control</p> | |
| <p>Multi-ring vortex</p> | |
| <h2>Performance:</h2> | |
| <p>SWE-bench: 98.33%</p> | |
| <p>Speed: 7.4× faster</p> | |
| </div> | |
| <div id="controls"> | |
| <h3 style="color: #ff6b6b; margin-top: 0; font-size: 14px;">Flow Controls</h3> | |
| <div class="control-group"> | |
| <label>Flow Speed:</label> | |
| <input type="range" id="flowSpeed" min="0.1" max="2.0" step="0.1" value="0.8"> | |
| </div> | |
| <div class="control-group"> | |
| <label>Number of Rings:</label> | |
| <input type="range" id="numberOfRings" min="1" max="5" step="1" value="1"> | |
| </div> | |
| <div class="control-group"> | |
| <label>Spiral Pitch:</label> | |
| <input type="range" id="spiralPitch" min="0.5" max="4.0" step="0.1" value="2.0"> | |
| </div> | |
| <div class="control-group"> | |
| <label>Spiral Tightness:</label> | |
| <input type="range" id="spiralTightness" min="2" max="20" step="1" value="8"> | |
| </div> | |
| <div class="control-group"> | |
| <label>Ring Size:</label> | |
| <input type="range" id="ringSize" min="2.0" max="8.0" step="0.2" value="4.0"> | |
| </div> | |
| <div class="control-group"> | |
| <label>Zoom Level:</label> | |
| <input type="range" id="zoomLevel" min="5.0" max="30.0" step="1.0" value="15.0"> | |
| </div> | |
| <div class="control-group"> | |
| <label>Rotation:</label> | |
| <input type="checkbox" id="rotationToggle" checked> | |
| <span style="color: #ff6b6b; font-size: 11px; margin-left: 5px;">Auto Orbit</span> | |
| </div> | |
| <div class="control-group"> | |
| <label>Vortex Intensity:</label> | |
| <input type="range" id="vortexIntensity" min="0.1" max="1.5" step="0.1" value="0.8"> | |
| </div> | |
| </div> | |
| <div id="legend"> | |
| <h3 style="color: #4ecdc4; margin-top: 0; font-size: 14px;">Flow Elements</h3> | |
| <div class="legend-item"> | |
| <div class="legend-color" style="background: #00ffff; box-shadow: 0 0 8px #00ffff;"></div> | |
| <div class="legend-text">Functions</div> | |
| </div> | |
| <div class="legend-item"> | |
| <div class="legend-color" style="background: #ff6b6b; box-shadow: 0 0 8px #ff6b6b;"></div> | |
| <div class="legend-text">Variables</div> | |
| </div> | |
| <div class="legend-item"> | |
| <div class="legend-color" style="background: #4ecdc4; box-shadow: 0 0 8px #4ecdc4;"></div> | |
| <div class="legend-text">Keywords</div> | |
| </div> | |
| <div class="legend-item"> | |
| <div class="legend-color" style="background: #ffd93d; box-shadow: 0 0 8px #ffd93d;"></div> | |
| <div class="legend-text">Operators</div> | |
| </div> | |
| <div class="legend-item"> | |
| <div class="legend-color" style="background: #a8e6cf; box-shadow: 0 0 8px #a8e6cf;"></div> | |
| <div class="legend-text">Comments</div> | |
| </div> | |
| <div class="legend-item"> | |
| <div class="legend-color" style="background: #dda0dd; box-shadow: 0 0 8px #dda0dd;"></div> | |
| <div class="legend-text">Strings</div> | |
| </div> | |
| </div> | |
| <canvas id="fallback-canvas" width="800" height="600"></canvas> | |
| </div> | |
| <script> | |
| // Global variables | |
| let scene, camera, renderer; | |
| let torus, codeSegments = []; | |
| let animationId; | |
| // Control variables | |
| let flowSpeed = 0.8; | |
| let spiralPitch = 2.0; | |
| let ringSize = 4.0; | |
| let vortexIntensity = 0.8; | |
| let zoomLevel = 15.0; | |
| let rotationEnabled = true; | |
| let numberOfRings = 1; // New: multiple rings control | |
| let spiralTightness = 8; // New: corkscrew tightness control | |
| // Mouse drag controls | |
| let isDragging = false; | |
| let previousMousePosition = { x: 0, y: 0 }; | |
| let cameraAngleX = 0; | |
| let cameraAngleY = 0; | |
| // Check WebGL support | |
| function isWebGLAvailable() { | |
| try { | |
| const canvas = document.createElement('canvas'); | |
| return !!(window.WebGLRenderingContext && ( | |
| canvas.getContext('webgl') || | |
| canvas.getContext('experimental-webgl') || | |
| canvas.getContext('webgl2') | |
| )); | |
| } catch (e) { | |
| return false; | |
| } | |
| } | |
| // Initialize Three.js with error handling | |
| function initThreeJS() { | |
| try { | |
| // Scene setup | |
| scene = new THREE.Scene(); | |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| // Renderer with fallback options | |
| const rendererOptions = { | |
| antialias: false, // Disable for compatibility | |
| alpha: true, | |
| powerPreference: "default" // Don't force high-performance | |
| }; | |
| renderer = new THREE.WebGLRenderer(rendererOptions); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.setClearColor(0x000000, 0); | |
| // Test if renderer actually works | |
| const testGeometry = new THREE.BoxGeometry(1, 1, 1); | |
| const testMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); | |
| const testMesh = new THREE.Mesh(testGeometry, testMaterial); | |
| scene.add(testMesh); | |
| renderer.render(scene, camera); | |
| scene.remove(testMesh); | |
| document.getElementById('container').appendChild(renderer.domElement); | |
| return true; | |
| } catch (error) { | |
| console.error('Three.js initialization failed:', error); | |
| return false; | |
| } | |
| } | |
| // 2D Canvas fallback | |
| function initFallbackCanvas() { | |
| const canvas = document.getElementById('fallback-canvas'); | |
| canvas.style.display = 'block'; | |
| canvas.style.position = 'absolute'; | |
| canvas.style.top = '50%'; | |
| canvas.style.left = '50%'; | |
| canvas.style.transform = 'translate(-50%, -50%)'; | |
| const ctx = canvas.getContext('2d'); | |
| let time = 0; | |
| function drawFallback() { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // Draw torus outline | |
| const centerX = canvas.width / 2; | |
| const centerY = canvas.height / 2; | |
| const outerRadius = ringSize * 30; | |
| const innerRadius = outerRadius * 0.4; | |
| // Outer circle | |
| ctx.strokeStyle = '#4ecdc4'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| ctx.arc(centerX, centerY, outerRadius, 0, Math.PI * 2); | |
| ctx.stroke(); | |
| // Inner circle (hole) | |
| ctx.beginPath(); | |
| ctx.arc(centerX, centerY, innerRadius, 0, Math.PI * 2); | |
| ctx.stroke(); | |
| // Draw flowing particles | |
| const colors = ['#00ffff', '#ff6b6b', '#4ecdc4', '#ffd93d', '#a8e6cf', '#dda0dd']; | |
| for (let i = 0; i < 60; i++) { | |
| const angle = (i / 60) * Math.PI * 2 + time * flowSpeed * 0.02; | |
| const spiralAngle = angle * spiralPitch + time * flowSpeed * 0.05; | |
| const radius = innerRadius + (outerRadius - innerRadius) * | |
| (0.5 + 0.4 * Math.sin(spiralAngle)); | |
| const x = centerX + radius * Math.cos(angle); | |
| const y = centerY + radius * Math.sin(angle); | |
| const colorIndex = Math.floor(i / 10) % colors.length; | |
| const intensity = 0.5 + 0.5 * Math.sin(spiralAngle + time * 0.01); | |
| ctx.fillStyle = colors[colorIndex]; | |
| ctx.globalAlpha = intensity; | |
| ctx.beginPath(); | |
| ctx.arc(x, y, 3, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| ctx.globalAlpha = 1; | |
| // Text overlay | |
| ctx.fillStyle = '#00ffff'; | |
| ctx.font = '16px Arial'; | |
| ctx.fillText('NGVT Vortex Torus (2D Fallback)', 20, 30); | |
| ctx.fillStyle = '#ff6b6b'; | |
| ctx.font = '12px Arial'; | |
| ctx.fillText('Spiral flow visualization', 20, 50); | |
| time++; | |
| requestAnimationFrame(drawFallback); | |
| } | |
| drawFallback(); | |
| } | |
| // Create multiple concentric torus rings and particles | |
| function createSimplifiedVisualization() { | |
| // Clear existing elements | |
| codeSegments.forEach(segment => scene.remove(segment)); | |
| codeSegments.length = 0; | |
| // Remove existing torus rings | |
| const torusesToRemove = []; | |
| scene.traverse((child) => { | |
| if (child.userData && child.userData.isTorusRing) { | |
| torusesToRemove.push(child); | |
| } | |
| }); | |
| torusesToRemove.forEach(torus => scene.remove(torus)); | |
| // Simple lighting | |
| const ambientLight = new THREE.AmbientLight(0x404040, 0.8); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0x00ffff, 0.5); | |
| directionalLight.position.set(0, 5, 5); | |
| scene.add(directionalLight); | |
| // Create multiple concentric torus rings | |
| for (let ringIndex = 0; ringIndex < numberOfRings; ringIndex++) { | |
| const currentRingSize = ringSize - (ringIndex * 1.2); // Smaller rings toward center | |
| if (currentRingSize <= 1.0) break; // Don't create rings that are too small | |
| const torusGeometry = new THREE.TorusGeometry(currentRingSize, 0.6, 8, 32); | |
| const ringHue = (ringIndex * 60) % 360; // Different colors for each ring | |
| const torusMaterial = new THREE.MeshBasicMaterial({ | |
| color: new THREE.Color().setHSL(ringHue/360, 0.7, 0.5), | |
| transparent: true, | |
| opacity: 0.2 + (ringIndex * 0.1), | |
| wireframe: true | |
| }); | |
| const torusMesh = new THREE.Mesh(torusGeometry, torusMaterial); | |
| torusMesh.userData.isTorusRing = true; | |
| torusMesh.userData.ringIndex = ringIndex; | |
| scene.add(torusMesh); | |
| if (ringIndex === 0) torus = torusMesh; // Keep reference to main torus | |
| } | |
| // Particles for multiple rings | |
| const colors = [0x00ffff, 0xff6b6b, 0x4ecdc4, 0xffd93d, 0xa8e6cf, 0xdda0dd]; | |
| const particlesPerRing = 200; | |
| for (let ringIndex = 0; ringIndex < numberOfRings; ringIndex++) { | |
| const currentRingSize = ringSize - (ringIndex * 1.2); | |
| if (currentRingSize <= 1.0) break; | |
| for (let i = 0; i < particlesPerRing; i++) { | |
| const particleGeometry = new THREE.SphereGeometry(0.03 + (ringIndex * 0.01), 6, 6); | |
| const particleMaterial = new THREE.MeshBasicMaterial({ | |
| color: colors[(i + ringIndex) % colors.length], | |
| transparent: true, | |
| opacity: 0.8 | |
| }); | |
| const particle = new THREE.Mesh(particleGeometry, particleMaterial); | |
| // Position particles on ring surface | |
| const spiralIndex = i % 6; | |
| const t = (i / particlesPerRing); // Progress along spiral | |
| const u = t * Math.PI * 2 * spiralTightness + (spiralIndex * Math.PI * 2 / 6); | |
| const v = (spiralIndex * Math.PI * 2 / 6) + (t * Math.PI * 2 * spiralPitch); | |
| const x = (currentRingSize + 0.6 * Math.cos(v)) * Math.cos(u); | |
| const y = (currentRingSize + 0.6 * Math.cos(v)) * Math.sin(u); | |
| const z = 0.6 * Math.sin(v); | |
| particle.position.set(x, y, z); | |
| particle.userData = { | |
| u, v, | |
| originalU: u, originalV: v, | |
| spiralIndex, t, | |
| ringIndex: ringIndex, | |
| ringSize: currentRingSize | |
| }; | |
| codeSegments.push(particle); | |
| scene.add(particle); | |
| } | |
| } | |
| // Initial camera position | |
| camera.position.set(0, 8, zoomLevel); | |
| camera.lookAt(0, 0, 0); | |
| } | |
| // Animation loop with multiple rings support | |
| function animate() { | |
| if (!renderer) return; | |
| animationId = requestAnimationFrame(animate); | |
| // Update particles for all rings | |
| const time = Date.now() * 0.001; | |
| codeSegments.forEach((particle, index) => { | |
| const data = particle.userData; | |
| // Move along spiral path with ring-specific speeds | |
| const ringSpeedMultiplier = 1.0 + (data.ringIndex * 0.3); | |
| data.t += flowSpeed * 0.008 * ringSpeedMultiplier; | |
| if (data.t > 1) data.t = 0; | |
| // Calculate spiral position with tightness control | |
| const u = (data.t * spiralTightness * Math.PI * 2) + (data.spiralIndex * Math.PI * 2 / 6); | |
| const baseV = (data.spiralIndex * Math.PI * 2 / 6); | |
| const vVariation = Math.sin(data.t * Math.PI * 2 * spiralPitch) * 0.3; | |
| const v = baseV + vVariation; | |
| // Position on respective ring surface | |
| const x = (data.ringSize + 0.6 * Math.cos(v)) * Math.cos(u); | |
| const y = (data.ringSize + 0.6 * Math.cos(v)) * Math.sin(u); | |
| const z = 0.6 * Math.sin(v); | |
| particle.position.set(x, y, z); | |
| // Visual effects | |
| const flowPosition = Math.abs(Math.cos(v)); | |
| particle.material.opacity = 0.6 + 0.4 * flowPosition; | |
| const distanceFromCenter = Math.sqrt(x*x + y*y); | |
| const scaleByDistance = 0.7 + 0.5 * (distanceFromCenter / data.ringSize); | |
| particle.scale.setScalar(scaleByDistance); | |
| }); | |
| // Rotate torus rings | |
| scene.traverse((child) => { | |
| if (child.userData && child.userData.isTorusRing) { | |
| child.rotation.z += 0.001 * (child.userData.ringIndex + 1); | |
| } | |
| }); | |
| // Camera control - auto orbit or mouse drag | |
| if (rotationEnabled) { | |
| const autoAngle = time * 0.05; | |
| cameraAngleY = autoAngle; | |
| cameraAngleX = 0.3; // Slight downward angle | |
| } | |
| // Apply camera position based on angles | |
| const x = Math.cos(cameraAngleY) * Math.cos(cameraAngleX) * zoomLevel; | |
| const y = Math.sin(cameraAngleX) * zoomLevel + 8; | |
| const z = Math.sin(cameraAngleY) * Math.cos(cameraAngleX) * zoomLevel; | |
| camera.position.set(x, y, z); | |
| camera.lookAt(0, 0, 0); | |
| try { | |
| renderer.render(scene, camera); | |
| } catch (error) { | |
| console.error('Render error:', error); | |
| showError(); | |
| } | |
| } | |
| // Show error message and fallback | |
| function showError() { | |
| document.getElementById('error-message').style.display = 'block'; | |
| if (animationId) { | |
| cancelAnimationFrame(animationId); | |
| } | |
| setTimeout(() => { | |
| document.getElementById('error-message').style.display = 'none'; | |
| initFallbackCanvas(); | |
| }, 3000); | |
| } | |
| // Control event listeners | |
| function setupControls() { | |
| document.getElementById('flowSpeed').addEventListener('input', (e) => { | |
| flowSpeed = parseFloat(e.target.value); | |
| }); | |
| document.getElementById('numberOfRings').addEventListener('input', (e) => { | |
| numberOfRings = parseInt(e.target.value); | |
| createSimplifiedVisualization(); | |
| }); | |
| document.getElementById('spiralTightness').addEventListener('input', (e) => { | |
| spiralTightness = parseInt(e.target.value); | |
| }); | |
| document.getElementById('spiralPitch').addEventListener('input', (e) => { | |
| spiralPitch = parseFloat(e.target.value); | |
| }); | |
| document.getElementById('ringSize').addEventListener('input', (e) => { | |
| ringSize = parseFloat(e.target.value); | |
| createSimplifiedVisualization(); // Recreate all rings with new size | |
| }); | |
| document.getElementById('zoomLevel').addEventListener('input', (e) => { | |
| zoomLevel = parseFloat(e.target.value); | |
| }); | |
| document.getElementById('rotationToggle').addEventListener('change', (e) => { | |
| rotationEnabled = e.target.checked; | |
| }); | |
| document.getElementById('vortexIntensity').addEventListener('input', (e) => { | |
| vortexIntensity = parseFloat(e.target.value); | |
| }); | |
| } | |
| // Mouse drag controls for camera | |
| function onMouseDown(event) { | |
| if (event.button === 0) { // Left mouse button | |
| isDragging = true; | |
| previousMousePosition.x = event.clientX; | |
| previousMousePosition.y = event.clientY; | |
| rotationEnabled = false; // Disable auto rotation when dragging | |
| document.getElementById('rotationToggle').checked = false; | |
| } | |
| } | |
| function onMouseMove(event) { | |
| if (isDragging) { | |
| const deltaX = event.clientX - previousMousePosition.x; | |
| const deltaY = event.clientY - previousMousePosition.y; | |
| // Update camera angles based on mouse movement | |
| cameraAngleY += deltaX * 0.01; | |
| cameraAngleX += deltaY * 0.01; | |
| // Clamp vertical angle to prevent flipping | |
| cameraAngleX = Math.max(-Math.PI/2, Math.min(Math.PI/2, cameraAngleX)); | |
| previousMousePosition.x = event.clientX; | |
| previousMousePosition.y = event.clientY; | |
| } | |
| } | |
| function onMouseUp(event) { | |
| isDragging = false; | |
| } | |
| // Touch controls for mobile | |
| function onTouchStart(event) { | |
| if (event.touches.length === 1) { | |
| isDragging = true; | |
| previousMousePosition.x = event.touches[0].clientX; | |
| previousMousePosition.y = event.touches[0].clientY; | |
| rotationEnabled = false; | |
| document.getElementById('rotationToggle').checked = false; | |
| event.preventDefault(); | |
| } | |
| } | |
| function onTouchMove(event) { | |
| if (isDragging && event.touches.length === 1) { | |
| const deltaX = event.touches[0].clientX - previousMousePosition.x; | |
| const deltaY = event.touches[0].clientY - previousMousePosition.y; | |
| cameraAngleY += deltaX * 0.01; | |
| cameraAngleX += deltaY * 0.01; | |
| cameraAngleX = Math.max(-Math.PI/2, Math.min(Math.PI/2, cameraAngleX)); | |
| previousMousePosition.x = event.touches[0].clientX; | |
| previousMousePosition.y = event.touches[0].clientY; | |
| event.preventDefault(); | |
| } | |
| } | |
| function onTouchEnd(event) { | |
| isDragging = false; | |
| } | |
| // Handle mouse wheel zoom | |
| function onMouseWheel(event) { | |
| event.preventDefault(); | |
| const zoomDelta = event.deltaY > 0 ? 1 : -1; | |
| zoomLevel = Math.max(5, Math.min(30, zoomLevel + zoomDelta)); | |
| // Update the slider to reflect the change | |
| const zoomSlider = document.getElementById('zoomLevel'); | |
| if (zoomSlider) { | |
| zoomSlider.value = zoomLevel; | |
| } | |
| } | |
| // Window resize handler | |
| function onWindowResize() { | |
| if (camera && renderer) { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| } | |
| // Initialize everything | |
| function init() { | |
| setupControls(); | |
| if (!isWebGLAvailable()) { | |
| showError(); | |
| return; | |
| } | |
| if (initThreeJS()) { | |
| createSimplifiedVisualization(); | |
| animate(); | |
| window.addEventListener('resize', onWindowResize); | |
| window.addEventListener('wheel', onMouseWheel, { passive: false }); | |
| // Mouse drag controls | |
| renderer.domElement.addEventListener('mousedown', onMouseDown); | |
| window.addEventListener('mousemove', onMouseMove); | |
| window.addEventListener('mouseup', onMouseUp); | |
| // Touch controls | |
| renderer.domElement.addEventListener('touchstart', onTouchStart); | |
| window.addEventListener('touchmove', onTouchMove); | |
| window.addEventListener('touchend', onTouchEnd); | |
| } else { | |
| showError(); | |
| } | |
| } | |
| // Start when page loads | |
| window.addEventListener('load', init); | |
| // Error handling | |
| window.addEventListener('error', (event) => { | |
| console.error('Global error:', event.error); | |
| showError(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |