voxolith

Creatures

@voxolith/gen-creature: a rigged, animated voxel rat with a 26-bone rig, seven generated clips and a layered interior that damage exposes.

The five creature presets: rat, grey rat, lab rat, black rat and fat rat

@voxolith/gen-creature makes rigged, animated creatures: a rat first, with other small quadrupeds to follow from the same shape and different parameters. The entity is a rest-pose model whose voxels each know their bone, a rig, and a set of generated clips. Play, blend and bake them with @voxolith/engine/animation.

import { seededRandom } from "@voxolith/renderer/core";
import { generateCreature, PRESETS } from "@voxolith/gen-creature";
import { bakePose, makeAnimator, poseMatrices } from "@voxolith/engine/animation";

const { entity } = generateCreature(PRESETS.rat, seededRandom(1));
const anim = makeAnimator(entity, "walk");

// every frame:
anim.update(dt);
const posed = bakePose(entity.model, entity.rig!, poseMatrices(entity.rig!, anim.pose()), { yaw });

For many animated creatures, bake through the engine's pose cache and crowd rather than one bakePose per frame each; see stamping and crowds.

Rig and clips

The rig has 26 bones: pelvis, spine, chest, neck, head, two ears, a seven-bone tail, and upper, lower and foot for each of the four legs (FL, FR, HL, HR, exported as LEGS). Each bone rotates about its head; the model binds every voxel to one bone through model.bones.

Frames of the walk, turn-left, turn-right, run, idle, sniff and death clips

The clips are generated from the gait parameters (stride, pace, bounce, tailSway):

cliploopnotes
walkyeslegs in diagonal pairs, a small bob, a foot.<leg> event at each footfall
turn-left, turn-rightyesthe walk with the spine, neck and head yawed into the turn and the tail swung out of it
runyesa bounding gallop with the spine flexing; foot.front and foot.hind events
idleyes3 s of breathing, bursts of sniffing and an ear twitch
sniffyes2 s rearing up on the hind legs and looking around
deathnoa roll onto the side, going limp

Clips are keyed at 12 frames per second, like sprite animation: it suits voxels, where a limb only moves a whole voxel or so between keys anyway, and lets a host cache baked poses per frame. Loops repeat their first key at the end, so they close exactly. Rotations are quaternions relative to rest, about each bone's head, in its parent's frame; clips can also carry a root offset (a bob, a crouch) and named events.

Inside

A rat cut open along its length, showing fur, fat, flesh, bone and organs

Under the fur there is fat, flesh and muscle by depth, a skeleton down the spine, legs and tail, a skull around a brain, lungs and a heart in the chest and a gut in the belly. The body is built with gen-kit's RiggedVolume: masses are filled per bone, parents before children so each child owns its joint, then layerInterior re-roles the mass by depth and bone cores and organs are stamped afterwards.

All of it is out of sight until wound carves the model or sever takes a limb off. Both work on the rest model, so damage follows the creature through every pose baked afterwards. When a pose uncovers flesh that was buried at rest (under a swinging haunch), the rig's cover table, which maps each interior role to fur, draws it as fur; bakePose uses it by default. Only damage ever shows the inside.

The 19 roles are the eleven visible on a whole animal (fur, dark and light fur, belly, skin, dark skin, claw, eye, eye shine, nose, tooth) and eight inside (fat, flesh, muscle, bone, marrow, organ, dark organ, blood), exported as INTERIOR. Eyes, organs and blood carry a glossy material hint.

Resolution rules

The proportions are chosen so the rat animates without losing the voxel look:

  • nothing animated is thinner than 2 voxels, except the last third of the tail;
  • at least 4 voxels between joints, so a 15 degree bend moves a limb end by a voxel;
  • the detail budget goes to the head (eyes, ears, snout, incisors); the body stays simple masses;
  • legs are longer than a real rat's (legLength 1.5): a real rat's legs hide under its belly, and a gait nobody can see does not read.

The default rat is about 3.5k voxels and 26 bones, and bakes a posed model in about 2 ms (bun run --cwd generators/creature bench measures it).

Sizing for a world

The presets are stylised game rats: 75 voxels nose to tail tip at size 1.2, which is right beside a 10 voxels-per-metre house, but a 75 cm rat in a 1 cm world. The creature has no finer scales: it is authored near that resolution already. Instead, atScale(params, voxelsPerMetre) sizes any preset for a world with a fixed unit, keeping the presets' ratios to each other and snapping to the 0.05 grid of shape.size (never below 0.7):

import { atScale, generateCreature, PRESETS } from "@voxolith/gen-creature";

const rat = generateCreature(atScale(PRESETS.rat, 100), seededRandom(1)).entity;
// size 0.8: 49 voxels long, a large brown rat of about 50 cm

The floor of 0.7 keeps the legs two voxels thick, so below a certain scale atScale cannot give a rat its real size and it comes out larger than life. For the default rat:

voxels per metreshape.sizenose to tail tip
100.7 (held)43 voxels, about 4.3 m
200.7 (held)about 2.15 m
500.7 (held)about 0.86 m
1000.849 voxels, about 0.5 m: real size

Two helpers share atScale's maths, so an app can decide without hard-coding a threshold. realSizeAt(params, voxelsPerMetre) says whether atScale needs no clamping there, and minVoxelsPerMetre(params) is the smallest scale where it holds: about 84.4 for the default rat, 88 for the fat rat. realSizeAt is also false above about 253 voxels per metre, where the size is held at its upper bound of 2 and the creature comes out smaller than life.

