Bioelectric consciousness engine
A bioelectric consciousness engine built with Three.js, webgl and glsl. Click anywhere to trigger signals; the HUD tracks live neural state.
Source code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bioelectric consciousness engine</title>
</head>
<body>
<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;
}
#loader {
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="loader">
<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.xzyw*sh.xxyy ;
vec4 a1 = b1.xzyw + s1.xzyw*sh.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('loader').style.opacity = '0';
setTimeout(() => document.getElementById('loader').style.display = 'none', 1000);
}, 1200);
window.addEventListener('resize', () => {
applyResponsiveScene();
});
animate();
</script>
</body>
</html>