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

article/2025/6/8 13:02:05

👨‍⚕️ 主页: 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 交互式神经网络可视化


http://www.hkcw.cn/article/XkxoIshsRn.shtml

相关文章

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Dad Jokes(冷笑话卡片)

&#x1f4c5; 我们继续 50 个小项目挑战&#xff01;—— DadJokes 组件 仓库地址&#xff1a;https://github.com/SunACong/50-vue-projects 项目预览地址&#xff1a;https://50-vue-projects.vercel.app/ 豆包翻译确实可以&#xff0c;冷笑话应该属于各类语言比较难理解的…

ESP32开发之LED闪烁和呼吸的实现

硬件电路介绍GPIO输出模式GPIO配置过程闪烁灯的源码LED PWM的控制器(LEDC)概述LEDC配置过程及现象整体流程 硬件电路介绍 电路图如下&#xff1a; 只要有硬件基础的应该都知道上图中&#xff0c;当GPIO4的输出电平为高时&#xff0c;LED灯亮&#xff0c;反之则熄灭。如果每间…

【办公类-48-04】202506每月电子屏台账汇总成docx-5(问卷星下载5月范围内容,自动获取excel文件名,并转移处理)

背景需求&#xff1a; 1-4月电子屏表格&#xff0c;都是用这个代码将EXCEL数据整理成分类成3个WORD表格。 【办公类-48-04】20250118每月电子屏台账汇总成docx-4&#xff08;提取EXCLE里面1月份的内容&#xff0c;自制月份文件夹&#xff09;-CSDN博客文章浏览阅读1.2k次&…

25年宁德时代新能源科技SHL 测评语言理解数字推理Verify题库

宁德时代新能源科技的SHL测评中&#xff0c;语言理解部分主要考察阅读理解、逻辑填空和语句排序等题型&#xff0c;要求应聘者在17分钟内完成30题。阅读理解需要快速捕捉文章主旨和理解细节信息&#xff1b;逻辑填空则要根据语句逻辑填入最合适的词汇&#xff1b;语句排序是将打…

Windows下WSL(Ubuntu)安装1Panel

1Panel 1Panel 提供了一个直观的 Web 界面和 MCP Server&#xff0c;帮助用户轻松管理 Linux 服务器中的网站、文件、容器、数据库以及大型语言模型&#xff08;LLMs&#xff09;。 官网地址&#xff1a;1Panel - 现代化、开源的 Linux 服务器运维管理面板 - 官网 前置条件 …

virtualbox安装扩展工具以支持共享文件夹

1.下载扩展镜像 https://download.virtualbox.org/virtualbox/7.0.16/ 2.加载扩展镜像并安装 3. 配置共享文件夹

实现仿中国婚博会微信小程序

主要功能&#xff1a; 1、完成底部标签导航设计、首页海报轮播效果设计和宫格导航设计&#xff0c;如图1所示 2、在首页里&#xff0c;单击全部分类宫格导航的时候&#xff0c;会进入到全部分类导航界面&#xff0c;把婚博会相关内容的导航集成到一个界面里&#xff0c;如图2…

Meta ASC广告:智能电商营销利器解析

Meta推出的ASC广告&#xff08;全称Advantage Shopping Campaign&#xff09;是专为电商卖家打造的智能营销利器。作为新一代自动化购物广告解决方案&#xff0c;它通过AI技术重塑了传统广告投放模式&#xff0c;为商家带来更高效的转化路径。 五大核心优势解析&#xff1a; 全…

1.1Nodejs和浏览器中的二进制处理

Buffer 在 Node.js 中&#xff0c;Buffer 类用于处理二进制数据。由于 JavaScript 在浏览器环境中主要用于处理字符串和数字等类型的数据&#xff0c;对二进制数据的处理能力较弱&#xff0c;因此 Node.js 引入了 Buffer 类来弥补这一不足&#xff0c;特别是在处理文件系统操作…

redis的哨兵模式和Redis cluster

目录 一. redis的主从复制 二. 哨兵模式 2.1 定义 2.2 作用 2.3 配置实例 三. Redis cluster 3.1 定义 3.2 作用 3.3 配置实例 1. 新建集群文件目录 2. 准备可执行文件到每个文件夹 3. 开启群集功能 4. 启动redis节点 5. 查看是否启动成功 6. 启动集群 7. 测试…

