Animation Added and Gif file updated
This commit is contained in:
192
app/components/canvas/BioNeurons.tsx
Normal file
192
app/components/canvas/BioNeurons.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { Canvas, useFrame, useThree } from "@react-three/fiber";
|
||||
import { MeshDistortMaterial, Sphere, Preload, Float } from "@react-three/drei";
|
||||
import * as THREE from "three";
|
||||
|
||||
// --- CONFIGURATION ---
|
||||
const NEURON_COUNT = 18; // Fewer, but more detailed neurons
|
||||
const CONNECTION_DISTANCE = 5.5; // Distance to form a synapse
|
||||
const PULSE_SPEED = 2.5; // Speed of the electric signal
|
||||
|
||||
// 1. SINGLE NEURON CELL (The Soma)
|
||||
// We use a distorted sphere to make it look like organic tissue
|
||||
function NeuronCell({ position }: { position: [number, number, number] }) {
|
||||
const meshRef = useRef<THREE.Mesh>(null!);
|
||||
const [hovered, setHover] = useState(false);
|
||||
|
||||
// Randomize size slightly for variety
|
||||
const scale = useMemo(() => 0.6 + Math.random() * 0.4, []);
|
||||
|
||||
useFrame((state) => {
|
||||
// Gentle heartbeat pulsation
|
||||
const t = state.clock.getElapsedTime();
|
||||
const pulse = Math.sin(t * 3) * 0.05 + 1;
|
||||
if (meshRef.current) {
|
||||
meshRef.current.scale.set(scale * pulse, scale * pulse, scale * pulse);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Float speed={2} rotationIntensity={0.5} floatIntensity={0.5}>
|
||||
<Sphere
|
||||
ref={meshRef}
|
||||
args={[1, 32, 32]} // Geometry args
|
||||
position={position}
|
||||
onPointerOver={() => setHover(true)}
|
||||
onPointerOut={() => setHover(false)}
|
||||
>
|
||||
<MeshDistortMaterial
|
||||
color={hovered ? "#06b6d4" : "#22d3ee"} // Cyan 500 to 400
|
||||
emissive={hovered ? "#0891b2" : "#0e7490"}
|
||||
emissiveIntensity={0.6}
|
||||
roughness={0.2}
|
||||
metalness={0.1}
|
||||
distort={0.4} // The "wobble" amount (0-1)
|
||||
speed={2} // Speed of the wobble
|
||||
/>
|
||||
<pointLight distance={3} intensity={2} color="#22d3ee" />
|
||||
</Sphere>
|
||||
</Float>
|
||||
);
|
||||
}
|
||||
|
||||
// 2. SYNAPSE (The Connection & The Impulse)
|
||||
// Draws a curved organic line and a traveling light pulse
|
||||
function Synapse({ start, end }: { start: THREE.Vector3; end: THREE.Vector3 }) {
|
||||
const curve = useMemo(() => {
|
||||
// Create a quadratic bezier curve for a natural "tendon" look
|
||||
// Control point is midway but offset randomly to create the curve
|
||||
const mid = new THREE.Vector3().lerpVectors(start, end, 0.5);
|
||||
mid.x += (Math.random() - 0.5) * 2;
|
||||
mid.y += (Math.random() - 0.5) * 2;
|
||||
mid.z += (Math.random() - 0.5) * 2;
|
||||
return new THREE.QuadraticBezierCurve3(start, mid, end);
|
||||
}, [start, end]);
|
||||
|
||||
// Create points for the line geometry
|
||||
const points = useMemo(() => curve.getPoints(20), [curve]);
|
||||
|
||||
// The traveling impulse ref
|
||||
const impulseRef = useRef<THREE.Mesh>(null!);
|
||||
|
||||
useFrame((state) => {
|
||||
// Move the impulse along the curve
|
||||
const t = (state.clock.getElapsedTime() * PULSE_SPEED) % 1; // 0 to 1 loop
|
||||
if (impulseRef.current) {
|
||||
const pos = curve.getPointAt(t);
|
||||
impulseRef.current.position.copy(pos);
|
||||
|
||||
// Scale impulse based on position (fade in/out at ends)
|
||||
const scale = Math.sin(t * Math.PI);
|
||||
impulseRef.current.scale.setScalar(scale * 0.15);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<group>
|
||||
{/* The Axon (Line) */}
|
||||
<line>
|
||||
<bufferGeometry>
|
||||
<bufferAttribute
|
||||
attach="attributes-position"
|
||||
count={points.length}
|
||||
array={new Float32Array(points.flatMap((p) => [p.x, p.y, p.z]))}
|
||||
itemSize={3}
|
||||
/>
|
||||
</bufferGeometry>
|
||||
<lineBasicMaterial
|
||||
color="#155e75" // Dark Cyan
|
||||
transparent
|
||||
opacity={0.3}
|
||||
linewidth={1}
|
||||
/>
|
||||
</line>
|
||||
|
||||
{/* The Electric Impulse (Glowing Dot) */}
|
||||
<mesh ref={impulseRef}>
|
||||
<sphereGeometry args={[1, 8, 8]} />
|
||||
<meshBasicMaterial color="#ecfeff" toneMapped={false} />
|
||||
<pointLight distance={2} intensity={2} color="#22d3ee" decay={2} />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
// 3. MAIN SCENE CONTROLLER
|
||||
function NeuralNetworkScene() {
|
||||
const { viewport } = useThree();
|
||||
const mouse = useRef(new THREE.Vector2());
|
||||
|
||||
// Generate Neurons
|
||||
const neurons = useMemo(() => {
|
||||
const temp = [];
|
||||
for (let i = 0; i < NEURON_COUNT; i++) {
|
||||
temp.push({
|
||||
position: new THREE.Vector3(
|
||||
(Math.random() - 0.5) * 20,
|
||||
(Math.random() - 0.5) * 20,
|
||||
(Math.random() - 0.5) * 10
|
||||
),
|
||||
id: i,
|
||||
});
|
||||
}
|
||||
return temp;
|
||||
}, []);
|
||||
|
||||
// Generate Connections (Proximity based)
|
||||
const connections = useMemo(() => {
|
||||
const conns = [];
|
||||
for (let i = 0; i < neurons.length; i++) {
|
||||
for (let j = i + 1; j < neurons.length; j++) {
|
||||
const dist = neurons[i].position.distanceTo(neurons[j].position);
|
||||
if (dist < CONNECTION_DISTANCE) {
|
||||
conns.push({ start: neurons[i].position, end: neurons[j].position, key: `${i}-${j}` });
|
||||
}
|
||||
}
|
||||
}
|
||||
return conns;
|
||||
}, [neurons]);
|
||||
|
||||
useFrame((state) => {
|
||||
// Soft camera movement based on mouse
|
||||
const x = (state.pointer.x * viewport.width) / 10;
|
||||
const y = (state.pointer.y * viewport.height) / 10;
|
||||
|
||||
state.camera.position.x = THREE.MathUtils.lerp(state.camera.position.x, x, 0.05);
|
||||
state.camera.position.y = THREE.MathUtils.lerp(state.camera.position.y, y, 0.05);
|
||||
state.camera.lookAt(0, 0, 0);
|
||||
});
|
||||
|
||||
return (
|
||||
<group>
|
||||
{/* Draw Neurons */}
|
||||
{neurons.map((n) => (
|
||||
<NeuronCell key={n.id} position={[n.position.x, n.position.y, n.position.z]} />
|
||||
))}
|
||||
|
||||
{/* Draw Synapses */}
|
||||
{connections.map((c) => (
|
||||
<Synapse key={c.key} start={c.start} end={c.end} />
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BioNeurons() {
|
||||
return (
|
||||
<div className="absolute inset-0 z-0 h-full w-full">
|
||||
<Canvas
|
||||
camera={{ position: [0, 0, 14], fov: 45 }}
|
||||
gl={{ alpha: true, antialias: true }}
|
||||
dpr={[1, 2]}
|
||||
>
|
||||
<ambientLight intensity={0.2} />
|
||||
<pointLight position={[10, 10, 10]} intensity={1} color="#06b6d4" />
|
||||
<NeuralNetworkScene />
|
||||
<Preload all />
|
||||
</Canvas>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user