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
| Setting | Meaning |
|---|---|
.subStepCount | The solver's substeps in each step. Required: Box3D has no default for it. |
.gravity, .restitutionThreshold, .hitEventThreshold, .contactHertz, .contactDampingRatio, .contactSpeed, .maximumLinearSpeed, .enableSleep, .enableContinuous | Fields of Box3D's world definition, under its own names. gravity is [x, y, z]. |
.capacity | Any of staticShapeCount, dynamicShapeCount, staticBodyCount, dynamicBodyCount, contactCount. |
.enableWarmStarting, .enableSpeculative, .contactRecycleDistance | Box3D 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, and made by no kinds mod, is never a body. |
.joints | Kind 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.
| Field | Meaning |
|---|---|
shapes | The body's shapes. Required. |
type | "dynamic", "kinematic" or "static". |
linearDamping, angularDamping, gravityScale, sleepThreshold, enableSleep, isBullet, allowFastRotation, enableContactRecycling | Fields of Box3D's body definition. |
motionLocks | Which linear and angular motions are locked, as Box3D's motion locks. |
enableHitEvents | Whether the body reports its impacts (see The rows). |
name | The 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.
| Field | Meaning |
|---|---|
density | Sets 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, enableSensorEvents | A sensor reports the bodies inside it as visitors when both are on and the visitor's shape has enableSensorEvents too. |
explosionScale, enableContactEvents, enableHitEvents, invokeContactCreation, updateBodyMass | Fields of Box3D'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, 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
| Key | Meaning |
|---|---|
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. |
mesh | Triangles: { 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 maker | Fields |
|---|---|
box | center, extent, identifyEdges |
hollowBox | center, extent |
grid | xCount, zCount, cellWidth, materialCount, identifyEdges |
wave | xCount, zCount, cellWidth, amplitude, rowFrequency, columnFrequency |
torus | radialResolution, tubularResolution, radius, thickness |
platform | center, height, topWidth, bottomWidth |
Joint entries
A kind's entry in .joints. A joint row names the two bodies it joins.
| Field | Meaning |
|---|---|
type | "distance", "revolute", "prismatic", "wheel", "weld", "spherical", "motor", "parallel" or "filter". Required. |
localFrameA, localFrameB | Each { pos, rot }: where the joint sits in body A's and body B's frames. |
forceThreshold, torqueThreshold | The force or torque above which the joint is strained on its row. |
constraintHertz, constraintDampingRatio, drawScale, collideConnected | Fields of Box3D's joint definition. |
| the type's own fields | The 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.
| Field | Meaning |
|---|---|
ground | A 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
| Field | Meaning |
|---|---|
pos, rot, vel, spin, awake | Written 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, name | Any body field that Box3D can change on a live body. The row's value is then the body's, over its kind's. |
friction | Sets 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, 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. 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. |
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 joint row
| Field | Meaning |
|---|---|
a, b | The 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. |
| settings | Any 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. |
measured | Written 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. |
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, motorForce |
revolute | angle, motorTorque |
prismatic | translation, speed, motorForce |
wheel | spinSpeed, spinTorque, steeringAngle, steeringTorque |
spherical | twistAngle, coneAngle, motorTorque |
weld, motor, parallel, filter | none |
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: theboneandbonejointkinds 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.