import { atScale, generateCreature, minVoxelsPerMetre, PRESETS, realSizeAt } from "@voxolith/gen-creature";

// Show rats only where they are true to size; otherwise say where they would be.
const kinds = ["rat", "grey rat", "black rat"] as const;
const rats = kinds.every((k) => realSizeAt(PRESETS[k], vpm))
  ? kinds.map((k, i) => generateCreature(atScale(PRESETS[k], vpm), seededRandom(seed + i), k).entity)
  : [];
if (!rats.length) console.log(`rats from ${Math.ceil(Math.max(...kinds.map((k) => minVoxelsPerMetre(PRESETS[k]))))} voxels per metre`);

The valley example does this: its rats appear only at 100 voxels per metre, and at 20 or 50 the HUD says "rats at 1 cm". A game that wants giant rats (the tutorial) just uses atScale and lets the floor win.

Small rats are where the resolution rules bite, so below size 1 the body keeps floors: lower legs and feet stay over 2 voxels across and the first part of the tail over 1.5; there is no fat layer; and bone and organs sit one voxel shallower, so a wound still shows them.

Presets

presetdiffers from rat
rata brown rat with a pale belly
grey ratgrey coat
lab ratwhite, red-eyed, barely mottled
black ratblack, slimmer, longer tail, bigger ears
fat ratmore girth, slower pace, more bounce

Development

bun run --cwd generators/creature verify            # binding, hidden interior, chest slice, every clip in one piece, footfalls, damage
bun run --cwd generators/creature preview species   # also: cut, clips, walk, turns; --scale 100 for the real-size rat
bun run --cwd generators/creature bench             # pose and bake cost

verify runs every clip and damage check at both the preset size and the 100 voxels-per-metre size.

Creature

Id
voxolith/creature
Version
0.2.0
Scales
10 voxels per metre

Parameters

Shape

ParameterPathRangeDefault
Size
scales the whole creature, about 62 voxels nose to tail per unit; below about 0.7 legs thin under 2 voxels
shape.size0.7 to 2, step 0.051.2
Body length
length of the torso as a multiple of the species' proportions
shape.bodyLength0.7 to 1.5, step 0.051
Girth
body width and depth as a multiple; higher is a plumper creature
shape.girth0.7 to 1.5, step 0.051
Head length
head length from neck to nose as a multiple
shape.headLength0.7 to 1.4, step 0.051
Snout taper
how far the snout narrows to the nose: 0 blunt, 1 pointed
shape.snout0 to 1, step 0.050.6
Ears
ear size as a multiple
shape.ears0.6 to 1.6, step 0.051
Leg length
leg length as a multiple; longer legs carry the body higher off the ground
shape.legLength0.8 to 2, step 0.051.5
Leg thickness
leg and foot thickness as a multiple
shape.legThickness0.8 to 1.5, step 0.051
Tail length
tail length as a multiple
shape.tailLength0.4 to 1.5, step 0.051
Tail thickness
tail thickness as a multiple
shape.tailThickness0.7 to 1.5, step 0.051

Look

ParameterPathRangeDefault
Coat
fur colour: brown, grey, white or black
look.coatbrown, grey, white, blackbrown
Belly
how much lighter the belly is than the back, 0 the same colour
look.belly0 to 1, step 0.050.7
Mottle
dark and light flecks through the fur; 0 is an even coat
look.mottle0 to 1, step 0.050.5
Red eyes
albino red eyes instead of black
look.redEyesyes / nono

Gait

ParameterPathRangeDefault
Stride
how far the legs swing in the walk and run clips, 1 natural
gait.stride0.5 to 1.5, step 0.051
Pace
walk cycles per second; the run is about twice as fast
gait.pace0.6 to 2.5, step 0.051.4
Bounce
how much the body bobs as it walks and runs; 0 glides level
gait.bounce0 to 2, step 0.051
Tail sway
how far the tail swings from side to side as it moves
gait.tailSway0 to 2, step 0.051

Presets

Parameterratgrey ratlab ratblack ratfat rat
Size1.21.21.21.21.15
Body length11111
Girth1110.91.3
Head length11111
Snout taper0.60.60.60.60.6
Ears1111.21
Leg length1.51.51.51.51.5
Leg thickness11111
Tail length1111.21
Tail thickness11111
Coatbrowngreywhiteblackbrown
Belly0.70.70.70.70.7
Mottle0.50.50.150.50.5
Red eyesnonoyesnono
Stride11111
Pace1.41.41.41.41.1
Bounce11111.3
Tail sway11111

Roles

Voxel values are these roles, in this order (value 1 is the first). The colours are the defaults; a host maps each role to a palette slot and can restyle it.

  • 1Fur fur
  • 2Fur dark fur.dark
  • 3Fur light fur.light
  • 4Belly belly
  • 5Skin skin
  • 6Skin dark skin.dark
  • 7Claw claw
  • 8Eye eye (metal)
  • 9Eye shine eye.shine
  • 10Nose nose
  • 11Tooth tooth
  • 12Fat fat
  • 13Flesh flesh
  • 14Muscle muscle
  • 15Bone bone
  • 16Marrow marrow
  • 17Organ organ (metal)
  • 18Organ dark organ.dark
  • 19Blood blood (metal)

See the API reference for generateCreature, atScale and the parameter types.

On this page