Files
Skyheal/app/components/canvas/BioNeurons.tsx

192 lines
5.9 KiB
TypeScript

"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>
);
}