A bioelectric consciousness engine built with Three.js, webgl and glsl. Click anywhere to trigger signals; the HUD tracks live neural state.
A Pen by Techartist on CodePen. A bioelectric consciousness engine built with Three.js, webgl and glsl. Click anywhere to trigger signals; the HUD tracks live neural state.
A Pen by Techartist on CodePen. | <style> | |
| :root { | |
| --cyan: #00f0ff; | |
| --gold: #ffaa00; | |
| --magenta: #ff00aa; | |
| --bg-dark: #010204; | |
| --glass-bg: linear-gradient(135deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.07) 45%, rgba(0, 240, 255, 0.08)); | |
| --glass-border: rgba(255, 255, 255, 0.24); | |
| --glass-highlight: rgba(255, 255, 255, 0.42); | |
| } | | | body, html { | |
| margin: 0; | |
| padding: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: var(--bg-dark); | |
| overflow: hidden; | |
| font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| color: white; | |
| user-select: none; | |
| } | | | canvas { | |
| display: block; | |
| width: 100vw; | |
| height: 100vh; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| z-index: 1; | |
| } | | | .overlay { | |
| position: absolute; | |
| top: 0; left: 0; width: 100%; height: 100%; | |
| pointer-events: none; | |
| z-index: 10; | |
| background: | |
| radial-gradient(circle at center, transparent 40%, rgba(0, 0, 0, 0.85) 100%), | |
| repeating-linear-gradient(0deg, rgba(0,0,0,0.15) 0px, rgba(0,0,0,0.15) 1px, transparent 1px, transparent 2px); | |
| box-shadow: inset 0 0 100px rgba(0,0,0,0.9); | |
| } | |
| .hud-panel { | |
| position: absolute; | |
| z-index: 20; | |
| background: var(--glass-bg); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 18px; | |
| backdrop-filter: blur(28px) saturate(170%); | |
| -webkit-backdrop-filter: blur(28px) saturate(170%); | |
| box-shadow: | |
| 0 18px 45px rgba(0, 0, 0, 0.38), | |
| inset 0 1px 0 var(--glass-highlight), | |
| inset 0 -1px 0 rgba(255, 255, 255, 0.08); | |
| transition: all 0.3s ease; | |
| pointer-events: auto; | |
| overflow: hidden; | |
| } | |
| #hud-left { | |
| top: clamp(12px, 3vw, 28px); | |
| left: clamp(12px, 3vw, 28px); | |
| padding: 14px; | |
| width: min(230px, calc(100vw - 24px)); | |
| } | |
| #hud-right { | |
| bottom: clamp(12px, 3vw, 28px); | |
| right: clamp(12px, 3vw, 28px); | |
| padding: 10px 14px; | |
| min-width: 130px; | |
| text-align: right; | |
| } | |
| .hud-header { | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); | |
| padding-bottom: 8px; | |
| margin-bottom: 10px; | |
| } | | | h1 { | |
| font-size: 11px; | |
| letter-spacing: 2.6px; | |
| color: var(--cyan); | |
| margin: 0 0 4px 0; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| text-shadow: 0 0 10px rgba(0, 240, 255, 0.4); | |
| } | | | .subtitle { | |
| font-size: 8px; | |
| color: rgba(255, 255, 255, 0.4); | |
| letter-spacing: 1.4px; | |
| } | |
| .hud-row { | |
| display: flex; | |
| justify-content: space-between; | |
| gap: 18px; | |
| margin-bottom: 8px; | |
| font-size: 9px; | |
| letter-spacing: 0.8px; | |
| } | |
| .hud-label { | |
| color: rgba(255, 255, 255, 0.5); | |
| text-transform: uppercase; | |
| } | |
| .hud-value { | |
| font-weight: 600; | |
| color: var(--cyan); | |
| text-shadow: 0 0 8px rgba(0, 240, 255, 0.5); | |
| transition: color 0.3s, text-shadow 0.3s; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .value-active { color: var(--gold); text-shadow: 0 0 10px rgba(255, 170, 0, 0.6); } | |
| .value-refract { color: var(--magenta); text-shadow: 0 0 10px rgba(255, 0, 170, 0.6); } | |
| .btn-trigger { | |
| margin-top: 12px; | |
| width: 100%; | |
| padding: 9px 10px; | |
| background: rgba(255, 255, 255, 0.1); | |
| border: 1px solid rgba(255, 255, 255, 0.24); | |
| color: var(--cyan); | |
| font-size: 9px; | |
| letter-spacing: 2px; | |
| border-radius: 999px; | |
| cursor: pointer; | |
| text-transform: uppercase; | |
| transition: all 0.2s; | |
| box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0 0 15px rgba(0, 240, 255, 0.1); | |
| } | |
| .btn-trigger:hover { | |
| background: rgba(0, 240, 255, 0.15); | |
| box-shadow: 0 0 20px rgba(0, 240, 255, 0.4); | |
| } | |
| .btn-trigger.disabled { | |
| border-color: rgba(255,255,255,0.1); | |
| color: rgba(255,255,255,0.3); | |
| background: transparent; | |
| box-shadow: none; | |
| cursor: not-allowed; | |
| } | | | #instructions { | |
| position: absolute; | |
| bottom: clamp(14px, 4vw, 40px); left: 50%; | |
| transform: translateX(-50%); | |
| z-index: 20; | |
| font-size: 9px; | |
| letter-spacing: 1.6px; | |
| color: rgba(255,255,255,0.4); | |
| pointer-events: none; | |
| text-transform: uppercase; | |
| animation: pulseText 2s infinite; | |
| white-space: nowrap; | |
| } | | | @keyframes pulseText { | |
| 0%, 100% { opacity: 0.3; } | |
| 50% { opacity: 0.8; } | |
| } | | | #cursor { | |
| position: fixed; | |
| top: 0; left: 0; | |
| width: 24px; height: 24px; | |
| border: 1px solid rgba(0, 240, 255, 0.8); | |
| border-radius: 50%; | |
| transform: translate(-50%, -50%); | |
| pointer-events: none; | |
| z-index: 9999; | |
| transition: width 0.2s, height 0.2s, background 0.2s; | |
| mix-blend-mode: screen; | |
| box-shadow: 0 0 10px rgba(0,240,255,0.4); | |
| } | | | #cursor.click { | |
| width: 12px; height: 12px; | |
| background: var(--cyan); | |
| border-color: transparent; | |
| } | | | # { | |
| position: absolute; | |
| top: 0; left: 0; width: 100%; height: 100%; | |
| background: var(--bg-dark); | |
| z-index: 9000; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| flex-direction: column; | |
| transition: opacity 1s ease; | |
| } | | | .spinner { | |
| width: 50px; height: 50px; | |
| border: 2px solid rgba(0,240,255,0.1); | |
| border-top-color: var(--cyan); | |
| border-radius: 50%; | |
| animation: spin 1s cubic-bezier(0.4, 0, 0.2, 1) infinite; | |
| margin-bottom: 24px; | |
| box-shadow: 0 0 20px rgba(0,240,255,0.2); | |
| } | |
| @keyframes spin { 100% { transform: rotate(360deg); } } | |
| @media (max-width: 720px) { | |
| .overlay { | | | background: | |
| radial-gradient(circle at center, transparent 52%, rgba(0, 0, 0, 0.78) 100%), | |
| repeating-linear-gradient(0deg, rgba(0,0,0,0.12) 0px, rgba(0,0,0,0.12) 1px, transparent 1px, transparent 2px); | |
| box-shadow: inset 0 0 60px rgba(0,0,0,0.82); | |
| } | |
| #hud-left { | |
| top: max(10px, env(safe-area-inset-top)); | |
| left: 10px; | |
| right: auto; | |
| width: min(220px, calc(100vw - 20px)); | |
| padding: 8px 9px; | |
| border-radius: 13px; | |
| } | |
| #hud-right { | |
| right: 10px; | |
| bottom: max(54px, env(safe-area-inset-bottom)); | |
| padding: 8px 10px; | |
| min-width: 112px; | |
| } | |
| .hud-header { | |
| display: flex; | |
| align-items: baseline; | |
| justify-content: space-between; | |
| gap: 8px; | |
| padding-bottom: 5px; | |
| margin-bottom: 6px; | |
| } | | | h1 { | |
| font-size: 9px; | |
| letter-spacing: 1.5px; | |
| margin-bottom: 0; | |
| } | | | .subtitle { | |
| font-size: 6px; | |
| letter-spacing: 0.7px; | |
| text-align: right; | |
| } | |
| .hud-row { | |
| gap: 10px; | |
| margin-bottom: 4px; | |
| font-size: 7px; | |
| letter-spacing: 0.55px; | |
| } | |
| .btn-trigger { | |
| margin-top: 6px; | |
| padding: 6px 8px; | |
| font-size: 7px; | |
| letter-spacing: 1.2px; | |
| } | | | #instructions { | |
| bottom: max(16px, env(safe-area-inset-bottom)); | |
| font-size: 8px; | |
| letter-spacing: 1.1px; | |
| } | | | #cursor { | | | display: none; | | | } | | | } | |
| @media (max-width: 420px) { | |
| #hud-left { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| column-gap: 10px; | |
| row-gap: 0; | |
| width: min(236px, calc(100vw - 16px)); | |
| left: 8px; | |
| top: max(8px, env(safe-area-inset-top)); | |
| padding: 7px 8px; | |
| } | | | .hud-header, | |
| .btn-trigger { | |
| grid-column: 1 / -1; | |
| } | |
| .hud-header { | |
| display: block; | |
| } | | | .subtitle { | | | display: none; | | | } | |
| .hud-row { | |
| gap: 6px; | |
| margin-bottom: 4px; | |
| } | |
| .hud-label { | |
| max-width: 60px; | |
| } | | | } | | | </style> | | | <script type="importmap"> | | | { | | | "imports": { | | | "three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js", | | | "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/" | | | } | | | } | | | </script> | |
| <div class="overlay"></div> | |
| <div id="cursor"></div> | |
| <div id=""> | |
| <div class="spinner"></div> | |
| <h1 style="color: rgba(255,255,255,0.6); font-size: 11px;">INITIALIZING BIOELECTRIC ENGINE</h1> | |
| </div> | |
| <div id="hud-left" class="hud-panel"> | |
| <div class="hud-header"> | |
| <h1>Neural Cathedral</h1> | |
| <div class="subtitle">ID: NC-09 // BIOELECTRIC ENGINE</div> | |
| </div> | |
| <div class="hud-row"> | |
| <span class="hud-label">Membrane V</span> | |
| <span class="hud-value" id="val-membrane">-70.0 mV</span> | |
| </div> | |
| <div class="hud-row"> | |
| <span class="hud-label">Dendrite State</span> | |
| <span class="hud-value" id="val-dendrites">CALM</span> | |
| </div> | |
| <div class="hud-row"> | |
| <span class="hud-label">Axon Load</span> | |
| <span class="hud-value" id="val-axon">02%</span> | |
| </div> | |
| <div class="hud-row"> | |
| <span class="hud-label">Signal Phase</span> | |
| <span class="hud-value" id="val-state">RESTING</span> | |
| </div> | |
| <div class="hud-row"> | |
| <span class="hud-label">Synaptic Coh</span> | |
| <span class="hud-value" id="val-sync">24%</span> | |
| </div> | | | <button class="btn-trigger" id="btn-fire">Manual Override</button> | | | </div> | |
| <div id="hud-right" class="hud-panel"> | |
| <div class="hud-row"> | |
| <span class="hud-label">Cam X</span> | |
| <span class="hud-value" id="val-cam-x">0.00</span> | |
| </div> | |
| <div class="hud-row"> | |
| <span class="hud-label">Cam Y</span> | |
| <span class="hud-value" id="val-cam-y">0.00</span> | |
| </div> | |
| <div class="hud-row"> | |
| <span class="hud-label">Cam Z</span> | |
| <span class="hud-value" id="val-cam-z">0.00</span> | |
| </div> | | | </div> | | | <div id="instructions">Click anywhere to trigger impulse</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'; | |
| const cursor = document.getElementById('cursor'); | |
| document.addEventListener('mousemove', (e) => { | |
| cursor.style.left = e.clientX + 'px'; | | | cursor.style.top = e.clientY + 'px'; | |
| }); | |
| document.addEventListener('mousedown', () => cursor.classList.add('click')); | |
| document.addEventListener('mouseup', () => cursor.classList.remove('click')); | |
| const shaderNoise = ` | |
| vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } | |
| vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } | |
| vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); } | |
| vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; } | |
| float snoise(vec3 v) { | |
| const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; | |
| const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); | |
| vec3 i = floor(v + dot(v, C.yyy) ); | |
| vec3 x0 = v - i + dot(i, C.xxx) ; | |
| vec3 g = step(x0.yzx, x0.xyz); | |
| vec3 l = 1.0 - g; | |
| vec3 i1 = min( g.xyz, l.zxy ); | |
| vec3 i2 = max( g.xyz, l.zxy ); | |
| vec3 x1 = x0 - i1 + C.xxx; | |
| vec3 x2 = x0 - i2 + C.yyy; | |
| vec3 x3 = x0 - D.yyy; | |
| i = mod289(i); | |
| vec4 p = permute( permute( permute( | |
| i.z + vec4(0.0, i1.z, i2.z, 1.0 )) | |
| + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) | |
| + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); | |
| float n_ = 0.142857142857; | |
| vec3 ns = n_ * D.wyz - D.xzx; | |
| vec4 j = p - 49.0 * floor(p * ns.z * ns.z); | |
| vec4 x_ = floor(j * ns.z); | |
| vec4 y_ = floor(j - 7.0 * x_ ); | |
| vec4 x = x_ *ns.x + ns.yyyy; | |
| vec4 y = y_ *ns.x + ns.yyyy; | |
| vec4 h = 1.0 - abs(x) - abs(y); | |
| vec4 b0 = vec4( x.xy, y.xy ); | |
| vec4 b1 = vec4( x.zw, y.zw ); | |
| vec4 s0 = floor(b0)*2.0 + 1.0; | |
| vec4 s1 = floor(b1)*2.0 + 1.0; | |
| vec4 sh = -step(h, vec4(0.0)); | |
| vec4 a0 = b0.xzyw + s0.xzywsh.xxyy ; | | | vec4 a1 = b1.xzyw + s1.xzywsh.zzww ; | |
| vec3 p0 = vec3(a0.xy,h.x); | |
| vec3 p1 = vec3(a0.zw,h.y); | |
| vec3 p2 = vec3(a1.xy,h.z); | |
| vec3 p3 = vec3(a1.zw,h.w); | |
| vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); | |
| p0 *= norm.x; | |
| p1 *= norm.y; | |
| p2 *= norm.z; | |
| p3 *= norm.w; | |
| vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); | |
| m = m * m; | |
| return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3) ) ); | |
| } | | | `; | | | const CONFIG = { | | | somaRadius: 4.0, | | | dendriteTrees: 14, | | | axonTrees: 4, | | | maxDistDendrite: 35.0, | | | maxDistAxon: 50.0, | |
| colors: { | |
| cyan: new THREE.Color('#00e5ff'), | |
| blue: new THREE.Color('#0033aa'), | |
| gold: new THREE.Color('#ffaa00'), | |
| orange: new THREE.Color('#ff4400'), | |
| magenta: new THREE.Color('#ff0066'), | |
| violet: new THREE.Color('#6600ff') | |
| } | |
| }; | |
| const STATE = { | |
| phase: 0, | | | progress: 0, | | | membraneV: -70.0, | | | axonLoad: 2, | | | sync: 24, | | | isAnimating: false | |
| }; | |
| const uniforms = { | |
| uTime: { value: 0 }, | |
| uPhase: { value: 0 }, | |
| uProgress: { value: 0 }, | |
| uColCyan: { value: CONFIG.colors.cyan }, | |
| uColBlue: { value: CONFIG.colors.blue }, | |
| uColGold: { value: CONFIG.colors.gold }, | |
| uColOrange: { value: CONFIG.colors.orange }, | |
| uColMagenta: { value: CONFIG.colors.magenta }, | |
| uColViolet: { value: CONFIG.colors.violet }, | |
| uSomaRadius: { value: CONFIG.somaRadius }, | |
| uMaxDistDendrite: { value: CONFIG.maxDistDendrite }, | |
| uMaxDistAxon: { value: CONFIG.maxDistAxon } | |
| }; | |
| const scene = new THREE.Scene(); | |
| scene.fog = new THREE.FogExp2(0x010204, 0.015); | |
| const desktopCameraPosition = new THREE.Vector3(30, 20, 40); | |
| const mobileCameraPosition = new THREE.Vector3(34, 24, 62); | |
| const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 500); | |
| camera.position.copy(desktopCameraPosition); | |
| const renderer = new THREE.WebGLRenderer({ antialias: false, powerPreference: "high-performance" }); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | | | renderer.toneMapping = THREE.ACESFilmicToneMapping; | | | renderer.toneMappingExposure = 1.2; | |
| document.body.appendChild(renderer.domElement); | |
| const composer = new EffectComposer(renderer); | |
| const renderPass = new RenderPass(scene, camera); | |
| composer.addPass(renderPass); | |
| const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.8, 0.6, 0.1); | |
| composer.addPass(bloomPass); | |
| const controls = new OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; | |
| controls.dampingFactor = 0.04; | |
| controls.autoRotate = true; | |
| controls.autoRotateSpeed = 0.6; | |
| controls.enablePan = false; | |
| controls.minDistance = 15; | |
| controls.maxDistance = 120; | |
| function isCompactViewport() { | |
| return window.innerWidth <= 720 || window.matchMedia('(pointer: coarse)').matches; | |
| } | |
| function applyResponsiveScene(resetCamera = false) { | |
| const width = window.innerWidth; | |
| const height = window.innerHeight; | | | const compact = isCompactViewport(); | | | const targetPosition = compact ? mobileCameraPosition : desktopCameraPosition; | |
| camera.aspect = width / height; | |
| camera.fov = compact ? 52 : 45; | |
| camera.updateProjectionMatrix(); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, compact ? 1.5 : 2)); | |
| renderer.setSize(width, height, false); | |
| composer.setSize(width, height); | |
| controls.minDistance = compact ? 24 : 15; | |
| controls.maxDistance = compact ? 140 : 120; | |
| if (resetCamera) { | |
| camera.position.copy(targetPosition); | |
| } else if (compact && camera.position.length() < targetPosition.length() * 0.8) { | |
| camera.position.setLength(targetPosition.length()); | |
| } | | | controls.update(); | | | } | |
| applyResponsiveScene(true); | |
| const somaMaterial = new THREE.ShaderMaterial({ | |
| uniforms: uniforms, | | | transparent: true, | | | blending: THREE.AdditiveBlending, | | | depthWrite: false, | | | side: THREE.DoubleSide, | | | vertexShader: ` | | | ${shaderNoise} | | | uniform float uTime; | | | uniform int uPhase; | | | uniform float uProgress; | | | varying vec3 vNormal; | | | varying vec3 vViewPosition; | | | varying float vNoise; | |
| void main() { | |
| vec3 pos = position; | |
| float noise = snoise(pos * 0.5 + uTime * 0.3) * 0.5; | |
| float burst = 0.0; | |
| if(uPhase == 2) { | |
| burst = sin(uProgress * 3.14159) * 0.4; | |
| } | |
| pos += normal * (noise + burst); | |
| vNoise = noise; | |
| vNormal = normalize(normalMatrix * normal); | |
| vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0); | |
| vViewPosition = -mvPosition.xyz; | |
| gl_Position = projectionMatrix * mvPosition; | |
| } | |
| , | | | fragmentShader: | |
| uniform int uPhase; | |
| uniform float uProgress; | |
| uniform float uTime; | |
| uniform vec3 uColCyan; | |
| uniform vec3 uColBlue; | |
| uniform vec3 uColGold; | |
| uniform vec3 uColMagenta; | |
| varying vec3 vNormal; | |
| varying vec3 vViewPosition; | |
| varying float vNoise; | |
| void main() { | |
| vec3 normal = normalize(vNormal); | |
| vec3 viewDir = normalize(vViewPosition); | |
| float fresnel = pow(1.0 - max(dot(normal, viewDir), 0.0), 2.5); | |
| vec3 color = mix(uColBlue * 0.2, uColCyan, fresnel); | |
| color += uColCyan * (vNoise * 0.5 + 0.5) * 0.3; | |
| if(uPhase == 2) { | |
| float intensity = sin(uProgress * 3.14159); | |
| color = mix(color, uColGold * 2.0 + uColCyan, intensity * fresnel * 2.0); | |
| color += uColGold * intensity * (1.0 - fresnel); | |
| } else if(uPhase == 4) { | |
| float intensity = 1.0 - uProgress; | |
| color = mix(color, uColMagenta, intensity * fresnel * 1.5); | |
| } | | | gl_FragColor = vec4(color, 0.8 * fresnel + 0.2); | | | } | | | ` | |
| }); | |
| const branchMaterial = new THREE.ShaderMaterial({ | |
| uniforms: Object.assign({}, uniforms, { uIsAxon: { value: 0 } }), | |
| transparent: true, | | | blending: THREE.AdditiveBlending, | | | depthWrite: false, | | | vertexShader: ` | | | ${shaderNoise} | | | uniform float uTime; | | | varying vec3 vWorldPos; | | | varying vec3 vNormal; | | | varying vec3 vViewPosition; | | | varying vec2 vUv; | |
| void main() { | |
| vUv = uv; | |
| vec3 pos = position; | |
| float wiggle = snoise(pos * 0.2 + uTime * 0.5) * 0.1; | |
| pos += normal * wiggle; | |
| vec4 worldPosition = modelMatrix * vec4(pos, 1.0); | |
| vWorldPos = worldPosition.xyz; | |
| vNormal = normalize(normalMatrix * normal); | |
| vec4 mvPosition = viewMatrix * worldPosition; | |
| vViewPosition = -mvPosition.xyz; | |
| gl_Position = projectionMatrix * mvPosition; | |
| } | |
| , | | | fragmentShader: | |
| ${shaderNoise} | |
| uniform float uTime; | |
| uniform int uPhase; | |
| uniform float uProgress; | |
| uniform int uIsAxon; | |
| uniform vec3 uColCyan; | |
| uniform vec3 uColBlue; | |
| uniform vec3 uColGold; | |
| uniform vec3 uColOrange; | |
| uniform vec3 uColMagenta; | |
| uniform vec3 uColViolet; | |
| uniform float uSomaRadius; | |
| uniform float uMaxDistDendrite; | |
| uniform float uMaxDistAxon; | |
| varying vec3 vWorldPos; | |
| varying vec3 vNormal; | |
| varying vec3 vViewPosition; | |
| varying vec2 vUv; | |
| void main() { | |
| vec3 normal = normalize(vNormal); | |
| vec3 viewDir = normalize(vViewPosition); | |
| float edge = pow(1.0 - abs(vUv.y - 0.5) * 2.0, 2.0); | |
| float fresnel = pow(1.0 - max(dot(normal, viewDir), 0.0), 2.0); | |
| float dist = length(vWorldPos); | |
| vec3 baseColor = mix(uColBlue * 0.1, uColCyan * 0.5, fresnel * edge); | |
| vec3 pulseColor = vec3(0.0); | |
| float flowNoise = snoise(vec3(vUv.x * 20.0 - uTime * 2.0, vUv.y * 10.0, uTime)) * 0.5 + 0.5; | |
| float axialFlow = 0.65 + 0.35 * sin(vUv.x * 34.0 - uTime * 8.0); | |
| if (uIsAxon == 0) { | |
| if (uPhase == 1) { | |
| float currentWaveDist = mix(uMaxDistDendrite, uSomaRadius, uProgress); | |
| float head = 1.0 - smoothstep(0.0, 2.3, abs(dist - currentWaveDist)); | |
| float outerTrail = step(currentWaveDist, dist) * exp(-(dist - currentWaveDist) * 0.18); | |
| float mergeGlow = (1.0 - smoothstep(uSomaRadius, uSomaRadius + 8.0, dist)) * smoothstep(0.65, 1.0, uProgress); | |
| float pulse = max(head * 1.6, outerTrail * 0.65) + mergeGlow * 0.8; | |
| pulseColor = uColGold * pulse * flowNoise * axialFlow * 3.2; | | | } | |
| } else { | |
| if (uPhase == 3) { | |
| float currentWaveDist = mix(uSomaRadius, uMaxDistAxon + 18.0, uProgress); | |
| float head = 1.0 - smoothstep(0.0, 3.4, abs(dist - currentWaveDist)); | |
| float innerTrail = step(dist, currentWaveDist) * exp(-(currentWaveDist - dist) * 0.1); | |
| float somaLaunch = (1.0 - smoothstep(uSomaRadius, uSomaRadius + 7.0, dist)) * (1.0 - smoothstep(0.0, 0.28, uProgress)); | |
| float pulse = max(head * 2.0, innerTrail * 0.9) + somaLaunch * 1.2; | |
| pulseColor = (uColOrange + uColGold * 0.45) * pulse * flowNoise * axialFlow * 4.6; | | | } | | | } | |
| if (uPhase == 4) { | |
| float intensity = 1.0 - uProgress; | |
| pulseColor += uColViolet * intensity * edge * 1.5; | | | } | | | gl_FragColor = vec4(baseColor + pulseColor, 1.0); | | | } | | | ` | |
| }); | |
| const axonMaterial = branchMaterial.clone(); | |
| Object.keys(uniforms).forEach((key) => { | |
| axonMaterial.uniforms[key] = uniforms[key]; | |
| }); | |
| axonMaterial.uniforms.uIsAxon = { value: 1 }; | |
| const networkGroup = new THREE.Group(); | |
| scene.add(networkGroup); | |
| const somaGeo = new THREE.IcosahedronGeometry(CONFIG.somaRadius, 32); | |
| const somaMesh = new THREE.Mesh(somaGeo, somaMaterial); | |
| networkGroup.add(somaMesh); | |
| const coreGeo = new THREE.IcosahedronGeometry(CONFIG.somaRadius * 0.8, 16); | |
| const coreMat = new THREE.MeshBasicMaterial({ color: CONFIG.colors.cyan, transparent: true, opacity: 0.1, blending: THREE.AdditiveBlending, wireframe: true }); | |
| networkGroup.add(new THREE.Mesh(coreGeo, coreMat)); | |
| function buildBranch(startPt, dir, length, radius, level, maxLevels, isAxon) { | |
| const segments = 12; | |
| const points = [startPt.clone()]; | |
| let cur = startPt.clone(); | |
| let cDir = dir.clone(); | |
| for(let i=0; i<segments; i++) { | |
| let curl = new THREE.Vector3( | |
| Math.random() - 0.5, | |
| Math.random() - 0.5, | |
| Math.random() - 0.5 | |
| ).multiplyScalar(isAxon ? 0.3 : 0.8); | |
| cDir.add(curl).normalize(); | |
| cur.add(cDir.clone().multiplyScalar(length / segments)); | |
| points.push(cur.clone()); | |
| } | | | const curve = new THREE.CatmullRomCurve3(points); | | | const tubeGeo = new THREE.TubeGeometry(curve, segments * 2, radius, 6, false); | |
| const mesh = new THREE.Mesh(tubeGeo, isAxon ? axonMaterial : branchMaterial); | |
| networkGroup.add(mesh); | |
| if (level < maxLevels) { | |
| const childCount = isAxon ? (Math.random() > 0.3 ? 1 : 2) : (Math.random() > 0.2 ? 2 : 3); | |
| for(let j=0; j<childCount; j++) { | |
| let splitDir = cDir.clone().add(new THREE.Vector3( | |
| Math.random()-0.5, Math.random()-0.5, Math.random()-0.5 | |
| ).multiplyScalar(0.8)).normalize(); | |
| buildBranch( | | | cur, | | | splitDir, | | | length * (0.6 + Math.random()*0.3), | | | radius * 0.65, | | | level + 1, | | | maxLevels, | | | isAxon | | | ); | | | } | |
| } else { | |
| addSynapse(cur); | |
| } | | | } | |
| const synapsePositions = []; | |
| function addSynapse(pos) { | |
| synapsePositions.push(pos.x, pos.y, pos.z); | |
| } | |
| for(let i=0; i<CONFIG.dendriteTrees; i++) { | |
| let dir = new THREE.Vector3(Math.random()-0.5, Math.random()-0.5, Math.random()-0.5).normalize(); | |
| if (dir.z > 0.3) dir.z -= 0.8; | |
| dir.normalize(); | |
| let start = dir.clone().multiplyScalar(CONFIG.somaRadius - 0.5); | |
| buildBranch(start, dir, 12 + Math.random()*5, 0.4, 0, 2, false); | |
| } | |
| for(let i=0; i<CONFIG.axonTrees; i++) { | |
| let dir = new THREE.Vector3((Math.random()-0.5)*0.5, (Math.random()-0.5)*0.5, 1.0).normalize(); | |
| let start = dir.clone().multiplyScalar(CONFIG.somaRadius - 0.5); | |
| buildBranch(start, dir, 25 + Math.random()*10, 0.6, 0, 2, true); | |
| } | |
| const synGeo = new THREE.BufferGeometry(); | |
| synGeo.setAttribute('position', new THREE.Float32BufferAttribute(synapsePositions, 3)); | |
| const synSizes = new Float32Array(synapsePositions.length / 3); | |
| for(let i=0; i<synSizes.length; i++) synSizes[i] = Math.random(); | |
| synGeo.setAttribute('aSize', new THREE.BufferAttribute(synSizes, 1)); | |
| const synMat = new THREE.ShaderMaterial({ | |
| uniforms: uniforms, | | | transparent: true, | | | blending: THREE.AdditiveBlending, | | | depthWrite: false, | | | vertexShader: ` | | | attribute float aSize; | | | uniform float uTime; | | | varying float vSize; | |
| void main() { | |
| vSize = aSize; | |
| vec3 pos = position; | |
| pos.y += sin(uTime * 2.0 + pos.x) * 0.5; | |
| vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0); | |
| gl_PointSize = (20.0 + aSize * 15.0) * (100.0 / -mvPosition.z); | |
| gl_Position = projectionMatrix * mvPosition; | |
| } | |
| , | | | fragmentShader: | |
| uniform int uPhase; | |
| uniform float uProgress; | |
| uniform vec3 uColCyan; | |
| uniform vec3 uColGold; | |
| uniform vec3 uColMagenta; | |
| varying float vSize; | |
| void main() { | |
| vec2 coord = gl_PointCoord - vec2(0.5); | |
| float dist = length(coord); | |
| if (dist > 0.5) discard; | |
| float alpha = (0.5 - dist) * 2.0; | |
| vec3 color = uColCyan * 0.5; | |
| if (uPhase == 1 || uPhase == 3) { | |
| float spark = step(0.8, fract(vSize * 10.0 + uProgress * 5.0)); | |
| color = mix(color, uColGold, spark * 2.0); | |
| } else if (uPhase == 4) { | |
| color = mix(color, uColMagenta, (1.0 - uProgress)); | |
| } | | | gl_FragColor = vec4(color, alpha); | | | } | | | ` | |
| }); | |
| const synapses = new THREE.Points(synGeo, synMat); | |
| networkGroup.add(synapses); | |
| const dustGeo = new THREE.BufferGeometry(); | |
| const dustCount = 800; | |
| const dustPos = new Float32Array(dustCount * 3); | |
| for(let i=0; i<dustCount * 3; i++) { | |
| dustPos[i] = (Math.random() - 0.5) * 150; | |
| } | |
| dustGeo.setAttribute('position', new THREE.BufferAttribute(dustPos, 3)); | |
| const dustMat = new THREE.PointsMaterial({ | |
| color: CONFIG.colors.cyan, | | | size: 0.2, | | | transparent: true, | | | opacity: 0.3, | | | blending: THREE.AdditiveBlending, | | | depthWrite: false | |
| }); | |
| const dust = new THREE.Points(dustGeo, dustMat); | |
| scene.add(dust); | |
| const btnFire = document.getElementById('btn-fire'); | |
| function triggerImpulse(e) { | |
| if (e && e.target && e.target.closest && e.target.closest('.hud-panel')) { | |
| if (e.target !== btnFire) return; | |
| } | |
| if (STATE.isAnimating) return; | |
| STATE.isAnimating = true; | |
| STATE.phase = 1; | |
| STATE.progress = 0; | |
| btnFire.classList.add('disabled'); | |
| btnFire.innerText = 'FIRING SEQUENCE...'; | | | } | | | window.addEventListener('pointerdown', triggerImpulse); | |
| const clock = new THREE.Clock(); | |
| const elMembrane = document.getElementById('val-membrane'); | |
| const elDendrites = document.getElementById('val-dendrites'); | |
| const elAxon = document.getElementById('val-axon'); | |
| const elState = document.getElementById('val-state'); | |
| const elSync = document.getElementById('val-sync'); | |
| const elCamX = document.getElementById('val-cam-x'); | |
| const elCamY = document.getElementById('val-cam-y'); | |
| const elCamZ = document.getElementById('val-cam-z'); | |
| function updateUI() { | |
| elCamX.innerText = camera.position.x.toFixed(2); | |
| elCamY.innerText = camera.position.y.toFixed(2); | |
| elCamZ.innerText = camera.position.z.toFixed(2); | |
| let targetMembrane = -70.0; | |
| let targetAxon = 2; | |
| let targetSync = 24 + Math.sin(uniforms.uTime.value * 2) * 5; | |
| let stateText = "RESTING"; | |
| let denText = "CALM"; | |
| elMembrane.className = "hud-value"; | |
| elAxon.className = "hud-value"; | |
| elState.className = "hud-value"; | |
| switch(STATE.phase) { | |
| case 1: | |
| stateText = "INCOMING STIMULUS"; | |
| denText = "ACTIVE LOAD"; | |
| targetMembrane = -55.0; | |
| targetSync = 45; | |
| elState.classList.add('value-active'); | |
| break; | | | case 2: | |
| stateText = "SOMA MERGE"; | |
| denText = "CONVERGED"; | |
| targetMembrane = +40.0; | |
| targetSync = 85; | |
| elMembrane.classList.add('value-active'); | |
| elState.classList.add('value-active'); | |
| break; | | | case 3: | |
| stateText = "AXON OUTFLOW"; | |
| targetMembrane = +20.0; | |
| targetAxon = 98; | |
| targetSync = 99; | |
| elAxon.classList.add('value-active'); | |
| elState.classList.add('value-active'); | |
| break; | | | case 4: | |
| stateText = "REFRACTORY PERIOD"; | |
| targetMembrane = -80.0; | |
| targetAxon = 15; | |
| targetSync = 15; | |
| elMembrane.classList.add('value-refract'); | |
| elState.classList.add('value-refract'); | |
| break; | | | } | |
| STATE.membraneV += (targetMembrane - STATE.membraneV) * 0.15; | |
| STATE.axonLoad += (targetAxon - STATE.axonLoad) * 0.15; | |
| STATE.sync += (targetSync - STATE.sync) * 0.15; | |
| elMembrane.innerText = `${STATE.membraneV > 0 ? '+' : ''}${STATE.membraneV.toFixed(1)} mV`; | |
| elAxon.innerText = `${Math.round(STATE.axonLoad).toString().padStart(2, '0')}%`; | |
| elSync.innerText = `${Math.round(STATE.sync).toString().padStart(2, '0')}%`; | |
| elState.innerText = stateText; | |
| elDendrites.innerText = denText; | |
| } | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| const dt = clock.getDelta(); | |
| const time = clock.getElapsedTime(); | |
| uniforms.uTime.value = time; | |
| controls.update(); | |
| dust.rotation.y = time * 0.02; | |
| dust.rotation.x = Math.sin(time * 0.01) * 0.05; | |
| if (STATE.isAnimating) { | |
| let speed = 1.0; | |
| if (STATE.phase === 1) speed = 0.8; | |
| if (STATE.phase === 2) speed = 2.5; | |
| if (STATE.phase === 3) speed = 0.95; | |
| if (STATE.phase === 4) speed = 0.5; | |
| STATE.progress += dt * speed; | |
| if (STATE.progress >= 1.0) { | |
| STATE.progress = 0.0; | |
| STATE.phase++; | |
| if (STATE.phase === 2) { | |
| const target = camera.position.clone().multiplyScalar(0.85); | |
| camera.position.lerp(target, 0.5); | |
| bloomPass.strength = 2.5; | |
| } | |
| if (STATE.phase === 3) { | |
| const target = camera.position.clone().multiplyScalar(1.2); | |
| camera.position.lerp(target, 0.5); | |
| } | |
| if (STATE.phase > 4) { | |
| STATE.phase = 0; | |
| STATE.isAnimating = false; | |
| btnFire.classList.remove('disabled'); | |
| btnFire.innerText = 'MANUAL OVERRIDE'; | | | } | | | } | |
| } else { | |
| bloomPass.strength += (1.8 - bloomPass.strength) * 0.05; | |
| } | | | uniforms.uPhase.value = STATE.phase; | | | uniforms.uProgress.value = STATE.progress; | |
| updateUI(); | |
| composer.render(); | |
| } | |
| setTimeout(() => { | |
| document.getElementById('').style.opacity = '0'; | |
| setTimeout(() => document.getElementById('').style.display = 'none', 1000); | |
| }, 1200); | |
| window.addEventListener('resize', () => { | |
| applyResponsiveScene(); | |
| }); | |
| animate(); | |
| </script> |