Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Complex Stable Life 3D</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;500;700&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --primary: #00d2d3; /* Cyan/Teal for a sci-fi biological look */ | |
| --glass-bg: rgba(10, 15, 20, 0.8); | |
| --glass-border: rgba(255, 255, 255, 0.08); | |
| --text: #f1f5f9; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| user-select: none; | |
| } | |
| body { | |
| overflow: hidden; | |
| background-color: #050505; | |
| font-family: 'Inter', sans-serif; | |
| color: var(--text); | |
| } | |
| /* Canvas */ | |
| #canvas-container { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| z-index: 1; | |
| background: radial-gradient(circle at center, #111 0%, #000 100%); | |
| } | |
| /* Header Link */ | |
| .brand-link { | |
| position: fixed; | |
| top: 20px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| z-index: 100; | |
| color: var(--text); | |
| text-decoration: none; | |
| font-weight: 600; | |
| font-size: 0.85rem; | |
| background: var(--glass-bg); | |
| padding: 8px 20px; | |
| border-radius: 30px; | |
| border: 1px solid var(--glass-border); | |
| backdrop-filter: blur(12px); | |
| transition: all 0.3s ease; | |
| letter-spacing: 0.5px; | |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); | |
| } | |
| .brand-link:hover { | |
| background: rgba(255, 255, 255, 0.1); | |
| border-color: rgba(255, 255, 255, 0.3); | |
| box-shadow: 0 0 15px var(--primary); | |
| } | |
| /* UI Overlay */ | |
| .ui-panel { | |
| position: fixed; | |
| top: 20px; | |
| right: 20px; | |
| width: 340px; | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(16px); | |
| -webkit-backdrop-filter: blur(16px); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 16px; | |
| padding: 24px; | |
| z-index: 10; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 16px; | |
| box-shadow: 0 20px 40px rgba(0,0,0,0.4); | |
| max-height: 90vh; | |
| overflow-y: auto; | |
| transition: transform 0.3s ease; | |
| } | |
| .panel-header h1 { | |
| font-size: 1.2rem; | |
| font-weight: 700; | |
| background: linear-gradient(135deg, #fff, var(--primary)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| margin-bottom: 4px; | |
| } | |
| .panel-header p { | |
| font-size: 0.75rem; | |
| color: #94a3b8; | |
| } | |
| .control-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| label { | |
| font-size: 0.75rem; | |
| color: #cbd5e1; | |
| display: flex; | |
| justify-content: space-between; | |
| font-weight: 500; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .value-tag { | |
| color: var(--primary); | |
| font-family: monospace; | |
| font-size: 0.85rem; | |
| } | |
| input[type="range"] { | |
| -webkit-appearance: none; | |
| width: 100%; | |
| height: 4px; | |
| background: rgba(255,255,255,0.1); | |
| border-radius: 2px; | |
| outline: none; | |
| } | |
| input[type="range"]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 14px; | |
| height: 14px; | |
| border-radius: 50%; | |
| background: var(--primary); | |
| cursor: pointer; | |
| transition: transform 0.2s; | |
| box-shadow: 0 0 10px rgba(0, 210, 211, 0.5); | |
| } | |
| input[type="range"]::-webkit-slider-thumb:hover { | |
| transform: scale(1.2); | |
| } | |
| .btn-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 8px; | |
| } | |
| button { | |
| background: rgba(255,255,255,0.03); | |
| border: 1px solid var(--glass-border); | |
| color: #e2e8f0; | |
| padding: 10px; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-weight: 600; | |
| font-size: 0.8rem; | |
| transition: all 0.2s ease; | |
| font-family: 'Inter', sans-serif; | |
| } | |
| button:hover { | |
| background: rgba(255,255,255,0.08); | |
| border-color: rgba(255,255,255,0.2); | |
| } | |
| button.primary { | |
| background: var(--primary); | |
| color: #000; | |
| border: none; | |
| box-shadow: 0 4px 12px rgba(0, 210, 211, 0.3); | |
| } | |
| button.primary:hover { | |
| background: #48dbfb; | |
| transform: translateY(-1px); | |
| } | |
| .checkbox-group { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| font-size: 0.75rem; | |
| color: #94a3b8; | |
| cursor: pointer; | |
| padding: 5px 0; | |
| } | |
| .checkbox-group input { | |
| accent-color: var(--primary); | |
| width: 14px; | |
| height: 14px; | |
| } | |
| .stats { | |
| font-size: 0.7rem; | |
| color: #576574; | |
| text-align: center; | |
| padding-top: 10px; | |
| border-top: 1px solid var(--glass-border); | |
| font-family: monospace; | |
| } | |
| /* Loading overlay */ | |
| #loading { | |
| position: fixed; | |
| top: 0; left: 0; width: 100%; height: 100%; | |
| background: #000; | |
| z-index: 200; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| color: var(--primary); | |
| transition: opacity 0.8s ease-out; | |
| pointer-events: none; | |
| font-weight: 300; | |
| letter-spacing: 2px; | |
| } | |
| @media (max-width: 600px) { | |
| .ui-panel { | |
| width: calc(100% - 30px); | |
| right: 15px; | |
| bottom: 20px; | |
| top: auto; | |
| max-height: 55vh; | |
| } | |
| .brand-link { | |
| top: 10px; | |
| padding: 6px 16px; | |
| } | |
| } | |
| </style> | |
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://unpkg.com/three@0.160.0/build/three.module.js", | |
| "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/" | |
| } | |
| } | |
| </script> | |
| </head> | |
| <body> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="brand-link">Built with anycoder</a> | |
| <div id="loading"> | |
| <div>GENERATING COMPLEXITY</div> | |
| </div> | |
| <div id="canvas-container"></div> | |
| <div class="ui-panel" id="uiPanel"> | |
| <div class="panel-header"> | |
| <h1>Complex Life Engine</h1> | |
| <p>Large Scale Structure Simulation</p> | |
| </div> | |
| <div class="control-group"> | |
| <label>Particles <span id="val-count" class="value-tag">1400</span></label> | |
| <input type="range" id="inp-count" min="500" max="2500" step="100" value="1400"> | |
| </div> | |
| <div class="control-group"> | |
| <label>Interaction Radius <span id="val-radius" class="value-tag">80</span></label> | |
| <input type="range" id="inp-radius" min="30" max="150" value="80"> | |
| </div> | |
| <div class="control-group"> | |
| <label>Force Strength <span id="val-force" class="value-tag">0.6</span></label> | |
| <input type="range" id="inp-force" min="0.1" max="3.0" step="0.1" value="0.6"> | |
| </div> | |
| <div class="control-group"> | |
| <label>Friction (Stability) <span id="val-friction" class="value-tag">0.88</span></label> | |
| <input type="range" id="inp-friction" min="0.50" max="0.98" step="0.01" value="0.88"> | |
| </div> | |
| <label class="checkbox-group"> | |
| <input type="checkbox" id="inp-symmetric" checked> | |
| Symmetric Rules (Physics Law) | |
| </label> | |
| <button id="btn-randomize" class="primary">🎲 Evolve New Rules</button> | |
| <div class="btn-grid"> | |
| <button id="btn-complex">🧬 Complex</button> | |
| <button id="btn-membranes">🕸️ Membranes</button> | |
| </div> | |
| <div class="btn-grid"> | |
| <button id="btn-snake">🐍 Snake</button> | |
| <button id="btn-cells">🦠 Cells</button> | |
| </div> | |
| <div class="stats" id="stats"> | |
| Initializing... | |
| </div> | |
| </div> | |
| <script type="module"> | |
| import * as THREE from 'three'; | |
| import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; | |
| import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; | |
| import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; | |
| import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; | |
| // --- Configuration --- | |
| const CONFIG = { | |
| particleCount: 1400, | |
| types: 6, | |
| radius: 80, // High radius for larger structures | |
| forceFactor: 0.6, // Lower force to balance high radius | |
| friction: 0.88, // High friction to stabilize large structures | |
| worldSize: 300, // Larger world | |
| symmetric: true, | |
| maxSpeed: 2.5, | |
| repulsionRadius: 0.3 // Distance where particles strongly push apart | |
| }; | |
| // --- State --- | |
| let particles = []; | |
| let rules = []; | |
| let typeColors = []; | |
| // --- Three.js Setup --- | |
| const container = document.getElementById('canvas-container'); | |
| const scene = new THREE.Scene(); | |
| scene.fog = new THREE.FogExp2(0x000000, 0.0015); | |
| const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1500); | |
| camera.position.set(0, 200, 400); | |
| const renderer = new THREE.WebGLRenderer({ antialias: false, powerPreference: "high-performance" }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |
| container.appendChild(renderer.domElement); | |
| // Post-processing | |
| const renderScene = new RenderPass(scene, camera); | |
| const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85); | |
| bloomPass.threshold = 0.1; | |
| bloomPass.strength = 0.8; // Stronger bloom for sci-fi effect | |
| bloomPass.radius = 0.5; | |
| const composer = new EffectComposer(renderer); | |
| composer.addPass(renderScene); | |
| composer.addPass(bloomPass); | |
| const controls = new OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; | |
| controls.autoRotate = true; | |
| controls.autoRotateSpeed = 0.3; | |
| // Lights | |
| const ambientLight = new THREE.AmbientLight(0x222222); | |
| scene.add(ambientLight); | |
| const lights = []; | |
| const lightColors = [0x00d2d3, 0xff9ff3, 0xfeca57]; | |
| for(let i=0; i<3; i++) { | |
| const l = new THREE.PointLight(lightColors[i], 1, 500); | |
| l.position.set( | |
| (Math.random()-0.5)*300, | |
| (Math.random()-0.5)*300, | |
| (Math.random()-0.5)*300 | |
| ); | |
| scene.add(l); | |
| lights.push(l); | |
| } | |
| // --- Particle System --- | |
| const geometry = new THREE.SphereGeometry(1, 8, 8); // Low poly is fine with bloom | |
| const material = new THREE.MeshBasicMaterial({ color: 0xffffff }); // Basic material is faster | |
| let instancedMesh; | |
| const dummy = new THREE.Object3D(); | |
| const _color = new THREE.Color(); | |
| // --- Logic --- | |
| function initSystem() { | |
| if (instancedMesh) { | |
| scene.remove(instancedMesh); | |
| instancedMesh.dispose(); | |
| } | |
| // Distinct color palette | |
| typeColors = [ | |
| new THREE.Color(0x00d2d3), // Cyan | |
| new THREE.Color(0xff6b6b), // Red | |
| new THREE.Color(0xfeca57), // Yellow | |
| new THREE.Color(0x5f27cd), // Purple | |
| new THREE.Color(0x54a0ff), // Blue | |
| new THREE.Color(0x1dd1a1) // Green | |
| ]; | |
| particles = new Float32Array(CONFIG.particleCount * 8); | |
| for (let i = 0; i < CONFIG.particleCount; i++) { | |
| const i8 = i * 8; | |
| particles[i8] = (Math.random() - 0.5) * CONFIG.worldSize; | |
| particles[i8+1] = (Math.random() - 0.5) * CONFIG.worldSize; | |
| particles[i8+2] = (Math.random() - 0.5) * CONFIG.worldSize; | |
| particles[i8+3] = 0; // vx | |
| particles[i8+4] = 0; // vy | |
| particles[i8+5] = 0; // vz | |
| particles[i8+6] = Math.floor(Math.random() * CONFIG.types); // type | |
| particles[i8+7] = 1.0; // Scale placeholder | |
| } | |
| instancedMesh = new THREE.InstancedMesh(geometry, material, CONFIG.particleCount); | |
| instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage); | |
| scene.add(instancedMesh); | |
| randomizeRules(); | |
| setTimeout(() => { | |
| document.getElementById('loading').style.opacity = 0; | |
| }, 500); | |
| } | |
| function randomizeRules(mode = 'random') { | |
| rules = []; | |
| for (let i = 0; i < CONFIG.types; i++) { | |
| rules[i] = []; | |
| for (let j = 0; j < CONFIG.types; j++) { | |
| if (mode === 'discrete') { | |
| // Discrete steps create sharper structures | |
| const steps = [-1.0, -0.5, 0.0, 0.2, 0.5, 0.8]; | |
| rules[i][j] = steps[Math.floor(Math.random() * steps.length)]; | |
| } else { | |
| rules[i][j] = Math.random() * 2 - 1; | |
| } | |
| } | |
| } | |
| if (CONFIG.symmetric) { | |
| for (let i = 0; i < CONFIG.types; i++) { | |
| for (let j = i + 1; j < CONFIG.types; j++) { | |
| rules[j][i] = rules[i][j]; | |
| } | |
| // Self-interaction usually needs to be slightly repulsive or weakly attractive for volume | |
| rules[i][i] = Math.max(-1.0, Math.min(0.5, rules[i][i])); | |
| } | |
| } | |
| } | |
| function setPreset(name) { | |
| // Base settings for stability | |
| CONFIG.symmetric = true; | |
| document.getElementById('inp-symmetric').checked = true; | |
| if (name === 'complex') { | |
| // Large, slow-moving complex structures | |
| CONFIG.radius = 90; | |
| CONFIG.friction = 0.88; | |
| CONFIG.forceFactor = 0.5; | |
| CONFIG.maxSpeed = 2.0; | |
| CONFIG.repulsionRadius = 0.3; | |
| randomizeRules('discrete'); | |
| } else if (name === 'membranes') { | |
| // Sheet-like structures | |
| CONFIG.radius = 70; | |
| CONFIG.friction = 0.82; | |
| CONFIG.forceFactor = 0.8; | |
| CONFIG.maxSpeed = 3.0; | |
| CONFIG.repulsionRadius = 0.25; | |
| // Generate specific membrane rules | |
| randomizeRules(); | |
| // Hack: enforce some strong chains | |
| for(let i=0; i<CONFIG.types; i++) { | |
| rules[i][i] = 0.6; // Like self | |
| rules[i][(i+1)%CONFIG.types] = 0.2; | |
| } | |
| } else if (name === 'snake') { | |
| CONFIG.radius = 60; | |
| CONFIG.friction = 0.85; | |
| CONFIG.forceFactor = 1.2; | |
| randomizeRules(); | |
| } else if (name === 'cells') { | |
| // High repulsion short range, high attraction long range | |
| CONFIG.radius = 100; | |
| CONFIG.friction = 0.90; // Very stable | |
| CONFIG.forceFactor = 0.4; | |
| randomizeRules('discrete'); | |
| } | |
| updateUiValues(); | |
| } | |
| // --- Physics Loop --- | |
| function updatePhysics() { | |
| const count = CONFIG.particleCount; | |
| const rMax = CONFIG.radius; | |
| const rMaxSq = rMax * rMax; | |
| const forceFactor = CONFIG.forceFactor; | |
| const friction = CONFIG.friction; | |
| const worldSize = CONFIG.worldSize; | |
| const halfWorld = worldSize / 2; | |
| const maxSpeed = CONFIG.maxSpeed; | |
| const repulsionRad = CONFIG.repulsionRadius; // 0.0 to 1.0 of rMax | |
| for (let i = 0; i < count; i++) { | |
| const i8 = i * 8; | |
| let fx = 0, fy = 0, fz = 0; | |
| const typeI = particles[i8+6]; | |
| const px = particles[i8]; | |
| const py = particles[i8+1]; | |
| const pz = particles[i8+2]; | |
| for (let j = 0; j < count; j++) { | |
| if (i === j) continue; | |
| const j8 = j * 8; | |
| let dx = particles[j8] - px; | |
| let dy = particles[j8+1] - py; | |
| let dz = particles[j8+2] - pz; | |
| // Toroidal Wrap | |
| if (dx > halfWorld) dx -= worldSize; | |
| else if (dx < -halfWorld) dx += worldSize; | |
| if (dy > halfWorld) dy -= worldSize; | |
| else if (dy < -halfWorld) dy += worldSize; | |
| if (dz > halfWorld) dz -= worldSize; | |
| else if (dz < -halfWorld) dz += worldSize; | |
| const distSq = dx*dx + dy*dy + dz*dz; | |
| if (distSq > 0.01 && distSq < rMaxSq) { | |
| const dist = Math.sqrt(distSq); | |
| const q = dist / rMax; | |
| const typeJ = particles[j8+6]; | |
| let f = 0; | |
| // STABILITY LOGIC: | |
| // 1. Strong Repulsion Zone (The Core) | |
| if (q < repulsionRad) { | |
| // Normalize q to 0..1 within the repulsion zone | |
| const subQ = q / repulsionRad; | |
| // Force scales up massively as we get closer to 0 | |
| f = (subQ - 1.0) * 2.0; | |
| } else { | |
| // 2. Interaction Zone | |
| // Normalize q to 0..1 within interaction zone | |
| const subQ = (q - repulsionRad) / (1.0 - repulsionRad); | |
| // Peak curve: 0 at edges, 1 in middle | |
| const strength = rules[typeI][typeJ]; | |
| // Smooth transition curve | |
| f = strength * (1.0 - Math.abs(2.0 * subQ - 1.0)); | |
| // Alternative: Linear falloff | |
| // f = strength * (1.0 - subQ); | |
| } | |
| // Apply force | |
| const fScaled = (f * forceFactor) / dist; | |
| fx += dx * fScaled; | |
| fy += dy * fScaled; | |
| fz += dz * fScaled; | |
| } | |
| } | |
| // Integration | |
| particles[i8+3] = (particles[i8+3] + fx) * friction; | |
| particles[i8+4] = (particles[i8+4] + fy) * friction; | |
| particles[i8+5] = (particles[i8+5] + fz) * friction; | |
| // Velocity Cap | |
| const vx = particles[i8+3]; | |
| const vy = particles[i8+4]; | |
| const vz = particles[i8+5]; | |
| const speedSq = vx*vx + vy*vy + vz*vz; | |
| if (speedSq > maxSpeed * maxSpeed) { | |
| const scale = maxSpeed / Math.sqrt(speedSq); | |
| particles[i8+3] *= scale; | |
| particles[i8+4] *= scale; | |
| particles[i8+5] *= scale; | |
| } | |
| // Position Update | |
| particles[i8] += particles[i8+3]; | |
| particles[i8+1] += particles[i8+4]; | |
| particles[i8+2] += particles[i8+5]; | |
| // Boundary Wrap | |
| if (particles[i8] <= -halfWorld) particles[i8] += worldSize; | |
| if (particles[i8] >= halfWorld) particles[i8] -= worldSize; | |
| if (particles[i8+1] <= -halfWorld) particles[i8+1] += worldSize; | |
| if (particles[i8+1] >= halfWorld) particles[i8+1] -= worldSize; | |
| if (particles[i8+2] <= -halfWorld) particles[i8+2] += worldSize; | |
| if (particles[i8+2] >= halfWorld) particles[i8+2] -= worldSize; | |
| } | |
| } | |
| function updateVisuals() { | |
| const count = CONFIG.particleCount; | |
| const rMax = CONFIG.radius; | |
| for (let i = 0; i < count; i++) { | |
| const i8 = i * 8; | |
| dummy.position.set( | |
| particles[i8], | |
| particles[i8+1], | |
| particles[i8+2] | |
| ); | |
| // Dynamic sizing based on neighbors/density could be cool, | |
| // but for now let's scale based on interaction radius to keep it looking "structural" | |
| // Smaller particles look better for large structures | |
| const scale = rMax / 35.0; | |
| dummy.scale.set(scale, scale, scale); | |
| dummy.updateMatrix(); | |
| instancedMesh.setMatrixAt(i, dummy.matrix); | |
| const type = particles[i8+6]; | |
| _color.copy(typeColors[type]); | |
| instancedMesh.setColorAt(i, _color); | |
| } | |
| instancedMesh.instanceMatrix.needsUpdate = true; | |
| if(instancedMesh.instanceColor) instancedMesh.instanceColor.needsUpdate = true; | |
| } | |
| // --- Loop --- | |
| const statsEl = document.getElementById('stats'); | |
| let frames = 0; | |
| let lastTime = 0; | |
| function animate(time) { | |
| requestAnimationFrame(animate); | |
| frames++; | |
| if (time - lastTime > 1000) { | |
| statsEl.innerText = `FPS: ${frames} | Radius: ${CONFIG.radius} | Friction: ${CONFIG.friction}`; | |
| frames = 0; | |
| lastTime = time; | |
| } | |
| updatePhysics(); | |
| updateVisuals(); | |
| controls.update(); | |
| composer.render(); | |
| } | |
| // --- UI Handlers --- | |
| document.getElementById('inp-count').addEventListener('change', (e) => { | |
| CONFIG.particleCount = parseInt(e.target.value); | |
| document.getElementById('val-count').innerText = CONFIG.particleCount; | |
| initSystem(); | |
| }); | |
| document.getElementById('inp-radius').addEventListener('input', (e) => { | |
| CONFIG.radius = parseInt(e.target.value); | |
| document.getElementById('val-radius').innerText = CONFIG.radius; | |
| }); | |
| document.getElementById('inp-force').addEventListener('input', (e) => { | |
| CONFIG.forceFactor = parseFloat(e.target.value); | |
| document.getElementById('val-force').innerText = CONFIG.forceFactor; | |
| }); | |
| document.getElementById('inp-friction').addEventListener('input', (e) => { | |
| CONFIG.friction = parseFloat(e.target.value); | |
| document.getElementById('val-friction').innerText = CONFIG.friction; | |
| }); | |
| document.getElementById('inp-symmetric').addEventListener('change', (e) => { | |
| CONFIG.symmetric = e.target.checked; | |
| }); | |
| document.getElementById('btn-randomize').addEventListener('click', () => { | |
| randomizeRules('discrete'); | |
| }); | |
| document.getElementById('btn-complex').addEventListener('click', () => setPreset('complex')); | |
| document.getElementById('btn-membranes').addEventListener('click', () => setPreset('membranes')); | |
| document.getElementById('btn-snake').addEventListener('click', () => setPreset('snake')); | |
| document.getElementById('btn-cells').addEventListener('click', () => setPreset('cells')); | |
| function updateUiValues() { | |
| document.getElementById('inp-friction').value = CONFIG.friction; | |
| document.getElementById('val-friction').innerText = CONFIG.friction; | |
| document.getElementById('inp-force').value = CONFIG.forceFactor; | |
| document.getElementById('val-force').innerText = CONFIG.forceFactor; | |
| document.getElementById('inp-radius').value = CONFIG.radius; | |
| document.getElementById('val-radius').innerText = CONFIG.radius; | |
| } | |
| window.addEventListener('resize', () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| composer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| // Initial Boot | |
| initSystem(); | |
| // Set initial complex preset | |
| setPreset('complex'); | |
| animate(0); | |
| </script> | |
| </body> | |
| </html> |