Modules / Physics & movement

box3dplugin

3D rigid bodies, joints, sensors and queries by Box3D (WebAssembly).

Putting it in a scene

The scene's sim lists the box3d kind from #plugins/box3d/box3d.sim.ts, and the scene has one { "is": "box3d" } actor, the world row. Every kind named in world.box3d.kinds is a body, every kind named in world.box3d.joints is a joint, and so is every kind that a plugin named in its kinds mods makes. A child scene inherits world.box3d from the manager scene. Any field a setting leaves out is Box3D's own default, and names that Box3D has keep Box3D's spelling, so its manual reads straight onto the scene file.

"world": {
  "box3d": {
    "gravity": [0, -9.8, 0],
    "subStepCount": 4,
    "static": [
      { "pos": [0, -0.5, 0], "rot": [0, 0, 0, 1], "shapes": [{ "box": [50, 0.5, 50], "baseMaterial": { "friction": 0.8 } }] }
    ],
    "kinds": {
      "crate": {
        "shapes": [{ "box": [0.5, 0.5, 0.5], "density": 1, "baseMaterial": { "friction": 0.6, "restitution": 0.1 }, "enableContactEvents": true }]
      }
    },
    "joints": {
      "hinge": { "type": "revolute", "enableLimit": true, "lowerAngle": -0.5, "upperAngle": 0.5 }
    },
    "mods": { "ground": "terrain3d" },
    "chunk": { "count": 33, "cell": 4 }
  }
},
"prefabs": {
  "box3d": {},
  "crate": { "pos": [0, 5, 0], "rot": [0, 0, 0, 1], "vel": [0, 0, 0], "spin": [0, 0, 0], "awake": true }
}

A point is [x, y, z] and a rotation is a quaternion [x, y, z, w].

Properties

Read from world.box3d. 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: Box3D has no default for it.
.gravity, .restitutionThreshold, .hitEventThreshold, .contactHertz, .contactDampingRatio, .contactSpeed, .maximumLinearSpeed, .enableSleep, .enableContinuousFields of Box3D's world definition, under its own names. gravity is [x, y, z].
.capacityAny of staticShapeCount, dynamicShapeCount, staticBodyCount, dynamicBodyCount, contactCount.
.enableWarmStarting, .enableSpeculative, .contactRecycleDistanceBox3D 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, and made by no kinds mod, is never a body.
.jointsKind name to joint entry.
.chunk{ count, cell }: the ground's chunks, each count heights a side, cell metres apart, for the ground mod.
.mods{ ground, props, kinds } (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, enableSleep, isBullet, allowFastRotation, enableContactRecyclingFields of Box3D's body definition.
motionLocksWhich linear and angular motions are locked, as Box3D's motion locks.
enableHitEventsWhether the body reports its impacts (see The rows).
nameThe body's name inside Box3D.
mass{ mass, center, inertia: { cx, cy, cz } }. 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.
baseMaterial{ friction, restitution, rollingResistance, tangentVelocity, 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.
explosionScale, enableContactEvents, enableHitEvents, invokeContactCreation, updateBodyMassFields of Box3D'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, z] 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
box[hx, hy, hz], the half extents.
sphere{ center, radius }.
capsule{ center1, center2, radius }.
hull{ points }: the convex hull of a flat [x, y, z, …] list.
cylinder{ height, radius, yOffset, sides }. Its base is at y = yOffset (0) and its top at height.
cone{ height, radius1, radius2, slices }, with the same base and top.
rock{ radius }, a rough convex rock.
meshTriangles: { positions, indices } inline, { asset } naming a json entry of the loader catalogue that holds those two, or { make, … } one of the makers below. Each takes a scale, [1, 1, 1] if left out.
heightField{ heights, countX, countZ, scale } inline, { asset, scale } naming a json entry that holds heights, countX and countZ, or { make: "grid", rowCount, columnCount, scale, makeHoles }, or { make: "wave", rowCount, columnCount, scale, rowFrequency, columnFrequency, makeHoles }.
compound{ hulls: [{ points, pos, rot }] }: convex children on one body. Box3D allows this only on a static body.

A hull holds at most 128 vertices and 255 half edges, so a cylinder or a cone of more than about 40 sides is refused. A box, hull, cylinder, cone or rock may also carry pos, rot and scale: it is placed, turned and stretched on its body.

Mesh makerFields
boxcenter, extent, identifyEdges
hollowBoxcenter, extent
gridxCount, zCount, cellWidth, materialCount, identifyEdges
wavexCount, zCount, cellWidth, amplitude, rowFrequency, columnFrequency
torusradialResolution, tubularResolution, radius, thickness
platformcenter, height, topWidth, bottomWidth

Joint entries

A kind's entry in .joints. A joint row names the two bodies it joins.

FieldMeaning
type"distance", "revolute", "prismatic", "wheel", "weld", "spherical", "motor", "parallel" or "filter". Required.
localFrameA, localFrameBEach { pos, rot }: where the joint sits in body A's and body B's frames.
forceThreshold, torqueThresholdThe force or torque above which the joint is strained on its row.
constraintHertz, constraintDampingRatio, drawScale, collideConnectedFields of Box3D's joint definition.
the type's own fieldsThe fields of Box3D's definition for that type, under their own names. A wheel joint's, for example, include enableSuspensionSpring, suspensionHertz, spinSpeed, maxSpinTorque, enableSteering and targetSteeringAngle.

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. Each step, rows are pushed into Box3D, the world is stepped, 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 world row

The box3d 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 [i, j]: the chunks of the ground the world holds. At the start of each step, box3d builds each listed key it lacks by calling the scene's ground mod and each props mod, and drops each key it holds that is no longer listed, ground and props together. 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, its props too.

A body row

FieldMeaning
pos, rot, vel, spin, awakeWritten by box3d after every step: the position, the rotation quaternion [x, y, z, w], the linear velocity, the angular velocity [x, y, z] and whether the body is awake. A rule may write any of them, and a body whose row was written since box3d last wrote it is moved to match and woken, unless the rule wrote awake itself.
type, enabled, linearDamping, angularDamping, gravityScale, sleepThreshold, motionLocks, enableSleep, isBullet, enableContactRecycling, enableHitEvents, nameAny body field that Box3D can change on a live body. The row's value is then the body's, over its kind's.
frictionSets the friction of every one of the body's shapes, over their baseMaterial. Box3D mixes a contact's friction every step, so it holds from the next step.
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. box3d 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, maxSpeed }, 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 joint row

FieldMeaning
a, bThe two bodies the joint joins, each a row's id or -(i + 1) for static body i. box3d 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 Box3D can change on a live joint, such as a wheel's spinSpeed, maxSpinTorque and targetSteeringAngle, 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 Box3D getter it comes from: constraintForce, constraintTorque, linearSeparation, angularSeparation, and the type's own. Several share a name with a setting: a wheel's spinSpeed is the motor's target in its entry and the wheel's own spin in measured.
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, motorForce
revoluteangle, motorTorque
prismatictranslation, speed, motorForce
wheelspinSpeed, spinTorque, steeringAngle, steeringTorque
sphericaltwistAngle, coneAngle, motorTorque
weld, motor, parallel, filternone

Methods

Acts

On the box3d row. A rule calls them with w.call.

explode( x, y, z, radius, falloff, impulsePerArea, maskBits? )

Blows the world apart on the next step. Each shape within radius of the point gets an impulse for every unit of its area, 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/box3d/box3d.sim.ts. The queries ask the world as the last step left it, so the box3d 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, z, …] 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, aabb, filter? ) : number[]

