Skip to main content
AI tutorials

Build Procedural, Animated Animals in Three.js

Learn how to install Procedural Animals, create a seeded animal, control movement and actions, handle events, configure workers, bake assets for fast loading, and select quality settings for anything from cinematic close-ups to large crowds.

Build Procedural, Animated Animals in Three.js

What Is three.js Procedural Animals?

three.js Procedural Animals is a library containing 24 runtime-generated animals for three.js. Instead of downloading models, textures, rigs, or animation clips, you call createAnimal() and the library constructs a complete animated animal in code.

Each animal is sculpted from signed distance fields, meshed, rigged, skinned, covered with fur, feathers, or scales, and animated with inverse-kinematics gaits and actions. The available body plans include pawed animals, hoofed animals, birds, swimmers, snakes, spiders, and hoppers.

You can explore every species, variant, action, and debug view in the live showcase, or inspect the source in the GitHub repository.

Key Features

  • 24 species: Options range from wolves, cats, horses, and rabbits to birds, fish, snakes, and spiders.
  • Seeded individuals: A seed produces repeatable size, proportions, coat, sex, age, and variant choices unless you override them.
  • Runtime animation: Animals automatically select suitable gaits and expose actions such as sitting, sleeping, eating, attacking, and jumping.
  • Five quality tiers: Choose from hero, high, medium, low, and crowd.
  • Worker-based construction: Builds run in module Web Workers by default, with a fallback to the calling thread.
  • Offline baking: Prebuild an individual as a .animal file for loading in approximately 20 milliseconds.
  • Game-oriented controls: Steer animals, move them to destinations, follow physics bodies, track targets, play actions, and receive gameplay events.
  • TypeScript support: The package uses ES modules and includes TypeScript declarations.

Installation

The library requires three.js r160 or later as its only peer dependency. Install the package and three.js with npm:

npm install procedural-animals three

If the package is not yet available from npm, install it directly from GitHub:

npm install github:majidmanzarpour/threejs-procedural-animals three

Create Your First Animal

Start by importing three.js and createAnimal. The function is asynchronous because building the mesh, rig, coverings, and related data takes time.

import * as THREE from 'three';
import { createAnimal } from 'procedural-animals';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0xa9c1e0);

const camera = new THREE.PerspectiveCamera(
  40,
  innerWidth / innerHeight,
  0.1,
  500
);
camera.position.set(6, 3, 8);

const sun = new THREE.DirectionalLight(0xfff3e4, 2.7);
sun.position.set(6, 10, 4);
sun.castShadow = true;

scene.add(
  sun,
  new THREE.HemisphereLight(0xdde6f2, 0x5f5a52, 1.15)
);

const floor = new THREE.Mesh(
  new THREE.PlaneGeometry(200, 200).rotateX(-Math.PI / 2),
  new THREE.MeshStandardMaterial({ color: 0x77736a })
);
floor.receiveShadow = true;
scene.add(floor);

async function main() {
  const wolf = await createAnimal('wolf', {
    seed: 3,
    quality: 'high'
  });

  scene.add(wolf.object);

  let last = performance.now();

  renderer.setAnimationLoop((now) => {
    const dt = (now - last) / 1000;
    last = now;

    wolf.move({
      speed: wolf.gears.walk,
      heading: now / 1000 * 0.3
    });

    wolf.update(dt);
    camera.lookAt(wolf.position);
    renderer.render(scene, camera);
  });
}

main();

There are several important rules in this example:

  1. Create the animal inside an async function and await the result.
  2. Add animal.object to the scene, but keep that object's transform at identity. The animal manages its own movement.
  3. Use methods such as move(), moveTo(), or follow() to steer it.
  4. Call update(dt) exactly once per frame, with dt measured in seconds.
  5. Use speeds from animal.gears because available gear names can vary by species.

Avoid top-level await in the entry module. According to the project documentation, it can deadlock a Vite production build.