[Java 基础]打印金字塔

实现一个 Java 程序&#xff0c;让用户输入金字塔的层数&#xff0c;打印出对应层数的金字塔。 比如&#xff0c;如果用户指定金字塔的层数是 5&#xff0c;那么将会打印如下的金字塔&#xff1a; 0000* 000*** 00***** 0******* 假如 i 代表的是行&#xff0c;i 从 1 开始…

ollama的安装及加速下载技巧

下载ollama ollama的安装可以从官网下载&#xff0c;地址&#xff1a; https://ollama.com 但是下载会很慢&#xff0c;我是去的这里下载&#xff1a;https://www.gy328.com/app/ollama/ 和官网一样。 下载好了&#xff0c;点击安装&#xff0c;安装好了&#xff…

Qiskit:量子计算模拟器

参考文献&#xff1a; IBM Qiskit 官网Qiskit DocumentationQiskit Benchpress packageQiskit Algorithms package量子计算&#xff1a;基本概念常见的几类矩阵&#xff08;正交矩阵、酉矩阵、正规矩阵等&#xff09;Qiskit 安装指南-博客园使用Python实现量子电路模拟&#x…

05 APP 自动化- Appium 单点触控 多点触控

文章目录 一、单点触控查看指针的指针位置实现手势密码&#xff1a; 二、多点触控 一、单点触控 查看指针的指针位置 方便查看手势密码-九宫格每个点的坐标 实现手势密码&#xff1a; 执行手势操作&#xff1a; 按压起点 -> 移动到下一点 -> 依次移动 -> 释放&am…

aardio 图像识别

今天终于学会了编程中的 OCR 技术&#xff01;原来计算机真的能识别图片里的文字&#xff0c;这种让程序 "看懂" 图像的能力太神奇了&#xff0c;赶紧把学习过程记录下来。 一、初识OCR&#xff1a;让程序读懂图片文字 &#xff08;一&#xff09;简单识别实验 OC…

【北邮 操作系统】第十二章 文件系统实现

一、文件的物理结构 1.1 文件块、磁盘块 类似于内存分页&#xff0c;磁盘中的存储单元也会被分为一个个“块/磁盘块/物理块”。很多操作系统中&#xff0c;磁盘块的大小与内存块、页面的大小相同 内存与磁盘之间的数据交换(即读/写操作、磁盘I/0)都是以“块”为单位进行的。即…

VS2022下C++ Boost库安装与使用使用

一.Boost概述 1.简介 Boost 是一个广泛使用的 C 库集合&#xff0c;提供了许多高质量、可移植、高效的工具和组件&#xff0c;被视为 C 标准库的延伸。自 1998 年成立以来&#xff0c;Boost 已成为 C 社区的核心资源&#xff0c;许多 Boost 库通过实践验证后被纳入 C 标准&am…

Unity-UI组件详解

今天我们来学习Unity的UI的详解&#xff0c;这部分的内容相对较少&#xff0c;对于程序员来说主要的工作是负责将各种格式的图片呈现在显示器上并允许操作这些图片。 本篇帖子的理论依据依然是官方开源的UGUI代码&#xff0c;网址为&#xff1a;GitHub - Unity-Technologies/u…

化工厂爆炸事件看制造业AI转型

一、事件警示&#xff1a;化工制造安全风险不容忽视 近日&#xff0c;某化学有限公司发生事故。涉事工厂主体工程建设有2座硝化装置区&#xff0c;1座加氢装置区&#xff0c;均属于危险工艺生产装置。硝化反应通常属于强放热反应&#xff0c;原料及产物具有爆炸危险性&#xf…

Ubuntu系统安装与配置NTP时间同步服务

Ubuntu系统安装与配置NTP时间同步服务 一、NTP服务介绍NTP服务简介工作原理系统环境准备检查当前时间状态二、方案选择:systemd-timesyncd vs ntpd三、使用systemd-timesyncd时间同步1. 方案介绍2. 配置优化3. 应用配置4. 验证状态5. 检查当前时间状态6. 查看当前实践四、使用…