The ids of the bodies whose shapes' bounds overlap the box [minX, minY, minZ, maxX, maxY, maxZ], 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 ) : Vec3

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, manifolds }: the two bodies, the other body, and a list of manifolds, each with normal, twistImpulse, frictionImpulse, rollingImpulse and points.

massOf( w, row ) : { mass, center, inertia }

A body's mass, its centre of mass in its own frame, and its rotational inertia.

pointVelocity( w, row, point ) : Vec3

The velocity of a point of the world as it rides on the body.

boundsOf( w, row ) : [minX, minY, minZ, maxX, maxY, maxZ]

The bounds of a body's shapes.

counters( w )

What the world holds, as Box3D counts it, with awakeBodyCount, the number of bodies that are awake.

staticAt( world, point ) : boolean

Whether point [x, y, z] 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 point on a surface is not inside. It answers for a box, a sphere and a capsule; a box that carries pos, rot or scale, and every other shape, is not asked of.

outline( list, i, shape, from? ) : Vec3[][]

Shape shape of static body i of list as it lies in the world, as groups of points, for a view from above to draw. A sphere is its centre and the six points a radius off it along the axes, a capsule is its two spheres so, a box, hull, cylinder, cone or rock is its hull's vertices, each child of a compound is its own group, a mesh is one group for each triangle, and a height field is the eight corners of its bounds. from is a rule's w or a page's wire, needed for a mesh or height field read from an asset.

On the page

Through plugins.box3d.

follow( id, obj )

Makes obj, anything with position.fromArray and quaternion.fromArray as a three.js object has, follow the row with that id. After every frame the object takes the row's pos and rot. The rotation is made a unit turn again before it is drawn, because a page that runs ahead eases it as a blend of two turns. If obj has userData, userData.row is set to the id. How the body is drawn is up to the actor.

drop( id )

Stops following the row with that id.

b3

Box3D itself, exported from the sim file as b3, for its standalone geometry functions (b3ShapeDistance, b3ShapeCast, b3TimeOfImpact, b3Collide…), which need no world.

Mods

Name them in world.box3d.mods, as { ground: "<plugin>", props: ["<plugin>", …], kinds: ["<plugin>", …] }. ground takes one plugin. props and kinds take a list, or one name, and every plugin in it is asked.

  • terrain3d
    ground: ground(w, [i, j]) hands back chunk [i, j] of the ground as a static body.
  • scatter3d
    props: props(w, [i, j]) hands back the fixed things that stand in chunk [i, j], each one's middle in the chunk's square, as a static body, or undefined where there are none. They are streamed with the ground.
  • box3dvehicle
    kinds: the chassis, wheel and joint kinds of its cars.
  • ragdoll3d
    kinds: the bone and bonejoint kinds of its ragdolls, in its physics mode.

kinds(w) hands back the kinds a plugin makes out of numbers of its own, { bodies: { kind: row => Body }, joints: { kind: row => Joint } }. Each entry is asked for when box3d makes that row's body or joint, so two rows of one kind may differ, as two wheels of one car with different sizes do. A kind that world.box3d.kinds or .joints lists takes that entry over a mod's, and of two plugins that make the same kind, the later in the list wins.

box3d gives one mod, to box3dvehicle: gravity(w), the gravity box3d's world steps with. It is the box3d row's gravity while the row carries one, else world.box3d.gravity, else Box3D's own default.

In the guide