A Babylon.js Physics V2 plugin backed by box3d-wasm, a WebAssembly build of Erin Catto's Box3D rigid body engine.
It follows the same shape as @babylonjs/havok's HavokPlugin: a class implementing IPhysicsEnginePluginV2 that
can be passed straight to scene.enablePhysics(gravity, plugin).
npm i @frsource/babylon-box3d box3d-wasm@babylonjs/core is a peer dependency -- install whichever version (^9.0.0) your project already uses.
import Box3D from 'box3d-wasm';
import { Box3DPlugin } from '@frsource/babylon-box3d';
const box3d = await Box3D();
const plugin = new Box3DPlugin(box3d);
scene.enablePhysics(new Vector3(0, -9.81, 0), plugin);Box3D's JS bindings are narrower than Havok's, so a few corners of IPhysicsEnginePluginV2 are best-effort:
- Shapes: box, sphere, capsule and convex hull are fully supported.
CONTAINERshapes are supported by attaching every child shape to the same box3d body.MESH,HEIGHTFIELDandCYLINDERare not supported by box3d-wasm and throw a descriptive error. - Bodies: only a single instance per
PhysicsBodyis supported (no thin-instance batching) —initBodyInstancesfalls back to treating the mesh's own transform as a single body. - Constraints:
BALL_AND_SOCKET,DISTANCE,HINGE,SLIDER/PRISMATIC,LOCKandSIX_DOF(approximated with a weld/motor joint) map onto box3d's spherical/distance/revolute/prismatic/weld joints. Per-axis limit APIs are best-effort since box3d joints are already axis-specific (unlike Havok's generic 6-DOF constraint). raycastonly returns the closest hit per query (box3d-wasm exposescastRayClosest, not a multi-hit sweep).
Some box3d-wasm features have no equivalent in Babylon's IPhysicsEnginePluginV2 -- most notably its car wheel
joint (suspension + steering + drive in one joint), used for vehicles rather than generic constraints, plus its
filter and parallel joints. Box3DPlugin exposes the underlying world: Box3DWorld publicly for exactly this:
create bodies through Babylon as usual, then reach plugin.world.createWheelJoint(bodyA, bodyB, options) (or
createFilterJoint/createParallelJoint) directly.
box3d-wasm ships no bundled types at all, so this package's box3d-wasm.d.ts declares only what Box3DPlugin
itself uses. extraJoints.d.ts augments it with the wheel/filter/parallel joint surface (verified present in the
compiled box3d-wasm@0.2.0 binary by instantiating it and inspecting the live prototypes) for consumers who need
raw access. Both are ambient module declarations; index.ts pulls them into the program via triple-slash
references, so any package that does import { Box3DPlugin } from '@frsource/babylon-box3d' gets them for free --
no tsconfig.json changes needed, as long as you access box3d-wasm's types only through Box3DPlugin itself
(e.g. plugin.world.createWheelJoint(...)).
If your own code also writes a direct import ... from 'box3d-wasm',
TypeScript won't honor a dependency's augmentation of an already-resolved-but-untyped module for that import (see
TS2665). In that case, we recommend adding "include" property into your tsconfig.json like on the example:
// tsconfig.json
{
"include": ["node_modules/@frsource/babylon-box3d/dist"]
}docs/ is a standalone Vite + Babylon.js app that ports
monteslu/threejs-box3d-demo (originally three.js) onto this
plugin, deployed to GitHub Pages from main. See docs/README.md for running it locally.
Copyright (c) 2026-present, Jakub FRS Freisler, FRSOURCE