Three.js 交互式神经网络可视化

2025-05-30 0 593

使用 Three.js 和 JavaScript 构建的动态 3D 网络。单击/点击通过自定义 GLSL 着色器发送动画能量脉冲,并在节点/连接扩展时使其变亮。包括主题和密度控制。

Three.js 交互式神经网络可视化
实现代码
    <link rel="preconnect" href="https://fonts.googleapis.com">    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
    <style>        * {            margin0;            padding0;            box-sizing: border-box;        }
        canvas {            display: block;            width100%;            height100%;            cursor: pointer;            position: absolute;            top0;            left0;            z-index1;        }
        .ui-panel {            position: absolute;            backdrop-filterblur(10px);            -webkit-backdrop-filterblur(10px);            backgroundrgba(000, .7);            border-radius12px;            border1px solid rgba(25512050, .3);            box-shadow0 4px 20px rgba(000, .5);            z-index10;            padding15px;            color#eee;            font-family'Inter', sans-serif;        }
        #instructions-container {            top20px;            left20px;            font-size14px;            line-height1.5;            max-width280px;        }
        #instruction-title {            font-weight600;            margin-bottom6px;            font-size15px;        }
        #theme-selector {            top20px;            right20px;            display: flex;            flex-direction: column;            gap12px;            max-width150px;        }
        #theme-selector-title {            font-weight600;            font-size15px;            margin-bottom2px;        }
        .theme-grid {            display: grid;            grid-template-columnsrepeat(21fr);            gap10px;        }
        .theme-button {            width36px;            height36px;            border-radius8px;            border2px solid rgba(255255255, .3);            cursor: pointer;            transition: transform .2s, border-color .2s;            outline: none;            overflow: hidden;        }
        .theme-button:hover.theme-button:focus {            transformscale(1.05);            border-colorrgba(255255255, .7);        }
        .theme-button.active {            transformscale(1.05);            border-colorrgba(255255255, .9);            box-shadow0 0 10px rgba(255200150, .6);        }
        #theme-1 { backgroundlinear-gradient(45deg#4F46E5#7C3AED#C026D3#DB2777); }        #theme-2 { backgroundlinear-gradient(45deg#F59E0B#F97316#DC2626#7F1D1D); }        #theme-3 { backgroundlinear-gradient(45deg#EC4899#8B5CF6#6366F1#3B82F6); }        #theme-4 { backgroundlinear-gradient(45deg#10B981#A3E635#FACC15#FB923C); }
        #density-controls {            margin-top8px;            display: flex;            flex-direction: column;            gap8px;        }
        .density-label {            font-size13px;            display: flex;            justify-content: space-between;        }
        .density-slider {            width100%;            appearance: none;            height4px;            border-radius2px;            backgroundrgba(25512050, .3);            outline: none;            cursor: pointer;        }
        .density-slider::-webkit-slider-thumb {            appearance: none;            width14px;            height14px;            border-radius50%;            backgroundrgba(25512050, .8);            cursor: pointer;            transition: transform .1s, background .1s;        }        .density-slider::-moz-range-thumb {             width14px;            height14px;            border-radius50%;            backgroundrgba(25512050, .8);            cursor: pointer;            border: none;            transition: transform .1s, background .1s;        }
        .density-slider::-webkit-slider-thumb:hover { transformscale(1.1); backgroundrgba(255140501); }        .density-slider::-moz-range-thumb:hover { transformscale(1.1); backgroundrgba(255140501); }
        #control-buttons {            position: absolute;            bottom20px;            left50%;            transformtranslateX(-50%);            display: flex;            gap15px;            z-index10;            backgroundrgba(000, .6);            padding10px 15px;            border-radius10px;             border1px solid rgba(25512050, .2);        }
        .control-button {            backgroundrgba(25512050, .2);            color#eee;            border1px solid rgba(25515050, .3);            padding8px 15px;            border-radius6px;            cursor: pointer;            font-size14px;            font-weight600;            transition: background-color 0.2s, transform 0.1s;            white-space: nowrap;            min-width80px;            text-align: center;            font-family'Inter', sans-serif;        }
        .control-button:hover.control-button:focus {            backgroundrgba(25512050, .4);            outline: none;        }        .control-button:active {            backgroundrgba(25512050, .6);            transformscale(0.95);        }
        @media (max-width640px) {            #instructions-container {                max-widthcalc(100% - 40px);                font-size13px;                padding10px 15px;                top10px;                left10px;            }            #instruction-title {                font-size14px;            }
            #theme-selector {                top: auto;                bottom20px;                right10px;                left: auto;                transform: none;                max-width120px;                padding10px;            }             #theme-selector-title {                font-size14px;            }            .theme-button {                width30px;                height30px;            }            .density-label { font-size12px; }

            #control-buttons {                bottom10px;                gap10px;                padding8px 10px;            }            .control-button {                padding6px 10px;                font-size12px;                min-width65px;            }        }         @media (max-width400px) {             #theme-selector {                flex-direction: column;                align-items: center;                max-width: none;                widthcalc(100% - 20px);                left10px;                right10px;                bottom75px;            }             .theme-grid {                grid-template-columnsrepeat(41fr);                width100%;                justify-items: center;            }             #density-controls {                width80%;                margin-top15px;            }             #control-buttons {                widthcalc(100% - 20px);                justify-content: space-around;            }        }
    </style>
    <div id="instructions-container" class="ui-panel">        <div id="instruction-title">Interactive Neural Network</div>        <div>Click or tap to create energy pulses through the network. Drag to rotate.</div>        </div>
    <div id="theme-selector" class="ui-panel">        <div id="theme-selector-title">Visual Theme</div>        <div class="theme-grid">            <button class="theme-button" id="theme-1" data-theme="0" aria-label="Theme 1"></button>            <button class="theme-button" id="theme-2" data-theme="1" aria-label="Theme 2"></button>            <button class="theme-button" id="theme-3" data-theme="2" aria-label="Theme 3"></button>            <button class="theme-button" id="theme-4" data-theme="3" aria-label="Theme 4"></button>        </div>        <div id="density-controls">            <div class="density-label"><span>Density</span><span id="density-value">100%</span></div>            <input type="range" min="20" max="100" value="100" class="density-slider" id="density-slider" aria-label="Network Density">        </div>    </div>
    <div id="control-buttons">        <button id="change-formation-btn" class="control-button">Formation</button>        <button id="pause-play-btn" class="control-button">Pause</button>        <button id="reset-camera-btn" class="control-button">Reset Cam</button>    </div>
    <canvas id="neural-network-canvas"></canvas>
    <script type="importmap">    {      "imports": {        "three""https://cdn.jsdelivr.net/npm/three@0.162.0/build/three.module.js",        "three/addons/""https://cdn.jsdelivr.net/npm/three@0.162.0/examples/jsm/"      }    }    </script>
    <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';        import { FilmPass } from 'three/addons/postprocessing/FilmPass.js';        import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
        const config = {            pausedfalse,            activePaletteIndex1,            currentFormation0,            numFormations4,            densityFactor1        };
        const colorPalettes = [            [new THREE.Color(0x4F46E5), new THREE.Color(0x7C3AED), new THREE.Color(0xC026D3), new THREE.Color(0xDB2777), new THREE.Color(0x8B5CF6)],            [new THREE.Color(0xF59E0B), new THREE.Color(0xF97316), new THREE.Color(0xDC2626), new THREE.Color(0x7F1D1D), new THREE.Color(0xFBBF24)],            [new THREE.Color(0xEC4899), new THREE.Color(0x8B5CF6), new THREE.Color(0x6366F1), new THREE.Color(0x3B82F6), new THREE.Color(0xA855F7)],            [new THREE.Color(0x10B981), new THREE.Color(0xA3E635), new THREE.Color(0xFACC15), new THREE.Color(0xFB923C), new THREE.Color(0x4ADE80)]        ];
        const scene = new THREE.Scene();        scene.fog = new THREE.FogExp2(0x0000000.0015);
        const camera = new THREE.PerspectiveCamera(60window.innerWidth / window.innerHeight0.11200);        camera.position.set(0522);
        const canvasElement = document.getElementById('neural-network-canvas'); // Get canvas element        const renderer = new THREE.WebGLRenderer({ canvas: canvasElement, antialiastruepowerPreference"high-performance" });        renderer.setSize(window.innerWidthwindow.innerHeight);        renderer.setPixelRatio(Math.min(window.devicePixelRatio2));        renderer.setClearColor(0x000000);        renderer.outputColorSpace = THREE.SRGBColorSpace;
        function createStarfield() {            const count = 5000, pos = [];            for (let i = 0; i < count; i++) {                const r = THREE.MathUtils.randFloat(40120);                const phi = Math.acos(THREE.MathUtils.randFloatSpread(2));                const theta = THREE.MathUtils.randFloat(0Math.PI * 2);                pos.push(                    r * Math.sin(phi) * Math.cos(theta),                    r * Math.sin(phi) * Math.sin(theta),                    r * Math.cos(phi)                );            }            const geo = new THREE.BufferGeometry();            geo.setAttribute('position'new THREE.Float32BufferAttribute(pos, 3));            const mat = new THREE.PointsMaterial({                color0xffffff,                size0.15,                sizeAttenuationtrue,                depthWritefalse,                opacity0.8,                transparenttrue            });            return new THREE.Points(geo, mat);        }        const starField = createStarfield();        scene.add(starField);
        const controls = new OrbitControls(camera, renderer.domElement);        controls.enableDamping = true;        controls.dampingFactor = 0.05;        controls.rotateSpeed = 0.5;        controls.minDistance = 5;        controls.maxDistance = 100;        controls.autoRotate = true;        controls.autoRotateSpeed = 0.15;        controls.enablePan = false;
        const composer = new EffectComposer(renderer);        composer.addPass(new RenderPass(scene, camera));
        const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidthwindow.innerHeight), 1.50.40.68);        composer.addPass(bloomPass);
        const filmPass = new FilmPass(0.350.552048false);        composer.addPass(filmPass);
        composer.addPass(new OutputPass());
        const pulseUniforms = {            uTime: { value0.0 },            uPulsePositions: { value: [new THREE.Vector3(1e31e31e3), new THREE.Vector3(1e31e31e3), new THREE.Vector3(1e31e31e3)] },            uPulseTimes: { value: [-1e3, -1e3, -1e3] },            uPulseColors: { value: [new THREE.Color(111), new THREE.Color(111), new THREE.Color(111)] },            uPulseSpeed: { value15.0 },            uBaseNodeSize: { value0.5 },            uActivePalette: { value0 }        };
        const noiseFunctions = `        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;return 42.0*dot(m*m,vec4(dot(p0,x0),dot(p1,x1),dot(p2,x2),dot(p3,x3)));        }        float fbm(vec3 p,float time){            float value=0.0;float amplitude=0.5;float frequency=1.0;int octaves=3;            for(int i=0;i<octaves;i++){                value+=amplitude*snoise(p*frequency+time*0.2*frequency);                amplitude*=0.5;frequency*=2.0;            }            return value;        }`;
        const nodeShader = {            vertexShader`${noiseFunctions}            attribute float nodeSize;attribute float nodeType;attribute vec3 nodeColor;attribute vec3 connectionIndices;attribute float distanceFromRoot;            uniform float uTime;uniform vec3 uPulsePositions[3];uniform float uPulseTimes[3];uniform float uPulseSpeed;uniform float uBaseNodeSize;            varying vec3 vColor;varying float vNodeType;varying vec3 vPosition;varying float vPulseIntensity;varying float vDistanceFromRoot;
            float getPulseIntensity(vec3 worldPos, vec3 pulsePos, float pulseTime) {                if (pulseTime < 0.0) return 0.0;                float timeSinceClick = uTime - pulseTime;                if (timeSinceClick < 0.0 || timeSinceClick > 3.0) return 0.0;
                float pulseRadius = timeSinceClick * uPulseSpeed;                float distToClick = distance(worldPos, pulsePos);                float pulseThickness = 2.0;                float waveProximity = abs(distToClick - pulseRadius);
                return smoothstep(pulseThickness, 0.0, waveProximity) * smoothstep(3.0, 0.0, timeSinceClick);            }
            void main() {                vNodeType = nodeType;                vColor = nodeColor;                vDistanceFromRoot = distanceFromRoot;
                vec3 worldPos = (modelMatrix * vec4(position, 1.0)).xyz;                vPosition = worldPos;
                float totalPulseIntensity = 0.0;                for (int i = 0; i < 3; i++) {                    totalPulseIntensity += getPulseIntensity(worldPos, uPulsePositions[i], uPulseTimes[i]);                }                vPulseIntensity = min(totalPulseIntensity, 1.0);
                float timeScale = 0.5 + 0.5 * sin(uTime * 0.8 + distanceFromRoot * 0.2);                float baseSize = nodeSize * (0.8 + 0.2 * timeScale);                float pulseSize = baseSize * (1.0 + vPulseIntensity * 2.0);
                vec3 modifiedPosition = position;                if (nodeType > 0.5) {                    float noise = fbm(position * 0.1, uTime * 0.1);                    modifiedPosition += normal * noise * 0.2;                }
                vec4 mvPosition = modelViewMatrix * vec4(modifiedPosition, 1.0);                gl_PointSize = pulseSize * uBaseNodeSize * (800.0 / -mvPosition.z);                gl_Position = projectionMatrix * mvPosition;            }`,
            fragmentShader`            uniform float uTime;uniform vec3 uPulseColors[3];uniform int uActivePalette;            varying vec3 vColor;varying float vNodeType;varying vec3 vPosition;varying float vPulseIntensity;varying float vDistanceFromRoot;
            void main() {                vec2 center = 2.0 * gl_PointCoord - 1.0;                float dist = length(center);                if (dist > 1.0) discard;
                float glowStrength = 1.0 - smoothstep(0.0, 1.0, dist);                glowStrength = pow(glowStrength, 1.4);
                vec3 baseColor = vColor * (0.8 + 0.2 * sin(uTime * 0.5 + vDistanceFromRoot * 0.3));                vec3 finalColor = baseColor;
                if (vPulseIntensity > 0.0) {                    vec3 pulseColor = mix(vec3(1.0), uPulseColors[0], 0.3);                    finalColor = mix(baseColor, pulseColor, vPulseIntensity);                    finalColor *= (1.0 + vPulseIntensity * 0.7);                }
                float alpha = glowStrength * (0.9 - 0.5 * dist);
                float camDistance = length(vPosition - cameraPosition);                float distanceFade = smoothstep(80.0, 10.0, camDistance);
                if (vNodeType > 0.5) {                    alpha *= 0.85;                } else {                    finalColor *= 1.2;                }
                gl_FragColor = vec4(finalColor, alpha * distanceFade);            }`        };
        const connectionShader = {            vertexShader`${noiseFunctions}            attribute vec3 startPoint;attribute vec3 endPoint;attribute float connectionStrength;attribute float pathIndex;attribute vec3 connectionColor;            uniform float uTime;uniform vec3 uPulsePositions[3];uniform float uPulseTimes[3];uniform float uPulseSpeed;            varying vec3 vColor;varying float vConnectionStrength;varying float vPulseIntensity;varying float vPathPosition;
            float getPulseIntensity(vec3 worldPos, vec3 pulsePos, float pulseTime) {                if (pulseTime < 0.0) return 0.0;                float timeSinceClick = uTime - pulseTime;                if (timeSinceClick < 0.0 || timeSinceClick > 3.0) return 0.0;                float pulseRadius = timeSinceClick * uPulseSpeed;                float distToClick = distance(worldPos, pulsePos);                float pulseThickness = 2.0;                float waveProximity = abs(distToClick - pulseRadius);                return smoothstep(pulseThickness, 0.0, waveProximity) * smoothstep(3.0, 0.0, timeSinceClick);            }
            void main() {                float t = position.x;                vPathPosition = t;
                vec3 midPoint = mix(startPoint, endPoint, 0.5);                float pathOffset = sin(t * 3.14159) * 0.1;                vec3 perpendicular = normalize(cross(normalize(endPoint - startPoint), vec3(0.0, 1.0, 0.0)));                if (length(perpendicular) < 0.1) perpendicular = vec3(1.0, 0.0, 0.0);                midPoint += perpendicular * pathOffset;
                vec3 p0 = mix(startPoint, midPoint, t);                vec3 p1 = mix(midPoint, endPoint, t);                vec3 finalPos = mix(p0, p1, t);
                float noiseTime = uTime * 0.2;                float noise = fbm(vec3(pathIndex * 0.1, t * 0.5, noiseTime), noiseTime);                finalPos += perpendicular * noise * 0.1;
                vec3 worldPos = (modelMatrix * vec4(finalPos, 1.0)).xyz;
                float totalPulseIntensity = 0.0;                for (int i = 0; i < 3; i++) {                    totalPulseIntensity += getPulseIntensity(worldPos, uPulsePositions[i], uPulseTimes[i]);                }                vPulseIntensity = min(totalPulseIntensity, 1.0);
                vColor = connectionColor;                vConnectionStrength = connectionStrength;
                gl_Position = projectionMatrix * modelViewMatrix * vec4(finalPos, 1.0);            }`,
            fragmentShader`            uniform float uTime;uniform vec3 uPulseColors[3];            varying vec3 vColor;varying float vConnectionStrength;varying float vPulseIntensity;varying float vPathPosition;
            void main() {                vec3 baseColor = vColor * (0.7 + 0.3 * sin(uTime * 0.5 + vPathPosition * 10.0));
                float flowPattern = sin(vPathPosition * 20.0 - uTime * 3.0) * 0.5 + 0.5;                float flowIntensity = 0.3 * flowPattern * vConnectionStrength;
                vec3 finalColor = baseColor;
                if (vPulseIntensity > 0.0) {                    vec3 pulseColor = mix(vec3(1.0), uPulseColors[0], 0.3);                    finalColor = mix(baseColor, pulseColor, vPulseIntensity);                    flowIntensity += vPulseIntensity * 0.5;                }
                finalColor *= (0.6 + flowIntensity + vConnectionStrength * 0.4);
                float alpha = 0.8 * vConnectionStrength + 0.2 * flowPattern;                alpha = mix(alpha, min(1.0, alpha * 2.0), vPulseIntensity);
                gl_FragColor = vec4(finalColor, alpha);            }`        };
        class Node {            constructor(position, level = 0, type = 0) {                this.position = position;                this.connections = [];                this.level = level;                this.type = type;                this.size = type === 0 ? THREE.MathUtils.randFloat(0.71.2) : THREE.MathUtils.randFloat(0.40.9);                this.distanceFromRoot = 0;            }
            addConnection(node, strength = 1.0) {                if (!this.isConnectedTo(node)) {                    this.connections.push({ node, strength });                    node.connections.push({ nodethis, strength });                }            }
            isConnectedTo(node) {                return this.connections.some(conn => conn.node === node);            }        }
        function generateNeuralNetwork(formationIndex, densityFactor = 1.0) {            let nodes = [];            let rootNode;
            function generateQuantumCortex() {                rootNode = new Node(new THREE.Vector3(000), 00); rootNode.size = 1.5; nodes.push(rootNode);                const layers = 5, primaryAxes = 6, nodesPerAxis = 8, axisLength = 20;                const axisEndpoints = [];
                for (let a = 0; a < primaryAxes; a++) {                    const phi = Math.acos(-1 + (2 * a) / primaryAxes);                    const theta = Math.PI * (1 + Math.sqrt(5)) * a;                    const dirVec = new THREE.Vector3(                        Math.sin(phi) * Math.cos(theta),                        Math.sin(phi) * Math.sin(theta),                        Math.cos(phi)                    );
                    let prevNode = rootNode;                    for (let i = 1; i <= nodesPerAxis; i++) {                        const t = i / nodesPerAxis;                        const distance = axisLength * Math.pow(t, 0.8);                        const pos = new THREE.Vector3().copy(dirVec).multiplyScalar(distance);                        const nodeType = (i === nodesPerAxis) ? 1 : 0;                        const newNode = new Node(pos, i, nodeType);                        newNode.distanceFromRoot = distance;                        nodes.push(newNode);                        prevNode.addConnection(newNode, 1.0 - (t * 0.3));                        prevNode = newNode;                        if (i === nodesPerAxis) axisEndpoints.push(newNode);                    }                }
                const ringDistances = [51015];                const ringNodes = [];                for (const ringDist of ringDistances) {                    const nodesInRing = Math.floor(ringDist * 3 * densityFactor);                    const ringLayer = [];                    for (let i = 0; i < nodesInRing; i++) {                        const t = i / nodesInRing;                        const ringPhi = Math.acos(2 * Math.random() - 1);                        const ringTheta = 2 * Math.PI * t;                        const pos = new THREE.Vector3(                            ringDist * Math.sin(ringPhi) * Math.cos(ringTheta),                            ringDist * Math.sin(ringPhi) * Math.sin(ringTheta),                            ringDist * Math.cos(ringPhi)                        );                        const level = Math.ceil(ringDist / 5);                        const nodeType = Math.random() < 0.4 ? 1 : 0;                        const newNode = new Node(pos, level, nodeType);                        newNode.distanceFromRoot = ringDist;                        nodes.push(newNode);                        ringLayer.push(newNode);                    }                    ringNodes.push(ringLayer);
                    for (let i = 0; i < ringLayer.length; i++) {                        const node = ringLayer[i];                        const nextNode = ringLayer[(i + 1) % ringLayer.length];                        node.addConnection(nextNode, 0.7);                        if (i % 4 === 0 && ringLayer.length > 5) {                            const jumpIdx = (i + Math.floor(ringLayer.length / 2)) % ringLayer.length;                            node.addConnection(ringLayer[jumpIdx], 0.4);                        }                    }                }
                for (const ring of ringNodes) {                    for (const node of ring) {                        let closestAxisNode = nulllet minDist = Infinity;                        for (const n of nodes) {                            if (n === rootNode || n === node) continue;                            if (n.level === 0 || n.type !== 0continue;                            const dist = node.position.distanceTo(n.position);                            if (dist < minDist) { minDist = dist; closestAxisNode = n; }                        }                        if (closestAxisNode && minDist < 8) {                            const strength = 0.5 + (1 - minDist / 8) * 0.5;                            node.addConnection(closestAxisNode, strength);                        }                    }                }
                for (let r = 0; r < ringNodes.length - 1; r++) {                    const innerRing = ringNodes[r];                    const outerRing = ringNodes[r + 1];                    const connectionsCount = Math.floor(innerRing.length * 0.5);                    for (let i = 0; i < connectionsCount; i++) {                        const innerNode = innerRing[Math.floor(Math.random() * innerRing.length)];                        const outerNode = outerRing[Math.floor(Math.random() * outerRing.length)];                        if (!innerNode.isConnectedTo(outerNode)) {                            innerNode.addConnection(outerNode, 0.6);                        }                    }                }
                 for (let i = 0; i < axisEndpoints.length; i++) {                    const startNode = axisEndpoints[i];                    const endNode = axisEndpoints[(i + 2) % axisEndpoints.length];                    const numIntermediates = 3;                    let prevNode = startNode;                    for (let j = 1; j <= numIntermediates; j++) {                        const t = j / (numIntermediates + 1);                        const pos = new THREE.Vector3().lerpVectors(startNode.position, endNode.position, t);                        pos.add(new THREE.Vector3(                            THREE.MathUtils.randFloatSpread(3),                            THREE.MathUtils.randFloatSpread(3),                            THREE.MathUtils.randFloatSpread(3)                        ));                        const newNode = new Node(pos, startNode.level0);                        newNode.distanceFromRoot = rootNode.position.distanceTo(pos);                        nodes.push(newNode);                        prevNode.addConnection(newNode, 0.5);                        prevNode = newNode;                    }                    prevNode.addConnection(endNode, 0.5);                }            }
            function generateHyperdimensionalMesh() {                rootNode = new Node(new THREE.Vector3(000), 00); rootNode.size = 1.5; nodes.push(rootNode);                const dimensions = 4;                const nodesPerDimension = Math.floor(40 * densityFactor);                const maxRadius = 20;
                const dimensionVectors = [                    new THREE.Vector3(111).normalize(),                    new THREE.Vector3(-11, -1).normalize(),                    new THREE.Vector3(1, -1, -1).normalize(),                    new THREE.Vector3(-1, -11).normalize()                ];
                const dimensionNodes = [];
                for (let d = 0; d < dimensions; d++) {                    const dimNodes = [];                    const dimVec = dimensionVectors[d];                    for (let i = 0; i < nodesPerDimension; i++) {                        const distance = maxRadius * Math.pow(Math.random(), 0.7);                        const randomVec = new THREE.Vector3(                            THREE.MathUtils.randFloatSpread(1),                            THREE.MathUtils.randFloatSpread(1),                            THREE.MathUtils.randFloatSpread(1)                        ).normalize();                        const biasedVec = new THREE.Vector3().addVectors(                            dimVec.clone().multiplyScalar(0.6 + Math.random() * 0.4),                            randomVec.clone().multiplyScalar(0.3)                        ).normalize();
                        const pos = biasedVec.clone().multiplyScalar(distance);                        const isLeaf = Math.random() < 0.4 || distance > maxRadius * 0.8;                        const level = Math.floor(distance / (maxRadius / 4)) + 1;                        const newNode = new Node(pos, level, isLeaf ? 1 : 0);                        newNode.distanceFromRoot = distance;                        newNode.dimension = d;                        nodes.push(newNode);                        dimNodes.push(newNode);                        if (distance < maxRadius * 0.3) rootNode.addConnection(newNode, 0.7);                    }                    dimensionNodes.push(dimNodes);                }
                for (let d = 0; d < dimensions; d++) {                    const dimNodes = dimensionNodes[d];                    dimNodes.sort((a, b) => a.distanceFromRoot - b.distanceFromRoot);
                    const layers = 4;                    const nodesPerLayer = Math.ceil(dimNodes.length / layers);                    for (let layer = 0; layer < layers; layer++) {                        const startIdx = layer * nodesPerLayer;                        const endIdx = Math.min(startIdx + nodesPerLayer, dimNodes.length);                        for (let i = startIdx; i < endIdx; i++) {                            const node = dimNodes[i];                            const connectionsCount = 1 + Math.floor(Math.random() * 3);                            const nearbyNodes = dimNodes.slice(startIdx, endIdx).filter(n => n !== node)                                .sort((a, b) => node.position.distanceTo(a.position) - node.position.distanceTo(b.position));                            for (let j = 0; j < Math.min(connectionsCount, nearbyNodes.length); j++) {                                if (!node.isConnectedTo(nearbyNodes[j])) {                                    node.addConnection(nearbyNodes[j], 0.4 + Math.random() * 0.4);                                }                            }                            if (layer > 0) {                                const prevLayer = dimNodes.slice((layer - 1) * nodesPerLayer, layer * nodesPerLayer)                                    .sort((a, b) => node.position.distanceTo(a.position) - node.position.distanceTo(b.position));                                if (prevLayer.length > 0 && !node.isConnectedTo(prevLayer[0])) {                                    node.addConnection(prevLayer[0], 0.8);                                }                            }                        }                    }                }
                 for (let d1 = 0; d1 < dimensions; d1++) {                    for (let d2 = d1 + 1; d2 < dimensions; d2++) {                        const connectionsCount = Math.floor(5 * densityFactor);                        for (let i = 0; i < connectionsCount; i++) {                            const n1 = dimensionNodes[d1][Math.floor(Math.random() * dimensionNodes[d1].length)];                            const n2 = dimensionNodes[d2][Math.floor(Math.random() * dimensionNodes[d2].length)];                            if (!n1.isConnectedTo(n2)) {                                const midPos = new THREE.Vector3().lerpVectors(n1.position, n2.position0.5);                                midPos.add(new THREE.Vector3(                                    THREE.MathUtils.randFloatSpread(2),                                    THREE.MathUtils.randFloatSpread(2),                                    THREE.MathUtils.randFloatSpread(2)                                ));                                const interNode = new Node(midPos, Math.max(n1.level, n2.level), 0);                                interNode.distanceFromRoot = rootNode.position.distanceTo(midPos);                                nodes.push(interNode);                                n1.addConnection(interNode, 0.5);                                interNode.addConnection(n2, 0.5);                            }                        }                    }                }
                const jumpConnections = Math.floor(10 * densityFactor);                for (let i = 0; i < jumpConnections; i++) {                    const startDim = Math.floor(Math.random() * dimensions);                    const endDim = (startDim + 2) % dimensions;                    const startNode = dimensionNodes[startDim][Math.floor(Math.random() * dimensionNodes[startDim].length)];                    const endNode = dimensionNodes[endDim][Math.floor(Math.random() * dimensionNodes[endDim].length)];                    if (!startNode.isConnectedTo(endNode)) {                        const numPoints = 3 + Math.floor(Math.random() * 3);                        let prevNode = startNode;                        for (let j = 1; j < numPoints; j++) {                            const t = j / numPoints;                            const pos = new THREE.Vector3().lerpVectors(startNode.position, endNode.position, t);                            pos.add(new THREE.Vector3(                                THREE.MathUtils.randFloatSpread(8) * Math.sin(t * Math.PI),                                THREE.MathUtils.randFloatSpread(8) * Math.sin(t * Math.PI),                                THREE.MathUtils.randFloatSpread(8) * Math.sin(t * Math.PI)                            ));                            const jumpNode = new Node(pos, Math.max(startNode.level, endNode.level), 0);                            jumpNode.distanceFromRoot = rootNode.position.distanceTo(pos);                            nodes.push(jumpNode);                            prevNode.addConnection(jumpNode, 0.4);                            prevNode = jumpNode;                        }                        prevNode.addConnection(endNode, 0.4);                    }                }            }
            function generateNeuralVortex() {                rootNode = new Node(new THREE.Vector3(000), 00); rootNode.size = 1.8; nodes.push(rootNode);                const numSpirals = 6;                const totalHeight = 30;                const maxRadius = 16;                const nodesPerSpiral = Math.floor(30 * densityFactor);                const spiralNodes = [];
                for (let s = 0; s < numSpirals; s++) {                    const spiralPhase = (s / numSpirals) * Math.PI * 2;                    const spiralArray = [];                    for (let i = 0; i < nodesPerSpiral; i++) {                        const t = i / (nodesPerSpiral - 1);
                        const heightCurve = 1 - Math.pow(2 * t - 12);                        const height = (t - 0.5) * totalHeight;                        const radiusCurve = Math.sin(t * Math.PI);                        const radius = maxRadius * radiusCurve;
                        const revolutions = 2.5;                        const angle = spiralPhase + t * Math.PI * 2 * revolutions;
                        const pos = new THREE.Vector3(radius * Math.cos(angle), height, radius * Math.sin(angle));                        pos.add(new THREE.Vector3(                            THREE.MathUtils.randFloatSpread(1.5),                            THREE.MathUtils.randFloatSpread(1.5),                            THREE.MathUtils.randFloatSpread(1.5)                        ));
                        const level = Math.floor(t * 5) + 1;                        const isLeaf = Math.random() < 0.3 || i > nodesPerSpiral - 3;                        const newNode = new Node(pos, level, isLeaf ? 1 : 0);                        newNode.distanceFromRoot = Math.sqrt(radius * radius + height * height);                        newNode.spiralIndex = s;                        newNode.spiralPosition = t;                        nodes.push(newNode);                        spiralArray.push(newNode);                    }                    spiralNodes.push(spiralArray);                }
                for (const spiral of spiralNodes) {                    rootNode.addConnection(spiral[0], 1.0);                    for (let i = 0; i < spiral.length - 1; i++) {                        spiral[i].addConnection(spiral[i + 1], 0.9);                    }                }
                for (let s = 0; s < numSpirals; s++) {                    const currentSpiral = spiralNodes[s];                    const nextSpiral = spiralNodes[(s + 1) % numSpirals];                    const connectionPoints = 5;                    for (let c = 0; c < connectionPoints; c++) {                        const t = c / (connectionPoints - 1);                        const idx1 = Math.floor(t * (currentSpiral.length - 1));                        const idx2 = Math.floor(t * (nextSpiral.length - 1));                        currentSpiral[idx1].addConnection(nextSpiral[idx2], 0.7);                    }                }
                for (let s = 0; s < numSpirals; s++) {                    const currentSpiral = spiralNodes[s];                    const jumpSpiral = spiralNodes[(s + 2) % numSpirals];                    const connections = 3;                    for (let c = 0; c < connections; c++) {                        const t1 = (c + 0.5) / connections;                        const t2 = (c + 1.0) / connections;                        const idx1 = Math.floor(t1 * (currentSpiral.length - 1));                        const idx2 = Math.floor(t2 * (jumpSpiral.length - 1));                        const start = currentSpiral[idx1];                        const end = jumpSpiral[idx2];
                        const midPoint = new THREE.Vector3().lerpVectors(start.position, end.position0.5).multiplyScalar(0.7);                        const bridgeNode = new Node(midPoint, Math.max(start.level, end.level), 0);                        bridgeNode.distanceFromRoot = rootNode.position.distanceTo(midPoint);                        nodes.push(bridgeNode);                        start.addConnection(bridgeNode, 0.6);                        bridgeNode.addConnection(end, 0.6);                    }                }
                const ringLevels = 5;                for (let r = 0; r < ringLevels; r++) {                    const height = (r / (ringLevels - 1) - 0.5) * totalHeight * 0.7;                    const ringNodes = nodes.filter(n => n !== rootNode && Math.abs(n.position.y - height) < 2);                    ringNodes.sort((a, b) => Math.atan2(a.position.z, a.position.x) - Math.atan2(b.position.z, b.position.x));                    if (ringNodes.length > 3) {                        for (let i = 0; i < ringNodes.length; i++) {                            ringNodes[i].addConnection(ringNodes[(i + 1) % ringNodes.length], 0.5);                        }                    }                }
                const radialConnections = Math.floor(10 * densityFactor);                const candidates = nodes.filter(n => n !== rootNode && n.position.length() > 5)                                        .sort(() => Math.random() - 0.5)                                        .slice(0, radialConnections);                for (const node of candidates) {                    const numSegments = 1 + Math.floor(Math.random() * 2);                    let prevNode = node;                    for (let i = 1; i <= numSegments; i++) {                        const t = i / (numSegments + 1);                        const segPos = node.position.clone().multiplyScalar(1 - t);                        segPos.add(new THREE.Vector3(                            THREE.MathUtils.randFloatSpread(2),                            THREE.MathUtils.randFloatSpread(2),                            THREE.MathUtils.randFloatSpread(2)                        ));                        const newNode = new Node(segPos, Math.floor(node.level * (1 - t)), 0);                        newNode.distanceFromRoot = rootNode.position.distanceTo(segPos);                        nodes.push(newNode);                        prevNode.addConnection(newNode, 0.7);                        prevNode = newNode;                    }                    prevNode.addConnection(rootNode, 0.8);                }            }
            function generateSynapticCloud() {                rootNode = new Node(new THREE.Vector3(000), 00); rootNode.size = 1.5; nodes.push(rootNode);                const numClusters = 6;                const maxDist = 18;                const clusterNodes = [];
                for (let c = 0; c < numClusters; c++) {                    const phi = Math.acos(2 * Math.random() - 1);                    const theta = 2 * Math.PI * Math.random();                    const distance = maxDist * (0.3 + 0.7 * Math.random());                    const pos = new THREE.Vector3(                        distance * Math.sin(phi) * Math.cos(theta),                        distance * Math.sin(phi) * Math.sin(theta),                        distance * Math.cos(phi)                    );                    const clusterNode = new Node(pos, 10);                    clusterNode.size = 1.2;                    clusterNode.distanceFromRoot = distance;                    nodes.push(clusterNode);                    clusterNodes.push(clusterNode);                    rootNode.addConnection(clusterNode, 0.9);                }
                for (let i = 0; i < clusterNodes.length; i++) {                    for (let j = i + 1; j < clusterNodes.length; j++) {                        const dist = clusterNodes[i].position.distanceTo(clusterNodes[j].position);                        const probability = 1.0 - (dist / (maxDist * 2));                        if (Math.random() < probability) {                            const strength = 0.5 + 0.5 * (1 - dist / (maxDist * 2));                            clusterNodes[i].addConnection(clusterNodes[j], strength);                        }                    }                }
                for (const cluster of clusterNodes) {                    const clusterSize = Math.floor(20 * densityFactor);                    const cloudRadius = 7 + Math.random() * 3;                    for (let i = 0; i < clusterSize; i++) {                        const radius = cloudRadius * Math.pow(Math.random(), 0.5);                        const dir = new THREE.Vector3(                            THREE.MathUtils.randFloatSpread(2),                            THREE.MathUtils.randFloatSpread(2),                            THREE.MathUtils.randFloatSpread(2)                        ).normalize();                        const pos = new THREE.Vector3().copy(cluster.position).add(dir.multiplyScalar(radius));
                        const distanceFromCluster = radius;                        const distanceFromRoot = rootNode.position.distanceTo(pos);                        const level = 2 + Math.floor(distanceFromCluster / 3);                        const isLeaf = Math.random() < 0.5;                        const newNode = new Node(pos, level, isLeaf ? 1 : 0);                        newNode.distanceFromRoot = distanceFromRoot;                        newNode.clusterRef = cluster;                        nodes.push(newNode);
                        const strength = 0.7 * (1 - distanceFromCluster / cloudRadius);                        cluster.addConnection(newNode, strength);
                        const nearbyNodes = nodes.filter(n =>                            n !== newNode && n !== cluster && n.clusterRef === cluster &&                            n.position.distanceTo(pos) < cloudRadius * 0.4                        );                        const connectionsCount = Math.floor(Math.random() * 3);                        nearbyNodes.sort((a, b) => pos.distanceTo(a.position) - pos.distanceTo(b.position));                        for (let j = 0; j < Math.min(connectionsCount, nearbyNodes.length); j++) {                            const dist = pos.distanceTo(nearbyNodes[j].position);                            const connStrength = 0.4 * (1 - dist / (cloudRadius * 0.4));                            newNode.addConnection(nearbyNodes[j], connStrength);                        }                    }                }
                 const interClusterCount = Math.floor(15 * densityFactor);                for (let i = 0; i < interClusterCount; i++) {                    const cluster1 = clusterNodes[Math.floor(Math.random() * clusterNodes.length)];                    let cluster2;                    do { cluster2 = clusterNodes[Math.floor(Math.random() * clusterNodes.length)]; } while (cluster2 === cluster1);
                    const bridgePos = new THREE.Vector3().lerpVectors(cluster1.position, cluster2.position0.3 + Math.random() * 0.4);                    bridgePos.add(new THREE.Vector3(                        THREE.MathUtils.randFloatSpread(5),                        THREE.MathUtils.randFloatSpread(5),                        THREE.MathUtils.randFloatSpread(5)                    ));                    const bridgeNode = new Node(bridgePos, 20);                    bridgeNode.distanceFromRoot = rootNode.position.distanceTo(bridgePos);                    nodes.push(bridgeNode);
                    cluster1.addConnection(bridgeNode, 0.5);                    cluster2.addConnection(bridgeNode, 0.5);
                    const nearbyNodes = nodes.filter(n => n !== bridgeNode && n !== cluster1 && n !== cluster2 && n.position.distanceTo(bridgePos) < 8);                    if (nearbyNodes.length > 0) {                        const target = nearbyNodes[Math.floor(Math.random() * nearbyNodes.length)];                        bridgeNode.addConnection(target, 0.4);                    }                }
                 const longRangeCount = Math.floor(10 * densityFactor);                const outerNodes = nodes.filter(n => n.distanceFromRoot > maxDist * 0.6)                                        .sort(() => Math.random() - 0.5)                                        .slice(0, longRangeCount);                for (const outerNode of outerNodes) {                    const numSegments = 2 + Math.floor(Math.random() * 2);                    let prevNode = outerNode;                    for (let i = 1; i <= numSegments; i++) {                        const t = i / (numSegments + 1);                        const segPos = outerNode.position.clone().multiplyScalar(1 - t * 0.8);                        segPos.add(new THREE.Vector3(                            THREE.MathUtils.randFloatSpread(4),                            THREE.MathUtils.randFloatSpread(4),                            THREE.MathUtils.randFloatSpread(4)                        ));                        const newNode = new Node(segPos, outerNode.level0);                        newNode.distanceFromRoot = rootNode.position.distanceTo(segPos);                        nodes.push(newNode);                        prevNode.addConnection(newNode, 0.6);                        prevNode = newNode;                    }                    const innerNodes = nodes.filter(n => n.distanceFromRoot < maxDist * 0.4 && n !== rootNode);                    if (innerNodes.length > 0) {                        const targetNode = innerNodes[Math.floor(Math.random() * innerNodes.length)];                        prevNode.addConnection(targetNode, 0.5);                    }                }            }
            switch (formationIndex % 4) {                case 0generateQuantumCortex(); break;                case 1generateHyperdimensionalMesh(); break;                case 2generateNeuralVortex(); break;                case 3generateSynapticCloud(); break;            }
             if (densityFactor < 1.0) {                const originalNodeCount = nodes.length;                nodes = nodes.filter((node, index) => {                    if (node === rootNode) return true;                    const hash = (index * 31 + Math.floor(densityFactor * 100)) % 100;                    return hash < (densityFactor * 100);                });
                nodes.forEach(node => {                    node.connections = node.connections.filter(conn => nodes.includes(conn.node));                });                console.log(`Density Filter: ${originalNodeCount} -> ${nodes.length} nodes`);            }
            return { nodes, rootNode };        }
        let neuralNetwork = null, nodesMesh = null, connectionsMesh = null;
        function createNetworkVisualization(formationIndex, densityFactor = 1.0) {            console.log(`Creating formation ${formationIndex}, density ${densityFactor}`);            if (nodesMesh) {                scene.remove(nodesMesh);                nodesMesh.geometry.dispose();                nodesMesh.material.dispose();                nodesMesh = null;            }            if (connectionsMesh) {                scene.remove(connectionsMesh);                connectionsMesh.geometry.dispose();                connectionsMesh.material.dispose();                connectionsMesh = null;            }
            neuralNetwork = generateNeuralNetwork(formationIndex, densityFactor);            if (!neuralNetwork || neuralNetwork.nodes.length === 0) {                console.error("Network generation failed or resulted in zero nodes.");                return;            }
            const nodesGeometry = new THREE.BufferGeometry();            const nodePositions = [], nodeTypes = [], nodeSizes = [], nodeColors = [], connectionIndices = [], distancesFromRoot = [];
            neuralNetwork.nodes.forEach((node, index) => {                nodePositions.push(node.position.x, node.position.y, node.position.z);                nodeTypes.push(node.type);                nodeSizes.push(node.size);                distancesFromRoot.push(node.distanceFromRoot);
                const indices = node.connections.slice(03).map(conn => neuralNetwork.nodes.indexOf(conn.node));                while (indices.length < 3) indices.push(-1);                connectionIndices.push(...indices);
                const palette = colorPalettes[config.activePaletteIndex];                const colorIndex = Math.min(node.level, palette.length - 1);                const baseColor = palette[colorIndex % palette.length].clone();                baseColor.offsetHSL(                    THREE.MathUtils.randFloatSpread(0.05),                    THREE.MathUtils.randFloatSpread(0.1),                    THREE.MathUtils.randFloatSpread(0.1)                );                nodeColors.push(baseColor.r, baseColor.g, baseColor.b);            });
            nodesGeometry.setAttribute('position'new THREE.Float32BufferAttribute(nodePositions, 3));            nodesGeometry.setAttribute('nodeType'new THREE.Float32BufferAttribute(nodeTypes, 1));            nodesGeometry.setAttribute('nodeSize'new THREE.Float32BufferAttribute(nodeSizes, 1));            nodesGeometry.setAttribute('nodeColor'new THREE.Float32BufferAttribute(nodeColors, 3));            nodesGeometry.setAttribute('connectionIndices'new THREE.Float32BufferAttribute(connectionIndices, 3));            nodesGeometry.setAttribute('distanceFromRoot'new THREE.Float32BufferAttribute(distancesFromRoot, 1));
            const nodesMaterial = new THREE.ShaderMaterial({                uniformsTHREE.UniformsUtils.clone(pulseUniforms),                vertexShader: nodeShader.vertexShader,                fragmentShader: nodeShader.fragmentShader,                transparenttrue,                depthWritefalse,                blendingTHREE.AdditiveBlending            });
            nodesMesh = new THREE.Points(nodesGeometry, nodesMaterial);            scene.add(nodesMesh);
            const connectionsGeometry = new THREE.BufferGeometry();            const connectionColors = [], connectionStrengths = [], connectionPositions = [], startPoints = [], endPoints = [], pathIndices = [];            const processedConnections = new Set();            let pathIndex = 0;
            neuralNetwork.nodes.forEach((node, nodeIndex) => {                node.connections.forEach(connection => {                    const connectedNode = connection.node;                    const connectedIndex = neuralNetwork.nodes.indexOf(connectedNode);                    if (connectedIndex === -1return;
                    const key = [Math.min(nodeIndex, connectedIndex), Math.max(nodeIndex, connectedIndex)].join('-');                    if (!processedConnections.has(key)) {                        processedConnections.add(key);
                        const startPoint = node.position;                        const endPoint = connectedNode.position;                        const numSegments = 15;
                        for (let i = 0; i < numSegments; i++) {                            const t = i / (numSegments - 1);                            connectionPositions.push(t, 00);                            startPoints.push(startPoint.x, startPoint.y, startPoint.z);                            endPoints.push(endPoint.x, endPoint.y, endPoint.z);                            pathIndices.push(pathIndex);                            connectionStrengths.push(connection.strength);
                            const palette = colorPalettes[config.activePaletteIndex];                            const avgLevel = Math.min(Math.floor((node.level + connectedNode.level) / 2), palette.length - 1);                            const baseColor = palette[avgLevel % palette.length].clone();                            baseColor.offsetHSL(                                THREE.MathUtils.randFloatSpread(0.05),                                THREE.MathUtils.randFloatSpread(0.1),                                THREE.MathUtils.randFloatSpread(0.1)                            );                            connectionColors.push(baseColor.r, baseColor.g, baseColor.b);                        }                        pathIndex++;                    }                });            });
            connectionsGeometry.setAttribute('position'new THREE.Float32BufferAttribute(connectionPositions, 3));            connectionsGeometry.setAttribute('startPoint'new THREE.Float32BufferAttribute(startPoints, 3));            connectionsGeometry.setAttribute('endPoint'new THREE.Float32BufferAttribute(endPoints, 3));            connectionsGeometry.setAttribute('connectionStrength'new THREE.Float32BufferAttribute(connectionStrengths, 1));            connectionsGeometry.setAttribute('connectionColor'new THREE.Float32BufferAttribute(connectionColors, 3));            connectionsGeometry.setAttribute('pathIndex'new THREE.Float32BufferAttribute(pathIndices, 1));
            const connectionsMaterial = new THREE.ShaderMaterial({                uniformsTHREE.UniformsUtils.clone(pulseUniforms),                vertexShader: connectionShader.vertexShader,                fragmentShader: connectionShader.fragmentShader,                transparenttrue,                depthWritefalse,                blendingTHREE.AdditiveBlending            });
            connectionsMesh = new THREE.LineSegments(connectionsGeometry, connectionsMaterial);            scene.add(connectionsMesh);
            const palette = colorPalettes[config.activePaletteIndex];            connectionsMaterial.uniforms.uPulseColors.value[0].copy(palette[0]);            connectionsMaterial.uniforms.uPulseColors.value[1].copy(palette[1]);            connectionsMaterial.uniforms.uPulseColors.value[2].copy(palette[2]);            nodesMaterial.uniforms.uPulseColors.value[0].copy(palette[0]);            nodesMaterial.uniforms.uPulseColors.value[1].copy(palette[1]);            nodesMaterial.uniforms.uPulseColors.value[2].copy(palette[2]);            nodesMaterial.uniforms.uActivePalette.value = config.activePaletteIndex;        }
        function updateTheme(paletteIndex) {            config.activePaletteIndex = paletteIndex;            if (!nodesMesh || !connectionsMesh) return;
            const palette = colorPalettes[paletteIndex];
            const nodeColorsAttr = nodesMesh.geometry.attributes.nodeColor;            const nodeLevels = neuralNetwork.nodes.map(n => n.level);
            for (let i = 0; i < nodeColorsAttr.count; i++) {                const node = neuralNetwork.nodes[i];                if (!node) continue;
                const colorIndex = Math.min(node.level, palette.length - 1);                const baseColor = palette[colorIndex % palette.length].clone();                baseColor.offsetHSL(                    THREE.MathUtils.randFloatSpread(0.05),                    THREE.MathUtils.randFloatSpread(0.1),                    THREE.MathUtils.randFloatSpread(0.1)                );                nodeColorsAttr.setXYZ(i, baseColor.r, baseColor.g, baseColor.b);            }            nodeColorsAttr.needsUpdate = true;
            const connectionColors = [];            const processedConnections = new Set();            neuralNetwork.nodes.forEach((node, nodeIndex) => {                 node.connections.forEach(connection => {                    const connectedNode = connection.node;                    const connectedIndex = neuralNetwork.nodes.indexOf(connectedNode);                    if (connectedIndex === -1return;
                    const key = [Math.min(nodeIndex, connectedIndex), Math.max(nodeIndex, connectedIndex)].join('-');                    if (!processedConnections.has(key)) {                        processedConnections.add(key);                        const numSegments = 15;                        for (let i = 0; i < numSegments; i++) {                             const avgLevel = Math.min(Math.floor((node.level + connectedNode.level) / 2), palette.length - 1);                             const baseColor = palette[avgLevel % palette.length].clone();                             baseColor.offsetHSL(                                 THREE.MathUtils.randFloatSpread(0.05),                                 THREE.MathUtils.randFloatSpread(0.1),                                 THREE.MathUtils.randFloatSpread(0.1)                             );                             connectionColors.push(baseColor.r, baseColor.g, baseColor.b);                        }                    }                 });            });            connectionsMesh.geometry.setAttribute('connectionColor'new THREE.Float32BufferAttribute(connectionColors, 3));            connectionsMesh.geometry.attributes.connectionColor.needsUpdate = true;
            nodesMesh.material.uniforms.uPulseColors.value.forEach((c, i) => c.copy(palette[i % palette.length]));            connectionsMesh.material.uniforms.uPulseColors.value.forEach((c, i) => c.copy(palette[i % palette.length]));            nodesMesh.material.uniforms.uActivePalette.value = paletteIndex;        }
        const raycaster = new THREE.Raycaster();        const pointer = new THREE.Vector2();        const interactionPlane = new THREE.Plane(new THREE.Vector3(001), 0);        const interactionPoint = new THREE.Vector3();        let lastPulseIndex = 0;
        function triggerPulse(clientX, clientY) {            pointer.x = (clientX / window.innerWidth) * 2 - 1;            pointer.y = -(clientY / window.innerHeight) * 2 + 1;
            raycaster.setFromCamera(pointer, camera);
            interactionPlane.normal.copy(camera.position).normalize();            interactionPlane.constant = -interactionPlane.normal.dot(camera.position) + camera.position.length() * 0.5;
            if (raycaster.ray.intersectPlane(interactionPlane, interactionPoint)) {                const time = clock.getElapsedTime();
                if (nodesMesh && connectionsMesh) {                    lastPulseIndex = (lastPulseIndex + 1) % 3;
                    nodesMesh.material.uniforms.uPulsePositions.value[lastPulseIndex].copy(interactionPoint);                    nodesMesh.material.uniforms.uPulseTimes.value[lastPulseIndex] = time;                    connectionsMesh.material.uniforms.uPulsePositions.value[lastPulseIndex].copy(interactionPoint);                    connectionsMesh.material.uniforms.uPulseTimes.value[lastPulseIndex] = time;
                    const palette = colorPalettes[config.activePaletteIndex];                    const randomColor = palette[Math.floor(Math.random() * palette.length)];                    nodesMesh.material.uniforms.uPulseColors.value[lastPulseIndex].copy(randomColor);                    connectionsMesh.material.uniforms.uPulseColors.value[lastPulseIndex].copy(randomColor);                }            }        }
        renderer.domElement.addEventListener('click'(e) => {            if (e.target.closest('.ui-panel, #control-buttons')) return;            if (!config.pausedtriggerPulse(e.clientX, e.clientY);        });        renderer.domElement.addEventListener('touchstart'(e) => {            if (e.target.closest('.ui-panel, #control-buttons')) return;            e.preventDefault();            if (e.touches.length > 0 && !config.paused) {                triggerPulse(e.touches[0].clientX, e.touches[0].clientY);            }        }, { passivefalse });
        const themeButtons = document.querySelectorAll('.theme-button');        themeButtons.forEach(btn => {            btn.addEventListener('click'(e) => {                e.stopPropagation();                const idx = parseInt(btn.dataset.theme10);                updateTheme(idx);                themeButtons.forEach(b => b.classList.remove('active'));                btn.classList.add('active');            });        });
        const densitySlider = document.getElementById('density-slider');        const densityValue = document.getElementById('density-value');        let densityTimeout;        densitySlider.addEventListener('input'(e) => {            e.stopPropagation();            const val = parseInt(densitySlider.value10);            config.densityFactor = val / 100;            densityValue.textContent = `${val}%`;
            clearTimeout(densityTimeout);            densityTimeout = setTimeout(() => {                createNetworkVisualization(config.currentFormation, config.densityFactor);            }, 300);        });
        const changeFormationBtn = document.getElementById('change-formation-btn');        const pausePlayBtn = document.getElementById('pause-play-btn');        const resetCameraBtn = document.getElementById('reset-camera-btn');
        changeFormationBtn.addEventListener('click'(e) => {            e.stopPropagation();            config.currentFormation = (config.currentFormation + 1) % config.numFormations;            createNetworkVisualization(config.currentFormation, config.densityFactor);             controls.autoRotate = false;             setTimeout(() => { controls.autoRotate = true; }, 2000);        });
        pausePlayBtn.addEventListener('click'(e) => {            e.stopPropagation();            config.paused = !config.paused;            pausePlayBtn.textContent = config.paused ? 'Play' : 'Pause';            controls.autoRotate = !config.paused;        });
        resetCameraBtn.addEventListener('click'(e) => {            e.stopPropagation();            controls.reset();             controls.autoRotate = false;             setTimeout(() => { controls.autoRotate = true; }, 1500);        });
        const clock = new THREE.Clock();
        function animate() {            requestAnimationFrame(animate);
            const t = clock.getElapsedTime();
            if (!config.paused) {                if (nodesMesh) {                    nodesMesh.material.uniforms.uTime.value = t;                    nodesMesh.rotation.y = Math.sin(t * 0.05) * 0.08;                }                if (connectionsMesh) {                    connectionsMesh.material.uniforms.uTime.value = t;                    connectionsMesh.rotation.y = Math.sin(t * 0.05) * 0.08;                }            }
            starField.rotation.y += 0.0003;
            controls.update();            composer.render();        }
        function init() {            createNetworkVisualization(config.currentFormation, config.densityFactor);             document.querySelectorAll('.theme-button').forEach(b => b.classList.remove('active'));             document.querySelector(`.theme-button[data-theme="${config.activePaletteIndex}"]`).classList.add('active');            updateTheme(config.activePaletteIndex);            animate();        }
        function onWindowResize() {            camera.aspect = window.innerWidth / window.innerHeight;            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidthwindow.innerHeight);            composer.setSize(window.innerWidthwindow.innerHeight);
             bloomPass.resolution.set(window.innerWidthwindow.innerHeight);        }        window.addEventListener('resize', onWindowResize);
        init();
    </script>

源码:

https://codepen.io/VoXelo/pen/EaabxLj

体验:

https://codepen.io/VoXelo/full/EaabxLj

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

免责声明 1、百创网作为第三方中介平台,依据交易合同(商品描述、交易前商定的内容)来保障交易的安全及买卖双方的权益; 2、非平台线上交易的项目,出现任何后果均与百创网无关;无论卖家以何理由要求线下交易的,请联系管理举报。 3. 百创网网站的资源均由店家上传出售,本站无法判断和识别资源的版权等合法性属性。如果您对本网站上传的信息资源的版权存有异议,请您及时联系 我们。如果需要删除链接,请下载下面的附件,正确填写信息后并发给我们,本站核实信息真实性后,在24小时内对商品进行删除处理。 联系邮箱:baicxx@baicxx.com (相关事务请发函至该邮箱)

百创网-源码交易平台_网站源码_商城源码_小程序源码 行业资讯 Three.js 交互式神经网络可视化 https://www.baicxx.com/31133.html

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、百创会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、百创无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在百创上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于百创介入快速处理。
查看详情
  • 1、百创作为第三方中介平台,依据交易合同(商品描述、交易前商定的内容)来保障交易的安全及买卖双方的权益; 2、非平台线上交易的项目,出现任何后果均与百创无关;无论卖家以何理由要求线下交易的,请联系管理举报。
查看详情
  • 免责声明 1、百创网作为第三方中介平台,依据交易合同(商品描述、交易前商定的内容)来保障交易的安全及买卖双方的权益; 2、非平台线上交易的项目,出现任何后果均与百创网无关;无论卖家以何理由要求线下交易的,请联系管理举报。 3. 百创网网站的资源均由店家上传出售,本站无法判断和识别资源的版权等合法性属性。如果您对本网站上传的信息资源的版权存有异议,请您及时联系 我们。如果需要删除链接,请下载下面的附件,正确填写信息后并发给我们,本站核实信息真实性后,在24小时内对商品进行删除处理。 联系邮箱:baicxx@baicxx.com (相关事务请发函至该邮箱)
查看详情

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务

  • 0 +

    访问总数

  • 0 +

    会员总数

  • 0 +

    文章总数

  • 0 +

    今日发布

  • 0 +

    本周发布

  • 0 +

    运行天数

你的前景,远超我们想象