Animation Added and Gif file updated
This commit is contained in:
183
app/components/canvas/NeuronsGravity.tsx
Normal file
183
app/components/canvas/NeuronsGravity.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
"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<THREE.Group>(null);
|
||||
const particlesRef = useRef<THREE.Points>(null);
|
||||
const linesRef = useRef<THREE.LineSegments>(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 (
|
||||
<group ref={groupRef}>
|
||||
<points ref={particlesRef}>
|
||||
<bufferGeometry>
|
||||
<bufferAttribute
|
||||
attach="attributes-position"
|
||||
count={PARTICLE_COUNT}
|
||||
array={positions}
|
||||
itemSize={3}
|
||||
/>
|
||||
</bufferGeometry>
|
||||
<pointsMaterial
|
||||
size={0.15}
|
||||
color="#22d3ee" // Cyan
|
||||
sizeAttenuation={true}
|
||||
transparent
|
||||
opacity={0.8}
|
||||
/>
|
||||
</points>
|
||||
|
||||
<lineSegments ref={linesRef}>
|
||||
<bufferGeometry />
|
||||
<lineBasicMaterial
|
||||
color="#0891b2" // Cyan-600
|
||||
transparent
|
||||
opacity={0.15}
|
||||
/>
|
||||
</lineSegments>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default function NeuronsGravity() {
|
||||
return (
|
||||
<div className="absolute inset-0 z-0 h-full w-full">
|
||||
<Canvas
|
||||
camera={{ position: [0, 0, 12], fov: 50 }}
|
||||
gl={{ alpha: true, antialias: true }}
|
||||
dpr={[1, 2]}
|
||||
>
|
||||
<NeuralMesh />
|
||||
<Preload all />
|
||||
</Canvas>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user