{"slug": "3d-quantum-neural-network", "title": "3D Quantum Neural Network", "summary": "Interactive 3D quantum neural network visualization created using Three.js and GLSL shaders, presented within a glassmorphic user interface. Users can click or drag to send pulses through the network, causing its form, colors, and density to update in real time. The project was created by a developer known as Techartist on CodePen.", "body_md": "Interactive quantum neural network built with Three.js and GLSL shaders, wrapped in a glassmorphic UI. Click or drag sends pulses while form, colors, and density update in real time through the interface.\n\nA [Pen](https://codepen.io/VoXelo/pen/dPMeGze) by [Techartist](https://codepen.io/VoXelo) on [CodePen](https://codepen.io).\n\nInteractive quantum neural network built with Three.js and GLSL shaders, wrapped in a glassmorphic UI. Click or drag sends pulses while form, colors, and density update in real time through the interface.\n\nA [Pen](https://codepen.io/VoXelo/pen/dPMeGze) by [Techartist](https://codepen.io/VoXelo) on [CodePen](https://codepen.io).\n\n| <meta charset=\"UTF-8\"> | |\n| <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"> | |\n| <title>Neural Network</title> | |\n| <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\"> | |\n| <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin> | |\n| <link href=\"https://fonts.googleapis.com/css2?family=Outfit:wght@200;300;400;500;600&display=swap\" rel=\"stylesheet\"> | |\n| <style> | |\n| :root { | |\n| --glass-bg: rgba(255, 255, 255, 0.03); | |\n| --glass-border: rgba(255, 255, 255, 0.08); | |\n| --glass-highlight: rgba(255, 255, 255, 0.2); | |\n| --neon-accent: #667eea; | |\n| --text-main: rgba(255, 255, 255, 0.9); | |\n| --text-muted: rgba(255, 255, 255, 0.6); | |\n| } | |\n| * { | |\n| margin: 0; | |\n| padding: 0; | |\n| box-sizing: border-box; | |\n| user-select: none; | |\n| -webkit-user-select: none; | |\n| } | |\n| body { | |\n| overflow: hidden; | |\n| background: #050508; | |\n| font-family: 'Outfit', sans-serif; | |\n| color: var(--text-main); | |\n| } | |\n| canvas { | |\n| display: block; | |\n| width: 100%; | |\n| height: 100%; | |\n| cursor: crosshair; | |\n| position: absolute; | |\n| top: 0; | |\n| left: 0; | |\n| z-index: 1; | |\n| } | |\n| .glass-panel { | |\n| backdrop-filter: blur(24px) saturate(120%); | |\n| -webkit-backdrop-filter: blur(24px) saturate(120%); | |\n| background: linear-gradient( | |\n| 145deg, | |\n| rgba(255, 255, 255, 0.05) 0%, | |\n| rgba(255, 255, 255, 0.01) 100% | |\n| ); | |\n| border: 1px solid var(--glass-border); | |\n| border-top: 1px solid var(--glass-highlight); | |\n| border-left: 1px solid var(--glass-highlight); | |\n| box-shadow: | |\n| 0 20px 40px rgba(0, 0, 0, 0.4), | |\n| inset 0 0 20px rgba(255, 255, 255, 0.02); | |\n| border-radius: 24px; | |\n| color: var(--text-main); | |\n| transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); | |\n| position: absolute; | |\n| z-index: 10; | |\n| overflow: hidden; | |\n| } | |\n| .glass-panel::before { | |\n| content: ''; | |\n| position: absolute; | |\n| top: 0; | |\n| left: -100%; | |\n| width: 100%; | |\n| height: 100%; | |\n| background: linear-gradient( | |\n| 90deg, | |\n| transparent, | |\n| rgba(255, 255, 255, 0.05), | |\n| transparent | |\n| ); | |\n| transform: skewX(-15deg); | |\n| transition: 0.5s; | |\n| pointer-events: none; | |\n| } | |\n| .glass-panel:hover { | |\n| background: linear-gradient( | |\n| 145deg, | |\n| rgba(255, 255, 255, 0.08) 0%, | |\n| rgba(255, 255, 255, 0.02) 100% | |\n| ); | |\n| box-shadow: | |\n| 0 30px 60px rgba(0, 0, 0, 0.5), | |\n| inset 0 0 20px rgba(255, 255, 255, 0.05); | |\n| transform: translateY(-2px); | |\n| border-color: rgba(255, 255, 255, 0.15); | |\n| } | |\n| .glass-panel:hover::before { | |\n| left: 150%; | |\n| transition: 0.7s ease-in-out; | |\n| } | |\n| #instructions-container { | |\n| top: 32px; | |\n| left: 32px; | |\n| width: 280px; | |\n| padding: 24px; | |\n| } | |\n| #instruction-title { | |\n| font-weight: 500; | |\n| font-size: 18px; | |\n| margin-bottom: 8px; | |\n| letter-spacing: -0.02em; | |\n| background: linear-gradient(135deg, #fff 30%, #a5b4fc 100%); | |\n| -webkit-background-clip: text; | |\n| -webkit-text-fill-color: transparent; | |\n| text-shadow: 0 10px 20px rgba(0,0,0,0.2); | |\n| } | |\n| .instruction-text { | |\n| font-size: 14px; | |\n| line-height: 1.5; | |\n| color: var(--text-muted); | |\n| font-weight: 300; | |\n| } | |\n| #theme-selector { | |\n| top: 32px; | |\n| right: 32px; | |\n| padding: 24px; | |\n| display: flex; | |\n| flex-direction: column; | |\n| gap: 16px; | |\n| width: 220px; | |\n| } | |\n| #theme-selector-title { | |\n| font-size: 12px; | |\n| text-transform: uppercase; | |\n| letter-spacing: 2px; | |\n| color: var(--text-muted); | |\n| font-weight: 600; | |\n| margin-bottom: 4px; | |\n| } | |\n| .theme-grid { | |\n| display: grid; | |\n| grid-template-columns: repeat(3, 1fr); | |\n| gap: 12px; | |\n| justify-items: center; | |\n| } | |\n| .theme-button { | |\n| width: 44px; | |\n| height: 44px; | |\n| border-radius: 50%; | |\n| border: none; | |\n| cursor: pointer; | |\n| position: relative; | |\n| transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); | |\n| box-shadow: | |\n| 0 4px 10px rgba(0,0,0,0.3), | |\n| inset 0 2px 4px rgba(255,255,255,0.4), | |\n| inset 0 -2px 4px rgba(0,0,0,0.2); | |\n| } | |\n| #theme-1 { background: radial-gradient(circle at 30% 30%, #a78bfa, #4c1d95); } | |\n| #theme-2 { background: radial-gradient(circle at 30% 30%, #fb7185, #9f1239); } | |\n| #theme-3 { background: radial-gradient(circle at 30% 30%, #38bdf8, #0c4a6e); } | |\n| .theme-button::after { | |\n| content: ''; | |\n| position: absolute; | |\n| top: -4px; | |\n| left: -4px; | |\n| right: -4px; | |\n| bottom: -4px; | |\n| border-radius: 50%; | |\n| border: 2px solid rgba(255,255,255,0.8); | |\n| opacity: 0; | |\n| transform: scale(1.1); | |\n| transition: all 0.3s ease; | |\n| } | |\n| .theme-button:hover { | |\n| transform: scale(1.15) translateY(-2px); | |\n| box-shadow: 0 8px 20px rgba(0,0,0,0.4), inset 0 2px 6px rgba(255,255,255,0.6); | |\n| } | |\n| .theme-button.active::after { | |\n| opacity: 1; | |\n| transform: scale(1); | |\n| border-color: rgba(255,255,255,0.9); | |\n| box-shadow: 0 0 15px rgba(255,255,255,0.3); | |\n| } | |\n| #density-controls { | |\n| display: flex; | |\n| flex-direction: column; | |\n| gap: 12px; | |\n| margin-top: 8px; | |\n| } | |\n| .density-label { | |\n| display: flex; | |\n| justify-content: space-between; | |\n| font-size: 13px; | |\n| color: var(--text-muted); | |\n| font-weight: 300; | |\n| } | |\n| #density-value { | |\n| color: white; | |\n| font-weight: 500; | |\n| text-shadow: 0 0 10px rgba(255,255,255,0.3); | |\n| } | |\n| .density-slider { | |\n| -webkit-appearance: none; | |\n| width: 100%; | |\n| height: 6px; | |\n| background: rgba(255,255,255,0.1); | |\n| border-radius: 10px; | |\n| outline: none; | |\n| box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); | |\n| } | |\n| .density-slider::-webkit-slider-thumb { | |\n| -webkit-appearance: none; | |\n| width: 18px; | |\n| height: 18px; | |\n| border-radius: 50%; | |\n| background: #fff; | |\n| cursor: pointer; | |\n| box-shadow: | |\n| 0 0 15px rgba(255,255,255,0.8), | |\n| 0 2px 5px rgba(0,0,0,0.3); | |\n| transition: all 0.2s ease; | |\n| margin-top: -6px; | |\n| position: relative; | |\n| z-index: 2; | |\n| } | |\n| .density-slider::-webkit-slider-runnable-track { | |\n| width: 100%; | |\n| height: 6px; | |\n| cursor: pointer; | |\n| background: linear-gradient(90deg, rgba(255,255,255,0.3) var(--val, 100%), rgba(255,255,255,0.05) var(--val, 100%)); | |\n| border-radius: 3px; | |\n| } | |\n| .density-slider::-webkit-slider-thumb:hover { | |\n| transform: scale(1.2); | |\n| box-shadow: 0 0 20px rgba(255,255,255,1); | |\n| } | |\n| #control-buttons { | |\n| position: absolute; | |\n| bottom: 40px; | |\n| left: 50%; | |\n| transform: translateX(-50%); | |\n| display: flex; | |\n| gap: 16px; | |\n| z-index: 20; | |\n| padding: 8px; | |\n| background: rgba(0,0,0,0.1); | |\n| } | |\n| .control-button { | |\n| backdrop-filter: blur(20px) saturate(140%); | |\n| -webkit-backdrop-filter: blur(20px) saturate(140%); | |\n| background: rgba(255, 255, 255, 0.04); | |\n| border: 1px solid rgba(255, 255, 255, 0.1); | |\n| border-top: 1px solid rgba(255, 255, 255, 0.25); | |\n| color: var(--text-main); | |\n| padding: 12px 24px; | |\n| border-radius: 50px; | |\n| cursor: pointer; | |\n| font-family: 'Outfit', sans-serif; | |\n| font-size: 13px; | |\n| font-weight: 500; | |\n| letter-spacing: 0.5px; | |\n| text-transform: uppercase; | |\n| transition: all 0.3s ease; | |\n| box-shadow: | |\n| 0 8px 20px rgba(0, 0, 0, 0.3), | |\n| inset 0 0 10px rgba(255,255,255,0.02); | |\n| overflow: hidden; | |\n| position: relative; | |\n| min-width: 100px; | |\n| text-align: center; | |\n| } | |\n| .control-button:hover { | |\n| background: rgba(255, 255, 255, 0.1); | |\n| border-color: rgba(255, 255, 255, 0.4); | |\n| transform: translateY(-4px); | |\n| box-shadow: | |\n| 0 15px 30px rgba(0, 0, 0, 0.4), | |\n| 0 0 20px rgba(255, 255, 255, 0.1); | |\n| text-shadow: 0 0 8px rgba(255,255,255,0.6); | |\n| } | |\n| .control-button:active { | |\n| transform: translateY(-1px); | |\n| } | |\n| .control-button span { | |\n| position: relative; | |\n| z-index: 2; | |\n| } | |\n| @media (max-width: 640px) { | |\n| #instructions-container { | |\n| top: 16px; | |\n| left: 16px; | |\n| right: 16px; | |\n| width: auto; | |\n| padding: 16px; | |\n| background: rgba(10, 10, 15, 0.6); | |\n| } | |\n| #theme-selector { | |\n| top: auto; | |\n| bottom: 100px; | |\n| left: 16px; | |\n| right: 16px; | |\n| width: auto; | |\n| padding: 16px; | |\n| flex-direction: row; | |\n| align-items: center; | |\n| justify-content: space-between; | |\n| } | |\n| .theme-grid { | |\n| margin-top: 0; | |\n| } | |\n| #control-buttons { | |\n| bottom: 24px; | |\n| width: 100%; | |\n| justify-content: center; | |\n| gap: 8px; | |\n| padding: 0 16px; | |\n| } | |\n| .control-button { | |\n| padding: 10px 16px; | |\n| min-width: auto; | |\n| font-size: 11px; | |\n| flex: 1; | |\n| } | |\n| } | |\n| </style> | |\n| <div id=\"instructions-container\" class=\"glass-panel\"> | |\n| <div id=\"instruction-title\">Quantum Neural Network</div> | |\n| <div class=\"instruction-text\">Click to send energy pulses. <br>Drag to explore the structure.</div> | |\n| </div> | |\n| <div id=\"theme-selector\" class=\"glass-panel\"> | |\n| <div style=\"flex: 1;\"> | |\n| <div id=\"theme-selector-title\">Crystal Theme</div> | |\n| <div class=\"theme-grid\"> | |\n| <button class=\"theme-button active\" id=\"theme-1\" data-theme=\"0\" aria-label=\"Purple Nebula\"></button> | |\n| <button class=\"theme-button\" id=\"theme-2\" data-theme=\"1\" aria-label=\"Sunset Fire\"></button> | |\n| <button class=\"theme-button\" id=\"theme-3\" data-theme=\"2\" aria-label=\"Ocean Aurora\"></button> | |\n| </div> | |\n| </div> | |\n| <div id=\"density-controls\" style=\"flex: 1;\"> | |\n| <div class=\"density-label\"><span>Density</span><span id=\"density-value\">100%</span></div> | |\n| <input type=\"range\" min=\"30\" max=\"100\" value=\"100\" class=\"density-slider\" id=\"density-slider\" | |\n| aria-label=\"Network Density\" oninput=\"this.style.setProperty('--val', this.value + '%')\"> | |\n| </div> | |\n| </div> | |\n| <div id=\"control-buttons\"> | |\n| <button id=\"change-formation-btn\" class=\"control-button\"><span>Morph</span></button> | |\n| <button id=\"pause-play-btn\" class=\"control-button\"><span>Freeze</span></button> | |\n| <button id=\"reset-camera-btn\" class=\"control-button\"><span>Reset</span></button> | |\n| </div> | |\n| <canvas id=\"neural-network-canvas\"></canvas> | |\n| <script type=\"importmap\"> | |\n| { | |\n| \"imports\": { | |\n| \"three\": \"https://cdn.jsdelivr.net/npm/three@0.162.0/build/three.module.js\", | |\n| \"three/addons/\": \"https://cdn.jsdelivr.net/npm/three@0.162.0/examples/jsm/\" | |\n| } | |\n| } | |\n| </script> | |\n| <script type=\"module\"> | |\n| import * as THREE from 'three'; | |\n| import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; | |\n| import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; | |\n| import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; | |\n| import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; | |\n| import { OutputPass } from 'three/addons/postprocessing/OutputPass.js'; | |\n| const config = { | |\n| paused: false, | |\n| activePaletteIndex: 0, | |\n| currentFormation: 0, | |\n| numFormations: 3, | |\n| densityFactor: 1 | |\n| }; | |\n| const colorPalettes = [ | |\n| [ | |\n| new THREE.Color(0x667eea), | |\n| new THREE.Color(0x764ba2), | |\n| new THREE.Color(0xf093fb), | |\n| new THREE.Color(0x9d50bb), | |\n| new THREE.Color(0x6e48aa) | |\n| ], | |\n| [ | |\n| new THREE.Color(0xf857a6), | |\n| new THREE.Color(0xff5858), | |\n| new THREE.Color(0xfeca57), | |\n| new THREE.Color(0xff6348), | |\n| new THREE.Color(0xff9068) | |\n| ], | |\n| [ | |\n| new THREE.Color(0x4facfe), | |\n| new THREE.Color(0x00f2fe), | |\n| new THREE.Color(0x43e97b), | |\n| new THREE.Color(0x38f9d7), | |\n| new THREE.Color(0x4484ce) | |\n| ] | |\n| ]; | |\n| const scene = new THREE.Scene(); | |\n| scene.fog = new THREE.FogExp2(0x000000, 0.002); | |\n| const camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000); | |\n| camera.position.set(0, 8, 28); | |\n| const canvasElement = document.getElementById('neural-network-canvas'); | |\n| const renderer = new THREE.WebGLRenderer({ | |\n| canvas: canvasElement, | |\n| antialias: true, | |\n| powerPreference: \"high-performance\" | |\n| }); | |\n| renderer.setSize(window.innerWidth, window.innerHeight); | |\n| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |\n| renderer.setClearColor(0x000000); | |\n| renderer.outputColorSpace = THREE.SRGBColorSpace; | |\n| function createStarfield() { | |\n| const count = 8000; | |\n| const positions = []; | |\n| const colors = []; | |\n| const sizes = []; | |\n| for (let i = 0; i < count; i++) { | |\n| const r = THREE.MathUtils.randFloat(50, 150); | |\n| const phi = Math.acos(THREE.MathUtils.randFloatSpread(2)); | |\n| const theta = THREE.MathUtils.randFloat(0, Math.PI * 2); | |\n| positions.push( | |\n| r * Math.sin(phi) * Math.cos(theta), | |\n| r * Math.sin(phi) * Math.sin(theta), | |\n| r * Math.cos(phi) | |\n| ); | |\n| const colorChoice = Math.random(); | |\n| if (colorChoice < 0.7) { | |\n| colors.push(1, 1, 1); | |\n| } else if (colorChoice < 0.85) { | |\n| colors.push(0.7, 0.8, 1); | |\n| } else { | |\n| colors.push(1, 0.9, 0.8); | |\n| } | |\n| sizes.push(THREE.MathUtils.randFloat(0.1, 0.3)); | |\n| } | |\n| const geo = new THREE.BufferGeometry(); | |\n| geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); | |\n| geo.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)); | |\n| geo.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1)); | |\n| const mat = new THREE.ShaderMaterial({ | |\n| uniforms: { | |\n| uTime: { value: 0 } | |\n| }, | |\n| vertexShader: ` | |\n| attribute float size; | |\n| attribute vec3 color; | |\n| varying vec3 vColor; | |\n| uniform float uTime; | |\n| void main() { | |\n| vColor = color; | |\n| vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); | |\n| float twinkle = sin(uTime * 2.0 + position.x * 100.0) * 0.3 + 0.7; | |\n| gl_PointSize = size * twinkle * (300.0 / -mvPosition.z); | |\n| gl_Position = projectionMatrix * mvPosition; | |\n| } | |\n| `, | |\n| fragmentShader: ` | |\n| varying vec3 vColor; | |\n| void main() { | |\n| vec2 center = gl_PointCoord - 0.5; | |\n| float dist = length(center); | |\n| if (dist > 0.5) discard; | |\n| float alpha = 1.0 - smoothstep(0.0, 0.5, dist); | |\n| gl_FragColor = vec4(vColor, alpha * 0.8); | |\n| } | |\n| `, | |\n| transparent: true, | |\n| depthWrite: false, | |\n| blending: THREE.AdditiveBlending | |\n| }); | |\n| return new THREE.Points(geo, mat); | |\n| } | |\n| const starField = createStarfield(); | |\n| scene.add(starField); | |\n| const controls = new OrbitControls(camera, renderer.domElement); | |\n| controls.enableDamping = true; | |\n| controls.dampingFactor = 0.05; | |\n| controls.rotateSpeed = 0.6; | |\n| controls.minDistance = 8; | |\n| controls.maxDistance = 80; | |\n| controls.autoRotate = true; | |\n| controls.autoRotateSpeed = 0.2; | |\n| controls.enablePan = false; | |\n| const composer = new EffectComposer(renderer); | |\n| composer.addPass(new RenderPass(scene, camera)); | |\n| const bloomPass = new UnrealBloomPass( | |\n| new THREE.Vector2(window.innerWidth, window.innerHeight), | |\n| 1.8, | |\n| 0.6, | |\n| 0.7 | |\n| ); | |\n| composer.addPass(bloomPass); | |\n| composer.addPass(new OutputPass()); | |\n| const pulseUniforms = { | |\n| uTime: { value: 0.0 }, | |\n| uPulsePositions: { value: [ | |\n| new THREE.Vector3(1e3, 1e3, 1e3), | |\n| new THREE.Vector3(1e3, 1e3, 1e3), | |\n| new THREE.Vector3(1e3, 1e3, 1e3) | |\n| ]}, | |\n| uPulseTimes: { value: [-1e3, -1e3, -1e3] }, | |\n| uPulseColors: { value: [ | |\n| new THREE.Color(1, 1, 1), | |\n| new THREE.Color(1, 1, 1), | |\n| new THREE.Color(1, 1, 1) | |\n| ]}, | |\n| uPulseSpeed: { value: 18.0 }, | |\n| uBaseNodeSize: { value: 0.6 } | |\n| }; | |\n| const noiseFunctions = ` | |\n| vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } | |\n| vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } | |\n| vec4 permute(vec4 x) { return mod289(((x * 34.0) + 1.0) * x); } | |\n| vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; } | |\n| float snoise(vec3 v) { | |\n| const vec2 C = vec2(1.0/6.0, 1.0/3.0); | |\n| const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); | |\n| vec3 i = floor(v + dot(v, C.yyy)); | |\n| vec3 x0 = v - i + dot(i, C.xxx); | |\n| vec3 g = step(x0.yzx, x0.xyz); | |\n| vec3 l = 1.0 - g; | |\n| vec3 i1 = min(g.xyz, l.zxy); | |\n| vec3 i2 = max(g.xyz, l.zxy); | |\n| vec3 x1 = x0 - i1 + C.xxx; | |\n| vec3 x2 = x0 - i2 + C.yyy; | |\n| vec3 x3 = x0 - D.yyy; | |\n| i = mod289(i); | |\n| vec4 p = permute(permute(permute( | |\n| i.z + vec4(0.0, i1.z, i2.z, 1.0)) | |\n| + i.y + vec4(0.0, i1.y, i2.y, 1.0)) | |\n| + i.x + vec4(0.0, i1.x, i2.x, 1.0)); | |\n| float n_ = 0.142857142857; | |\n| vec3 ns = n_ * D.wyz - D.xzx; | |\n| vec4 j = p - 49.0 * floor(p * ns.z * ns.z); | |\n| vec4 x_ = floor(j * ns.z); | |\n| vec4 y_ = floor(j - 7.0 * x_); | |\n| vec4 x = x_ * ns.x + ns.yyyy; | |\n| vec4 y = y_ * ns.x + ns.yyyy; | |\n| vec4 h = 1.0 - abs(x) - abs(y); | |\n| vec4 b0 = vec4(x.xy, y.xy); | |\n| vec4 b1 = vec4(x.zw, y.zw); | |\n| vec4 s0 = floor(b0) * 2.0 + 1.0; | |\n| vec4 s1 = floor(b1) * 2.0 + 1.0; | |\n| vec4 sh = -step(h, vec4(0.0)); | |\n| vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; | |\n| vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; | |\n| vec3 p0 = vec3(a0.xy, h.x); | |\n| vec3 p1 = vec3(a0.zw, h.y); | |\n| vec3 p2 = vec3(a1.xy, h.z); | |\n| vec3 p3 = vec3(a1.zw, h.w); | |\n| vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3))); | |\n| p0 *= norm.x; | |\n| p1 *= norm.y; | |\n| p2 *= norm.z; | |\n| p3 *= norm.w; | |\n| vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); | |\n| m = m * m; | |\n| return 42.0 * dot(m * m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3))); | |\n| }`; | |\n| const nodeShader = { | |\n| vertexShader: `${noiseFunctions} | |\n| attribute float nodeSize; | |\n| attribute float nodeType; | |\n| attribute vec3 nodeColor; | |\n| attribute float distanceFromRoot; | |\n| uniform float uTime; | |\n| uniform vec3 uPulsePositions[3]; | |\n| uniform float uPulseTimes[3]; | |\n| uniform float uPulseSpeed; | |\n| uniform float uBaseNodeSize; | |\n| varying vec3 vColor; | |\n| varying float vNodeType; | |\n| varying vec3 vPosition; | |\n| varying float vPulseIntensity; | |\n| varying float vDistanceFromRoot; | |\n| varying float vGlow; | |\n| float getPulseIntensity(vec3 worldPos, vec3 pulsePos, float pulseTime) { | |\n| if (pulseTime < 0.0) return 0.0; | |\n| float timeSinceClick = uTime - pulseTime; | |\n| if (timeSinceClick < 0.0 || timeSinceClick > 4.0) return 0.0; | |\n| float pulseRadius = timeSinceClick * uPulseSpeed; | |\n| float distToClick = distance(worldPos, pulsePos); | |\n| float pulseThickness = 3.0; | |\n| float waveProximity = abs(distToClick - pulseRadius); | |\n| return smoothstep(pulseThickness, 0.0, waveProximity) * smoothstep(4.0, 0.0, timeSinceClick); | |\n| } | |\n| void main() { | |\n| vNodeType = nodeType; | |\n| vColor = nodeColor; | |\n| vDistanceFromRoot = distanceFromRoot; | |\n| vec3 worldPos = (modelMatrix * vec4(position, 1.0)).xyz; | |\n| vPosition = worldPos; | |\n| float totalPulseIntensity = 0.0; | |\n| for (int i = 0; i < 3; i++) { | |\n| totalPulseIntensity += getPulseIntensity(worldPos, uPulsePositions[i], uPulseTimes[i]); | |\n| } | |\n| vPulseIntensity = min(totalPulseIntensity, 1.0); | |\n| float breathe = sin(uTime * 0.7 + distanceFromRoot * 0.15) * 0.15 + 0.85; | |\n| float baseSize = nodeSize * breathe; | |\n| float pulseSize = baseSize * (1.0 + vPulseIntensity * 2.5); | |\n| vGlow = 0.5 + 0.5 * sin(uTime * 0.5 + distanceFromRoot * 0.2); | |\n| vec3 modifiedPosition = position; | |\n| if (nodeType > 0.5) { | |\n| float noise = snoise(position * 0.08 + uTime * 0.08); | |\n| modifiedPosition += normal * noise * 0.15; | |\n| } | |\n| vec4 mvPosition = modelViewMatrix * vec4(modifiedPosition, 1.0); | |\n| gl_PointSize = pulseSize * uBaseNodeSize * (1000.0 / -mvPosition.z); | |\n| gl_Position = projectionMatrix * mvPosition; | |\n| }`, | |\n| fragmentShader: ` | |\n| uniform float uTime; | |\n| uniform vec3 uPulseColors[3]; | |\n| varying vec3 vColor; | |\n| varying float vNodeType; | |\n| varying vec3 vPosition; | |\n| varying float vPulseIntensity; | |\n| varying float vDistanceFromRoot; | |\n| varying float vGlow; | |\n| void main() { | |\n| vec2 center = 2.0 * gl_PointCoord - 1.0; | |\n| float dist = length(center); | |\n| if (dist > 1.0) discard; | |\n| float glow1 = 1.0 - smoothstep(0.0, 0.5, dist); | |\n| float glow2 = 1.0 - smoothstep(0.0, 1.0, dist); | |\n| float glowStrength = pow(glow1, 1.2) + glow2 * 0.3; | |\n| float breatheColor = 0.9 + 0.1 * sin(uTime * 0.6 + vDistanceFromRoot * 0.25); | |\n| vec3 baseColor = vColor * breatheColor; | |\n| vec3 finalColor = baseColor; | |\n| if (vPulseIntensity > 0.0) { | |\n| vec3 pulseColor = mix(vec3(1.0), uPulseColors[0], 0.4); | |\n| finalColor = mix(baseColor, pulseColor, vPulseIntensity * 0.8); | |\n| finalColor *= (1.0 + vPulseIntensity * 1.2); | |\n| glowStrength *= (1.0 + vPulseIntensity); | |\n| } | |\n| float coreBrightness = smoothstep(0.4, 0.0, dist); | |\n| finalColor += vec3(1.0) * coreBrightness * 0.3; | |\n| float alpha = glowStrength * (0.95 - 0.3 * dist); | |\n| float camDistance = length(vPosition - cameraPosition); | |\n| float distanceFade = smoothstep(100.0, 15.0, camDistance); | |\n| if (vNodeType > 0.5) { | |\n| finalColor *= 1.1; | |\n| alpha *= 0.9; | |\n| } | |\n| finalColor *= (1.0 + vGlow * 0.1); | |\n| gl_FragColor = vec4(finalColor, alpha * distanceFade); | |\n| }` | |\n| }; | |\n| const connectionShader = { | |\n| vertexShader: `${noiseFunctions} | |\n| attribute vec3 startPoint; | |\n| attribute vec3 endPoint; | |\n| attribute float connectionStrength; | |\n| attribute float pathIndex; | |\n| attribute vec3 connectionColor; | |\n| uniform float uTime; | |\n| uniform vec3 uPulsePositions[3]; | |\n| uniform float uPulseTimes[3]; | |\n| uniform float uPulseSpeed; | |\n| varying vec3 vColor; | |\n| varying float vConnectionStrength; | |\n| varying float vPulseIntensity; | |\n| varying float vPathPosition; | |\n| varying float vDistanceFromCamera; | |\n| float getPulseIntensity(vec3 worldPos, vec3 pulsePos, float pulseTime) { | |\n| if (pulseTime < 0.0) return 0.0; | |\n| float timeSinceClick = uTime - pulseTime; | |\n| if (timeSinceClick < 0.0 || timeSinceClick > 4.0) return 0.0; | |\n| float pulseRadius = timeSinceClick * uPulseSpeed; | |\n| float distToClick = distance(worldPos, pulsePos); | |\n| float pulseThickness = 3.0; | |\n| float waveProximity = abs(distToClick - pulseRadius); | |\n| return smoothstep(pulseThickness, 0.0, waveProximity) * smoothstep(4.0, 0.0, timeSinceClick); | |\n| } | |\n| void main() { | |\n| float t = position.x; | |\n| vPathPosition = t; | |\n| vec3 midPoint = mix(startPoint, endPoint, 0.5); | |\n| float pathOffset = sin(t * 3.14159) * 0.15; | |\n| vec3 perpendicular = normalize(cross(normalize(endPoint - startPoint), vec3(0.0, 1.0, 0.0))); | |\n| if (length(perpendicular) < 0.1) perpendicular = vec3(1.0, 0.0, 0.0); | |\n| midPoint += perpendicular * pathOffset; | |\n| vec3 p0 = mix(startPoint, midPoint, t); | |\n| vec3 p1 = mix(midPoint, endPoint, t); | |\n| vec3 finalPos = mix(p0, p1, t); | |\n| float noiseTime = uTime * 0.15; | |\n| float noise = snoise(vec3(pathIndex * 0.08, t * 0.6, noiseTime)); | |\n| finalPos += perpendicular * noise * 0.12; | |\n| vec3 worldPos = (modelMatrix * vec4(finalPos, 1.0)).xyz; | |\n| float totalPulseIntensity = 0.0; | |\n| for (int i = 0; i < 3; i++) { | |\n| totalPulseIntensity += getPulseIntensity(worldPos, uPulsePositions[i], uPulseTimes[i]); | |\n| } | |\n| vPulseIntensity = min(totalPulseIntensity, 1.0); | |\n| vColor = connectionColor; | |\n| vConnectionStrength = connectionStrength; | |\n| vDistanceFromCamera = length(worldPos - cameraPosition); | |\n| gl_Position = projectionMatrix * modelViewMatrix * vec4(finalPos, 1.0); | |\n| }`, | |\n| fragmentShader: ` | |\n| uniform float uTime; | |\n| uniform vec3 uPulseColors[3]; | |\n| varying vec3 vColor; | |\n| varying float vConnectionStrength; | |\n| varying float vPulseIntensity; | |\n| varying float vPathPosition; | |\n| varying float vDistanceFromCamera; | |\n| void main() { | |\n| float flowPattern1 = sin(vPathPosition * 25.0 - uTime * 4.0) * 0.5 + 0.5; | |\n| float flowPattern2 = sin(vPathPosition * 15.0 - uTime * 2.5 + 1.57) * 0.5 + 0.5; | |\n| float combinedFlow = (flowPattern1 + flowPattern2 * 0.5) / 1.5; | |\n| vec3 baseColor = vColor * (0.8 + 0.2 * sin(uTime * 0.6 + vPathPosition * 12.0)); | |\n| float flowIntensity = 0.4 * combinedFlow * vConnectionStrength; | |\n| vec3 finalColor = baseColor; | |\n| if (vPulseIntensity > 0.0) { | |\n| vec3 pulseColor = mix(vec3(1.0), uPulseColors[0], 0.3); | |\n| finalColor = mix(baseColor, pulseColor * 1.2, vPulseIntensity * 0.7); | |\n| flowIntensity += vPulseIntensity * 0.8; | |\n| } | |\n| finalColor *= (0.7 + flowIntensity + vConnectionStrength * 0.5); | |\n| float baseAlpha = 0.7 * vConnectionStrength; | |\n| float flowAlpha = combinedFlow * 0.3; | |\n| float alpha = baseAlpha + flowAlpha; | |\n| alpha = mix(alpha, min(1.0, alpha * 2.5), vPulseIntensity); | |\n| float distanceFade = smoothstep(100.0, 15.0, vDistanceFromCamera); | |\n| gl_FragColor = vec4(finalColor, alpha * distanceFade); | |\n| }` | |\n| }; | |\n| class Node { | |\n| constructor(position, level = 0, type = 0) { | |\n| this.position = position; | |\n| this.connections = []; | |\n| this.level = level; | |\n| this.type = type; | |\n| this.size = type === 0 ? THREE.MathUtils.randFloat(0.8, 1.4) : THREE.MathUtils.randFloat(0.5, 1.0); | |\n| this.distanceFromRoot = 0; | |\n| } | |\n| addConnection(node, strength = 1.0) { | |\n| if (!this.isConnectedTo(node)) { | |\n| this.connections.push({ node, strength }); | |\n| node.connections.push({ node: this, strength }); | |\n| } | |\n| } | |\n| isConnectedTo(node) { | |\n| return this.connections.some(conn => conn.node === node); | |\n| } | |\n| } | |\n| function generateNeuralNetwork(formationIndex, densityFactor = 1.0) { | |\n| let nodes = []; | |\n| let rootNode; | |\n| function generateCrystallineSphere() { | |\n| rootNode = new Node(new THREE.Vector3(0, 0, 0), 0, 0); | |\n| rootNode.size = 2.0; | |\n| nodes.push(rootNode); | |\n| const layers = 5; | |\n| const goldenRatio = (1 + Math.sqrt(5)) / 2; | |\n| for (let layer = 1; layer <= layers; layer++) { | |\n| const radius = layer * 4; | |\n| const numPoints = Math.floor(layer * 12 * densityFactor); | |\n| for (let i = 0; i < numPoints; i++) { | |\n| const phi = Math.acos(1 - 2 * (i + 0.5) / numPoints); | |\n| const theta = 2 * Math.PI * i / goldenRatio; | |\n| const pos = new THREE.Vector3( | |\n| radius * Math.sin(phi) * Math.cos(theta), | |\n| radius * Math.sin(phi) * Math.sin(theta), | |\n| radius * Math.cos(phi) | |\n| ); | |\n| const isLeaf = layer === layers || Math.random() < 0.3; | |\n| const node = new Node(pos, layer, isLeaf ? 1 : 0); | |\n| node.distanceFromRoot = radius; | |\n| nodes.push(node); | |\n| if (layer > 1) { | |\n| const prevLayerNodes = nodes.filter(n => n.level === layer - 1 && n !== rootNode); | |\n| prevLayerNodes.sort((a, b) => | |\n| pos.distanceTo(a.position) - pos.distanceTo(b.position) | |\n| ); | |\n| for (let j = 0; j < Math.min(3, prevLayerNodes.length); j++) { | |\n| const dist = pos.distanceTo(prevLayerNodes[j].position); | |\n| const strength = 1.0 - (dist / (radius * 2)); | |\n| node.addConnection(prevLayerNodes[j], Math.max(0.3, strength)); | |\n| } | |\n| } else { | |\n| rootNode.addConnection(node, 0.9); | |\n| } | |\n| } | |\n| const layerNodes = nodes.filter(n => n.level === layer && n !== rootNode); | |\n| for (let i = 0; i < layerNodes.length; i++) { | |\n| const node = layerNodes[i]; | |\n| const nearby = layerNodes.filter(n => n !== node) | |\n| .sort((a, b) => | |\n| node.position.distanceTo(a.position) - node.position.distanceTo(b.position) | |\n| ).slice(0, 5); | |\n| for (const nearNode of nearby) { | |\n| const dist = node.position.distanceTo(nearNode.position); | |\n| if (dist < radius * 0.8 && !node.isConnectedTo(nearNode)) { | |\n| node.addConnection(nearNode, 0.6); | |\n| } | |\n| } | |\n| } | |\n| } | |\n| const outerNodes = nodes.filter(n => n.level >= 3); | |\n| for (let i = 0; i < Math.min(20, outerNodes.length); i++) { | |\n| const n1 = outerNodes[Math.floor(Math.random() * outerNodes.length)]; | |\n| const n2 = outerNodes[Math.floor(Math.random() * outerNodes.length)]; | |\n| if (n1 !== n2 && !n1.isConnectedTo(n2) && | |\n| Math.abs(n1.level - n2.level) > 1) { | |\n| n1.addConnection(n2, 0.4); | |\n| } | |\n| } | |\n| } | |\n| function generateHelixLattice() { | |\n| rootNode = new Node(new THREE.Vector3(0, 0, 0), 0, 0); | |\n| rootNode.size = 1.8; | |\n| nodes.push(rootNode); | |\n| const numHelices = 4; | |\n| const height = 30; | |\n| const maxRadius = 12; | |\n| const nodesPerHelix = Math.floor(50 * densityFactor); | |\n| const helixArrays = []; | |\n| for (let h = 0; h < numHelices; h++) { | |\n| const helixPhase = (h / numHelices) * Math.PI * 2; | |\n| const helixNodes = []; | |\n| for (let i = 0; i < nodesPerHelix; i++) { | |\n| const t = i / (nodesPerHelix - 1); | |\n| const y = (t - 0.5) * height; | |\n| const radiusScale = Math.sin(t * Math.PI) * 0.7 + 0.3; | |\n| const radius = maxRadius * radiusScale; | |\n| const angle = helixPhase + t * Math.PI * 6; | |\n| const pos = new THREE.Vector3( | |\n| radius * Math.cos(angle), | |\n| y, | |\n| radius * Math.sin(angle) | |\n| ); | |\n| const level = Math.ceil(t * 5); | |\n| const isLeaf = i > nodesPerHelix - 5 || Math.random() < 0.25; | |\n| const node = new Node(pos, level, isLeaf ? 1 : 0); | |\n| node.distanceFromRoot = Math.sqrt(radius * radius + y * y); | |\n| node.helixIndex = h; | |\n| node.helixT = t; | |\n| nodes.push(node); | |\n| helixNodes.push(node); | |\n| } | |\n| helixArrays.push(helixNodes); | |\n| rootNode.addConnection(helixNodes[0], 1.0); | |\n| for (let i = 0; i < helixNodes.length - 1; i++) { | |\n| helixNodes[i].addConnection(helixNodes[i + 1], 0.85); | |\n| } | |\n| } | |\n| for (let h = 0; h < numHelices; h++) { | |\n| const currentHelix = helixArrays[h]; | |\n| const nextHelix = helixArrays[(h + 1) % numHelices]; | |\n| for (let i = 0; i < currentHelix.length; i += 5) { | |\n| const t = currentHelix[i].helixT; | |\n| const targetIdx = Math.round(t * (nextHelix.length - 1)); | |\n| if (targetIdx < nextHelix.length) { | |\n| currentHelix[i].addConnection(nextHelix[targetIdx], 0.7); | |\n| } | |\n| } | |\n| } | |\n| for (const helix of helixArrays) { | |\n| for (let i = 0; i < helix.length; i += 8) { | |\n| const node = helix[i]; | |\n| const innerNodes = nodes.filter(n => | |\n| n !== node && | |\n| n !== rootNode && | |\n| n.distanceFromRoot < node.distanceFromRoot * 0.5 | |\n| ); | |\n| if (innerNodes.length > 0) { | |\n| const nearest = innerNodes.sort((a, b) => | |\n| node.position.distanceTo(a.position) - node.position.distanceTo(b.position) | |\n| )[0]; | |\n| node.addConnection(nearest, 0.5); | |\n| } | |\n| } | |\n| } | |\n| const allHelixNodes = nodes.filter(n => n !== rootNode); | |\n| for (let i = 0; i < Math.floor(30 * densityFactor); i++) { | |\n| const n1 = allHelixNodes[Math.floor(Math.random() * allHelixNodes.length)]; | |\n| const nearby = allHelixNodes.filter(n => { | |\n| const dist = n.position.distanceTo(n1.position); | |\n| return n !== n1 && dist < 8 && dist > 3 && !n1.isConnectedTo(n); | |\n| }); | |\n| if (nearby.length > 0) { | |\n| const n2 = nearby[Math.floor(Math.random() * nearby.length)]; | |\n| n1.addConnection(n2, 0.45); | |\n| } | |\n| } | |\n| } | |\n| function generateFractalWeb() { | |\n| rootNode = new Node(new THREE.Vector3(0, 0, 0), 0, 0); | |\n| rootNode.size = 1.6; | |\n| nodes.push(rootNode); | |\n| const branches = 6; | |\n| const maxDepth = 4; | |\n| function createBranch(startNode, direction, depth, strength, scale) { | |\n| if (depth > maxDepth) return; | |\n| const branchLength = 5 * scale; | |\n| const endPos = new THREE.Vector3() | |\n| .copy(startNode.position) | |\n| .add(direction.clone().multiplyScalar(branchLength)); | |\n| const isLeaf = depth === maxDepth || Math.random() < 0.3; | |\n| const newNode = new Node(endPos, depth, isLeaf ? 1 : 0); | |\n| newNode.distanceFromRoot = rootNode.position.distanceTo(endPos); | |\n| nodes.push(newNode); | |\n| startNode.addConnection(newNode, strength); | |\n| if (depth < maxDepth) { | |\n| const subBranches = 3; | |\n| for (let i = 0; i < subBranches; i++) { | |\n| const angle = (i / subBranches) * Math.PI * 2; | |\n| const perpDir1 = new THREE.Vector3(-direction.y, direction.x, 0).normalize(); | |\n| const perpDir2 = direction.clone().cross(perpDir1).normalize(); | |\n| const newDir = new THREE.Vector3() | |\n| .copy(direction) | |\n| .add(perpDir1.clone().multiplyScalar(Math.cos(angle) * 0.7)) | |\n| .add(perpDir2.clone().multiplyScalar(Math.sin(angle) * 0.7)) | |\n| .normalize(); | |\n| createBranch(newNode, newDir, depth + 1, strength * 0.7, scale * 0.75); | |\n| } | |\n| } | |\n| } | |\n| for (let i = 0; i < branches; i++) { | |\n| const phi = Math.acos(1 - 2 * (i + 0.5) / branches); | |\n| const theta = Math.PI * (1 + Math.sqrt(5)) * i; | |\n| const direction = new THREE.Vector3( | |\n| Math.sin(phi) * Math.cos(theta), | |\n| Math.sin(phi) * Math.sin(theta), | |\n| Math.cos(phi) | |\n| ).normalize(); | |\n| createBranch(rootNode, direction, 1, 0.9, 1.0); | |\n| } | |\n| const leafNodes = nodes.filter(n => n.level >= 2); | |\n| for (let i = 0; i < leafNodes.length; i++) { | |\n| const node = leafNodes[i]; | |\n| const nearby = leafNodes.filter(n => { | |\n| const dist = n.position.distanceTo(node.position); | |\n| return n !== node && dist < 10 && !node.isConnectedTo(n); | |\n| }).sort((a, b) => | |\n| node.position.distanceTo(a.position) - node.position.distanceTo(b.position) | |\n| ).slice(0, 3); | |\n| for (const nearNode of nearby) { | |\n| if (Math.random() < 0.5 * densityFactor) { | |\n| node.addConnection(nearNode, 0.5); | |\n| } | |\n| } | |\n| } | |\n| const midLevelNodes = nodes.filter(n => n.level >= 2 && n.level <= 3); | |\n| for (const node of midLevelNodes) { | |\n| if (Math.random() < 0.3) { | |\n| const innerNodes = nodes.filter(n => | |\n| n !== node && | |\n| n.distanceFromRoot < node.distanceFromRoot * 0.6 | |\n| ); | |\n| if (innerNodes.length > 0) { | |\n| const target = innerNodes[Math.floor(Math.random() * innerNodes.length)]; | |\n| if (!node.isConnectedTo(target)) { | |\n| node.addConnection(target, 0.4); | |\n| } | |\n| } | |\n| } | |\n| } | |\n| } | |\n| switch (formationIndex % 3) { | |\n| case 0: generateCrystallineSphere(); break; | |\n| case 1: generateHelixLattice(); break; | |\n| case 2: generateFractalWeb(); break; | |\n| } | |\n| if (densityFactor < 1.0) { | |\n| const targetCount = Math.ceil(nodes.length * Math.max(0.3, densityFactor)); | |\n| const toKeep = new Set([rootNode]); | |\n| const sortedNodes = nodes.filter(n => n !== rootNode) | |\n| .sort((a, b) => { | |\n| const scoreA = a.connections.length * (1 / (a.distanceFromRoot + 1)); | |\n| const scoreB = b.connections.length * (1 / (b.distanceFromRoot + 1)); | |\n| return scoreB - scoreA; | |\n| }); | |\n| for (let i = 0; i < Math.min(targetCount - 1, sortedNodes.length); i++) { | |\n| toKeep.add(sortedNodes[i]); | |\n| } | |\n| nodes = nodes.filter(n => toKeep.has(n)); | |\n| nodes.forEach(node => { | |\n| node.connections = node.connections.filter(conn => toKeep.has(conn.node)); | |\n| }); | |\n| console.log(`Density: ${nodes.length} nodes`); | |\n| } | |\n| return { nodes, rootNode }; | |\n| } | |\n| let neuralNetwork = null; | |\n| let nodesMesh = null; | |\n| let connectionsMesh = null; | |\n| function createNetworkVisualization(formationIndex, densityFactor = 1.0) { | |\n| console.log(`Creating formation ${formationIndex}, density ${densityFactor}`); | |\n| if (nodesMesh) { | |\n| scene.remove(nodesMesh); | |\n| nodesMesh.geometry.dispose(); | |\n| nodesMesh.material.dispose(); | |\n| } | |\n| if (connectionsMesh) { | |\n| scene.remove(connectionsMesh); | |\n| connectionsMesh.geometry.dispose(); | |\n| connectionsMesh.material.dispose(); | |\n| } | |\n| neuralNetwork = generateNeuralNetwork(formationIndex, densityFactor); | |\n| if (!neuralNetwork || neuralNetwork.nodes.length === 0) { | |\n| console.error(\"Network generation failed\"); | |\n| return; | |\n| } | |\n| const nodesGeometry = new THREE.BufferGeometry(); | |\n| const nodePositions = []; | |\n| const nodeTypes = []; | |\n| const nodeSizes = []; | |\n| const nodeColors = []; | |\n| const distancesFromRoot = []; | |\n| const palette = colorPalettes[config.activePaletteIndex]; | |\n| neuralNetwork.nodes.forEach((node) => { | |\n| nodePositions.push(node.position.x, node.position.y, node.position.z); | |\n| nodeTypes.push(node.type); | |\n| nodeSizes.push(node.size); | |\n| distancesFromRoot.push(node.distanceFromRoot); | |\n| const colorIndex = Math.min(node.level, palette.length - 1); | |\n| const baseColor = palette[colorIndex % palette.length].clone(); | |\n| baseColor.offsetHSL( | |\n| THREE.MathUtils.randFloatSpread(0.03), | |\n| THREE.MathUtils.randFloatSpread(0.08), | |\n| THREE.MathUtils.randFloatSpread(0.08) | |\n| ); | |\n| nodeColors.push(baseColor.r, baseColor.g, baseColor.b); | |\n| }); | |\n| nodesGeometry.setAttribute('position', new THREE.Float32BufferAttribute(nodePositions, 3)); | |\n| nodesGeometry.setAttribute('nodeType', new THREE.Float32BufferAttribute(nodeTypes, 1)); | |\n| nodesGeometry.setAttribute('nodeSize', new THREE.Float32BufferAttribute(nodeSizes, 1)); | |\n| nodesGeometry.setAttribute('nodeColor', new THREE.Float32BufferAttribute(nodeColors, 3)); | |\n| nodesGeometry.setAttribute('distanceFromRoot', new THREE.Float32BufferAttribute(distancesFromRoot, 1)); | |\n| const nodesMaterial = new THREE.ShaderMaterial({ | |\n| uniforms: THREE.UniformsUtils.clone(pulseUniforms), | |\n| vertexShader: nodeShader.vertexShader, | |\n| fragmentShader: nodeShader.fragmentShader, | |\n| transparent: true, | |\n| depthWrite: false, | |\n| blending: THREE.AdditiveBlending | |\n| }); | |\n| nodesMesh = new THREE.Points(nodesGeometry, nodesMaterial); | |\n| scene.add(nodesMesh); | |\n| const connectionsGeometry = new THREE.BufferGeometry(); | |\n| const connectionColors = []; | |\n| const connectionStrengths = []; | |\n| const connectionPositions = []; | |\n| const startPoints = []; | |\n| const endPoints = []; | |\n| const pathIndices = []; | |\n| const processedConnections = new Set(); | |\n| let pathIndex = 0; | |\n| neuralNetwork.nodes.forEach((node, nodeIndex) => { | |\n| node.connections.forEach(connection => { | |\n| const connectedNode = connection.node; | |\n| const connectedIndex = neuralNetwork.nodes.indexOf(connectedNode); | |\n| if (connectedIndex === -1) return; | |\n| const key = [Math.min(nodeIndex, connectedIndex), Math.max(nodeIndex, connectedIndex)].join('-'); | |\n| if (!processedConnections.has(key)) { | |\n| processedConnections.add(key); | |\n| const startPoint = node.position; | |\n| const endPoint = connectedNode.position; | |\n| const numSegments = 20; | |\n| for (let i = 0; i < numSegments; i++) { | |\n| const t = i / (numSegments - 1); | |\n| connectionPositions.push(t, 0, 0); | |\n| startPoints.push(startPoint.x, startPoint.y, startPoint.z); | |\n| endPoints.push(endPoint.x, endPoint.y, endPoint.z); | |\n| pathIndices.push(pathIndex); | |\n| connectionStrengths.push(connection.strength); | |\n| const avgLevel = Math.min(Math.floor((node.level + connectedNode.level) / 2), palette.length - 1); | |\n| const baseColor = palette[avgLevel % palette.length].clone(); | |\n| baseColor.offsetHSL( | |\n| THREE.MathUtils.randFloatSpread(0.03), | |\n| THREE.MathUtils.randFloatSpread(0.08), | |\n| THREE.MathUtils.randFloatSpread(0.08) | |\n| ); | |\n| connectionColors.push(baseColor.r, baseColor.g, baseColor.b); | |\n| } | |\n| pathIndex++; | |\n| } | |\n| }); | |\n| }); | |\n| connectionsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(connectionPositions, 3)); | |\n| connectionsGeometry.setAttribute('startPoint', new THREE.Float32BufferAttribute(startPoints, 3)); | |\n| connectionsGeometry.setAttribute('endPoint', new THREE.Float32BufferAttribute(endPoints, 3)); | |\n| connectionsGeometry.setAttribute('connectionStrength', new THREE.Float32BufferAttribute(connectionStrengths, 1)); | |\n| connectionsGeometry.setAttribute('connectionColor', new THREE.Float32BufferAttribute(connectionColors, 3)); | |\n| connectionsGeometry.setAttribute('pathIndex', new THREE.Float32BufferAttribute(pathIndices, 1)); | |\n| const connectionsMaterial = new THREE.ShaderMaterial({ | |\n| uniforms: THREE.UniformsUtils.clone(pulseUniforms), | |\n| vertexShader: connectionShader.vertexShader, | |\n| fragmentShader: connectionShader.fragmentShader, | |\n| transparent: true, | |\n| depthWrite: false, | |\n| blending: THREE.AdditiveBlending | |\n| }); | |\n| connectionsMesh = new THREE.LineSegments(connectionsGeometry, connectionsMaterial); | |\n| scene.add(connectionsMesh); | |\n| palette.forEach((color, i) => { | |\n| if (i < 3) { | |\n| connectionsMaterial.uniforms.uPulseColors.value[i].copy(color); | |\n| nodesMaterial.uniforms.uPulseColors.value[i].copy(color); | |\n| } | |\n| }); | |\n| } | |\n| function updateTheme(paletteIndex) { | |\n| config.activePaletteIndex = paletteIndex; | |\n| if (!nodesMesh || !connectionsMesh || !neuralNetwork) return; | |\n| const palette = colorPalettes[paletteIndex]; | |\n| const nodeColorsAttr = nodesMesh.geometry.attributes.nodeColor; | |\n| for (let i = 0; i < nodeColorsAttr.count; i++) { | |\n| const node = neuralNetwork.nodes[i]; | |\n| if (!node) continue; | |\n| const colorIndex = Math.min(node.level, palette.length - 1); | |\n| const baseColor = palette[colorIndex % palette.length].clone(); | |\n| baseColor.offsetHSL( | |\n| THREE.MathUtils.randFloatSpread(0.03), | |\n| THREE.MathUtils.randFloatSpread(0.08), | |\n| THREE.MathUtils.randFloatSpread(0.08) | |\n| ); | |\n| nodeColorsAttr.setXYZ(i, baseColor.r, baseColor.g, baseColor.b); | |\n| } | |\n| nodeColorsAttr.needsUpdate = true; | |\n| const connectionColors = []; | |\n| const processedConnections = new Set(); | |\n| neuralNetwork.nodes.forEach((node, nodeIndex) => { | |\n| node.connections.forEach(connection => { | |\n| const connectedNode = connection.node; | |\n| const connectedIndex = neuralNetwork.nodes.indexOf(connectedNode); | |\n| if (connectedIndex === -1) return; | |\n| const key = [Math.min(nodeIndex, connectedIndex), Math.max(nodeIndex, connectedIndex)].join('-'); | |\n| if (!processedConnections.has(key)) { | |\n| processedConnections.add(key); | |\n| const numSegments = 20; | |\n| for (let i = 0; i < numSegments; i++) { | |\n| const avgLevel = Math.min(Math.floor((node.level + connectedNode.level) / 2), palette.length - 1); | |\n| const baseColor = palette[avgLevel % palette.length].clone(); | |\n| baseColor.offsetHSL( | |\n| THREE.MathUtils.randFloatSpread(0.03), | |\n| THREE.MathUtils.randFloatSpread(0.08), | |\n| THREE.MathUtils.randFloatSpread(0.08) | |\n| ); | |\n| connectionColors.push(baseColor.r, baseColor.g, baseColor.b); | |\n| } | |\n| } | |\n| }); | |\n| }); | |\n| connectionsMesh.geometry.setAttribute('connectionColor', new THREE.Float32BufferAttribute(connectionColors, 3)); | |\n| connectionsMesh.geometry.attributes.connectionColor.needsUpdate = true; | |\n| palette.forEach((color, i) => { | |\n| if (i < 3) { | |\n| nodesMesh.material.uniforms.uPulseColors.value[i].copy(color); | |\n| connectionsMesh.material.uniforms.uPulseColors.value[i].copy(color); | |\n| } | |\n| }); | |\n| } | |\n| const raycaster = new THREE.Raycaster(); | |\n| const pointer = new THREE.Vector2(); | |\n| const interactionPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0); | |\n| const interactionPoint = new THREE.Vector3(); | |\n| let lastPulseIndex = 0; | |\n| function triggerPulse(clientX, clientY) { | |\n| pointer.x = (clientX / window.innerWidth) * 2 - 1; | |\n| pointer.y = -(clientY / window.innerHeight) * 2 + 1; | |\n| raycaster.setFromCamera(pointer, camera); | |\n| interactionPlane.normal.copy(camera.position).normalize(); | |\n| interactionPlane.constant = -interactionPlane.normal.dot(camera.position) + camera.position.length() * 0.5; | |\n| if (raycaster.ray.intersectPlane(interactionPlane, interactionPoint)) { | |\n| const time = clock.getElapsedTime(); | |\n| if (nodesMesh && connectionsMesh) { | |\n| lastPulseIndex = (lastPulseIndex + 1) % 3; | |\n| nodesMesh.material.uniforms.uPulsePositions.value[lastPulseIndex].copy(interactionPoint); | |\n| nodesMesh.material.uniforms.uPulseTimes.value[lastPulseIndex] = time; | |\n| connectionsMesh.material.uniforms.uPulsePositions.value[lastPulseIndex].copy(interactionPoint); | |\n| connectionsMesh.material.uniforms.uPulseTimes.value[lastPulseIndex] = time; | |\n| const palette = colorPalettes[config.activePaletteIndex]; | |\n| const randomColor = palette[Math.floor(Math.random() * palette.length)]; | |\n| nodesMesh.material.uniforms.uPulseColors.value[lastPulseIndex].copy(randomColor); | |\n| connectionsMesh.material.uniforms.uPulseColors.value[lastPulseIndex].copy(randomColor); | |\n| } | |\n| } | |\n| } | |\n| renderer.domElement.addEventListener('click', (e) => { | |\n| if (e.target.closest('.glass-panel, #control-buttons')) return; | |\n| if (!config.paused) triggerPulse(e.clientX, e.clientY); | |\n| }); | |\n| renderer.domElement.addEventListener('touchstart', (e) => { | |\n| if (e.target.closest('.glass-panel, #control-buttons')) return; | |\n| e.preventDefault(); | |\n| if (e.touches.length > 0 && !config.paused) { | |\n| triggerPulse(e.touches[0].clientX, e.touches[0].clientY); | |\n| } | |\n| }, { passive: false }); | |\n| const themeButtons = document.querySelectorAll('.theme-button'); | |\n| themeButtons.forEach(btn => { | |\n| btn.addEventListener('click', (e) => { | |\n| e.stopPropagation(); | |\n| const idx = parseInt(btn.dataset.theme, 10); | |\n| updateTheme(idx); | |\n| themeButtons.forEach(b => b.classList.remove('active')); | |\n| btn.classList.add('active'); | |\n| }); | |\n| }); | |\n| const densitySlider = document.getElementById('density-slider'); | |\n| const densityValue = document.getElementById('density-value'); | |\n| let densityTimeout; | |\n| densitySlider.addEventListener('input', (e) => { | |\n| e.stopPropagation(); | |\n| const val = parseInt(densitySlider.value, 10); | |\n| config.densityFactor = val / 100; | |\n| densityValue.textContent = `${val}%`; | |\n| clearTimeout(densityTimeout); | |\n| densityTimeout = setTimeout(() => { | |\n| createNetworkVisualization(config.currentFormation, config.densityFactor); | |\n| }, 400); | |\n| }); | |\n| const changeFormationBtn = document.getElementById('change-formation-btn'); | |\n| const pausePlayBtn = document.getElementById('pause-play-btn'); | |\n| const resetCameraBtn = document.getElementById('reset-camera-btn'); | |\n| changeFormationBtn.addEventListener('click', (e) => { | |\n| e.stopPropagation(); | |\n| config.currentFormation = (config.currentFormation + 1) % config.numFormations; | |\n| createNetworkVisualization(config.currentFormation, config.densityFactor); | |\n| controls.autoRotate = false; | |\n| setTimeout(() => { controls.autoRotate = true; }, 2500); | |\n| }); | |\n| pausePlayBtn.addEventListener('click', (e) => { | |\n| e.stopPropagation(); | |\n| config.paused = !config.paused; | |\n| pausePlayBtn.querySelector('span').textContent = config.paused ? 'Play' : 'Freeze'; | |\n| controls.autoRotate = !config.paused; | |\n| }); | |\n| resetCameraBtn.addEventListener('click', (e) => { | |\n| e.stopPropagation(); | |\n| controls.reset(); | |\n| controls.autoRotate = false; | |\n| setTimeout(() => { controls.autoRotate = true; }, 2000); | |\n| }); | |\n| const clock = new THREE.Clock(); | |\n| function animate() { | |\n| requestAnimationFrame(animate); | |\n| const t = clock.getElapsedTime(); | |\n| if (!config.paused) { | |\n| if (nodesMesh) { | |\n| nodesMesh.material.uniforms.uTime.value = t; | |\n| nodesMesh.rotation.y = Math.sin(t * 0.04) * 0.05; | |\n| } | |\n| if (connectionsMesh) { | |\n| connectionsMesh.material.uniforms.uTime.value = t; | |\n| connectionsMesh.rotation.y = Math.sin(t * 0.04) * 0.05; | |\n| } | |\n| } | |\n| starField.rotation.y += 0.0002; | |\n| starField.material.uniforms.uTime.value = t; | |\n| controls.update(); | |\n| composer.render(); | |\n| } | |\n| function init() { | |\n| createNetworkVisualization(config.currentFormation, config.densityFactor); | |\n| document.querySelectorAll('.theme-button').forEach(b => b.classList.remove('active')); | |\n| document.querySelector(`.theme-button[data-theme=\"${config.activePaletteIndex}\"]`).classList.add('active'); | |\n| animate(); | |\n| } | |\n| function onWindowResize() { | |\n| camera.aspect = window.innerWidth / window.innerHeight; | |\n| camera.updateProjectionMatrix(); | |\n| renderer.setSize(window.innerWidth, window.innerHeight); | |\n| composer.setSize(window.innerWidth, window.innerHeight); | |\n| bloomPass.resolution.set(window.innerWidth, window.innerHeight); | |\n| } | |\n| window.addEventListener('resize', onWindowResize); | |\n| init(); | |\n| </script> |", "url": "https://wpnews.pro/news/3d-quantum-neural-network", "canonical_source": "https://gist.github.com/kazzohikaru/a302a39e5f37dacc0dfbb9b1d5bd70bb", "published_at": "2026-05-21 19:01:53+00:00", "updated_at": "2026-05-21 19:08:58.021752+00:00", "lang": "en", "topics": ["artificial-intelligence", "machine-learning", "research"], "entities": ["Three.js", "GLSL", "CodePen", "Techartist"], "alternates": {"html": "https://wpnews.pro/news/3d-quantum-neural-network", "markdown": "https://wpnews.pro/news/3d-quantum-neural-network.md", "text": "https://wpnews.pro/news/3d-quantum-neural-network.txt", "jsonld": "https://wpnews.pro/news/3d-quantum-neural-network.jsonld"}}