threejs-fauna
v0.1.0
Published
Procedural animals for three.js: fish schools, birds and wildlife that live in the right biomes
Maintainers
Readme
threejs-fauna
Procedural animals for three.js that live where they should. Fish schools swim in rivers and seas, matched to
water type, temperature and depth. Bird flocks fly over the biomes they like. Bodies, colour patterns, swimming and
wing beats are generated (TSL, no textures or models). Groups spawn deterministically in tiles around the camera and
move as seeded, fixed-step boids that never leave their habitat. Built on a
threejs-biomes World, part of
threejs-worldgen.
npm i threejs-fauna threejs-biomes threeimport { World } from 'threejs-biomes'
import { Fauna, Fish, FishSchools } from 'threejs-fauna'
const world = new World({ seed: 1 })
const fauna = await Fauna.create({ camera, scene, renderer, world, seed: 7 }) // fish + birds
fauna.set({ birds: { density: 8 }, fish: { species: { shark: { habitat: { weight: 0 } } } } })
const schools = await FishSchools.create({ camera, scene, renderer, world }) // or one kind on its own
schools.set({ predators: [{ object: camera, radius: 3 }] }) // fish scatter from the camera
const nemo = await Fish.create({ scene, renderer, preset: 'clownfish', seed: 3 }) // one hero fish
nemo.set({ speed: 2, species: { pattern: { stripes: { count: 4 } } } })import { Fauna, Fish, FishSchools, BirdFlocks } from 'threejs-fauna/react'
<Fauna world={world} birds={{ density: 6 }} />
<group position={[0, 1, 0]}><Fish preset="tang" speed={1.5} /></group>| Export | What it does |
|---|---|
| Fish | One fish of any species, swimming in place. set({ preset, species, speed, turn, seed, states }). |
| FishSchools | Schools streamed around the camera wherever there is water deep enough for the species. |
| BirdFlocks | Flocks streamed around the camera over their biomes, in a height band above ground or water. |
| Fauna | Every kind in FAUNA_KINDS at once, sharing seed, origin, states and predators. Set a kind to false to leave it out. |
| FISH_SPECIES / BIRD_SPECIES | The built-in species as plain JSON. Pass edited or new ones through species. |
| buildFishMesh(shape) / buildBirdMesh(shape) | The generators, as typed arrays (no three.js, so they run in workers and tests). |
| Flock, GridEnvironment, spawnTile | The pure boids, habitat grid and per-tile placement the layers are built from. |
| FaunaLayer | Base class for new kinds (land herds): implement species, habitat, mesh, material and animation. |
Fish (12 presets). Freshwater: trout, carp, perch and pike. Warm reef: clownfish, tang and parrotfish. Cold sea: cod, herring and mackerel. Large open water: tuna and shark. The body is lofted from superellipse cross-sections along a spine and is watertight. Parameters: depth, width, where it is deepest, nose shape, back/belly split, tail stalk and squareness. Fins are two-sided sheets: tail (forked, lunate, rounded or truncate, with optional shark asymmetry), dorsal and anal fins (any number), pectoral and pelvic fins. The mouth and eye are painted. The pattern is drawn in the shader: countershading, bars or lengthwise stripes (wavy, banded, outlined), spots, iridescence and a silver sheen. Each fish varies its size, girth, hue, brightness and pattern phase from the seed.
Swimming is vertex animation on one InstancedMesh per species. A travelling wave A(s)·sin(k·s − phase) grows
toward the tail. Its frequency comes from the swimming speed, and the phase is integrated on the CPU, so speed
changes never jump. Turning bends the body by the turn rate, and the paired fins flutter. Birds flap with the wing
bending toward the tip, glide by turns, and bank into turns.
Placement samples the world: water depth is waterLevel − elevation. Fresh water means a river
(river > water.riverStrength) or inland water (continentalness > water.inland). Species are then picked by
water type, temperature and depth (birds by biome weights). Each group bakes a small, conservative grid of its band
(bed to surface for fish, ground plus altitude for birds), and the boids treat everything outside it as a wall. They
also keep margin plus each fish's own half-height and pitch from the bed and the surface, so no fin ever breaks
the water. Everything comes from seed, the tile and the world (no Math.random). The tests check that every fish
stays under the surface and above the bed against the real world.
Scale. Groups live in the world's local frame, which is re-centred on the camera on planets. Instance matrices
are relative to an anchor near the camera, so GPU numbers stay small anywhere (set origin for a floating origin).
World sampling is the expensive part (~0.2–0.6 ms a sample), so spawning is a generator capped at buildBudget
ms per frame. Groups beyond simDistance are drawn but not simulated. Instance buffers are written in place each
frame, with explicit bounding spheres.
Weathering. states: { wetness, snow, burn, damage } (0…1) matches the shared material-state layer of
threejs-weathering. Fish mostly ignore it (burnt and damaged fish darken and dull). Birds darken when wet and
whiten on top with snow.
Not done yet. Boids run on the CPU and are O(n²) per group: fine for hundreds of animals and fully
deterministic. For thousands, the next step is a WebGPU compute pass, with positions and velocities in
instancedArray storage buffers and a uniform-grid neighbour search. It would give up exact cross-machine
determinism. There are no lakes yet, only sea and noise rivers (the World has no lake surface), and no land herds
yet (FaunaLayer is the slot).
Full API docs: https://askalice.github.io/threejs-worldgen/docs/. Sandbox: npm run dev, then /fauna.html
(species gallery) and /fauna.html?world.
License
Apache-2.0
