当前位置: 首页 > news >正文

学习threejs,交互式神经网络可视化

👨‍⚕️ 主页: gis分享者
👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!
👨‍⚕️ 收录于专栏:threejs gis工程师


文章目录

  • 一、🍀前言
    • 1.1 ☘️THREE.EffectComposer 后期处理
      • 1.1.1 ☘️代码示例
      • 1.1.2 ☘️构造函数
      • 1.1.3 ☘️属性
      • 1.1.4 ☘️方法
    • 1.2 ☘️THREE.RenderPass
      • 1.2.1 ☘️构造函数
      • 1.2.2 ☘️属性
      • 1.2.3 ☘️方法
    • 1.3 ☘️THREE.UnrealBloomPass
      • 1.3.1 ☘️构造函数
      • 1.3.2 ☘️使用示例
      • 1.3.3 ☘️方法
    • 1.4 ☘️THREE.FilmPass
      • 1.4.1 ☘️构造函数
      • 1.4.2 ☘️属性
      • 1.4.3 ☘️方法
    • 1.5 ☘️OutputPass
      • 1.5.1 ☘️构造函数
      • 1.4.2 ☘️使用示例
      • 1.4.3 ☘️方法
  • 二、🍀交互式神经网络可视化
    • 1. ☘️实现思路
    • 2. ☘️代码样例


一、🍀前言

本文详细介绍如何基于threejs在三维场景中实现交互式神经网络可视化,亲测可用。希望能帮助到您。一起学习,加油!加油!

1.1 ☘️THREE.EffectComposer 后期处理

THREE.EffectComposer 用于在three.js中实现后期处理效果。该类管理了产生最终视觉效果的后期处理过程链。 后期处理过程根据它们添加/插入的顺序来执行,最后一个过程会被自动渲染到屏幕上。

1.1.1 ☘️代码示例

import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
// 初始化 composer
const composer = new EffectComposer(renderer);
// 创建 RenderPass 并添加到 composer
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// 添加其他后期处理通道(如模糊)
// composer.addPass(blurPass);
// 在动画循环中渲染
function animate() {composer.render();requestAnimationFrame(animate);
}

1.1.2 ☘️构造函数

EffectComposer( renderer : WebGLRenderer, renderTarget : WebGLRenderTarget )
renderer – 用于渲染场景的渲染器。
renderTarget – (可选)一个预先配置的渲染目标,内部由 EffectComposer 使用。

1.1.3 ☘️属性

.passes : Array
一个用于表示后期处理过程链(包含顺序)的数组。

渲染通道:
BloomPass   该通道会使得明亮区域参入较暗的区域。模拟相机照到过多亮光的情形
DotScreenPass   将一层黑点贴到代表原始图片的屏幕上
FilmPass    通过扫描线和失真模拟电视屏幕
MaskPass    在当前图片上贴一层掩膜,后续通道只会影响被贴的区域
RenderPass  该通道在指定的场景和相机的基础上渲染出一个新的场景
SavePass    执行该通道时,它会将当前渲染步骤的结果复制一份,方便后面使用。这个通道实际应用中作用不大;
ShaderPass  使用该通道你可以传入一个自定义的着色器,用来生成高级的、自定义的后期处理通道
TexturePass 该通道可以将效果组合器的当前状态保存为一个纹理,然后可以在其他EffectCoposer对象中将该纹理作为输入参数
UnrealBloomPass 用于实现 虚幻引擎风格泛光效果(Bloom) 的后期处理通道。它通过模拟光线散射和光晕效果,增强场景中高光区域的视觉表现
OutputPass  主要用于对最终渲染结果进行色调映射(Tone Mapping)、Gamma 校正等输出调整。它通常作为 EffectComposer 的最后一个环节,将处理后的图像输出到屏幕

.readBuffer : WebGLRenderTarget
内部读缓冲区的引用。过程一般从该缓冲区读取先前的渲染结果。

.renderer : WebGLRenderer
内部渲染器的引用。

.renderToScreen : Boolean
最终过程是否被渲染到屏幕(默认帧缓冲区)。

.writeBuffer : WebGLRenderTarget
内部写缓冲区的引用。过程常将它们的渲染结果写入该缓冲区。

1.1.4 ☘️方法

.addPass ( pass : Pass ) : undefined
pass – 将被添加到过程链的过程

将传入的过程添加到过程链。

.dispose () : undefined
释放此实例分配的 GPU 相关资源。每当您的应用程序不再使用此实例时调用此方法。

.insertPass ( pass : Pass, index : Integer ) : undefined
pass – 将被插入到过程链的过程。

index – 定义过程链中过程应插入的位置。

