Animals
24 real animals for three.js, built, rigged and furred entirely in code: createAnimal('wolf'), add it to your scene and steer it. No model, texture or animation files. It has gaits, actions (attack, sleep, eat, die) and events for footsteps and hits.
It's procedural-animals by Majid Manzarpour (MIT), version 0.1.0, kept here as a fixed copy in vendor/procedural-animals (VENDOR.md). Its llms.txt is the complete reference, written for agents: read that first, then src/index.d.ts. The demo shows a wolf pack roaming a hill, and the other 23 species.
Animals: procedural-animals by Majid Manzarpour (MIT). three.js by its authors (MIT). The hill comes from the library's showcase (MIT). Everything else here is Patch's, MIT.
WebGL only. It's written for THREE.WebGLRenderer, and its fur is GLSL. It doesn't run on three's WebGPU renderer, so it can't share a scene with this toolbox's WebGPU blocks.
Use it in a game
Copy vendor/procedural-animals into your project. Its only dependency is three (r160 or later; the demo uses 0.186.1).
import * as THREE from 'three';
import { createAnimal } from './vendor/procedural-animals/src/index.js';
async function main() {
const wolf = await createAnimal('wolf', { seed: 3, quality: 'low', ground: (x, z) => terrainHeight(x, z) });
scene.add(wolf.object); // never move wolf.object yourself: the animal moves itself
wolf.moveTo(new THREE.Vector3(10, 0, 4), { speed: wolf.gears.trot }); // walks there, emits 'arrive'
wolf.on('footstep', (e) => sfx.play('step', e.position, e.strength));
wolf.on('attackHit', (e) => damage(e.position, e.direction));
await wolf.play('attack'); // 'done' | 'interrupted' | 'stopped' | 'refused'
let last = performance.now();
renderer.setAnimationLoop((now) => {
wolf.update((now - last) / 1000); // seconds, once a frame
last = now;
renderer.render(scene, camera);
});
}
main(); // no top-level await in your entry module: it can deadlock a Vite production build
The contract, from the library:
const a = await createAnimal(id, { seed, quality, ground, position, heading })inside anasyncfunction.scene.add(a.object), and never transforma.objectyourself.- Steer with
a.move({ speed, heading }),a.moveTo(point)ora.follow(pos, vel). a.play(action)for actions.a.update(dt)once a frame withdtin seconds, anda.dispose()when done.
Units are metres, seconds and radians, +Y is up, and heading 0 faces +Z (Math.atan2(dx, dz) faces a point). Read speeds from a.gears, since the names differ per species. ground must match the terrain you draw. Swimmers need water and a start below the surface.
Vite needs worker: { format: 'es' } and build: { target: 'es2022' }, as in vite.config.mjs. Other setups are in llms.txt.
A wolf's howl isn't an action. Stand it still and set wolf.motion.input.howl = true.
Load it fast: bake
A build takes about a second at low and about 4 s at high, in workers, so nothing freezes, but you wait. A baked animal is on screen in milliseconds:
// in Node, ahead of time: see tools/make-bakes.mjs
fs.writeFileSync('wolf.animal', new Uint8Array(writeBake(await buildAnimalData('wolf', { seed: 3, quality: 'low', worker: false }))));
// in the game
const wolf = await createAnimal('wolf', { baked: fetch('wolf.animal').then((r) => r.arrayBuffer()), ground });
A low bake is about 2 MB, or 0.8 MB gzipped. src/app.js shows how the demo unpacks gzipped ones with the browser's DecompressionStream.
A pack
The library moves one animal where it's told. src/pack.js is what the demo adds, and is meant to be copied or replaced: a leader with a goal, followers in slots, keeping apart, going round water and rocks, big birds circling and swimmers staying in the lake.
Warm it up
Do this before you show anything. src/app.js, compileAll, does all of it:
- Compile before the first animal. The coat's main shader takes about 5 s to compile on Windows the first time (measured on an i9 and an RTX 4090, on Direct3D 11), and it blocks if you draw before it's done. Use
renderer.compileAsync. - Keep one of each alive. three frees a shader with the last material using it. Dispose your last wolf and the next animal compiles the coat again, a freeze of seconds. Keep a hidden animal for good. Goats have their own variant of the coat, and fish their own fin shader, so the demo keeps one of each.
- Eyes are nine more programs, one per kind of eye (
EYE_KINDSinsrc/app.js). - Give the buffers a frame each.
uploadinsrc/app.jssends a new animal to the graphics card before it's seen.firstDrawsdoes the same for every program during the load: all at once, they freeze not just your game but the whole browser, whose drawing goes through the same graphics process. - Be wary of drawing under an opaque element. Under one, the whole page's frames stopped for about a second near the end of the load, in every run while the GPU was busy, and not with no cover. The demo's loading cover is at 99.6%, which ended it on a quiet GPU. Why isn't proven, and in a visible window with other work on the GPU it can still come back, at about the same moment.
- Watch for three's environment map.
PMREMGenerator.fromScenelinks two shaders at their first draw, and its 256-sample filter costs a third of a second on Direct3D.prepareLightinsrc/world/environment.jscompiles them first and lowers the samples, which uses two of three's private fields: check them if you change three's version. - Light them. The coat doesn't read
scene.environment. Calla.render.setEnvironmentLight(sh)with your sky's light (lightinsrc/app.js), or their shaded sides go nearly black.
Play as the animal
Pack.stepPlayer in src/pack.js is the library showcase's manual control, and placeCamera in src/app.js its chase camera. Keys are relative to the camera, and the library picks the gait from the speed:
const forward = camera.getWorldDirection(new THREE.Vector3()).setY(0).normalize();
const right = new THREE.Vector3(-forward.z, 0, forward.x);
const dir = forward.multiplyScalar(keyW - keyS).addScaledVector(right, keyD - keyA);
const speed = dir.lengthSq() === 0 ? 0 : shift ? topGear * 1.15 : wolf.gears.walk * 1.4;
wolf.move({ speed, heading: Math.atan2(dir.x, dir.z) });
Birds take off with climb of 0.5 or more, or when asked to go faster than their fastest ground gear. Swimmers use climb from -1 to 1. Keep the camera's heading behind the animal's with a lag (1 - exp(-dt * 2.5) here), and don't leave other animals in slots behind it, where they'd stand in front of the camera.
What it costs
Fur is the same mesh drawn again per shell, so triangles multiply. These are the demo's real numbers for one wolf:
| Quality | Mesh triangles | Shells | Drawn a frame | Use |
|---|---|---|---|---|
hero | about 240k | 40 + fins | about 10M (worked out, not measured) | Close-ups. Not in the demo's choices |
high | 148k | 28 + fins | about 4.7M | The player's animal |
medium | 62k | 16 | about 1M | Mid distance |
low | 26k | 8 | about 240k | Where the demo starts. A pack of 5 |
crowd | 14k | none | 14k | Crowds |
a.setQuality(tier) thins the coat on a mesh that's already built, with no rebuild. The demo does this at High: only the nearest two keep the whole coat.
What it's bad at
- It's heavy at its default quality.
highis the default, at about 4.7M triangles a frame per wolf. Chooselowormediumyourself unless it's one animal on a strong graphics card. Nobody has run this on a weak one: every number here is from an RTX 4090. - Crowds cost more than the library says. Each animal is its own draw, with no instancing: 100 sheep took 201 draw calls and 6.7 ms of processor a frame. Use
crowdquality,setLod(2)andcastShadow: false, and hide what's off screen, since nothing is frustum culled. - The first visit waits for shaders, as above. Plan for it in your loading screen.
- Some animals and moves are a bit janky. It's a month-old 0.1.0. Feet slide a little (under 1% of leg length in its own test on wolf and horse), the horse is plainer than the wolf, and the actions are of uneven quality from species to species.
- Real animals only. No dragons or monsters. Adding a species as data is covered in docs/AUTHORING.md, which we haven't tried.
- No collisions. It knows the ground and the water and nothing else. Steer round things yourself, or drive it from your physics with
follow. - No sound. The events are there to hang your own on (
footstep,attackHit,vocalize,wingbeat), but neither the library nor this demo has any. - Not every species has a call or a real attack. A wolf howls and a lion roars, but a horse has no call, so the demo has it jump.
The demo
Choose an animal, and a click on the ground sends them there. A click on an animal stops them. Drag to orbit and scroll to zoom.
Play as it: press WASD or the arrows, or turn on Play as the animal. Shift runs, Space jumps (or climbs, for what flies and swims, with C to go down), F attacks and H calls. The pack runs with you. The camera follows, and a drag moves it. P, the Leave button in the frame, that switch or Reset gets you out. Esc does too when the demo is opened on its own, but not on a block's page: Patch's game host keeps Esc in a frame for its own menu, so a demo there never sees it. Any block that needs a way out needs another key.
For people: Animal, Speed, Quality, Pack size, Play as the animal, and the buttons New animal, Howl or call, Attack and Sleep.
Agents also get these, in recipe.json with their ranges:
| Control | Range | Default | What it does |
|---|---|---|---|
seed | 1 to 9999 | 1 | Which individuals. 1 ships baked, and any other is built in a worker |
age | adult, juvenile | adult | Young animals have their own proportions, built in a worker |
coverings | on, off | on | Off shows the bare skinned mesh |
grass | 0.1 to 1 | 1 | How much grass is drawn |
slow-motion | 0.1 to 1 | 1 | How fast time runs, to read the gaits |
The demo opens at Low with a pack of five, and if the graphics card can't keep up it lowers the pixel count and the grass before anything is shown, and again later if it has to. On a very slow card, the last steps also take the fur off the far members of a pack. The Quality control stays what the person chose.
Build it
npm install
npm run bake # public/baked: every species at low, and the wolf at medium too
npm run build # dist/, the release
npm run dev # http://127.0.0.1:5417/
npm run thumbs # with dev running: thumbs/species, the Animal choice's pictures
tools/ also has tour.mjs (every species in turn, reporting long frames and new shader programs), moves.mjs (every move with a real mouse), play-check.mjs (playing, every kind of body), page-check.mjs (the demo in a real block page's frame: hints, keys, Reset), page-visit.mjs (a first visit, measuring what reaches the screen, around the demo too), stress.mjs (minutes of hard play, logging slow frames and memory), software.mjs and software-parts.mjs (software rendering, as a floor for a weak graphics card, and where its frame goes) and eyes-list.mjs (the kinds of eye). Credits are in CREDITS.txt, and every part's source is in LICENCE-NOTES.md.