| |
| |
| |
| |
| |
| |
| |
|
|
| import * as THREE from "three"; |
|
|
| const canvas = document.getElementById("neural-bg"); |
| if (canvas) { |
| const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true }); |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); |
|
|
| const scene = new THREE.Scene(); |
| const camera = new THREE.PerspectiveCamera(60, 1, 0.1, 1000); |
| camera.position.z = 60; |
|
|
| const PARTICLE_COUNT = 140; |
| const FIELD_SIZE = 80; |
| const LINK_DISTANCE = 14; |
|
|
| const positions = new Float32Array(PARTICLE_COUNT * 3); |
| const velocities = new Float32Array(PARTICLE_COUNT * 3); |
| for (let i = 0; i < PARTICLE_COUNT; i++) { |
| positions[i * 3] = (Math.random() - 0.5) * FIELD_SIZE; |
| positions[i * 3 + 1] = (Math.random() - 0.5) * FIELD_SIZE; |
| positions[i * 3 + 2] = (Math.random() - 0.5) * FIELD_SIZE * 0.6; |
| velocities[i * 3] = (Math.random() - 0.5) * 0.04; |
| velocities[i * 3 + 1] = (Math.random() - 0.5) * 0.04; |
| velocities[i * 3 + 2] = (Math.random() - 0.5) * 0.04; |
| } |
|
|
| const pGeom = new THREE.BufferGeometry(); |
| pGeom.setAttribute("position", new THREE.BufferAttribute(positions, 3)); |
| const pMat = new THREE.PointsMaterial({ |
| size: 1.4, |
| color: 0x67e8f9, |
| transparent: true, |
| opacity: 0.85, |
| sizeAttenuation: true, |
| }); |
| const points = new THREE.Points(pGeom, pMat); |
| scene.add(points); |
|
|
| |
| |
| const MAX_LINKS = PARTICLE_COUNT * 8; |
| const linePositions = new Float32Array(MAX_LINKS * 2 * 3); |
| const lineGeom = new THREE.BufferGeometry(); |
| lineGeom.setAttribute("position", new THREE.BufferAttribute(linePositions, 3)); |
| const lineMat = new THREE.LineBasicMaterial({ |
| color: 0xe879f9, |
| transparent: true, |
| opacity: 0.18, |
| }); |
| const lines = new THREE.LineSegments(lineGeom, lineMat); |
| scene.add(lines); |
|
|
| function resize() { |
| const w = window.innerWidth, h = window.innerHeight; |
| renderer.setSize(w, h, false); |
| camera.aspect = w / h; |
| camera.updateProjectionMatrix(); |
| } |
| resize(); |
| window.addEventListener("resize", resize); |
|
|
| let running = true; |
| document.addEventListener("visibilitychange", () => { |
| running = document.visibilityState === "visible"; |
| if (running) animate(); |
| }); |
|
|
| function animate() { |
| if (!running) return; |
| requestAnimationFrame(animate); |
|
|
| |
| for (let i = 0; i < PARTICLE_COUNT; i++) { |
| for (let axis = 0; axis < 3; axis++) { |
| const idx = i * 3 + axis; |
| positions[idx] += velocities[idx]; |
| const limit = axis === 2 ? FIELD_SIZE * 0.3 : FIELD_SIZE * 0.5; |
| if (positions[idx] > limit) positions[idx] = -limit; |
| else if (positions[idx] < -limit) positions[idx] = limit; |
| } |
| } |
| pGeom.attributes.position.needsUpdate = true; |
|
|
| |
| let written = 0; |
| for (let i = 0; i < PARTICLE_COUNT; i++) { |
| for (let j = i + 1; j < PARTICLE_COUNT; j++) { |
| const dx = positions[i*3] - positions[j*3]; |
| const dy = positions[i*3+1] - positions[j*3+1]; |
| const dz = positions[i*3+2] - positions[j*3+2]; |
| const d2 = dx*dx + dy*dy + dz*dz; |
| if (d2 < LINK_DISTANCE * LINK_DISTANCE && written < MAX_LINKS) { |
| const k = written * 6; |
| linePositions[k] = positions[i*3]; |
| linePositions[k+1] = positions[i*3+1]; |
| linePositions[k+2] = positions[i*3+2]; |
| linePositions[k+3] = positions[j*3]; |
| linePositions[k+4] = positions[j*3+1]; |
| linePositions[k+5] = positions[j*3+2]; |
| written++; |
| } |
| } |
| } |
| lineGeom.setDrawRange(0, written * 2); |
| lineGeom.attributes.position.needsUpdate = true; |
|
|
| points.rotation.y += 0.0008; |
| lines.rotation.y += 0.0008; |
|
|
| renderer.render(scene, camera); |
| } |
| animate(); |
| } |
|
|