将传入的过程插入到过程链中所给定的索引处。

.isLastEnabledPass ( passIndex : Integer ) : Boolean
passIndex – 被用于检查的过程

如果给定索引的过程在过程链中是最后一个启用的过程,则返回true。 由EffectComposer所使用,来决定哪一个过程应当被渲染到屏幕上。

.removePass ( pass : Pass ) : undefined
pass – 要从传递链中删除的传递。

从传递链中删除给定的传递。

.render ( deltaTime : Float ) : undefined
deltaTime – 增量时间值。

执行所有启用的后期处理过程,来产生最终的帧,

.reset ( renderTarget : WebGLRenderTarget ) : undefined
renderTarget – (可选)一个预先配置的渲染目标,内部由 EffectComposer 使用。

重置所有EffectComposer的内部状态。

.setPixelRatio ( pixelRatio : Float ) : undefined
pixelRatio – 设备像素比

设置设备的像素比。该值通常被用于HiDPI设备,以阻止模糊的输出。 因此,该方法语义类似于WebGLRenderer.setPixelRatio()。

.setSize ( width : Integer, height : Integer ) : undefined
width – EffectComposer的宽度。
height – EffectComposer的高度。

考虑设备像素比,重新设置内部渲染缓冲和过程的大小为(width, height)。 因此,该方法语义类似于WebGLRenderer.setSize()。

.swapBuffers () : undefined
交换内部的读/写缓冲。

1.2 ☘️THREE.RenderPass

THREE.RenderPass用于将场景渲染到中间缓冲区,为后续的后期处理效果(如模糊、色调调整等)提供基础。

1.2.1 ☘️构造函数

RenderPass(scene, camera, overrideMaterial, clearColor, clearAlpha)

  • scene THREE.Scene 要渲染的 Three.js 场景对象。
  • camera THREE.Camera 场景对应的相机(如 PerspectiveCamera)。
  • overrideMaterial THREE.Material (可选) 覆盖场景中所有物体的材质(默认 null)。
  • clearColor THREE.Color (可选) 渲染前清除画布的颜色(默认不主动清除)。
  • clearAlpha number (可选) 清除画布的透明度(默认 0)。

1.2.2 ☘️属性

.enabled:boolean
是否启用此通道(默认 true)。设为 false 可跳过渲染。

.clear:boolean
渲染前是否清除画布(默认 true)。若需叠加多个 RenderPass,可设为 false。

.needsSwap:boolean
是否需要在渲染后交换缓冲区(通常保持默认 false)。

1.2.3 ☘️方法

.setSize(width, height)
调整通道的渲染尺寸(通常由 EffectComposer 自动调用)。
width: 画布宽度(像素)。
height: 画布高度(像素)。

1.3 ☘️THREE.UnrealBloomPass

THREE.UnrealBloomPass是 是 Three.js 中用于实现 虚幻引擎风格泛光效果(Bloom) 的后期处理通道。它通过模拟光线散射和光晕效果,增强场景中高光区域的视觉表现。

1.3.1 ☘️构造函数

THREE.UnrealBloomPass(
new THREE.Vector2(width, height), // 渲染目标尺寸(通常与画布一致)
strength, // 泛光强度 (默认 1)
radius, // 泛光半径 (默认 0)
threshold // 泛光阈值 (默认 0)
)

new THREE.Vector2(width, height)
渲染目标的分辨率,通常与画布尺寸一致(如 new THREE.Vector2(window.innerWidth, window.innerHeight))。
strength(强度)
控制泛光效果的强度(亮度)。值越大,泛光越明显。
范围:0(无效果)到 3(强烈)。
radius(半径)
控制泛光的扩散半径。值越大,光晕范围越广。
范围:0(无扩散)到 1(较大扩散)。
threshold(阈值)
仅对亮度高于此值的像素应用泛光。值越低,更多区域会被处理。
范围:0(所有像素)到 1(仅最亮像素)。

1.3.2 ☘️使用示例

// 初始化
const composer = new THREE.EffectComposer(renderer);
const renderPass = new THREE.RenderPass(scene, camera);
const bloomPass = new THREE.UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight),1.2, 0.2, 0.8
);// 添加通道
composer.addPass(renderPass);
composer.addPass(bloomPass);// 渲染
function animate() {requestAnimationFrame(animate);composer.render();
}

1.3.3 ☘️方法

.setSize(width, height)
调整通道的渲染尺寸(通常由 EffectComposer 自动调用)。
width: 画布宽度(像素)。
height: 画布高度(像素)。

.render(renderer, writeBuffer, readBuffer, deltaTime, maskActive)
内部方法,通常由 EffectComposer 自动调用,无需手动执行。

