File size: 4,409 Bytes
08fc97e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// Three.js neural-particle hero scene.
//
// A drifting cloud of particles connected by short lines whenever any two
// particles fall within a threshold distance. Loose neuro/network theme,
// keeps memory and CPU low (no model loads, no shaders beyond the
// PointsMaterial built-in). Resizes with the viewport and pauses when
// the tab is hidden so it doesn't drain battery in the background.

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,         // cyan-300
    transparent: true,
    opacity: 0.85,
    sizeAttenuation: true,
  });
  const points = new THREE.Points(pGeom, pMat);
  scene.add(points);

  // Pre-allocate the lines geometry to a generous upper bound so we don't
  // re-allocate buffers every frame. Capacity is the worst-case pair count.
  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,         // fuchsia-400
    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);

    // Drift particles, wrap around the field box.
    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;

    // Recompute neighbor links. O(N²) but N=140 → ~20K dist checks/frame, fine.
    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();
}