Movement and Navigation

Direct Steering

The move() method takes a speed in metres per second and a heading in radians. A heading of 0 faces the positive Z axis.

wolf.move({
  speed: wolf.gears.walk,
  heading: Math.PI / 2
});

Call stop() when the animal should stop moving:

wolf.stop();

Move to a Destination

Use moveTo() to send an animal toward a world-space point. It emits an arrive event after reaching the destination.

wolf.moveTo(
  new THREE.Vector3(10, 0, 4),
  { speed: 3 }
);

wolf.on('arrive', () => {
  console.log('The wolf arrived.');
});

Follow a Physics Body

If another system controls a body, pass its current position and velocity to follow() every frame:

wolf.follow(body.position, body.velocity);

Track a Target

Make the animal turn its head toward a target with lookAt(). Pass null to stop tracking.

wolf.lookAt(player.position);

// Later:
wolf.lookAt(null);

Play Actions

Every species supports the common actions jump, sit, lie, sleep, eat, drink, attack, hit, death, and stand. Species can also provide specialized actions. For example, snakes can coil and strike, birds can take off and land, and rabbits can hop and thump.

The play() method returns a Promise whose result can be done, interrupted, stopped, or refused.

const result = await wolf.play('attack');
console.log(result);

Inspect animal.actions rather than assuming that a species supports a specialized action. You can stop a named action with stopAction():

wolf.stopAction('attack');

Respond to Animal Events

The event API lets game systems react to animation and movement. Available events include footstep, attackHit, actionStart, actionEnd, arrive, takeoff, land, death, vocalize, and wingbeat.

const onFootstep = (event) => {
  sfx.play('step', event.position, event.strength);
};

wolf.on('footstep', onFootstep);

wolf.on('attackHit', (event) => {
  damage(event.position, event.direction);
});

// Remove a listener when it is no longer needed.
wolf.off('footstep', onFootstep);

Configure Terrain and Water

The ground option is a function that returns terrain height for an X and Z coordinate. Its coordinates must be in the space of animal.object.parent, and the returned height must match the terrain rendered in the scene.

const deer = await createAnimal('deer', {
  ground: (x, z) => terrainHeightAt(x, z),
  position: new THREE.Vector3(4, 0, 8),
  heading: Math.PI
});

Swimmers require a water function and a starting position below the surface. The function returns the water surface height or null where there is no water.

const fish = await createAnimal('fish', {
  water: (x, z) => 2,
  position: new THREE.Vector3(0, 1, 0)
});

scene.add(fish.object);

Land animals can also use the water definition for drinking and wading. Birds can receive a perches array containing THREE.Vector3 points they may fly to.

Choose Species, Variants, and Individuals

The library includes wolves, dogs, foxes, cats, lions, cheetahs, bears, rats, rabbits, horses, deer, cows, pigs, sheep, goats, boars, frogs, chickens, crows, eagles, fish, sharks, snakes, and spiders.

Use seed to create a repeatable individual. You may also explicitly select its variant, sex, or age:

const cat = await createAnimal('cat', {
  seed: 12,
  quality: 'high',
  variant: 'tuxedo',
  sex: 'female',
  age: 'adult'
});

String species identifiers are loaded on demand. If an application only needs one species, import that species directly to keep the bundle focused:

import wolfSpecies from 'procedural-animals/species/wolf';
import { createAnimal } from 'procedural-animals';

const wolf = await createAnimal(wolfSpecies, {
  seed: 3
});

Select the Right Quality Tier

  • hero: Approximately 110,000 to 130,000 vertices for cinematic close-ups.
  • high: Approximately 65,000 to 80,000 vertices for a player's animal. This is the default.
  • medium: Approximately 25,000 to 32,000 vertices for mid-distance views and phones.
  • low: Approximately 11,000 to 15,000 vertices for herds of roughly 10 to 20 animals.
  • crowd: Approximately 5,000 to 8,000 vertices for crowds of around 100 animals, with one draw call.

