A browser-based 3D flocking (boids) simulation of fish, built with React Three Fiber. Twenty fish swim inside a bounded tank, each running the same small set of steering rules against its own local neighborhood — separation, alignment, cohesion, wander, field-of-view culling, and Fibonacci-spiral obstacle evasion — with no central script driving the group. The school behavior emerges from the math.
- CPU-based O(N²) boid simulation — position/velocity state lives in plain
Float32Arrays outside React, updated once per frame in a single loop - Seven-rule steering system — separation, alignment, cohesion, wander, FOV-based neighbor culling, Fibonacci-cone obstacle evasion with panic-weighted blending, and hard boundary clamping
- Rapier physics integration — static wall colliders used purely for raycasting (obstacle detection), not for moving the fish themselves
- Custom GLSL vertex shader — swims the fish model via per-vertex sine displacement instead of skeletal animation, for performance at scale
- Underwater atmosphere — exponential fog, animated caustics floor shader, directional lighting with shadows
- Glass-morphism intro overlay — a draggable "hatch" panel with a porthole preview of a single fish (poke it to spin it), listing the seven boid rules
Beyond the base Vite + React setup, this project uses:
| Package | Why |
|---|---|
three |
Core 3D engine — peer dependency of R3F |
@react-three/fiber |
React renderer for three.js — lets the scene be written as JSX |
@react-three/drei |
Helper components (OrbitControls, etc.) |
leva |
Live-tunable debug panel for boid parameters (weights, speeds, radii) |
@react-three/rapier |
R3F bindings for the Rapier physics engine — used here for raycasting-based obstacle detection, not gravity/collision response |
framer-motion |
Drag gesture + spring animation for the intro overlay's "slide to dive in" interaction |
npm installThis pulls in everything listed in package.json, including the above.
npm run devThen open the local URL Vite prints (typically http://localhost:5173).
- Intro screen: drag the panel right (or drag past roughly a third of the screen width) to dismiss it and reveal the tank. Hover/poke the fish in the porthole to spin it.
- Main scene: click-drag to orbit, scroll to zoom (bounded so you can't zoom outside the tank walls).
- Leva panel (top-right): live-tune boid behavior — perception radius, steering weights, wander intensity, FOV angle, obstacle vision radius, and panic curve exponent.
- Simulation is O(N²) CPU-bound — fine at ~20 fish, would need spatial partitioning or a GPU compute (GPGPU) rewrite to scale to hundreds/thousands
- The custom fish swim shader and caustics floor shader are hand-written
ShaderMaterials, so they don't automatically receive scene lighting or shadows the wayMeshStandardMaterialdoes — this is a known trade-off, not a bug - Wall