Physics · interface

Body

Moving body: center position + half extents. Mutated in place by moveAxis.

Explained in Your first game, Rigid bodies.

interface Body
import type { Body } from '@driftengine/physics';

Properties

NameTypeDescription
xnumber
ynumber
znumber
hxnumber
hynumber
hznumber
upXoptionalnumberWhich way is up for this body, as a unit vector. Omitted means world up.
More

A body is a box, and a box has an orientation whether or not anything models one. Nothing here did: every volume in this module was world-axis, so a body tilted to lie along a banked deck — which is how a route is drawn and how a character is drawn standing on it — collided as though it were still standing bolt upright. The drawn body then reaches outside its own collision volume by roughly hy * sin(tilt), which on the 41-degree deck where every measured failure sits is about half a metre of body with nothing solid around it: it passes into geometry that looks solid, and geometry that looks clear stops it.

Only the up axis, deliberately. It is what a surface has to give (SurfaceHit carries a normal, not a frame) and what a body resting on one needs; roll about that axis cannot change a box's world-axis bounds by more than its own cross-section, and inventing a full frame here would mean inventing a heading for bodies that have none. bodyBounds treats the remaining freedom conservatively.

Two invariants for anything that sets it: it must be unit length, because bodyBounds trusts that rather than paying a square root per query in the tick; and it must be set, not accumulated, so a fixed-timestep replay cannot drift.

upYoptionalnumber
upZoptionalnumber
fwdXoptionalnumberWhich way this body faces, as a hint. Omitted means +Z, and it is only a hint: bodyFrame orthogonalises it against up, so a caller with a yaw and a surface normal does not have to reconcile them itself.
More

Matters only for a body whose cross-section is not square — a box that is wider than it is deep collides differently depending on which way it is turned, and that is exactly the "crossing objects with different orientation" case.

fwdYoptionalnumber
fwdZoptionalnumber
shapeoptionalConvexShapeThe volume this body really is, in its own frame. Unset means the hx/hy/hz box, which is also what the shape's absence has always meant.
partsoptionalreadonly BodyPart[]A body made of parts — a character is a torso, a head, limbs and skates, and with parts set it collides as exactly those, wherever its frame tilts them. Takes precedence over shape. Set, not accumulated, like up.
pivotYoptionalnumberWhere the frame rotates, as an offset from the centre along local Y. Unset means the centre; a standing character wants its feet (-hy), because that is where the drawn body pivots when it leans onto a bank — a centre-pivoted tilt swings the feet through the floor and buries a ground-snapped body up to 0.4 m into the very deck it stands on, while a feet-pivoted one keeps them planted and swings the head, exactly like the avatar it collides for. Upright bodies are unaffected bit for bit.
contactSupportoptionalbooleanWhether the contact that stopped the last blocked downward moveAxis was support — the body arriving on top of a face whose plane is walkably horizontal — as opposed to a graze against a side, an edge or an underside. Written by moveAxis on downward Y moves only; true when the move was not blocked at all.
More

Exists because "the fall was stopped" and "the body is standing on something" are different facts, and every rest rule that conflated them let a body hang in mid-air by a skate corner kissing a slab's flank — measured live as a character standing over the void beside a drawn pad, half a metre below its top. A caller that never reads it loses nothing: the resolution itself is unchanged, bit for bit.

contactRimXoptionalnumberThe way off a face this body is perched on the rim of, as a horizontal unit vector. Zero when the last downward move was supported, was not blocked, or was refused for any other reason.
More

A rim perch's contact normal points straight up and that is not a mistake — up is genuinely the way out of the clamp. It is also of no use to a caller trying to get a body off something it has been told it is not standing on: the only direction that helps is sideways, and which sideways is a fact about the obstacle's footprint that nothing outside the sweep can see.

Without it a body could be held in clear air indefinitely. contactSupport correctly refuses a foot's corner on a nine-centimetre sliver of a slab, so nothing grounds; the blocked fall re-clamps the vertical speed every tick, so gravity never accumulates; and a consumer sliding along the contact normal finds no horizontal component to slide along. Measured on a courtyard pillar: five seconds of held input, zero metres of descent, and the body still exactly where it started.

contactRimZoptionalnumber
contactNormalXoptionalnumberWhich way is out of whatever clamped the last blocked moveAxis — a unit vector pointing from the obstacle toward the body.
More

Only meaningful on a move that was actually blocked (compare the returned distance with the requested one); left as it was otherwise, so a caller reading it after an unobstructed move is reading history.

The sweep has always known this and always discarded it. foldAxis picks the separating axis that decides the contact and already judges which side of it the body is on — that is exactly contactSupport, reduced to a boolean about one special case. Keeping the direction instead of the boolean is what lets a caller slide along a face rather than stop dead against it, and that distinction turns out to be the difference between a wall and a trap: a body whose every axis is refused has nowhere to go, and axis-separated movement refuses both axes at any corner. Measured on two separate reports as a character held motionless and unable to jump for as long as anyone waited.

Written, not accumulated, exactly like up — a fixed-timestep replay cannot afford a field that drifts.

contactNormalYoptionalnumber
contactNormalZoptionalnumber