1.4 ☘️THREE.FilmPass

THREE.FilmPass是 Three.js 后期处理模块中的一个特效通道,用于模拟电影胶片效果(如扫描线、颗粒噪声和画面抖动)。适用于复古风格或科幻场景的视觉增强。

1.4.1 ☘️构造函数

FilmPass(
noiseIntensity, // 噪声强度
scanlinesIntensity,// 扫描线强度
scanlinesCount, // 扫描线数量
grayscale // 是否转为灰度
)

1.4.2 ☘️属性

.enabled:boolean
是否启用此通道(默认 true)。设为 false 可临时禁用效果。

.uniforms:object
着色器 uniforms 对象,可直接修改参数(动态调整效果):

filmPass.uniforms.nIntensity.value = 0.8; // 调整噪声强度
filmPass.uniforms.sIntensity.value = 0.5; // 调整扫描线强度
filmPass.uniforms.sCount.value = 1024;    // 调整扫描线密度
filmPass.uniforms.grayscale.value = 1;    // 启用灰度(1 是,0 否)

1.4.3 ☘️方法

.setSize(width, height)
调整通道的渲染尺寸(通常由 EffectComposer 自动调用)。
width: 画布宽度(像素)。
height: 画布高度(像素)。

1.5 ☘️OutputPass

OutputPass 是 Three.js 后期处理(Post-Processing)流程中的一个通道(Pass),主要用于对最终渲染结果进行色调映射(Tone Mapping)、Gamma 校正等输出调整。它通常作为 EffectComposer 的最后一个环节,将处理后的图像输出到屏幕。

1.5.1 ☘️构造函数

OutputPass(renderer, scene, camera)
renderer:WebGLRenderer类型 Three.js 渲染器实例
scene:Scene类型 Three.js 场景对象
camera:Camera类型 Three.js 相机对象

1.4.2 ☘️使用示例

import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js';// 1. 创建 EffectComposer
const composer = new EffectComposer(renderer);// 2. 添加渲染通道(RenderPass)
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);// 3. 添加 OutputPass(通常作为最后一个通道)
const outputPass = new OutputPass(renderer, scene, camera);
composer.addPass(outputPass);// 4. 执行渲染循环
function animate() {requestAnimationFrame(animate);composer.render(); // 替代 renderer.render(scene, camera)
}
animate();

1.4.3 ☘️方法

.setSize(width, height)
调整通道的渲染尺寸(通常由 EffectComposer 自动调用)。
width: 画布宽度(像素)。
height: 画布高度(像素)。
.render(renderer, writeBuffer, readBuffer, deltaTime, maskActive)
执行渲染流程,将处理后的图像输出到屏幕。

二、🍀交互式神经网络可视化

单击/点击通过自定义 GLSL 着色器发送动画能量脉冲,并在节点/连接扩展时使其变亮。包括主题和密度控制。

1. ☘️实现思路

通过EffectComposer后期处理组合器,RenderPass、UnrealBloomPass(泛光)、FilmPass(电影胶片效果)、OutputPass后期处理通道,以及自定义shader着色器实现交互式神经网络可视化。具体代码参考代码样例。可以直接运行。

