creature
Rigged, animated rats from gen-creature, baked through a pose cache and stamped with makeBrickStamper. Tap one to wound it, tap again to take a limb off.
Eight rats in four coats wander a small field, turning, sniffing and idling. Tap or click one: the first hit opens a wound where you hit it (fur onto fat, flesh and bone) and it runs; a second hit on a leg, the tail or the head takes it off, and the piece flies off as a gib that tumbles and comes to rest. Blood drops fly and stain the ground. Enough damage and the rat dies.
What it shows
- Rigged entities.
generateCreaturereturns a model whose voxels each belong to a bone, a rig and clips. See Creatures for the parameters and resolution rules. - Playing and baking by hand.
makeAnimatorplays and cross-fades clips;sampleClip,poseMatricesandbakePoseturn a clip time into posed voxels at any heading;makePoseCachekeeps them, keyed by variant, damage, clip, frame and one of 16 headings, so most frames cost a stamp, not a bake. - Moving things through the world.
makeBrickStamperandtoSpritewrite each rat, gib and blood drop, and restore what they covered when they move. - Damage.
woundcarves the rest model around a point;seversplits off a bone's subtree as a piece with its own rig; both take arimrole for the cut surface. - Picking a bone. A tap is marched through each rat's posed box; the hit voxel's bone and the
inverse of its pose matrix (
invertRigid,transformPoint) give the point in rest space where the wound goes. - Taps, not drags.
recogniseGestureswith the orbit camera on the same input: a drag turns the view, a tap hits.
The interesting part
The pose for a frame, from the cache:
const posed = (r: Rat) => {
const clip = r.anim.clip();
const f = Math.floor(r.anim.time() * FPS);
const yb = ((Math.round((r.yaw / (Math.PI * 2)) * YAWS) % YAWS) + YAWS) % YAWS;
const key = `${r.kind}.${r.id}.${r.damage}|${clip}|${f}|${yb}`;
return cache.get(key, () => {
const c = r.entity.clips!.find((k) => k.id === clip)!;
const n = r.rig.bones.length;
const mats = poseMatrices(r.rig, sampleClip(c, f / FPS, n));
const yaw = (yb / YAWS) * Math.PI * 2;
const model = bakePose(r.rest, r.rig, mats, { yaw });
return { model, sprite: toSprite(model), mats, yaw };
});
};and a hit, which wounds on the first tap and severs on the next:
const cut = r.hits > 1 ? severable(r, h.bone) : null;
if (cut !== null && !r.lost.has(cut)) {
const res = sever(r.rest, r.rig, cut, { rim: ROLE.BLOOD });
r.rest = res.body;
// ... the piece becomes a gib
} else {
r.rest = wound(r.rest, h.rest, 2.2, { rim: ROLE.BLOOD });
// ...
}
r.damage++;Bumping damage changes the cache key, so the damaged body is baked afresh while undamaged rats
keep sharing poses.
For many creatures without the per-rat damage, makeCrowd
does the caching and stamping for you: see the swarm and the
tutorial's rats.
URL options
| option | default | effect |
|---|---|---|
?count= | 8 | number of rats, 1 to 40 |
?seed= | rats | a different field and different rats |
Plus the common ones, including ?perf.
temporal
A small moonlit scene with a one-tap toggle for the renderer's temporal accumulation of shadows and AO, and a readout of what a frame costs with it off and on.
swarm
Hundreds of animated rats through one makeCrowd: a shared pose cache, a bake budget, distance levels of detail, and a live overlay of what it costs.