Ragdoll Solver

The acron.ragdoll module is a small, fast simulation of a limp body: the ragdoll that a character becomes when it dies, tumbles down the stairs and comes to rest. It is a port of the ragdoll solver of the Tesseract engine (the Cube 2 family), a Verlet particle system: the body is a cloud of weighted points held together by distance limits, with triangles of points that give the bones their orientation and rotation limits that keep joints in range. There is no rigid-body engine behind it, which is the point – a body costs microseconds per step, so a battlefield’s worth of them fit in a frame.

It is not a general physics engine. The world is a flat floor, collision is with that floor only, and the body does not push anything. For characters that interact with the scene while alive, use Using Bullet with Acron and its constraints; for the moment they stop being alive, use this.

Two classes

RagdollSkel

The shared, immutable definition of a kind of body: its vertices with their radii, the triangles that carry bones, the distance limits and the rotation limits between triangles. Built once per character type and shared by every body of that type.

RagdollData

One body: seeded from the pose at the moment of death, stepped once per frame, read back into the character’s joints, and frozen once it has come to rest.

Defining a skeleton

A skeleton is built with the add_* methods, in the terms Tesseract’s ragdoll.cfg files use, so an existing definition transcribes line by line:

The rotation limit’s middle matrix is given as column vectors, the convention of Tesseract’s matrix3, so that a definition compares directly with the original.

Driving a body

A RagdollData is created from the skeleton and the scale the model is rendered at. It simulates in world space, in double precision, so a body settles identically however the engine’s float type is built.

  1. Set the floor: RagdollData.set_floor_z() with the floor height under the body, probed from the scene by the caller.

  2. Seed every vertex with RagdollData.seed_vert() – its world position in the pose at death and its velocity – and call RagdollData.finish_seed(). The animated pose gives the positions; the character’s velocity, plus the hit that killed it, the velocities.

  3. Call RagdollData.step() with the frame time, every frame. The step pays the elapsed time down in fixed substeps between get_timestep_min() and get_timestep_max(), at most get_max_substeps() of them per call, so a stalled frame cannot buy a burst of work that stalls the next.

  4. Read the vertices back with RagdollData.get_vert() and pose the character’s joints from them: each triangle’s three points give the orientation of the bone it carries, the same way Tesseract does it.

  5. Once the body has rested for get_expire_offset() seconds of simulated time, RagdollData.frozen becomes True, the pose is final and step is a no-op from then on. Bodies that are frozen can be stopped and, when the corpse is removed, dropped.

RagdollData.time is the simulated time so far and RagdollData.get_collisions() the number of floor contacts, both useful for sound and dust effects.

Tuning

The constants – gravity, the substep bounds, the constraint iteration count, the rotation, body, ground and air friction, and the settle time – default to Tesseract’s, and each has a setter and getter on RagdollData (set_gravity, set_constraint_iters, set_ground_fric, …). Set them right after construction, before seeding, and keep the values in one place in your program rather than in every body.

Why it exists

The same solver in Python costs about half a millisecond per substep, enough for a handful of falling bodies; in the engine it costs microseconds. The Arena sample, which switched to this module, went from an unplayable frame rate with thirty simultaneous ragdolls to sixty frames per second, and keeps the Python solver only as the reference its parity test compares against.