box2dplugin
2D rigid bodies, joints, sensors and queries by Box2D v3 (WebAssembly).
Putting it in a scene
The scene's sim lists the box2d kind from #plugins/box2d/box2d.sim.ts, and the scene has one { "is": "box2d" } actor, the world row. Every kind named in world.box2d.kinds is a body, and every kind named in world.box2d.joints is a joint. A child scene inherits world.box2d from the manager scene. Any field a setting leaves out is Box2D's own default, and names that Box2D has keep Box2D's spelling, so its manual reads straight onto the scene file.
"world": {
"box2d": {
"gravity": [0, -10],
"subStepCount": 4,
"static": [
{ "pos": [0, 0], "rot": [1, 0], "shapes": [{ "box": [20, 0.5], "material": { "friction": 0.8 } }] }
],
"kinds": {
"crate": {
"shapes": [{ "box": [0.5, 0.5], "density": 1, "material": { "friction": 0.6, "restitution": 0.1 }, "enableContactEvents": true }]
}
}
}
},
"prefabs": {
"box2d": {},
"crate": { "pos": [0, 5], "rot": [1, 0], "vel": [0, 0], "spin": 0, "awake": true }
}
A point is [x, y] and a rotation is [c, s], the cosine and sine of the angle, so a row holds exactly the numbers Box2D holds.
Properties
Read from world.box2d. The world settings are read when the world is made, a kind's entry when each of its bodies or joints is made.
World
| Setting | Meaning |
|---|---|
.subStepCount | The solver's substeps in each step. Required: Box2D has no default for it. |
.gravity, .restitutionThreshold, .restitutionIterations, .enableRestitutionPropagation, .hitEventThreshold, .contactHertz, .contactDampingRatio, .contactSpeed, .maximumLinearSpeed, .enableSleep, .enableContinuous, .capacity | The fields of Box2D's world definition, under its own names. gravity is [x, y]. |
.enableWarmStarting, .contactRecycleDistance | Box2D switches that its world definition has no field for. They are set once the world is made. |
.static | The bodies that never move and that no row carries, each { pos, rot, shapes }. A body or a query's answer names static body i as -(i + 1). |
.kinds | Kind name to body entry. A kind left out is never a body. |
.joints | Kind name to joint entry. A kind left out is never a joint. |
.chunk | The ground's chunks, in whatever numbers the ground mod reads. |
.mods | { ground: "<plugin>" } (see Mods). |
Body entries
A kind's entry in .kinds. Where a body is and how it moves are on its row.
| Field | Meaning |
|---|---|
shapes | The body's shapes. Required. |
type | "dynamic", "kinematic" or "static". |
linearDamping, angularDamping, gravityScale, sleepThreshold, safetyFactor, enableSleep, isBullet, allowFastRotation, enableContactRecycling | Fields of Box2D's body definition. |
motionLocks | { linearX, linearY, angularZ }, each true to lock that motion. |
enableContactEvents, enableHitEvents | Whether the body reports what it touches and what it hits (see The rows). |
name | The body's name inside Box2D. |
mass | { mass, center, rotationalInertia }. It replaces the mass that the shapes' densities give. |
Shapes
One geometry from the list below, plus any of these fields beside it.
| Field | Meaning |
|---|---|
density | Sets the mass the shape gives its body. |
material | { friction, restitution, rollingResistance, tangentSpeed, userMaterialId, customColor }, any of them. |
filter | { categoryBits, maskBits, groupIndex }. The bits are numbers, as JSON holds no bigint. |
isSensor, enableSensorEvents | A sensor reports the bodies inside it as visitors when both are on and the visitor's shape has enableSensorEvents too. |
enableContactEvents, enableHitEvents, enablePreSolveEvents, invokeContactCreation, updateBodyMass | Fields of Box2D's shape definition. A body's touching and hits are reported only when one of its shapes turns the matching event on. |
oneWay | A direction [x, y] in the body's frame. A contact on the shape is kept only while the other shape pushes against its face that looks that way, as with a platform a body jumps up through and lands on. |
Geometries
| Key | Meaning |
|---|---|
circle | { center, radius }. |
capsule | { center1, center2, radius }. |
segment | { point1, point2 }. |
box | [hx, hy], the half extents. |
polygon | { points }: the convex hull of a flat [x, y, …] list of at most 8 points, not all on a line. |
chain | A chain of one-sided segments. { points, isLoop, materials? }, and for a chain that is not a loop ghost1 and ghost2, the points before its first and after its last. points is a flat [x, y, …] list going counter-clockwise, and each segment's solid side is on the right of its direction. materials is a list of material overrides, one per segment. Instead of those fields, { asset } names a json entry of the loader catalogue that holds them, for a chain as long as a level's ground. Of the shape fields only filter and enableSensorEvents apply to a chain, and its material is every segment's where materials gives none. |
A box or a polygon may also carry pos, rot and radius: it is placed and turned on its body, and its corners are rounded by radius.
Joint entries
A kind's entry in .joints. A joint row names the two bodies it joins.
| Field | Meaning |
|---|---|
type | "distance", "filter", "motor", "mover", "pogo", "prismatic", "revolute", "weld" or "wheel". Required. |
localFrameA, localFrameB | Each { pos, rot }: where the joint sits in body A's and body B's frames. { "pos": [0, 0], "rot": [1, 0] } if left out. |
forceThreshold, torqueThreshold | The force or torque above which the joint is strained on its row. |
constraintHertz, constraintDampingRatio, drawScale, collideConnected | Fields of Box2D's joint definition. |
| the type's own fields | The fields of Box2D's definition for that type, under their own names: enableSpring, hertz, dampingRatio, enableLimit, lowerAngle, enableMotor, motorSpeed and so on. |
The rows
The rows are all the state a seat is sent and a rule reads and writes, so two runs of the same presses land on the same bytes.
The world row
The box2d row may carry any numeric or switch setting of the world above, subStepCount and gravity included but not capacity, static, kinds, joints, chunk or mods. The row's value is then the world's, pushed on each step it differs from the one pushed last.
| Field | Meaning |
|---|---|
ground | A list of keys, each a list of numbers: the chunks of the ground the world holds. At the start of each step, box2d builds each listed key it lacks by calling the scene's ground mod, and drops each key it holds that is no longer listed. A key listed step after step is built once. A rule writes the list. A body or a query's answer names every chunk 0. |
A body row
| Field | Meaning |
|---|---|
pos, rot, vel, spin, awake | Written by box2d after every step: the position, the rotation [c, s], the linear velocity, the angular velocity (a number) and whether the body is awake. A rule may write any of them, and a body whose row was written since box2d last wrote it is moved to match. Giving a body a velocity wakes it, and writing awake sets it as written. |
type, enabled, linearDamping, angularDamping, gravityScale, sleepThreshold, safetyFactor, motionLocks, enableSleep, isBullet, enableContactRecycling, enableContactEvents, enableHitEvents, name | Any body field that Box2D can change on a live body. The row's value is then the body's, over its kind's. |
force, torque | Held: a world-frame force at the centre of mass and a torque, applied every step while the row carries them. |
pushes | One shot: a list of { impulse, point?, angular? }. Each is a linear impulse at point (the centre of mass if left out) and an angular impulse, applied on the next step. box2d then takes the field off the row. |
target | { pos, rot }. A kinematic body is moved there over the next step, and again every step while the field stays. |
wind | { wind, drag, lift }, applied to each of the body's shapes every step while the row carries it. |
touching | The ids of the bodies it is in contact with, sorted. Present only while there are any, and only when a shape of its kind has enableContactEvents. |
hits | This step's impacts, each { with, point, normal, speed }. Present only while there are any, and only when a shape of its kind has enableHitEvents. |
visitors | The ids of the bodies inside its sensor shapes, sorted. Present only while there are any. |
A force, a torque, a push, a target or wind wakes the body, unless the rule wrote awake: false in the same step.
A joint row
| Field | Meaning |
|---|---|
a, b | The two bodies the joint joins, each a row's id or -(i + 1) for static body i. box2d makes the joint on the first step that both are bodies, and it goes with either of them. |
| settings | Any field of the kind's entry that Box2D can change on a live joint, such as a wheel's motorSpeed and maxMotorTorque, together with collideConnected, constraintHertz with constraintDampingRatio, forceThreshold, torqueThreshold, localFrameA and localFrameB. The row's value is then the joint's, pushed on each step it differs. |
measured | Written every step while the joint stands, each reading named after the Box2D getter it comes from: constraintForce, constraintTorque, linearSeparation, angularSeparation, and the type's own (see below). |
strained | True on a step the joint's force or torque passed its threshold, and absent otherwise. The joint holds, and the rule decides whether it breaks. |
| Type | Extra measured readings |
|---|---|
distance | currentLength, springForce, motorForce |
revolute | angle, motorTorque |
prismatic | translation, speed, motorForce |
wheel | motorTorque |
pogo | length, velocity, impulse |
weld, motor, mover, filter | none |
Methods
Acts
On the box2d row. A rule calls them with w.call.
explode( x, y, radius, falloff, impulsePerLength, maskBits? )
Blows the world apart on the next step. Each shape whose outline lies within radius of the point gets an impulse for every unit of outline length, less toward the edge by falloff. Only shapes whose category maskBits names are pushed, and every shape is if it is left out.
Rules
From #plugins/box2d/box2d.sim.ts. The queries ask the world as the last step left it, so the box2d row must have stepped once. They answer in row ids, with a static body as -(i + 1) and a ground chunk as 0. filter is { categoryBits?, maskBits? }.
castRayClosest( w, origin, translation, filter? ) : RayHit | undefined
The first thing a ray from origin along translation meets. A RayHit is { id, point, normal, fraction }, with fraction the share of translation travelled.
castRay( w, origin, translation, filter? ) : RayHit[]
Everything a ray meets, nearest first, one hit per shape.
castShape( w, origin, points, radius, translation, filter? ) : RayHit[]
Everything a convex shape meets as it is swept along translation, nearest first. The shape is the hull of the flat [x, y, …] list points about origin, rounded by radius.
overlapShape( w, origin, points, radius, filter? ) : number[]
The ids of the bodies that a convex shape at origin overlaps, sorted, each once.
overlapAABB( w, origin, aabb, filter? ) : number[]
The ids of the bodies whose shapes' bounds overlap the box [minX, minY, maxX, maxY] taken about origin, sorted, each once.
castMover( w, origin, mover, translation, filter? ) : number
For a character moved as a capsule { center1, center2, radius }: how far along translation, from 0 to 1, a capsule at origin moves before it meets something.
collideMover( w, origin, mover, filter? ) : (Plane & { id })[]
The planes of everything that a capsule at origin touches or is inside, each { id, normal, offset, point } with the id of the body it belongs to.
solvePlanes( targetDelta, planes ) : { delta, iterationCount }
The move nearest targetDelta that the planes allow. Each plane is { plane: { normal, offset }, pushLimit, push, clipVelocity }. It needs no world.
clipVector( vector, planes ) : Vec2
The velocity with the part of it that goes into the planes taken off. It needs no world.
contacts( w, row )
Every contact a body is in. Each is { a, b, with, manifold }: the two bodies, the other body, and the manifold's normal, rollingImpulse and points.
massOf( w, row ) : { mass, center, rotationalInertia }
A body's mass, its centre of mass in its own frame, and its rotational inertia.
pointVelocity( w, row, point ) : Vec2
The velocity of a point of the world as it rides on the body.
boundsOf( w, row ) : [minX, minY, maxX, maxY]
The bounds of a body's shapes.
counters( w )
What the world holds, as Box2D counts it, with awakeBodyCount, the number of bodies that are awake.
stateHash( w ) : bigint
Box2D's hash of the whole simulation state, including what it remembers between steps. Two worlds with the same hash step alike.
bodyOf( w, row ) / jointOf( w, row )
A body row's or a joint row's Box2D id, to pass to any of Box2D's own b2Body_… and b2Joint_… functions.
staticAt( world, point, from? ) : boolean
Whether point [x, y] is inside one of the bodies of world.static, which is what to ask when a thing put somewhere might be in a wall. A segment and a chain have no inside. world is the object with static, and from is a rule's w or a page's wire, needed for a chain read from an asset.
outline( list, i, shape, from? ) : Vec2[][]
Shape shape of static body i of list as it lies in the world, as groups of points for a flat view to draw. A circle is its centre and the four points a radius off it along the axes, a capsule is its two circles so, a box or a polygon is its vertices, a segment is its two points, and a chain is one group for each of its segments.
On the page
Through plugins.box2d.
follow( id, obj )
Makes obj, anything with position.set(x, y) and a rotation in radians, follow the row with that id. After every frame the object stands where the row's pos is and turns as its rot does. With a render2d world, the point is put where render2d's place puts it, and the object turns the way the body's x axis goes on screen, so a world whose y grows up the screen draws a body's turn counter-clockwise, as Box2D's angle goes. Without one, pos is in canvas pixels and the object turns by the body's angle, from x toward y. How the body is drawn is up to the actor.
drop( id )
Stops following the row with that id.
b2
Box2D itself, exported from the sim file as b2, for its standalone geometry functions (b2ShapeDistance, b2ShapeCast, b2TimeOfImpact, b2Collide…), which need no world.
Mods
Name them in world.box2d.mods.
- tilemap
ground:ground(w, key)hands back the chunk of the ground atkeyas a static body, which the world builds for each key in the world row'sgroundlist.