You can change coat detail on an existing mesh with setQuality(). For large crowds, combine the crowd tier with setLod(2) to reduce motion detail:

const sheep = await createAnimal('sheep', {
  seed: 5,
  quality: 'crowd'
});

sheep.setLod(2);

Build results are cached by species, seed, quality, and variant. A herd of 100 animals using six seeds therefore requires only six distinct builds. The documented CPU cost is approximately 0.1 milliseconds per animal per frame.

Configure Web Workers

Animal builds take approximately 0.4 to 6 seconds. By default, construction runs in module Web Workers. If Worker is unavailable, the library falls back to the calling thread.

Vite Configuration

// vite.config.js
export default {
  worker: { format: 'es' },
  build: { target: 'es2022' },
  optimizeDeps: {
    exclude: ['procedural-animals']
  }
};

webpack 5 and Parcel 2 require no additional worker configuration.

Other Bundlers

For another bundler, bundle the worker manually:

npx esbuild node_modules/procedural-animals/src/worker.js --bundle --format=iife --minify --outfile=public/animal-worker.js

Then register a factory that creates the worker:

import { setWorkerFactory } from 'procedural-animals';

setWorkerFactory(
  () => new Worker('/animal-worker.js')
);

You can disable worker construction for a particular animal, but doing so blocks the calling thread for the duration of the build:

const wolf = await createAnimal('wolf', {
  worker: false
});

Bake Animals for Fast Loading

For a known individual, bake it ahead of time into a .animal file. This replaces the runtime build delay with loading that the README estimates at approximately 20 milliseconds.

npx procedural-animals-bake wolf --seed 3 --quality high -o public/wolf-3.animal

Fetch the generated file as an ArrayBuffer and pass it through the baked option:

const wolf = await createAnimal('wolf', {
  baked: fetch('/wolf-3.animal')
    .then((response) => response.arrayBuffer())
});

The baked individual should correspond to the species and configuration prepared by the command.

Advanced Tips

Use Read-Only Motion State

Read position, heading, speed, velocity, and state to coordinate cameras, gameplay, or interfaces. Do not manipulate the animal's root object to produce movement.

Attach Props to Named Points

The attachments collection provides points such as mouth, head, and back. Birds also expose foot attachments, while horses provide riding-related points including seat, stirrups, and bits. Parent props to these attachment objects rather than manually approximating their animated positions.

Use Debug and Covering Controls

The animal API includes setDebug(mode, on) with the modes albedo, weights, material, and bare. Use setCoverings(on) to toggle fur shells.

wolf.setDebug('weights', true);
wolf.setCoverings(false);

Calculate a Heading Toward a Point

Because heading zero faces positive Z, use Math.atan2(dx, dz) when calculating the direction toward a target:

const dx = target.x - wolf.position.x;
const dz = target.z - wolf.position.z;
const heading = Math.atan2(dx, dz);

wolf.move({
  speed: wolf.gears.walk,
  heading
});

Clean Up Resources

Call dispose() when an animal is permanently removed. This frees its GPU resources and removes it from the scene.

wolf.dispose();

Useful Exports and Documentation

The main package also exports listSpecies, loadSpecies, setWorkerFactory, clearBuildCache, and QUALITY:

import {
  createAnimal,
  listSpecies,
  loadSpecies,
  setWorkerFactory,
  clearBuildCache,
  QUALITY
} from 'procedural-animals';

For every option, event payload, state field, and recipe, consult the repository's complete reference and TypeScript declarations.

Conclusion

three.js Procedural Animals provides a code-first way to add varied, rigged, and animated wildlife to a three.js application. Begin with createAnimal(), add the returned object to the scene, steer it through the animal API, and update it once per frame. Workers, baking, seeded caching, and multiple quality tiers let the same workflow support both detailed close-ups and large crowds.