2. ☘️代码样例

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><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">
</head>
<style>* {margin: 0;padding: 0;box-sizing: border-box;}canvas {display: block;width: 100%;height: 100%;cursor: pointer;position: absolute;top: 0;left: 0;z-index: 1;}.ui-panel {position: absolute;backdrop-filter: blur(10px);-webkit-backdrop-filter: blur(10px);background: rgba(0, 0, 0, .7);border-radius: 12px;border: 1px solid rgba(255, 120, 50, .3);box-shadow: 0 4px 20px rgba(0, 0, 0, .5);z-index: 10;padding: 15px;color: #eee;font-family: 'Inter', sans-serif;}#instructions-container {top: 20px;left: 20px;font-size: 14px;line-height: 1.5;max-width: 280px;}#instruction-title {font-weight: 600;margin-bottom: 6px;font-size: 15px;}#theme-selector {top: 20px;right: 20px;display: flex;flex-direction: column;gap: 12px;max-width: 150px;}#theme-selector-title {font-weight: 600;font-size: 15px;margin-bottom: 2px;}.theme-grid {display: grid;grid-template-columns: repeat(2, 1fr);gap: 10px;}.theme-button {width: 36px;height: 36px;border-radius: 8px;border: 2px solid rgba(255, 255, 255, .3);cursor: pointer;transition: transform .2s, border-color .2s;outline: none;overflow: hidden;}.theme-button:hover, .theme-button:focus {transform: scale(1.05);border-color: rgba(255, 255, 255, .7);}.theme-button.active {transform: scale(1.05);border-color: rgba(255, 255, 255, .9);box-shadow: 0 0 10px rgba(255, 200, 150, .6);}#theme-1 { background: linear-gradient(45deg, #4F46E5, #7C3AED, #C026D3, #DB2777); }#theme-2 { background: linear-gradient(45deg, #F59E0B, #F97316, #DC2626, #7F1D1D); }#theme-3 { background: linear-gradient(45deg, #EC4899, #8B5CF6, #6366F1, #3B82F6); }#theme-4 { background: linear-gradient(45deg, #10B981, #A3E635, #FACC15, #FB923C); }#density-controls {margin-top: 8px;display: flex;flex-direction: column;gap: 8px;}.density-label {font-size: 13px;display: flex;justify-content: space-between;}.density-slider {width: 100%;appearance: none;height: 4px;border-radius: 2px;background: rgba(255, 120, 50, .3);outline: none;cursor: pointer;}.density-slider::-webkit-slider-thumb {appearance: none;width: 14px;height: 14px;border-radius: 50%;background: rgba(255, 120, 50, .8);cursor: pointer;transition: transform .1s, background .1s;}.density-slider::-moz-range-thumb {width: 14px;height: 14px;border-radius: 50%;background: rgba(255, 120, 50, .8);cursor: pointer;border: none;transition: transform .1s, background .1s;}.density-slider::-webkit-slider-thumb:hover { transform: scale(1.1); background: rgba(255, 140, 50, 1); }.density-slider::-moz-range-thumb:hover { transform: scale(1.1); background: rgba(255, 140, 50, 1); }#control-buttons {position: absolute;bottom: 20px;left: 50%;transform: translateX(-50%);display: flex;gap: 15px;z-index: 10;background: rgba(0, 0, 0, .6);padding: 10px 15px;border-radius: 10px;border: 1px solid rgba(255, 120, 50, .2);}.control-button {background: rgba(255, 120, 50, .2);color: #eee;border: 1px solid rgba(255, 150, 50, .3);padding: 8px 15px;border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;transition: background-color 0.2s, transform 0.1s;white-space: nowrap;min-width: 80px;text-align: center;font-family: 'Inter', sans-serif;}.control-button:hover, .control-button:focus {background: rgba(255, 120, 50, .4);outline: none;}.control-button:active {background: rgba(255, 120, 50, .6);transform: scale(0.95);}@media (max-width: 640px) {#instructions-container {max-width: calc(100% - 40px);font-size: 13px;padding: 10px 15px;top: 10px;left: 10px;}#instruction-title {font-size: 14px;}#theme-selector {top: auto;bottom: 20px;right: 10px;left: auto;transform: none;max-width: 120px;padding: 10px;}#theme-selector-title {font-size: 14px;}.theme-button {width: 30px;height: 30px;}.density-label { font-size: 12px; }#control-buttons {bottom: 10px;gap: 10px;padding: 8px 10px;}.control-button {padding: 6px 10px;font-size: 12px;min-width: 65px;}}@media (max-width: 400px) {#theme-selector {flex-direction: column;align-items: center;max-width: none;width: calc(100% - 20px);left: 10px;right: 10px;bottom: 75px;}.theme-grid {grid-template-columns: repeat(4, 1fr);width: 100%;justify-items: center;}#density-controls {width: 80%;margin-top: 15px;}#control-buttons {width: calc(100% - 20px);justify-content: space-around;}}</style>
<body><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 = {paused: false,activePaletteIndex: 1,currentFormation: 0,numFormations: 4,densityFactor: 1};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(0x000000, 0.0015);const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1200);camera.position.set(0, 5, 22);const canvasElement = document.getElementById('neural-network-canvas'); // Get canvas elementconst renderer = new THREE.WebGLRenderer({ canvas: canvasElement, antialias: true, powerPreference: "high-performance" });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));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(40, 120);const phi = Math.acos(THREE.MathUtils.randFloatSpread(2));const theta = THREE.MathUtils.randFloat(0, Math.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({color: 0xffffff,size: 0.15,sizeAttenuation: true,depthWrite: false,opacity: 0.8,transparent: true});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.innerWidth, window.innerHeight), 1.5, 0.4, 0.68);composer.addPass(bloomPass);const filmPass = new FilmPass(0.35, 0.55, 2048, false);composer.addPass(filmPass);composer.addPass(new OutputPass());const pulseUniforms = {uTime: { value: 0.0 },uPulsePositions: { value: [new THREE.Vector3(1e3, 1e3, 1e3), new THREE.Vector3(1e3, 1e3, 1e3), new THREE.Vector3(1e3, 1e3, 1e3)] },uPulseTimes: { value: [-1e3, -1e3, -1e3] },uPulseColors: { value: [new THREE.Color(1, 1, 1), new THREE.Color(1, 1, 1), new THREE.Color(1, 1, 1)] },uPulseSpeed: { value: 15.0 },uBaseNodeSize: { value: 0.5 },uActivePalette: { value: 0 }};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.7, 1.2) : THREE.MathUtils.randFloat(0.4, 0.9);this.distanceFromRoot = 0;}addConnection(node, strength = 1.0) {if (!this.isConnectedTo(node)) {this.connections.push({ node, strength });node.connections.push({ node: this, 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(0, 0, 0), 0, 0); 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 = [5, 10, 15];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 = null; let minDist = Infinity;for (const n of nodes) {if (n === rootNode || n === node) continue;if (n.level === 0 || n.type !== 0) continue;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.level, 0);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(0, 0, 0), 0, 0); rootNode.size = 1.5; nodes.push(rootNode);const dimensions = 4;const nodesPerDimension = Math.floor(40 * densityFactor);const maxRadius = 20;const dimensionVectors = [new THREE.Vector3(1, 1, 1).normalize(),new THREE.Vector3(-1, 1, -1).normalize(),new THREE.Vector3(1, -1, -1).normalize(),new THREE.Vector3(-1, -1, 1).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.position, 0.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(0, 0, 0), 0, 0); 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 - 1, 2);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.position, 0.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(0, 0, 0), 0, 0); 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, 1, 0);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.position, 0.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, 2, 0);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.level, 0);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 0: generateQuantumCortex(); break;case 1: generateHyperdimensionalMesh(); break;case 2: generateNeuralVortex(); break;case 3: generateSynapticCloud(); 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(0, 3).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({uniforms: THREE.UniformsUtils.clone(pulseUniforms),vertexShader: nodeShader.vertexShader,fragmentShader: nodeShader.fragmentShader,transparent: true,depthWrite: false,blending: THREE.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 === -1) return;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, 0, 0);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({uniforms: THREE.UniformsUtils.clone(pulseUniforms),vertexShader: connectionShader.vertexShader,fragmentShader: connectionShader.fragmentShader,transparent: true,depthWrite: false,blending: THREE.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 === -1) return;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(0, 0, 1), 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.paused) triggerPulse(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);}}, { passive: false });const themeButtons = document.querySelectorAll('.theme-button');themeButtons.forEach(btn => {btn.addEventListener('click', (e) => {e.stopPropagation();const idx = parseInt(btn.dataset.theme, 10);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.value, 10);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.innerWidth, window.innerHeight);composer.setSize(window.innerWidth, window.innerHeight);bloomPass.resolution.set(window.innerWidth, window.innerHeight);}window.addEventListener('resize', onWindowResize);init();</script>
</body>
</html>

