"use client"; import { useMemo, useRef } from "react"; import { Canvas, useFrame, useThree } from "@react-three/fiber"; import * as THREE from "three"; import { Preload } from "@react-three/drei"; // CONFIGURATION const PARTICLE_COUNT = 80; const CONNECT_DISTANCE = 3.5; const MOUSE_INFLUENCE_RADIUS = 6; // Radius of the mouse wave const WAVE_AMPLITUDE = 1.5; // How high the wave ripples (Z-axis) function NeuralMesh() { const groupRef = useRef(null); const particlesRef = useRef(null); const linesRef = useRef(null); // Get viewport to map mouse coordinates correctly to world space const { viewport } = useThree(); // 1. Generate Initial Random Positions & Velocities // We store "original positions" to calculate the wave offset from const [positions, velocities, originalPositions] = useMemo(() => { const pos = new Float32Array(PARTICLE_COUNT * 3); const origPos = new Float32Array(PARTICLE_COUNT * 3); const vel = []; for (let i = 0; i < PARTICLE_COUNT; i++) { const x = (Math.random() - 0.5) * 18; const y = (Math.random() - 0.5) * 18; const z = (Math.random() - 0.5) * 10; pos[i * 3] = x; pos[i * 3 + 1] = y; pos[i * 3 + 2] = z; origPos[i * 3] = x; origPos[i * 3 + 1] = y; origPos[i * 3 + 2] = z; vel.push({ x: (Math.random() - 0.5) * 0.05, // Slower natural drift y: (Math.random() - 0.5) * 0.05, z: (Math.random() - 0.5) * 0.05, }); } return [pos, vel, origPos]; }, []); useFrame((state, delta) => { // 2. Map Mouse to World Coordinates // state.pointer is normalized (-1 to 1). Convert to world units using viewport. const mouseX = (state.pointer.x * viewport.width) / 2; const mouseY = (state.pointer.y * viewport.height) / 2; if (particlesRef.current && linesRef.current) { const positionsAttr = particlesRef.current.geometry.attributes.position; const currentPositions = positionsAttr.array as Float32Array; // 3. Update Particles for (let i = 0; i < PARTICLE_COUNT; i++) { const i3 = i * 3; // A. Natural Drift (Gravity) // We use the "original" position as an anchor to prevent them from flying away too far // but we add the velocity to keep them moving organically. originalPositions[i3] += velocities[i].x * delta * 2; originalPositions[i3 + 1] += velocities[i].y * delta * 2; // Bounce logic for the anchor points if (Math.abs(originalPositions[i3]) > 10) velocities[i].x *= -1; if (Math.abs(originalPositions[i3 + 1]) > 10) velocities[i].y *= -1; // B. Mouse Interaction (The Wave) // Calculate distance from particle to mouse const dx = mouseX - originalPositions[i3]; const dy = mouseY - originalPositions[i3 + 1]; const dist = Math.sqrt(dx * dx + dy * dy); // Apply Wave Effect // If the mouse is close, we disturb the position let xOffset = 0; let yOffset = 0; let zOffset = 0; if (dist < MOUSE_INFLUENCE_RADIUS) { // 1. Repulsion force (XY Plane) - pushes them slightly aside const force = (MOUSE_INFLUENCE_RADIUS - dist) / MOUSE_INFLUENCE_RADIUS; const angle = Math.atan2(dy, dx); xOffset = -Math.cos(angle) * force * 2; // Push away X yOffset = -Math.sin(angle) * force * 2; // Push away Y // 2. Wave Ripple (Z Plane) - Sine wave based on distance and time // This creates the "water ripple" effect in 3D depth zOffset = Math.sin(dist * 1.5 - state.clock.elapsedTime * 3) * WAVE_AMPLITUDE * force; } // Apply calculated positions currentPositions[i3] = originalPositions[i3] + xOffset; currentPositions[i3 + 1] = originalPositions[i3 + 1] + yOffset; currentPositions[i3 + 2] = originalPositions[i3 + 2] + zOffset; } positionsAttr.needsUpdate = true; // 4. Update Connections (Plexus) // Re-calculate lines based on the NEW modified positions const linePositions: number[] = []; for (let i = 0; i < PARTICLE_COUNT; i++) { for (let j = i + 1; j < PARTICLE_COUNT; j++) { const x1 = currentPositions[i * 3]; const y1 = currentPositions[i * 3 + 1]; const z1 = currentPositions[i * 3 + 2]; const x2 = currentPositions[j * 3]; const y2 = currentPositions[j * 3 + 1]; const z2 = currentPositions[j * 3 + 2]; const dist = Math.sqrt( Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2) + Math.pow(z2 - z1, 2) ); if (dist < CONNECT_DISTANCE) { linePositions.push(x1, y1, z1, x2, y2, z2); } } } linesRef.current.geometry.setAttribute( "position", new THREE.Float32BufferAttribute(linePositions, 3) ); } }); return ( ); } export default function NeuronsGravity() { return (
); }