Technical Documentation: erincatto/box3d
โน๏ธ Provenance: Hybrid Fusion:
erincatto/box3d(README + 10 In-Tree Chapters) ยท CodeWiki Reference ยท Recency: Active (< 180 days)
1. Project Overview & Quickstart (erincatto/box3d)
Box3D
Box3D is a 3D physics engine for games.
Features
Collision
- Continuous collision detection
- Contact events
- Convex hulls, capsules, spheres, triangle meshes, and height fields
- Multiple shapes per body
- Collision filtering
- Ray casts, shape casts, and overlap queries
- Sensor system
- Character mover
Physics
- Robust Soft Step rigid body solver
- Continuous physics for fast translations and rotations
- Island based sleep
- Revolute, prismatic, distance, motor, weld, and wheel joints
- Joint limits, motors, springs, and friction
- Joint and contact forces
- Body movement events and sleep notification
System
- Data-oriented design
- Written in portable C17
- Extensive multithreading and SIMD
- Optimized for large piles of bodies
- Cross platform determinism
- Recording and replay
Samples
- Uses sokol to run with D3D11 on Windows, Metal on macOS, and OpenGL 4.5 on Linux.
- Graphical user interface with imgui.
- Many samples to demonstrate features and performance.
Building all platforms
Building with CMake presets (recommended)
This uses the presets in CMakePresets.json.
- Windows:
cmake --preset windowsthencmake --build --preset windows-release - Linux:
cmake --preset linux-releasethencmake --build --preset linux-release - macOS:
cmake --preset macosthencmake --build --preset macos-release - Windows MinGW:
cmake --preset mingw-releasethencmake --build --preset mingw-release
Run the samples app (must be in the Box3D directory).
- Windows:
.\build\bin\Release\samples.exe - Linux:
./build/bin/samples - macOS:
./build/bin/Release/samples
Building for Visual Studio
- Install Visual Studio
- Run
build_vs2026.bat - Open and build
build/box3d.slnx
Building for Linux
- Run
build.shfrom a bash shell - Results are in the build sub-folder
Building for Xcode
- mkdir build
- cd build
- cmake -G Xcode ..
- Open
box3d.xcodeproj - Select the samples scheme
- Build and run the samples
Building for Web
- Emscripten SDK
emcmake cmake -B build -DBOX3D_SAMPLES=OFFcmake --build build
Box3D uses SSE2 with WebAssembly. Define BOX3D_DISABLE_SIMD to disable SSE2.
Building and installing
- mkdir build
- cd build
- cmake ..
- cmake --build . --config Release
- cmake --install . (might need sudo)
Using Box3D in your project
The core library has no dependencies beyond the C runtime (and libm on Unix). Linking it
gives you the box3d::box3d target.
I recommend to use FetchContent:
include(FetchContent)
FetchContent_Declare(box3d
GIT_REPOSITORY https://github.com/erincatto/box3d.git
GIT_TAG v0.1.0)
FetchContent_MakeAvailable(box3d)
target_link_libraries(my_app PRIVATE box3d::box3d)For a vendored copy or git submodule, point add_subdirectory at it:
add_subdirectory(extern/box3d)
target_link_libraries(my_app PRIVATE box3d::box3d)To use a copy installed with cmake --install, find the package:
find_package(box3d 0.1 REQUIRED)
target_link_libraries(my_app PRIVATE box3d::box3d)See docs/hello.md for a minimal first program.
Compatibility
The Box3D library and samples build and run on Windows, Linux, and Mac.
You will need a compiler that supports C17 to build the Box3D library.
You will need a compiler that supports C++20 to build the samples.
Box3D uses SSE2 and Neon SIMD math to improve performance. SIMD can be disabled by defining BOX3D_DISABLE_SIMD.
Documentation
The user manual lives in docs/ and is built with Doxygen. Enable the BOX3D_DOCS CMake option and build the doc target.
Community
Contributing
Pull requests are currently disabled. Instead, please file an issue for bugs or feature requests. For support, please visit the Discord server.
Giving feedback
Please file an issue or start a chat on discord. You can also use GitHub Discussions.
License
Box3D is developed by Erin Catto and uses the MIT license.
Sponsorship
Support development of Box3D through Github Sponsors.
Please consider starring this repository and subscribing to my YouTube channel.
LLM Usage
LLMs are used in the following areas:
- unit tests
- samples app
- migrating code between Box2D and Box3D
- build configuration
- code reviews
- benchmarking
Elsewhere all code is developed and written by me. I take responsibility for every line of code in Box2D/3D.
2. In-Tree Documentation Chapters (erincatto/box3d)
File: README.md
Box3D
Box3D is a 3D physics engine for games.
Features
Collision
- Continuous collision detection
- Contact events
- Convex hulls, capsules, spheres, triangle meshes, and height fields
- Multiple shapes per body
- Collision filtering
- Ray casts, shape casts, and overlap queries
- Sensor system
- Character mover
Physics
- Robust Soft Step rigid body solver
- Continuous physics for fast translations and rotations
- Island based sleep
- Revolute, prismatic, distance, motor, weld, and wheel joints
- Joint limits, motors, springs, and friction
- Joint and contact forces
- Body movement events and sleep notification
System
- Data-oriented design
- Written in portable C17
- Extensive multithreading and SIMD
- Optimized for large piles of bodies
- Cross platform determinism
- Recording and replay
Samples
- Uses sokol to run with D3D11 on Windows, Metal on macOS, and OpenGL 4.5 on Linux.
- Graphical user interface with imgui.
- Many samples to demonstrate features and performance.
Building all platforms
Building with CMake presets (recommended)
This uses the presets in CMakePresets.json.
- Windows:
cmake --preset windowsthencmake --build --preset windows-release - Linux:
cmake --preset linux-releasethencmake --build --preset linux-release - macOS:
cmake --preset macosthencmake --build --preset macos-release - Windows MinGW:
cmake --preset mingw-releasethencmake --build --preset mingw-release
Run the samples app (must be in the Box3D directory).
- Windows:
.\build\bin\Release\samples.exe - Linux:
./build/bin/samples - macOS:
./build/bin/Release/samples
Building for Visual Studio
- Install Visual Studio
- Run
build_vs2026.bat - Open and build
build/box3d.slnx
Building for Linux
- Run
build.shfrom a bash shell - Results are in the build sub-folder
Building for Xcode
- mkdir build
- cd build
- cmake -G Xcode ..
- Open
box3d.xcodeproj - Select the samples scheme
- Build and run the samples
Building for Web
- Emscripten SDK
emcmake cmake -B build -DBOX3D_SAMPLES=OFFcmake --build build
Box3D uses SSE2 with WebAssembly. Define BOX3D_DISABLE_SIMD to disable SSE2.
Building and installing
- mkdir build
- cd build
- cmake ..
- cmake --build . --config Release
- cmake --install . (might need sudo)
Using Box3D in your project
The core library has no dependencies beyond the C runtime (and libm on Unix). Linking it
gives you the box3d::box3d target.
I recommend to use FetchContent:
include(FetchContent)
FetchContent_Declare(box3d
GIT_REPOSITORY https://github.com/erincatto/box3d.git
GIT_TAG v0.1.0)
FetchContent_MakeAvailable(box3d)
target_link_libraries(my_app PRIVATE box3d::box3d)For a vendored copy or git submodule, point add_subdirectory at it:
add_subdirectory(extern/box3d)
target_link_libraries(my_app PRIVATE box3d::box3d)To use a copy installed with cmake --install, find the package:
find_package(box3d 0.1 REQUIRED)
target_link_libraries(my_app PRIVATE box3d::box3d)See docs/hello.md for a minimal first program.
Compatibility
The Box3D library and samples build and run on Windows, Linux, and Mac.
You will need a compiler that supports C17 to build the Box3D library.
You will need a compiler that supports C++20 to build the samples.
Box3D uses SSE2 and Neon SIMD math to improve performance. SIMD can be disabled by defining BOX3D_DISABLE_SIMD.
Documentation
The user manual lives in docs/ and is built with Doxygen. Enable the BOX3D_DOCS CMake option and build the doc target.
Community
Contributing
Pull requests are currently disabled. Instead, please file an issue for bugs or feature requests. For support, please visit the Discord server.
Giving feedback
Please file an issue or start a chat on discord. You can also use GitHub Discussions.
License
Box3D is developed by Erin Catto and uses the MIT license.
Sponsorship
Support development of Box3D through Github Sponsors.
Please consider starring this repository and subscribing to my YouTube channel.
LLM Usage
LLMs are used in the following areas:
- unit tests
- samples app
- migrating code between Box2D and Box3D
- build configuration
- code reviews
- benchmarking
Elsewhere all code is developed and written by me. I take responsibility for every line of code in Box2D/3D.
File: docs/character.md
Character Mover
Caution: The character mover API is experimental.
Box3D provides a geometric character mover: a capsule that exists outside the
rigid body simulation and is driven entirely by application code. Because it is
not a simulated body, you have full control over movement without fighting the
solver, at the cost of having to resolve collisions yourself. This is the style
of mover common in first-person shooters and games with precise platforming.
The Mover Capsule
The mover is represented by a b3Capsule in world space. A capsule is a good
shape for movement because its round profile slides smoothly along edges and
corners without snagging. Give the capsule a meaningful radius โ a very thin
capsule behaves poorly with the encroachment handling described below.
b3Capsule mover;
mover.center1 = (b3Vec3){ 0.0f, 0.35f, 0.0f }; // bottom sphere center
mover.center2 = (b3Vec3){ 0.0f, 1.45f, 0.0f }; // top sphere center
mover.radius = 0.35f;The mover has no explicit rotation handling. Slow rotation can be made to work
by updating the capsule each frame, but rapid spinning is not supported.
Workflow
Each frame:
- Compute a desired translation from input and physics (gravity, velocity).
- Call
b3World_CastMoverto find how far the mover can actually travel. - Move the capsule by the returned fraction of the desired translation.
- Call
b3World_CollideMoverto gather all contact planes at the new position. - Filter and assemble the planes into a
b3CollisionPlanearray. - Call
b3SolvePlanesto compute a corrected position delta. - Apply the delta and call
b3ClipVectorto remove velocity components that
push into surfaces.
Swept Motion: b3World_CastMover
float b3World_CastMover(
b3WorldId worldId,
b3Pos origin, // world position the mover is relative to
const b3Capsule* mover,
b3Vec3 translation,
b3QueryFilter filter,
b3MoverFilterFcn* fcn, // optional, may be NULL
void* context
);This casts the capsule through the world and returns the fraction [0, 1] oftranslation that can be traveled before hitting something. Multiply the
translation by this fraction to get the safe displacement.
The cast handles encroachment: when the mover starts out touching a surface,
the inner line segment of the capsule may move slightly into that surface without
the full capsule generating an overlap. This lets the mover slide along walls
and floors it is already resting against without stopping dead at the first
frame.
The optional b3MoverFilterFcn callback lets you exclude specific shapes from
the cast (e.g., ignore triggers, teammates, or one-way platforms):
typedef bool b3MoverFilterFcn(b3ShapeId shapeId, void* context);
// Return true to accept the shape, false to skip it.b3World_CastMover is intended for movement, not for gathering contact
information. Use b3World_CollideMover for that.
Contact Planes: b3World_CollideMover
void b3World_CollideMover(
b3WorldId worldId,
b3Pos origin, // mover and returned planes are relative to this
const b3Capsule* mover,
b3QueryFilter filter,
b3PlaneResultFcn* fcn,
void* context
);This gathers all surfaces the mover is touching or overlapping and delivers them
via the callback:
typedef bool b3PlaneResultFcn(
b3ShapeId shapeId,
const b3PlaneResult* plane,
int planeCount,
void* context
);
// Return true to continue gathering planes.b3PlaneResult carries the contact plane and a world-space contact point:
typedef struct b3PlaneResult {
b3Plane plane; // normal + offset: separation = dot(normal, p) - offset
b3Vec3 point;
} b3PlaneResult;The mover is treated as having fixed rotation, so only planes are needed โ no
full contact manifolds.
Per-Body Query: b3Body_CollideMover
When you need to test the mover against a single specific body (useful for
moving platforms or elevators):
int b3Body_CollideMover(
b3BodyId bodyId,
b3BodyPlaneResult* bodyPlanes,
int planeCapacity,
b3Pos origin,
const b3Capsule* mover,
b3QueryFilter filter,
b3WorldTransform bodyTransform
);Returns the number of planes written into bodyPlanes. Each entry pairs the
originating b3ShapeId with a b3PlaneResult.
Resolving Overlap: b3SolvePlanes
Convert the raw b3PlaneResult values from b3World_CollideMover intob3CollisionPlane entries and call:
typedef struct b3CollisionPlane {
b3Plane plane;
float pushLimit; // FLT_MAX for rigid; smaller values allow soft penetration
float push; // output: how much the solver pushed along this plane
bool clipVelocity; // set true to clip velocity against this plane
} b3CollisionPlane;
b3PlaneSolverResult b3SolvePlanes(
b3Vec3 targetDelta,
b3CollisionPlane* planes,
int count
);b3SolvePlanes finds the position delta closest to targetDelta that satisfies
all planes. The result contains the corrected delta and an iterationCount.
pushLimit controls softness. FLT_MAX gives a rigid surface. A smaller value
allows the mover to push through โ useful for other players, enemies, or doors
that should yield but not fully block.
Velocity Clipping: b3ClipVector
After resolving position, clip the mover's velocity so it does not keep
accelerating into blocked directions:
b3Vec3 b3ClipVector(b3Vec3 vector, const b3CollisionPlane* planes, int count);This removes the components of vector that push into any plane whereclipVelocity is true. Without this, velocity accumulates every frame the
mover is pressed against a surface.
Putting It Together
// The mover capsule and the planes are relative to origin. Keep origin near the
// character (its world position) so the query stays precise far from the world origin.
b3Pos origin = b3Pos_zero;
// 1. Desired translation from input + gravity integration
b3Vec3 translation = b3MulSV(timeStep, velocity);
// 2. Swept cast
float fraction = b3World_CastMover(worldId, origin, &mover, translation, filter, NULL, NULL);
b3Vec3 safeDelta = b3MulSV(fraction, translation);
// 3. Move the capsule
mover.center1 = b3Add(mover.center1, safeDelta);
mover.center2 = b3Add(mover.center2, safeDelta);
// 4. Gather contact planes
#define MAX_PLANES 16
b3CollisionPlane collisionPlanes[MAX_PLANES];
int planeCount = 0;
// (user callback stores planes into collisionPlanes / planeCount)
b3World_CollideMover(worldId, origin, &mover, filter, MyPlaneCallback, &planeCtx);
// 5. Solve planes
b3PlaneSolverResult result = b3SolvePlanes(b3Vec3_zero, collisionPlanes, planeCount);
// 6. Apply correction
mover.center1 = b3Add(mover.center1, result.delta);
mover.center2 = b3Add(mover.center2, result.delta);
// 7. Clip velocity
velocity = b3ClipVector(velocity, collisionPlanes, planeCount);The Mover sample in the samples application shows a complete implementation
including acceleration, friction, jumping, and a pogo stick.
File: docs/collision.md
Collision
Box3D provides geometric types and functions. These include:
- primitives: spheres, capsules, and convex hulls
- triangle meshes and height fields for static terrain
- convex hull construction from point clouds
- mass and bounding box computation
- local ray and shape casts
- contact manifolds
- shape distance (GJK)
- time of impact
- dynamic bounding volume tree
The collision interface is designed to be usable outside of rigid body simulation.
For example, you can use the dynamic tree for other aspects of your game besides physics.
The main purpose of Box3D is rigid body simulation, so the collision module only
contains features that are also useful in the physics engine.
Shape Primitives
Shape primitives describe collision geometry and may be used independently of
physics simulation. At a minimum, you should understand how to create primitives
that can later be attached to rigid bodies.
Box3D shape primitives support several operations:
- Test a point or proxy for overlap with the primitive
- Perform a ray cast against the primitive
- Compute the primitive's bounding box
- Compute the mass properties of the primitive
Spheres
Spheres have a center and radius. Spheres are solid.
b3Sphere sphere;
sphere.center = (b3Vec3){2.0f, 3.0f, 0.0f};
sphere.radius = 0.5f;You can also initialize a sphere inline:
b3Sphere sphere = {{2.0f, 3.0f, 0.0f}, 0.5f};Capsules
Capsules have two center points and a radius. The center points are the centers of two
hemispheres connected by a cylinder.
b3Capsule capsule;
capsule.center1 = (b3Vec3){0.0f, -1.0f, 0.0f};
capsule.center2 = (b3Vec3){0.0f, 1.0f, 0.0f};
capsule.radius = 0.25f;Convex Hulls
Box3D convex hulls are solid convex polyhedra. The geometry lives in a heavy, immutableb3HullData object. The world shares identical hull data through a reference counted
database, so many shapes built from the same data hold one copy. A shape is convex when
all line segments connecting two interior points remain inside the shape.
The most common hull is a box. Use b3MakeBoxHull for an axis-aligned box andb3MakeCubeHull for a cube. The values are half-extents (half-widths):
b3BoxHull box = b3MakeBoxHull(0.5f, 1.0f, 0.5f); // half-widths hx, hy, hz
b3BoxHull cube = b3MakeCubeHull(0.5f); // uniform half-widthb3BoxHull stores everything inline โ do not call b3DestroyHull on one.
Its .base member is the b3HullData that you pass to shape creation:
b3CreateHullShape(bodyId, &shapeDef, &box.base);For a box that is offset or rotated from the body origin:
b3Transform localTransform = { offset, rotation };
b3BoxHull rotatedBox = b3MakeTransformedBoxHull(0.5f, 1.0f, 0.5f, localTransform);For arbitrary convex geometry, provide a point cloud and let Box3D compute the hull:
b3HullData* data = b3CreateHull(points, pointCount, maxVertexCount);
if (data == NULL)
{
// degenerate input: coincident or coplanar points, or insufficient volume
}
b3CreateHullShape(bodyId, &shapeDef, data);
// The world keeps its own copy, so you may free yours immediately
b3DestroyHull(data);maxVertexCount limits the output complexity; pass the same value as pointCount
to allow the full hull. b3CreateHull returns NULL on degenerate input (e.g. fewer
than four non-coplanar points, or nearly-zero volume). Always check before using it.
Box3D also provides helpers to create cylindrical and conical hulls:
b3HullData* cylinder = b3CreateCylinder(height, radius, yOffset, sides);
b3HullData* cone = b3CreateCone(height, radius1, radius2, slices);
b3DestroyHull(cylinder);
b3DestroyHull(cone);Hulls created with b3CreateCylinder, b3CreateCone, and b3CreateHull are
heap-allocated and must be freed with b3DestroyHull. b3BoxHull values are
stack/struct-allocated and must not be freed.
Triangle Meshes
Triangle meshes let you describe concave or open surfaces using a triangle soup.
They are intended for static geometry: b3CreateMeshShape only creates contacts
on static bodies.
The mesh is built from a b3MeshDef and cooked into a b3MeshData that contains
an internal BVH for efficient collision queries:
b3MeshDef def = {0};
def.vertices = myVerts;
def.vertexCount = myVertCount;
def.indices = myIndices; // 3 per triangle
def.triangleCount = myTriCount;
def.weldVertices = true;
def.identifyEdges = true; // adjacency info for smooth inter-triangle normals
b3MeshData* mesh = b3CreateMesh(&def, NULL, 0);Pass identifyEdges = true when triangles share edges so Box3D can suppress
internal-edge collisions between adjacent triangles (the 3D equivalent of ghost
collision handling on polygon chains).
The b3Mesh struct pairs a b3MeshData pointer with a scale vector:
b3Mesh meshShape;
meshShape.data = mesh;
meshShape.scale = (b3Vec3){1.0f, 1.0f, 1.0f};Scale may be non-uniform and may have negative components, but no component may
be zero.
Per-triangle surface materials are supported. Provide a b3SurfaceMaterial array
in b3ShapeDef::materials and an index array in b3MeshDef::materialIndices:
b3SurfaceMaterial materials[2] = { ... };
def.materialIndices = perTriangleMaterialIndex; // uint8_t, 1 per triangle
b3ShapeDef shapeDef = b3DefaultShapeDef();
shapeDef.materials = materials;
shapeDef.materialCount = 2;
b3ShapeId id = b3CreateMeshShape(bodyId, &shapeDef, mesh, scale);Destroy the mesh data when no longer needed, after the shape referencing it has
been destroyed:
b3DestroyMesh(mesh);Box3D provides factory helpers for common mesh configurations:b3CreateGridMesh, b3CreateWaveMesh, b3CreateBoxMesh, and others.
Height Fields
Height fields describe terrain as a regular grid of sample heights. Like meshes,
they are only valid on static bodies.
b3HeightFieldDef def = {0};
def.heights = heightSamples; // float[countX * countZ]
def.countX = 256;
def.countZ = 256;
def.scale = (b3Vec3){1.0f, 1.0f, 1.0f};
def.globalMinimumHeight = -10.0f;
def.globalMaximumHeight = 50.0f;
b3HeightFieldData* hf = b3CreateHeightField(&def);
b3ShapeId id = b3CreateHeightFieldShape(bodyId, &shapeDef, hf);scale controls cell spacing (x/z) and height scale (y). Setting a material index
to B3_HEIGHT_FIELD_HOLE (0xFF) punches a hole in that grid cell, useful for
cave entrances and tunnels.
When placing multiple height fields side by side, give all of them the sameglobalMinimumHeight and globalMaximumHeight so compressed heights quantize
identically and the seams line up.
Destroy the height field after the shape referencing it has been destroyed:
b3DestroyHeightField(hf);Compound Shapes
A compound shape aggregates spheres, capsules, hulls, and meshes into a single
static collision shape. Compounds are only allowed on static bodies. They are
designed for offline baking and open-world streaming โ see the dedicated
compound page for the full API and usage pattern.
Geometric Queries
Shape Point Test
Box3D does not expose standalone b3PointInShape functions. Point overlap is
expressed through the b3ShapeProxy abstraction that GJK uses. A degenerate
proxy โ a single point with zero radius โ serves as a point test:
b3Vec3 queryPoint = {5.0f, 2.0f, 1.0f};
b3ShapeProxy proxy;
proxy.points = &queryPoint;
proxy.count = 1;
proxy.radius = 0.0f;
b3Transform shapeTransform = b3Transform_identity;
bool hit = b3OverlapHull(&myHull, shapeTransform, &proxy);The same pattern works with b3OverlapSphere, b3OverlapCapsule,b3OverlapMesh, and b3OverlapHeightField.
Ray Cast
Cast a ray at a shape to get the point of first intersection and the surface normal.
Caution: No hit will register if the ray starts inside a convex shape such
as a sphere or hull. Convex shapes are treated as solid.
b3RayCastInput input = {0};
input.origin = (b3Vec3){0.0f, 10.0f, 0.0f};
input.translation = (b3Vec3){0.0f, -20.0f, 0.0f};
input.maxFraction = 1.0f;
b3CastOutput output = b3RayCastHull(&myHull, &input);
if (output.hit)
{
// output.point, output.normal, output.fraction
}Per-shape ray cast functions: b3RayCastSphere, b3RayCastCapsule,b3RayCastHull, b3RayCastMesh, b3RayCastHeightField, b3RayCastCompound.
All operate in the shape's local space. Use b3IsValidRay to validate input
before calling.
To cast against the full simulation world, use b3World_CastRay or the
convenience function b3World_CastRayClosest.
Shape Cast
A shape cast sweeps an abstract point cloud (a b3ShapeProxy) through space and
finds where it first contacts another shape. A sphere is a single point with
a non-zero radius; a capsule is two points with a radius; a box is eight points
with zero radius.
b3Vec3 proxyPoints[] = {{-0.5f, -0.5f, -0.5f}, {0.5f, 0.5f, 0.5f}};
b3ShapeCastInput input = {0};
input.proxy.points = proxyPoints;
input.proxy.count = 2;
input.proxy.radius = 0.1f;
input.translation = (b3Vec3){0.0f, -5.0f, 0.0f};
input.maxFraction = 1.0f;
b3CastOutput output = b3ShapeCastHull(&myHull, &input);
if (output.hit)
{
// output.point, output.normal, output.fraction
}Per-shape cast functions: b3ShapeCastSphere, b3ShapeCastCapsule,b3ShapeCastHull, b3ShapeCastMesh, b3ShapeCastHeightField,b3ShapeCastCompound.
For the most general form โ sweeping one proxy against another โ useb3ShapeCast with a b3ShapeCastPairInput. All shape cast functions call this
internally.
Distance
b3ShapeDistance computes the closest points and separation distance between two
shapes, each expressed as a b3ShapeProxy. It uses the GJK algorithm.
b3DistanceInput input = {0};
input.proxyA = proxyA; // b3ShapeProxy for shape A
input.proxyB = proxyB; // b3ShapeProxy for shape B
input.transform = b3InvMulWorldTransforms(worldA, worldB); // relative pose of B in A
input.useRadii = true;
b3SimplexCache cache = b3_emptyDistanceCache;
b3DistanceOutput output = b3ShapeDistance(&input, &cache, NULL, 0);
// output.distance, output.pointA, output.pointB, output.normal are in shape A's frameThe query is origin independent and runs in frame A, so the witness points and
normal come back in shape A's frame. Lift them into world space with shape A's
transform if needed.
The simplex cache warm-starts the algorithm when shapes move by small amounts
between calls. On the first call zero-initialize the cache (or useb3_emptyDistanceCache).
Time of Impact
If two shapes move fast they may tunnel through each other in a single time step.
b3TimeOfImpact finds the earliest time at which two swept shapes touch. Box3D
uses this internally to prevent dynamic bodies from tunneling through static geometry.
The algorithm identifies an initial separating axis and advances the shapes along
it until they touch or pass each other. It may miss collisions that only become
apparent at the final positions, but those tend to be glancing contacts that rarely
matter in practice.
b3TOIInput input = {0};
input.proxyA = proxyA;
input.proxyB = proxyB;
input.sweepA = sweepA; // b3Sweep: localCenter, c1, c2, q1, q2
input.sweepB = sweepB;
input.maxFraction = 1.0f;
b3TOIOutput output = b3TimeOfImpact(&input);
if (output.state == b3_toiStateHit)
{
// output.fraction is the time of impact in [0, maxFraction]
// output.point and output.normal give the contact geometry
}b3Sweep describes a rigid body's motion as a translation of the center of mass
plus a quaternion rotation, interpolated from (c1, q1) to (c2, q2). Useb3GetSweepTransform to evaluate the transform at any fraction.
Contact Manifolds
Box3D computes contact points for overlapping shapes. In 3D, hull-hull contact
can produce up to B3_MAX_MANIFOLD_POINTS (4) points. All points share the same
contact normal so Box3D groups them into a manifold. The contact solver uses
this for stable stacking.
Normally you do not compute manifolds directly โ you use the contact data
returned by the simulation. The b3Manifold struct contains:
normal: unit normal from shape A toward shape Bpoints[B3_MAX_MANIFOLD_POINTS]: contact points with separation, impulses,
and feature idspointCount: 0โ4 valid pointsfrictionImpulse,twistImpulse,rollingImpulse: accumulated friction state
For direct use, the low-level collide functions produce a b3LocalManifold in
the frame of shape A:
b3LocalManifoldPoint points[B3_MAX_MANIFOLD_POINTS];
b3LocalManifold manifold;
manifold.points = points;
b3SATCache cache = {0};
b3CollideHulls(&manifold, B3_MAX_MANIFOLD_POINTS,
&hullA, &hullB, transformBtoA, &cache);Available collide functions:
b3CollideSpheresb3CollideCapsuleAndSphereb3CollideCapsulesb3CollideHullAndSphereb3CollideHullAndCapsuleb3CollideHullsb3CollideTriangleAndCapsuleb3CollideTriangleAndHullb3CollideTriangleAndSphere
The SAT cache in b3CollideHulls warm-starts the separating axis search between
frames, the same idea as the simplex cache for GJK distance.
Box3D uses speculative collision: some contact points in a b3Manifold may
report a positive (separated) separation. Check totalNormalImpulse to
determine whether a speculative point actually had an interaction during the step.
Dynamic Tree
b3DynamicTree organizes large numbers of AABBs into a hierarchical binary tree
for fast ray casts and region queries. Box3D uses it internally to manage the
broad phase, but it is also available for organizing spatial game data unrelated
to physics.
Each tree node stores a b3AABB and a 64-bit userData value. Internal nodes
have two children; leaf nodes are proxies you manage directly.
// Create a tree with an initial proxy capacity
b3DynamicTree tree = b3DynamicTree_Create(256);
// Insert a proxy
b3AABB aabb = { lowerBound, upperBound };
int proxyId = b3DynamicTree_CreateProxy(&tree, aabb, categoryBits, userData);
// Move it
b3DynamicTree_MoveProxy(&tree, proxyId, newAabb);
// Remove it
b3DynamicTree_DestroyProxy(&tree, proxyId);
// Done
b3DynamicTree_Destroy(&tree);Category bits allow broad-phase filtering without invoking the callback.
A proxy is only visited if (maskBits & node->categoryBits) != 0 (or ifrequireAllBits is set, the AND must equal maskBits).
AABB Query
Find all proxies whose AABBs overlap a query box:
b3TreeStats stats = b3DynamicTree_Query(
&tree, queryAabb, maskBits, requireAllBits, myQueryCallback, context);The callback receives proxyId and userData and returns true to continue.
Closest Query
Find the proxy closest to a point:
float minDistSqr = FLT_MAX;
b3TreeStats stats = b3DynamicTree_QueryClosest(
&tree, point, maskBits, requireAllBits,
myClosestCallback, context, &minDistSqr);The callback receives the current minimum squared distance and returns the
squared distance to the user object inside the proxy, allowing the tree to
prune distant branches.
Ray Cast
b3TreeStats stats = b3DynamicTree_RayCast(
&tree, &rayInput, maskBits, requireAllBits,
myRayCastCallback, context);The callback returns a new maxFraction. Return 0 to stop, a fraction less
than input->maxFraction to clip the ray (e.g. for closest-hit semantics),
or input->maxFraction to continue unclipped.
Box Cast
b3TreeStats stats = b3DynamicTree_BoxCast(
&tree, &boxCastInput, maskBits, requireAllBits,
myBoxCastCallback, context);The tree sweeps the AABB in boxCastInput; the caller folds the cast shape's radius (and any
world origin) into that box. The callback then does the precise narrow-phase cast against each
leaf, taking only the advancing fraction from the tree.
b3TreeStats reports nodeVisits and leafVisits for performance profiling.
The tree can be rebuilt with b3DynamicTree_Rebuild to reclaim quality after
many insertions, and saved/loaded with b3DynamicTree_Save / b3DynamicTree_Load
for debugging.
Normally you will not use b3DynamicTree directly. For world-level ray casts and
overlap queries use b3World_CastRay, b3World_OverlapAABB, andb3World_OverlapShape. See the DynamicTree sample for direct usage examples.
File: docs/compound.md
Compound Shapes
A compound is a single immutable shape that aggregates many child primitives โ
spheres, capsules, convex hulls, and triangle meshes โ under one internal AABB
tree. To the simulation it appears as one shape, but collision queries descend
the internal tree and test individual children, so the cost scales with what is
actually touched rather than the total child count.
Design Intent
Compounds are static-body only and immutable after creation. They are
not a runtime mutation primitive. The intended workflow is:
- Author geometry offline (level mesh, terrain tile, building shell).
- Bake the compound once with
b3CreateCompound. - Serialize it to a byte buffer with
b3ConvertCompoundToBytesand store it on
disk or in a streaming cache. - At runtime, load the bytes and reconstruct the compound with
b3ConvertBytesToCompound, then attach it to a static body.
This makes compounds well-suited to open-world streaming: tiles are baked
offline, kept in memory as flat byte buffers (which the engine uses directly
without copying), and attached to static bodies when a region loads, then
detached when it unloads.
Do not use a compound on dynamic or kinematic bodies โ b3CreateCompoundShape
enforces static-only attachment.
Building a Compound
Fill a b3CompoundDef with arrays of child-shape descriptors:
// Child type descriptors
b3CompoundCapsuleDef capsuleDefs[N_CAPSULES];
b3CompoundHullDef hullDefs[N_HULLS];
b3CompoundMeshDef meshDefs[N_MESHES];
b3CompoundSphereDef sphereDefs[N_SPHERES];
// Fill capsuleDefs[i].capsule, .material ...
// Fill hullDefs[i].hull, .transform, .material ...
// Fill meshDefs[i].meshData, .transform, .scale, .materials, .materialCount ...
// Fill sphereDefs[i].sphere, .material ...
b3CompoundDef def;
def.capsules = capsuleDefs; def.capsuleCount = N_CAPSULES;
def.hulls = hullDefs; def.hullCount = N_HULLS;
def.meshes = meshDefs; def.meshCount = N_MESHES;
def.spheres = sphereDefs; def.sphereCount = N_SPHERES;
b3Compound* compound = b3CreateCompound(&def);b3CreateCompound clones all input data into the compound. The source arrays
can be freed immediately after the call.
Mesh children share the mesh pointer rather than cloning triangle data โ theb3MeshData must remain valid for the lifetime of the compound. Triangle
materials are limited to B3_MAX_COMPOUND_MESH_MATERIALS (4) slots per mesh
child; if your mesh needs more materials, attach it as a standalone mesh shape
on the static body instead.
The b3Compound Structure
typedef struct b3Compound {
uint64_t version; // versioned against the tree/mesh/hull formats
int byteCount; // total size when serialized
// internal tree, child arrays, material table
// ... (treat as opaque)
} b3Compound;version is a compile-time constant (B3_COMPOUND_VERSION) that incorporates
the dynamic tree, mesh, and hull format versions. A version mismatch at load
time means the bytes were baked with a different engine version and cannot be
used.
Serialization
Convert a live compound to a self-contained byte buffer:
uint8_t* bytes = b3ConvertCompoundToBytes(compound);
// compound is now in an unusable state; its pointers have been nullified.
// Write bytes[0 .. compound->byteCount - 1] to disk.Reconstruct from bytes at runtime (zero-copy; bytes must remain in scope):
b3Compound* compound = b3ConvertBytesToCompound(bytes, byteCount);The bytes are mutated in place to fixup internal pointers. Multiple static
bodies can use the same byte buffer simultaneously (instancing), since the
compound itself holds no per-body state.
Attaching to a Static Body
b3BodyDef bodyDef = b3DefaultBodyDef();
bodyDef.type = b3_staticBody;
bodyDef.position = tileOrigin;
b3BodyId bodyId = b3CreateBody(worldId, &bodyDef);
b3ShapeDef shapeDef = b3DefaultShapeDef();
b3ShapeId shapeId = b3CreateCompoundShape(bodyId, &shapeDef, compound);b3CreateCompoundShape asserts that the body is static.
Querying a Compound
Compute the world AABB:
b3AABB aabb = b3ComputeCompoundAABB(compound, bodyTransform);Query child shapes by AABB:
typedef bool b3CompoundQueryFcn(const b3Compound* compound, int childIndex, void* context);
b3QueryCompound(compound, queryAABB, MyChildCallback, ctx);Access a specific child by index:
b3ChildShape child = b3GetCompoundChild(compound, childIndex);
// child.type tells you capsule / hull / mesh / sphere
// child.transform is the child's local transform within the compoundIndividual typed accessors are also available:
b3CompoundCapsule cc = b3GetCompoundCapsule(compound, i);
b3CompoundHull ch = b3GetCompoundHull(compound, i);
b3CompoundMesh cm = b3GetCompoundMesh(compound, i);
b3CompoundSphere cs = b3GetCompoundSphere(compound, i);
const b3SurfaceMaterial* mats = b3GetCompoundMaterials(compound);Teardown
Destroy a live compound (not needed if you are using the byte-buffer path, where
you manage the buffer lifetime yourself):
b3DestroyCompound(compound);Do not call b3DestroyCompound on a compound reconstructed from bytes withb3ConvertBytesToCompound โ free the byte buffer instead.
File: docs/faq.md
FAQ
What is Box3D?
Box3D is a feature rich 3D rigid body physics engine, written in C17 by Erin Catto. It has been used in games and game engines
as a 3D counterpart to the well-established Box2D engine.
Box3D uses the MIT license and can be used free of charge. Credit
should be included if possible. Support is appreciated.
What platforms does Box3D support?
Box3D is developed using C17. It is portable and can be compiled for any platform with a conforming C17 compiler.
Erin Catto maintains the C version. Community ports and bindings for other languages are not officially supported.
Who makes it?
Erin Catto is the creator and primary author of Box3D. It is an open source project, and accepts community feedback via
GitHub Issues and
GitHub Discussions.
How do I get help?
You should read the documentation and the rest of this FAQ first. Also, you should study the examples included in the source
distribution. Then you can visit the Discord to ask any remaining questions.
Please do not message or email Erin Catto directly for support. It is best to ask questions on the Discord server so that
everyone can benefit from the discussion.
Documentation
Why isn't a feature documented?
If you grab the latest code from the git main branch you will likely find features that are not documented in the manual.
New features are added to the manual after they are mature and a new point release is imminent. However, all major features
added to Box3D are accompanied by example code in the samples application to test the feature and show the intended usage.
Prerequisites
Programming
You should have a working knowledge of C before you use Box3D. You should understand functions, structures, and pointers.
There are plenty of resources on the web for learning C. You should also understand your development environment:
compilation, linking, and debugging.
Math and Physics
You should have a basic knowledge of rigid bodies, force, torque, and impulses. In 3D you will also encounter quaternions
for orientation and 3x3 inertia tensors for rotational dynamics. If you come across a math or physics concept you don't
understand, please read about it on Wikipedia. Visit this page if you want a deeper
knowledge of the algorithms used in Box3D.
Working with Unreal
You can use Box3D in Unreal as a plug-in. Here is the module file I use:
Unreal uses centimeters as units while Box3D has tolerances tuned for meters. You can either scale dimensions you send and recieve from Box3D or you can use this to set the length units:
b3SetLengthUnitsPerMeter(100.0f);This doesn't scale the units. Instead it scales the tolerances used internally in Box3D to be more appropriate for centimeters.
Another issue is that Box3D is right-handed while Unreal is left-handed. Box3D has way too much math for it to be reasonable to support a left-handed option. However, it doesn't matter. In practice you don't need to do any conversions. Box3D will run in a mirrored world and the outputs from Box3D are fully compatible with the Unreal coordinates.
I would just be careful about using Box3D math functions like b3Cross and expecting it to give the same result as the Unreal cross product.
API
What units does Box3D use?
Box3D is tuned for meters-kilograms-seconds (MKS). This is recommended as the unit system for your game. However, you may
use different units if you are careful. Call b3SetLengthUnitsPerMeter() at startup to change the length unit.
What coordinate system does Box3D use?
Box3D has no built-in notion of up. The gravity vector in b3WorldDef can point in any direction. The default is(0, -10, 0) (negative Y is down) but you can set it to whatever suits your application.
Why don't you use this awesome language?
Box3D is designed to be portable and easy to wrap with other languages, so I decided to use C17. I used C17 to get
support for atomics.
Can I use Box3D in a DLL?
Yes. See the CMake option BUILD_SHARED_LIBS.
Is Box3D thread-safe?
No. Box3D will likely never be fully thread-safe from the outside. Box3D has a large API and trying to make such an API
thread-safe would have a large performance and complexity impact. However, you can call read-only functions from multiple
threads. For example, all the spatial query functions are read-only.
Box3D does use multithreading internally during b3World_Step. You supply your own task system via the enqueueTask andfinishTask callbacks in b3WorldDef.
Build Issues
Why doesn't my code compile and/or link?
There are many reasons why a build can go bad. Here are a few that have come up:
- Using old Box3D headers with new code
- Not linking the Box3D library with your application
- Using old project files that don't include some new source files
Rendering
What are Box3D's rendering capabilities?
Box3D is only a physics engine. How you draw stuff is up to you.
But the samples application draws stuff
Visualization is very important for debugging collision and physics. The samples application helps test Box3D and gives
you examples of how to use Box3D. The samples are not part of the Box3D library.
How do I draw shapes?
Fill out a b3DebugDraw struct with your drawing callbacks and call b3World_Draw(worldId, &draw, maskBits).
The mask bits let you filter which shape categories are drawn.
Accuracy
Box3D uses approximate methods for a few reasons.
- Performance
- Some differential equations don't have known solutions
- Some constraints cannot be determined uniquely
What this means is that constraints are not perfectly rigid and sometimes you will see some bounce even when the restitution
is zero. Box3D uses Gauss-Seidel to approximately solve
constraints. Box3D also uses Semi-implicit Euler to
approximately solve the differential equations. Box3D also does not have exact collision between dynamic shapes. Slow
moving shapes may have small overlap for a few time steps. In extreme stacking scenarios, shapes may have sustained
overlap.
Making Games
Tile / Voxel Based Environments
Using many boxes for terrain may not work well because box-like characters can get snagged on internal corners. Box3D
provides capsules and convex hulls that may work better for characters. Consider the character mover API
(b3World_CastMover, b3World_CollideMover) for smooth first/third person movement.
Asteroid Type Coordinate Systems
Box3D does not have any support for coordinate frame wrapping. You would likely need to customize Box3D for this purpose.
Determinism
Is Box3D deterministic?
For the same input Box3D will reproduce any simulation. Box3D does not use any random numbers nor base any computation
on random events (such as timers, etc).
Box3D is also deterministic under multithreading. A simulation using two threads will give the same result as eight threads.
Box3D inherits cross-platform determinism from its design: floating-point contraction is disabled (-ffp-contract=off)
and IEEE 754 arithmetic is relied upon consistently.
However, Box3D does not have rollback determinism. There is no mechanism to set a world back to a prior state and then
resume simulation expecting identical results. Box3D caches a lot of internal state to improve simulation stability and
performance.
But I really want determinism
This naturally leads to the question of fixed-point math. Box3D does not support fixed-point math. Fixed-point math is
slower and more tedious to develop, and I have chosen not to use it.
What are the common mistakes made by new users?
- Using non-metric units instead of meters
- Expecting Box3D to give pixel-perfect results
- Testing their code in release mode (always use Debug for testing โ it enables assertions and validation)
- Not learning C before using Box3D
- Confusing b2 (Box2D) and b3 (Box3D) symbols when reading documentation
File: docs/foundation.md
Foundations
Box3D provides minimal base functionality for allocation hooks and vector math. The C interface
allows most runtime data and types to be defined internally in the src folder.
Assertions
Box3D will assert on bad input. This includes things like sending in NaN or infinity for values.
It will assert if you use negative values for things that should only be positive, such as density.
Box3D will also assert if an internal bug is detected. For this reason, it is advisable to build
Box3D from source. The library compiles in about a second.
You may wish to capture assertions in your application. In that case use b3SetAssertFcn(). This
lets you override the debugger break and/or perform your own error handling.
Allocation
Box3D uses memory efficiently and minimizes per-frame allocations by pooling memory. The engine
quickly adapts to the simulation size. After the first step or two of simulation there should be
no further per-frame allocations.
As bodies, shapes, and joints are created and destroyed, their memory is recycled. Internally all
this data is stored in contiguous arrays. When an object is destroyed, the array element is marked
empty. When an object is created it fills an empty slot via an efficient free list.
Once the internal memory pools are initially filled, the only allocations should be for sleeping
islands, since their data is copied out of the main simulation. Those allocations are generally
infrequent.
You can provide a custom allocator using b3SetAllocator() and query the total bytes currently
allocated using b3GetByteCount().
Version
The b3Version structure holds the current version so you can query it at run-time usingb3GetVersion().
b3Version version = b3GetVersion();
printf("Box3D version %d.%d.%d\n", version.major, version.minor, version.revision);Vector Math
Box3D includes a vector math library covering types b3Vec3, b3Quat, b3Transform,b3Matrix3, and b3AABB. The library is designed to suit the internal needs of Box3D
and its interface. All members are exposed, so you can use them freely in your application.
b3Vec3
Three-component float vector with fields x, y, z. Useful constants and operations:
b3Vec3 a = {1.0f, 0.0f, 0.0f}; // inline init
b3Vec3 z = b3Vec3_zero; // {0,0,0}
b3Vec3 c = b3Add(a, b); // component-wise add
b3Vec3 d = b3Sub(a, b); // subtract
b3Vec3 e = b3MulSV(2.0f, a); // scalar * vector
float f = b3Dot(a, b); // dot product
b3Vec3 g = b3Cross(a, b); // cross product
float h = b3Length(a); // Euclidean length
b3Vec3 n = b3Normalize(a); // unit vector
b3Vec3 p = b3Perp(a); // any perpendicular unit vector
b3Vec3 q = b3Lerp(a, b, 0.5f); // linear interpolationb3Quat
Unit quaternion representing orientation. Stored as a vector part q.v (x, y, z) and a
scalar part q.s. The identity quaternion is b3Quat_identity.
Useful operations:
// Construct from axis (must be unit) and angle in radians
b3Quat q = b3MakeQuatFromAxisAngle(axis, radians);
// Rotate a vector
b3Vec3 r = b3RotateVector(q, v);
// Inverse-rotate a vector (equivalent to rotating by the conjugate)
b3Vec3 s = b3InvRotateVector(q, v);
// Compose two rotations: apply q2 first, then q1
b3Quat qc = b3MulQuat(q1, q2);
// Conjugate (same as inverse for a unit quaternion)
b3Quat qi = b3Conjugate(q);
// Extract axis-angle
float angle;
b3Vec3 axis = b3GetAxisAngle(&angle, q);
// Total rotation angle (ignoring axis)
float totalAngle = b3GetQuatAngle(q);
// Convert to rotation matrix
b3Matrix3 m = b3MakeMatrixFromQuat(q);
// Normalized linear interpolation
b3Quat qi = b3NLerp(q1, q2, alpha);Because orientation in 3D is three-dimensional, there is no single scalar angle as there was
in 2D. Always work with the full quaternion or the derived matrix.
b3Transform
A rigid transform: a position vector t.p (b3Vec3) combined with an orientation t.q
(b3Quat). The identity transform is b3Transform_identity.
// Apply transform to a point in the transform's local frame -> world frame
b3Vec3 world = b3TransformPoint(t, localPoint);
// Inverse: world frame -> local frame
b3Vec3 local = b3InvTransformPoint(t, worldPoint);
// Compose: t_child expressed in t_parent's frame
b3Transform combined = b3MulTransforms(t_parent, t_child);
// Relative transform: t_b expressed in t_a's frame
b3Transform rel = b3InvMulTransforms(t_a, t_b);
// Invert a transform
b3Transform inv = b3InvertTransform(t);b3Matrix3
3ร3 matrix stored as three column vectors cx, cy, cz. Primarily used for inertia
tensors and rotation matrices. Useful operations include b3MulMV (matrix-vector multiply),b3MulMM (matrix-matrix multiply), b3Transpose, b3InvertMatrix, and b3MakeMatrixFromQuat.
b3AABB
Axis-aligned bounding box with lowerBound and upperBound as b3Vec3. Helpers includeb3AABB_Overlaps, b3AABB_Contains, b3AABB_ContainsPoint, b3AABB_Union,b3AABB_Center, b3AABB_Extents, b3AABB_Inflate, and b3AABB_Transform.
Multithreading {#multi}
Box3D has been optimized for multithreading. Multithreading is not required and by default Box3D will run single-threaded. If performance is important for your application, you should consider using the multithreading features.
Internal scheduler
Box3D has a built-in task scheduler that creates threads. You can use the built-in scheduler by setting the worker count in the world definition. This example shows how to use 4 workers. In this case Box3D will create 3 threads, and count the thread that calls b3World_Step as the fourth worker. I recommend to use the core count of the CPU as the worker count, not counting hyper-threads or efficiency cores.
b3WorldDef worldDef = b3DefaultWorldDef();
worldDef.workerCount = 4;External scheduler
You can optionally connect your own task scheduler if you want more control. Multithreading is established for each Box3D world you create and must be hooked up to the world definition. See b3TaskCallback(), b3EnqueueTaskCallback(), and b3FinishTaskCallback() for more details. Also see b3WorldDef::workerCount, b3WorldDef::enqueueTask, and b3WorldDef::finishTask.
// Implement b3EnqueueTaskCallback
void* MyEnqueueTask(b3TaskCallback* task, void* taskContext,
void* userContext, const char* taskName)
{
MyTask* t = AllocTask();
t->task = task;
t->taskContext = taskContext;
Scheduler* myScheduler = (Scheduler*)userContact;
SubmitToThreadPool(myScheduler, t);
return t;
}
// Implement b3FinishTaskCallback
void MyFinishTask(void* userTask, void* userContext)
{
MyTask* t = (MyTask*)userTask;
WaitForCompletion(t);
FreeTask(t);
}
b3WorldDef worldDef = b3DefaultWorldDef();
worldDef.enqueueTask = MyEnqueueTask;
worldDef.finishTask = MyFinishTask;
worldDef.userTaskContext = myScheduler;
worldDef.workerCount = GetMyWorkerCount();Threading model
The multithreading design for Box3D is focused on data parallelism.
The goal is to use multiple cores to finish the world simulation as fast as possible.
Box3D multithreading is not designed for task parallelism.
Often in games you have a render thread or an audio thread doing work in isolation from the
main thread. Those are examples of task parallelism.
So when you design your game loop, you should let Box3D go wide and use multiple cores to
finish its work quickly, without other threads interacting with the Box3D world at the same time.
It is expected that the thread that calls b3World_Step participates in making progress. Do not callb3World_Step and park that fiber. Tasks will only be enqueued on the thread that calls b3World_Step.MyFinishTask must block until the task has completed. Ideally your scheduler is helping make progress
when MyFinishTask is called.
Avoiding race conditions
In a multithreaded environment you must be careful to avoid race conditions.
Modifying the world while it is simulating will lead to unpredictable behavior and is never safe.
It is also not safe to read data from a Box3D world while it is simulating. Box3D may move data
structures to improve cache performance, so you could easily read garbage.
Caution:
Do not perform read or write operations on a Box3D world duringb3World_Step().
Do not write to the Box3D world from multiple threads. Any operation that wakes
a body is not thread-safe.
It is safe to do ray-casts, shape-casts, and overlap tests from multiple threads outside ofb3World_Step(). Generally any read-only operation is safe to do multithreaded outside ofb3World_Step(). This can be very useful if you have multithreaded game logic.
Multithreading Multiple Worlds
Some applications may wish to create multiple Box3D worlds and simulate them on different threads.
This works fine because Box3D has very limited use of globals.
There are a few caveats:
- You will get a race condition if you create or destroy Box3D worlds from multiple threads. Use a mutex to guard those operations.
- If you simulate multiple Box3D worlds simultaneously, they should probably not share a task system. Otherwise you risk preemption between worlds competing for the same workers.
- Any callbacks you hook up to Box3D must be thread-safe, including memory allocators.
- All the limitations for single-world simulation still apply.
File: docs/hello.md
Hello Box3D {#hello}
The Box3D distribution includes a Hello World unit test written in C. The test
creates a large static ground box and a small dynamic box. This code does not
contain any graphics. All you will see is text output in the console showing
the box's position over time.
This is a good example of how to get up and running with Box3D.
Creating a World
Every Box3D program begins with the creation of a world object.
The world is the physics hub that manages memory, objects, and simulation.
The world is represented by an opaque handle called b3WorldId.
It is easy to create a Box3D world. First, create the world definition:
b3WorldDef worldDef = b3DefaultWorldDef();The world definition is a temporary object you can create on the stack. The functionb3DefaultWorldDef() populates the world definition with default values. This is necessary
because C does not have constructors and zero-initializing b3WorldDef is not appropriate.
Box3D has no built-in concept of up. The gravity vector is a b3Vec3 and can point in
any direction. Convention in Box3D examples uses +Y as the up axis. The default gravity
is already {0, -10, 0}, but it can be set explicitly:
worldDef.gravity = (b3Vec3){ 0.0f, -10.0f, 0.0f };Now create the world:
b3WorldId worldId = b3CreateWorld(&worldDef);World creation copies all the data it needs from the definition, so the definition can
go out of scope immediately afterward.
Creating a Ground Box
Bodies are built using the following steps:
- Define a body with position, type, etc.
- Use the world id to create the body.
- Build a hull shape with the desired extents.
- Create the shape on the body.
For step 1, create the ground body definition and set its initial position:
b3BodyDef groundBodyDef = b3DefaultBodyDef();
groundBodyDef.position = (b3Vec3){ 0.0f, -10.0f, 0.0f };For step 2, use the world id to create the ground body. Bodies are static by default,
meaning they have zero mass, never move, and do not collide with other static bodies.
b3BodyId groundId = b3CreateBody(worldId, &groundBodyDef);Notice that worldId is passed by value. Ids are small structures and are always passed
by value.
For steps 3 and 4, build a box hull and attach it. Box3D uses convex hulls for box
shapes. The b3MakeBoxHull helper takes three half-extents (hx, hy, hz), so the
ground slab below is 100 units wide in X, 20 units tall in Y, and 100 units deep in Z:
b3BoxHull groundBox = b3MakeBoxHull(50.0f, 10.0f, 50.0f);
b3ShapeDef groundShapeDef = b3DefaultShapeDef();
b3CreateHullShape(groundId, &groundShapeDef, &groundBox.base);The .base field holds the b3HullData that b3CreateHullShape expects. Box3D copies
the hull data into a shared internal database, so groundBox does not need to outlive
the call. Do not call b3DestroyHull on a b3BoxHull; it is stack-allocated.
Box3D is tuned for meters, kilograms, and seconds, so the extents above are in meters.
The engine works best when objects are sized like real-world objects (a barrel is roughly
1 m tall). Simulating glaciers or dust particles would push the limits of single-precision
floating point.
Every shape must have a parent body, even static shapes. You can attach multiple shapes
to one body. A shape's world transform is inherited from its parent body; there is no
independent shape transform.
Creating a Dynamic Body
Creating a dynamic body follows the same steps. The key difference is setting the body
type to b3_dynamicBody and giving the shape a non-zero density.
b3BodyDef bodyDef = b3DefaultBodyDef();
bodyDef.type = b3_dynamicBody;
bodyDef.position = (b3Vec3){ 0.0f, 4.0f, 0.0f };
b3BodyId bodyId = b3CreateBody(worldId, &bodyDef);Caution:
You must set the body type tob3_dynamicBodyif you want the body to
move in response to forces such as gravity.
Create a unit cube hull and a shape definition with density and friction:
b3BoxHull dynamicBox = b3MakeCubeHull(1.0f);
b3ShapeDef shapeDef = b3DefaultShapeDef();
shapeDef.density = 1.0f;
shapeDef.baseMaterial.friction = 0.3f;
b3CreateHullShape(bodyId, &shapeDef, &dynamicBox.base);b3MakeCubeHull(r) is a convenience that produces a cube with half-extent r on all
three axes, equivalent to b3MakeBoxHull(r, r, r).
Caution:
A dynamic body should have at least one shape with a non-zero density.
Otherwise you will get unexpected behavior.
That completes initialization. We are now ready to simulate.
Simulating the World
Box3D uses a numerical integrator that advances the simulation by discrete time steps.
A fixed time step of 1/60 seconds (60 Hz) is recommended for most games. Avoid tying
the time step to your frame rate; a variable time step produces variable results that
are hard to debug.
float timeStep = 1.0f / 60.0f;In addition to integration, Box3D uses a constraint solver. Box3D advances through the
time step in several sub-steps, giving each constraint multiple chances to react. Four
sub-steps is the suggested value:
int subStepCount = 4;At 60 Hz with 4 sub-steps the constraints run at 240 Hz internally. More sub-steps
improve accuracy at the cost of performance.
The simulation loop calls b3World_Step once per game tick:
for (int i = 0; i < 90; ++i)
{
b3World_Step(worldId, timeStep, subStepCount);
b3Vec3 position = b3Body_GetPosition(bodyId);
b3Quat rotation = b3Body_GetRotation(bodyId);
printf("%4.2f %4.2f %4.2f %4.2f %4.2f %4.2f %4.2f\n",
position.x, position.y, position.z,
rotation.v.x, rotation.v.y, rotation.v.z, rotation.s);
}b3Body_GetPosition returns a b3Vec3 with the body origin in world space.b3Body_GetRotation returns a b3Quat โ a unit quaternion stored as a vector partq.v (x, y, z) and a scalar part q.s. There is no single angle to extract as there
was in 2D; orientation in 3D requires the full quaternion. To convert to an axis-angle
representation use b3GetAxisAngle:
float angle;
b3Vec3 axis = b3GetAxisAngle(&angle, rotation);angle is in radians and axis is the unit rotation axis. Useb3MakeMatrixFromQuat when you need a 3ร3 rotation matrix, for example to feed a
renderer.
The output should show the box falling from y = 4 and coming to rest on the ground at
approximately y = 1 (the box half-height sits at y = 0 + 1 after the ground surface
at y = 0):
0.00 4.00 0.00 ...
0.00 3.99 0.00 ...
0.00 3.98 0.00 ...
...
0.00 1.25 0.00 ...
0.00 1.13 0.00 ...
0.00 1.01 0.00 ...For advice on managing a fixed simulation rate alongside a variable render rate, see
Fix Your Timestep!.
Multithreading (optional)
By default Box3D runs single-threaded. The b3DefaultWorldDef leaves workerCount
at 1 and the task callbacks null, which is fine for getting started. When performance
matters, Box3D can drive a task system. Supply workerCount plus enqueueTask,finishTask, and userTaskContext on the world definition before callingb3CreateWorld. See the Foundations page for details.
Cleanup
When you are done with the simulation, destroy the world:
b3DestroyWorld(worldId);This efficiently destroys all bodies, shapes, and joints in the simulation.
File: docs/large_worlds.md
Large Worlds (Double Precision) {#large-worlds}
Box3D can be built with double precision world positions for large worlds: simulations that range
far from the origin, where a single precision float can no longer resolve a position. At a
coordinate of 1e7 meters a float has a step of about one meter, so bodies snap to a coarse grid and
contacts jitter. Double precision keeps full sub-millimeter resolution out to planetary distances.
Only the world position boundary is double. Velocities, forces, shapes, contact manifolds, the
contact solver, and the broad-phase tree all stay float. This is the same boundary design Jolt
Physics uses for its big-world mode: doubles carry the absolute position, everything inside one
body's frame stays float, and the per-step motion the solver integrates is small and meters-scale
where float precision is ample. The cost is a few percent, not the 2x of an all-double build.
Double precision is off by default. With it off every double-precision type collapses to its
float counterpart through a typedef and the boundary helpers reduce to plain float operations, so a
float build behaves exactly as Box3D always has, with no measurable cost.
This implementation is inspired by Jolt.
Enabling it
Set the CMake option:
set(BOX3D_DOUBLE_PRECISION ON)The define is propagated to consumers as a PUBLIC compile definition, so anything that links Box3D
through CMake sees the same precision mode in the headers and cannot mismatch. For non-CMake
consumers there is a link-time guard: a float application linked against a double-precision library
(or the reverse) fails to link on the first Box3D call rather than miscompiling silently. At runtimeb3IsDoublePrecision() reports which mode the library was built in, for bindings and diagnostics.
The two world-position types
Double precision adds two types and leaves the existing math types alone:
b3Posโ a world position. Three doubles in large world mode, an alias forb3Vec3
otherwise.b3WorldTransformโ a world transform: ab3Postranslation and a floatb3Quatrotation.
An alias forb3Transformotherwise.
b3Vec3, b3Quat, b3Transform, and b3AABB stay float in both modes. b3Transform remains the
type for local and relative frames; b3WorldTransform is used for world space. Rotations are float
quaternions in both modes, so the trig and the cross-platform determinism properties are unchanged.
The public API uses these types wherever it accepts or returns a world position: b3BodyDef.position,b3Body_GetPosition / b3Body_GetTransform, b3Body_SetTransform, b3Body_GetWorldPoint /b3Body_GetLocalPoint, b3Body_GetWorldCenter, the explosion and ray-cast origins, contact
and ray-cast result points, and the body move event. With double precision off these are all the
float types they have always been, so existing code compiles unchanged.
With double precision on, b3Pos and b3Vec3 are distinct structs by design. Code that
passed a b3Vec3 where a world position is now required no longer compiles, which is the intended
cost: enabling large world mode is a deliberate source migration, and the compiler points at every
site that needs a conversion. Helpers cover the boundary:
b3Pos p = b3ToPos( v ); // float vector -> world position
b3Vec3 v = b3ToVec3( p ); // world position -> float vector (lossy far from origin)
b3Vec3 d = b3SubPos( a, b ); // a - b, demoted to float (the precision boundary)
b3Pos q = b3OffsetPos( p, d ); // p + d
float x = b3RoundDownFloat( p.x ); // conservative narrowing, pair with b3RoundUpFloat to
// build a float box that always contains double boundsOperating range
Full simulation correctness holds everywhere b3Pos can represent. Body integration, the
contact solver, joints, and continuous collision all run in float relative to each body's own
moving frame, so a stack settles and a bullet is caught the same way at 1e7 as at the origin.
The practical limit comes from the broad phase, which stays float and stores conservative
(outward-rounded) float bounds. Far from the origin the float bound quantization grows: about one
meter at 1e7, sixteen meters at 1e8. Overlapping shapes always still produce a pair, so correctness
is preserved, but beyond roughly 1e7 to 1e8 meters the broad phase reports extra false pairs and
loses some margin hysteresis, which costs performance. Stay within about ยฑ1e7 to ยฑ1e8 meters.
Queries far from the origin
Every spatial query takes a caller supplied b3Pos origin and re-differences each shape against a
nearby base at full precision, so hit points and fractions stay accurate far from the world origin.
The one shared limit is the broad phase: the tree is traversed in conservative outward rounded float,
so it never misses a pair, but a cast that grazes a shape by less than a coordinate float ULP far
from the origin can still miss at the tree level. Only the explosion is a pure float carve-out.
b3World_OverlapShape,b3World_CastShape,b3World_CastMover, andb3World_CollideMovertake ab3Posorigin. Their proxy, mover, and returned planes are relative to that origin and each shape
is re-differenced against it in float, so a query around a mover at 1e7 is as precise as one at the
origin. Shape cast hit points come back asb3Pos.b3World_CastRay/b3World_CastRayClosesttake ab3Posorigin and re-difference each shape
against its body in full precision, so hit points and fractions stay accurate far from the origin.
The tree traversal itself is float (see the broad phase limit above). Hit points come back asb3Pos.b3Shape_RayCasttakes ab3Posorigin and a translation and returns ab3WorldCastOutputwhose
hit point is a worldb3Pos, re-centered on the origin for full precision.b3World_Exploderesolves the per-shape impulse in float around the explosion position.
The character controller (b3World_CastMover / b3World_CollideMover) drives this: pass the
character's world position as the origin and the mover stays precise even at 1e7. The only remaining
float carve-out is the broad phase tree traversal, which Box2D shares.
Debug drawing
b3World_Draw hands every callback world coordinates in the double-capable b3Pos andb3WorldTransform types, so the engine stays camera agnostic. The host shifts into its own camera
frame inside the callbacks: keep a draw origin near the camera and difference against it in double
before the coordinates demote to float, and a distant scene draws crisply instead of snapping to the
coarse float grid around the absolute origin. A zero origin reproduces absolute coordinates and leaves
a near-origin scene unchanged. The sample app sets the draw origin from the camera eye each frame, and
the Large World sample uses it to render a stack at 1e7 with no jitter.
Determinism
Both precision modes are internally deterministic and reproduce across worker counts. The numerics
differ between modes โ double precision accumulates body positions in double, so a body settles and
sleeps on a slightly different step and the state hash differs โ so a double-precision build is not
bit-identical to a float build. The DeterminismTest carries a separate set of expected values for
each mode.
SIMD
Double precision is orthogonal to SIMD. The wide types used in the broad phase and mesh collision
stay 4-wide float, and the contact solver is untouched in both modes. There is no double-precision
SIMD path and none is needed: the hot interior never sees an absolute world coordinate.
File: docs/loose_ends.md
Loose Ends
User Data
Bodies, shapes, and joints allow you to attach user data as a void*. This is
useful when you receive a body, shape, or joint id from an event or query and
need to map it back to a game object.
A common pattern is storing a game-entity pointer on the body:
GameEntity* entity = GameCreateEntity();
b3BodyDef bodyDef = b3DefaultBodyDef();
bodyDef.userData = entity;
entity->bodyId = b3CreateBody(myWorldId, &bodyDef);The circular reference lets you go from entity to body and back. Some typical
uses:
- Applying damage to an entity from a contact event.
- Triggering a scripted event when a body enters a region.
- Cleaning up game state when a joint is destroyed.
Keep the type consistent. If one body stores a GameEntity*, all bodies should.
Mixing pointer types and casting without a discriminant leads to crashes.
Setters and getters:
// Body
b3Body_SetUserData(bodyId, ptr);
void* ptr = b3Body_GetUserData(bodyId);
// Shape
b3Shape_SetUserData(shapeId, ptr);
void* ptr = b3Shape_GetUserData(shapeId);
// Joint
b3Joint_SetUserData(jointId, ptr);
void* ptr = b3Joint_GetUserData(jointId);
// World
b3World_SetUserData(worldId, ptr);
void* ptr = b3World_GetUserData(worldId);Coordinate Systems
Box3D uses a right-handed coordinate system. Positive Y is up by default,
meaning the default gravity vector points in the negative Y direction
({0, -9.8f, 0}). Nothing in the engine hard-codes this โ gravity is just ab3Vec3 set on the world and you can orient it however your application
requires.
Use MKS units: meters, kilograms, seconds, and radians. The solver is tuned for
objects in the range of roughly 0.1 to 10 meters. Very small or very large
objects relative to this range can degrade numerical stability. If your content
is authored at a different unit scale, apply a single conversion factor at the
boundary between your asset pipeline and Box3D, not scattered throughout the
simulation code.
Bodies in Box3D have 6 degrees of freedom. Position is a b3Vec3, orientation
is a b3Quat, angular velocity and torque are b3Vec3 values. The rotational
inertia tensor is a b3Matrix3.
Debug Drawing
Implement the function pointers in b3DebugDraw to get detailed drawing of the
Box3D world. The struct lives in types.h and has slots for:
DrawShapeFcnโ draws a shape; receives a user-shape object created byb3WorldDef::createDebugShapeand the current transform and color.DrawSegmentFcnโ draws a line segment.DrawTransformFcnโ draws a coordinate frame.DrawPointFcnโ draws a point.DrawBoundsFcnโ draws an AABB as a wireframe box.DrawBoxFcnโ draws an oriented box by extents and transform.DrawStringFcnโ draws a world-space label.
Category flags on the struct control what gets drawn:
| Flag | What it shows |
|---|---|
drawShapes |
Shape geometry |
drawJoints |
Joint frames and constraints |
drawJointExtras |
Extra joint information (limits, motors) |
drawBounds |
Shape AABBs |
drawMass |
Center-of-mass marker and mass value for dynamic bodies |
drawBodyNames |
Body name strings |
drawContacts |
Contact points and anchors |
drawContactNormals |
Contact normal directions |
drawContactForces |
Normal impulse magnitudes |
drawGraphColors |
Constraint-graph color assignment |
drawContactFeatures |
Raw contact feature indices |
drawIslands |
Island bounding boxes |
Call the draw function after stepping the world:
b3DebugDraw draw = b3DefaultDebugDraw();
draw.DrawShapeFcn = MyDrawShape;
draw.DrawSegmentFcn = MyDrawSegment;
// ... fill remaining callbacks ...
draw.drawShapes = true;
draw.drawingBounds = myViewAABB;
b3World_Draw(worldId, &draw, UINT64_MAX);The maskBits argument is tested against each shape's categoryBits in the
broad-phase tree. Only shapes where (maskBits & shape->categoryBits) != 0 are
visited. Pass UINT64_MAX to draw everything, or use a subset of your category
bits to limit drawing to specific layers (e.g. skip debris for a clean overhead
view).
The shape draw path uses a two-callback model set on b3WorldDef:createDebugShape is called once when a shape is first drawn, giving you a
chance to upload GPU geometry; destroyDebugShape is called when the shape is
modified or destroyed so you can release it. The DrawShapeFcn callback
receives the opaque handle returned by createDebugShape, which enables
efficient multi-pass rendering without re-uploading geometry every frame.
The samples application demonstrates a complete b3DebugDraw implementation.
Limitations
Box3D uses several approximations to simulate rigid body physics efficiently.
As a v0.1 engine it is still maturing, so expect rougher edges than Box2D.
Current limitations:
- Extreme mass ratios between connected bodies can cause joint stretching and
contact overlap. - Soft constraints improve robustness but allow a small amount of flexing in
joints and contacts. - Continuous collision handles fast-moving dynamic bodies against static
geometry (and bullets against dynamic bodies), but not general
dynamic-versus-dynamic continuous collision. - Continuous collision does not propagate through joints, so fast-moving
articulated chains may momentarily stretch. - The integrator is semi-implicit Euler, which gives first-order accuracy.
Projectile arcs are approximate. IncreasingsubStepCountinb3World_Stepimproves accuracy at a proportional cost. - Constraint resolution uses an iterative Gauss-Seidel solver for real-time
performance. Collisions are not perfectly rigid and contacts are not
pixel-accurate. More sub-steps tighten the result. - Mesh and height-field shapes are static-only. They do not participate in
dynamic-versus-dynamic contact. - The compound shape type is static-body only and immutable after creation.
- The character mover API is experimental.
File: docs/overview.md
Overview {#mainpage}
Caution:
The written part of this manual is a work in progress.
It will be updated as v1.0 approaches.
The reference section should be complete and accurate.
Box3D is a 3D rigid body simulation library for games. Programmers can
use it in their games to make objects move in realistic ways and make
the game world more interactive. From the game engine's point of view,
a physics engine is a system for procedural animation.
Box3D also provides many collision routines that can be used even when
rigid body simulation is not used. There are functions for overlap and
cast queries. There is also a bounding volume hierarchy (dynamic tree)
that can be used for game specific spatial sorting needs.
Box3D is written in portable C17. Most of the types defined in the
engine begin with the b3 prefix. Hopefully this is sufficient to avoid
name clashing with your application.
Prerequisites
In this manual I'll assume you are familiar with basic physics
concepts, such as mass, force, torque, and impulses. If not, please
first consult Google search and Wikipedia.
Box2D (the 2D sibling of Box3D) was created as part of a physics tutorial
at the Game Developer Conference. You can get those tutorials from the
publications section of box2d.org,
which cover the underlying algorithms shared by both engines.
Since Box3D is written in C, you are expected to be experienced in C
programming. Box3D should not be your first C programming project. You
should be comfortable with compiling, linking, and debugging.
Caution:
Box3D should not be your first C project. Please learn C
programming, compiling, linking, and debugging before working with
Box3D. There are many resources for this online.
Scope
This manual covers the majority of the Box3D API. However, not every
aspect is covered. Please look at the Reference section and samples
application included with Box3D to learn more.
This manual is only updated with new releases. The latest version of
Box3D may be out of sync with this manual.
Caution:
This manual applies to the associated release and not necessarily the
latest version on the main branch.
Feedback and Bugs
Please file bugs and feature requests here:
Box3D Issues
You can help to ensure your issue gets fixed if you provide sufficient
detail. A testbed example that reproduces the problem is ideal. You can
read about the testbed later in this document.
There is also a Discord server and
GitHub Discussions.
Core Concepts
Box3D works with several fundamental concepts and objects. I briefly
define these objects here and more details are given later in this
document.
rigid body
A chunk of matter that is so strong that the distance between any two
bits of matter on the chunk is constant. They are hard like a diamond.
In the following discussion I use body interchangeably with rigid body.
shape
A shape binds collision geometry to a body and adds material properties such as
density, friction, and restitution. A shape puts collision geometry into the
collision system (broad-phase) so that it can collide with other shapes.
constraint
A constraint is a physical connection that removes degrees of freedom
from bodies. A 3D body has 6 degrees of freedom (three translation
coordinates and three rotation coordinates). If I take a body and pin it
to the wall (like a pendulum) I have constrained the body to the wall.
At this point the body can only rotate about the pin, so the constraint
has removed 3 translational degrees of freedom, leaving the body free
to swing in 3D about the fixed anchor.
contact constraint
A special constraint designed to prevent penetration of rigid bodies and
to simulate friction and restitution. You do not create contact
constraints; they are created automatically by Box3D.
joint constraint
This is a constraint used to hold two or more bodies together. Box3D
supports several joint types: revolute, prismatic, distance, spherical,
weld, wheel, motor, parallel, and filter. Joints may have limits,
motors, and/or springs.
joint limit
A joint limit restricts the range of motion of a joint. For example, the
human elbow only allows a certain range of angles.
joint motor
A joint motor drives the motion of the connected bodies according to the
joint's degrees of freedom. For example, you can use a motor to drive
the rotation of an elbow. Motors have a target speed and a maximum force
or torque. The simulation will apply the force or torque required to
achieve the desired speed.
joint spring
A joint spring has a stiffness and damping. In Box3D spring stiffness is
expressed in terms of Hertz or cycles per second. This lets you configure how
quickly a spring reacts regardless of the body masses. Joint springs also
have a damping ratio to let you specify how quickly the spring will come to
rest.
world
A physics world is a collection of bodies, shapes, joints, and contacts
that interact together. Box3D supports the creation of multiple worlds which
are completely independent.
solver
The physics world has a solver that is used to advance time and to
resolve contact and joint constraints. The Box3D solver is a high
performance sequential solver that operates in order N time, where N is
the number of constraints.
continuous collision
The solver advances bodies in time using discrete time steps. Without
intervention this can lead to tunneling.
Box3D contains specialized algorithms to deal with tunneling. First, the
collision algorithms can interpolate the motion of two bodies to find
the first time of impact (TOI). Second, speculative collision is used to create
contact constraints between bodies before they touch.
events
World simulation leads to the creation of events that are available at the end
of the time step:
- body movement events
- contact begin and end events
- sensor begin and end events
- contact hit events
These events allow your application to react to changes in the simulation.
Modules
Box3D's primary purpose is to provide rigid body simulation. However,
there are math and collision features that may be useful apart from the
rigid body simulation. These are provided in the include directory. Anything
in the include directory is considered public, while everything in the src
directory is considered internal.
Public features are supported and you can get help with these on the Discord
server. Using internal code directly is not supported. However, feel free to
study the code and ask questions. I'm happy to share all the details of how
Box3D works internally.
Units
Box3D works with floating point numbers and tolerances have to be used
to make Box3D perform well. These tolerances have been tuned to work
well with meters-kilogram-second (MKS) units. In particular, Box3D has
been tuned to work well with moving shapes between 0.1 and 10 meters. So
this means objects between soup cans and buses in size should work well.
Static shapes may be up to 50 meters long without trouble. If you have a
large world, you should split it up into multiple static bodies. This will
improve precision and simulation behavior.
Caution:
Box3D is tuned for MKS units. Keep the size of moving objects larger than 1cm.
You'll need to use some scaling system when you render your environment and
actors. Do not use non-metric units unless you understand the implications.
Another limitation to consider is overall world size. If your world units
become larger than 12 kilometers or so, then the lost precision can affect
stability.
Caution:
Box3D works best with world sizes less than 12 kilometers. If you are
careful with your simulation tuning, this can be pushed up to around 24
kilometers, which is much larger than most game worlds.
Box3D uses radians for angles. Orientation is stored as a quaternion
(b3Quat), so there is no concept of a body angle clamped to a range;
the full 3D rotation is tracked continuously.
Caution:
Box3D uses radians, not degrees.
Changing the length units
Advanced users may change the length unit by calling b3SetLengthUnitsPerMeter()
at application startup. If you keep Box3D in a shared library, you will need
to call this if the shared library is reloaded.
It is harder to get support for using Box3D if you change the unit
system, because values are harder to communicate and may become non-intuitive.
One of the benefits of using MKS units for physics simulation is that you can
use real world values to get reasonable results.
Ids and Definitions
Fast memory management plays a central role in the design of the Box3D
interface. When you create a world, body, shape or joint, you will receive
a handle called an id. These ids are opaque and are passed to various functions
to access the underlying data.
These ids provide some safety. If you use an id after it has been freed you will
usually get an assertion. All ids support 64k generations of safety. All ids
also have a corresponding function you can call to check if it is valid.
When you create a world, body, shape, or joint, you need to provide a definition structure.
These definitions contain all the information needed to build the Box3D object. By using
this approach I can prevent construction errors, keep the number of function parameters
small, provide sensible defaults, and reduce the number of accessors.
Here is an example of body creation:
b3BodyDef bodyDef = b3DefaultBodyDef();
bodyDef.position = (b3Vec3){10.0f, 0.0f, 5.0f};
b3BodyId myBodyId = b3CreateBody(myWorldId, &bodyDef);Notice the body definition is initialized by calling b3DefaultBodyDef(). This is needed
because C does not have constructors and zero initialization is generally not suitable for the
definitions used in Box3D.
Also notice that the body definition is a temporary object that is fully copied into the internal
body data structures. Definitions should usually be created on the stack as temporaries.
This is how a body is destroyed:
b3DestroyBody(myBodyId);
myBodyId = b3_nullBodyId;Notice that the body id is set to null using the constant b3_nullBodyId. You should treat
ids as opaque data, however you may zero initialize all Box3D ids and they will be considered
null.
Shapes are created in a similar way. For example, here is how a box shape is created:
b3ShapeDef shapeDef = b3DefaultShapeDef();
shapeDef.baseMaterial.friction = 0.42f;
b3BoxHull boxHull = b3MakeBoxHull(0.5f, 0.25f, 0.5f);
b3ShapeId myShapeId = b3CreateHullShape(myBodyId, &shapeDef, &boxHull.base);And the shape may be destroyed as follows:
b3DestroyShape(myShapeId, true);
myShapeId = b3_nullShapeId;For convenience, Box3D will destroy all shapes on a body when the body is destroyed. You don't need to store the shape id.
There are some macros to assist using ids in logical operations.
bool isNull = B3_IS_NULL(myBodyId);
bool isNonNull = B3_IS_NON_NULL(myJointId);
bool areEqual = B3_ID_EQUALS(myShapeIdA, myShapeIdB);--- METRICS ---
- Files Extracted: 11
- Estimated Token Budget: ~22451 tokens
- Recency Window: Active (< 180 days)
- Canonical Reference: https://codewiki.google/github.com/erincatto/box3d