效果如下
在这里插入图片描述

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

相关文章:

  • 三、kafka消费的全流程
  • 论文分类打榜赛Baseline:ms-swift微调InternLM实践
  • LangChain基本概念
  • Numpy入门2——视图和副本、伪随机数、切片和索引、数组的轴操作
  • Python训练打卡Day41
  • BugKu Web渗透之game1
  • 20250603在荣品的PRO-RK3566开发板的Android13下的使用命令行来查看RK3566的温度【显示优化版本】
  • 【 java 集合知识 第一篇 】
  • Python趣学篇:从零打造智能AI井字棋游戏(Python + Tkinter + Minimax算法)
  • 云原生周刊:探索 Gateway API v1.3.0
  • 深入理解Android进程间通信机制
  • 开疆智能Profinet转Profibus网关连接CMDF5-8ADe分布式IO配置案例
  • 【大模型:知识图谱】--1.py2neo连接图数据库neo4j
  • vue-14(使用 ‘router.push‘ 和 ‘router.replace‘ 进行编程导航)
  • Spark 单机模式部署与启动
  • 6-2 MySQL 数据结构选择的合理性
  • 【数据结构 -- B树】
  • Mac版本Android Studio配置LeetCode插件
  • tryhackme——Abusing Windows Internals(进程注入)
  • Spring AI Advisor机制
  • 广西建设领域证书查询官方网站/搜索引擎优化的目的是对用户友好
  • 装修网站制作设计价格费用/steam交易链接在哪复制
  • 微信小店可以做分类网站/中国十大企业培训公司
  • wordpress 视差滚动/河南seo推广
  • 做网站前台用什么问题/360优化大师官方官网
  • 投资手机网站源码/seo一个关键词多少钱