Modules / Physics & movement

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

SettingMeaning
.subStepCountThe solver's substeps in each step. Required: Box2D has no default for it.
.gravity, .restitutionThreshold, .restitutionIterations, .enableRestitutionPropagation, .hitEventThreshold, .contactHertz, .contactDampingRatio, .contactSpeed, .maximumLinearSpeed, .enableSleep, .enableContinuous, .capacityThe fields of Box2D's world definition, under its own names. gravity is [x, y].
.enableWarmStarting, .contactRecycleDistanceBox2D switches that its world definition has no field for. They are set once the world is made.
.staticThe 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).
.kindsKind name to body entry. A kind left out is never a body.
.jointsKind name to joint entry. A kind left out is never a joint.
.chunkThe 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.

FieldMeaning
shapesThe body's shapes. Required.
type"dynamic", "kinematic" or "static".
linearDamping, angularDamping, gravityScale, sleepThreshold, safetyFactor, enableSleep, isBullet, allowFastRotation, enableContactRecyclingFields of Box2D's body definition.
motionLocks{ linearX, linearY, angularZ }, each true to lock that motion.
enableContactEvents, enableHitEventsWhether the body reports what it touches and what it hits (see The rows).
nameThe 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.

FieldMeaning
densitySets 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, enableSensorEventsA sensor reports the bodies inside it as visitors when both are on and the visitor's shape has enableSensorEvents too.
enableContactEvents, enableHitEvents, enablePreSolveEvents, invokeContactCreation, updateBodyMassFields of Box2D's shape definition. A body's touching and hits are reported only when one of its shapes turns the matching event on.
oneWayA 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

KeyMeaning
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.
chainA 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.

FieldMeaning
type"distance", "filter", "motor", "mover", "pogo", "prismatic", "revolute", "weld" or "wheel". Required.
localFrameA, localFrameBEach { 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, torqueThresholdThe force or torque above which the joint is strained on its row.
constraintHertz, constraintDampingRatio, drawScale, collideConnectedFields of Box2D's joint definition.
the type's own fieldsThe fields of Box2D's definition for that type, under their own names: enableSpring, hertz, dampingRatio, enableLimit, lowerAngle, enableMotor, motorSpeed and so on.

The rows

Figure 1. One step of the box2d row. Each row's changes are pushed into Box2D, the world is stepped, then the events are collected and every body's and joint's row is written back. The world is made on the first step and kept for as long as the row lives.

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.

FieldMeaning
groundA 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

FieldMeaning
pos, rot, vel, spin, awakeWritten 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, nameAny body field that Box2D can change on a live body. The row's value is then the body's, over its kind's.
force, torqueHeld: a world-frame force at the centre of mass and a torque, applied every step while the row carries them.
pushesOne 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.
touchingThe 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.
hitsThis step's impacts, each { with, point, normal, speed }. Present only while there are any, and only when a shape of its kind has enableHitEvents.
visitorsThe 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

FieldMeaning
a, bThe 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.
settingsAny 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.
measuredWritten 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).
strainedTrue 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.
TypeExtra measured readings
distancecurrentLength, springForce, motorForce
revoluteangle, motorTorque
prismatictranslation, speed, motorForce
wheelmotorTorque
pogolength, velocity, impulse
weld, motor, mover, filternone

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 at key as a static body, which the world builds for each key in the world row's ground list.

In the guide