Box3D 0.1.0
A 3D physics engine for games
Loading...
Searching...
No Matches
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 using b3GetVersion().

b3Version version = b3GetVersion();
printf("Box3D version %d.%d.%d\n", version.major, version.minor, version.revision);
int revision
Bug fixes.
Definition base.h:157
int minor
Incremental changes.
Definition base.h:154
int major
Significant changes.
Definition base.h:151
b3Version b3GetVersion(void)
Get the current version of Box3D.
Version numbering scheme.
Definition base.h:149

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 interpolation
b3Vec3 b3Sub(b3Vec3 a, b3Vec3 b)
Vector subtraction.
Definition math_functions.h:229
float b3Dot(b3Vec3 a, b3Vec3 b)
Vector dot product.
Definition math_functions.h:247
b3Vec3 b3Add(b3Vec3 a, b3Vec3 b)
Vector addition.
Definition math_functions.h:223
b3Vec3 b3MulSV(float s, b3Vec3 a)
s * a
Definition math_functions.h:347
b3Vec3 b3Lerp(b3Vec3 a, b3Vec3 b, float alpha)
Linearly interpolate between two vectors.
Definition math_functions.h:363
b3Vec3 b3Perp(b3Vec3 a)
Get a unit vector that is perpendicular to the supplied vector.
Definition math_functions.h:309
b3Vec3 b3Normalize(b3Vec3 a)
Normalize a vector. Returns a zero vector if the input vector is very small.
Definition math_functions.h:279
b3Vec3 b3Cross(b3Vec3 a, b3Vec3 b)
https://en.wikipedia.org/wiki/Cross_product
Definition math_functions.h:353
float b3Length(b3Vec3 v)
Vector length.
Definition math_functions.h:253
A 3D vector.
Definition math_functions.h:41

b3Quat

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
// Inverse-rotate a vector (equivalent to rotating by the conjugate)
// Compose two rotations: apply q2 first, then q1
b3Quat qc = b3MulQuat(q1, q2);
// Conjugate (same as inverse for a unit quaternion)
// Extract axis-angle
float angle;
b3Vec3 axis = b3GetAxisAngle(&angle, q);
// Total rotation angle (ignoring axis)
float totalAngle = b3GetQuatAngle(q);
// Convert to rotation matrix
// Normalized linear interpolation
b3Quat qi = b3NLerp(q1, q2, alpha);
b3Vec3 b3InvRotateVector(b3Quat q, b3Vec3 v)
Inverse rotate a vector.
Definition math_functions.h:465
B3_FORCE_INLINE b3Matrix3 b3MakeMatrixFromQuat(b3Quat q)
Make a matrix from a quaternion.
Definition math_functions.h:932
float b3GetQuatAngle(b3Quat q)
Get the angle for a quaternion in radians.
Definition math_functions.h:553
b3Quat b3NLerp(b3Quat q1, b3Quat q2, float alpha)
Linearly interpolate and normalize between two quaternions.
Definition math_functions.h:588
b3Vec3 b3RotateVector(b3Quat q, b3Vec3 v)
Rotate a vector.
Definition math_functions.h:454
b3Vec3 b3GetAxisAngle(float *radians, b3Quat q)
Get the axis and angle from a quaternion. Assumes the quaternion is normalized.
Definition math_functions.h:538
b3Quat b3Conjugate(b3Quat q)
Quaternion conjugate (cheap inverse).
Definition math_functions.h:503
b3Quat b3MakeQuatFromAxisAngle(b3Vec3 axis, float radians)
Make a quaternion that is equivalent to rotating around an axis by a specified angle.
Definition math_functions.h:529
b3Quat b3MulQuat(b3Quat q1, b3Quat q2)
Multiply two quaternions.
Definition math_functions.h:482
A 3x3 matrix.
Definition math_functions.h:99
A quaternion.
Definition math_functions.h:58

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
// Invert a transform
b3Transform b3MulTransforms(b3Transform a, b3Transform b)
Multiply two transforms.
Definition math_functions.h:606
b3Vec3 b3InvTransformPoint(b3Transform t, b3Vec3 v)
Inverse transform a point.
Definition math_functions.h:642
B3_FORCE_INLINE b3Transform b3InvMulTransforms(b3Transform a, b3Transform b)
Creates a transform that converts a local point in frame B to a local point in frame A.
Definition math_functions.h:617
b3Transform b3InvertTransform(b3Transform t)
Get the inverse of a transform.
Definition math_functions.h:626
b3Vec3 b3TransformPoint(b3Transform t, b3Vec3 v)
Transform a point.
Definition math_functions.h:635
A rigid transform.
Definition math_functions.h:65

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 include b3AABB_Overlaps, b3AABB_Contains, b3AABB_ContainsPoint, b3AABB_Union, b3AABB_Center, b3AABB_Extents, b3AABB_Inflate, and b3AABB_Transform.

Multithreading

Box3D has been highly optimized for multithreading. Multithreading is not required and by default Box3D runs single-threaded. If performance matters, you should consider wiring up the multithreading interface.

Box3D does not create threads itself. You supply your own thread pool and hook it up through the world definition before calling b3CreateWorld. See b3EnqueueTaskCallback, b3FinishTaskCallback, b3WorldDef::workerCount, b3WorldDef::enqueueTask, and b3WorldDef::finishTask:

worldDef.workerCount = myThreadCount;
worldDef.enqueueTask = MyEnqueueTask;
worldDef.finishTask = MyFinishTask;
worldDef.userTaskContext = myScheduler;
b3WorldId worldId = b3CreateWorld(&worldDef);
World id references a world instance. This should be treated as an opaque handle.
Definition id.h:38
uint32_t workerCount
Number of workers to use with the provided task system.
Definition types.h:186
b3FinishTaskCallback * finishTask
function to finish a task
Definition types.h:192
void * userTaskContext
User context that is provided to enqueueTask and finishTask.
Definition types.h:195
b3EnqueueTaskCallback * enqueueTask
function to spawn task
Definition types.h:189
b3WorldDef b3DefaultWorldDef(void)
Use this to initialize your world definition.
b3WorldId b3CreateWorld(const b3WorldDef *def)
Create a world for rigid body simulation.
World definition used to create a simulation world.
Definition types.h:141

Your MyEnqueueTask callback receives a b3TaskCallback* function pointer plus a context pointer that must be forwarded to the task exactly once on a worker thread. Return a non-null void* handle representing your pending task object, or null if the work was executed serially inside the callback (Box3D then skips the corresponding b3FinishTaskCallback).

// Enqueue callback signature
void* MyEnqueueTask(b3TaskCallback* task, void* taskContext,
void* userContext, const char* taskName)
{
MyTask* t = AllocTask();
t->task = task;
t->taskContext = taskContext;
SubmitToThreadPool(t); // runs task(taskContext) on a worker
return t; // non-null => Box3D will call MyFinishTask(t, ...)
}
// Finish callback signature
void MyFinishTask(void* userTask, void* userContext)
{
MyTask* t = (MyTask*)userTask;
WaitForCompletion(t);
FreeTask(t);
}
void b3TaskCallback(void *taskContext)
Task interface This is the prototype for a Box3D task.
Definition types.h:20

MyFinishTask must block until the task has completed. Because the step blocks here on the tasks it spawned, b3World_Step holds its stack across every fork and join inside the step. This has a consequence for how you drive it. Run b3World_Step from a thread you can dedicate to the step, or from a fiber that MyFinishTask can park so the underlying thread is freed to run the queued tasks. In a job system that cannot park a job's stack, do not call b3World_Step from inside a job: a job that blocks on the sub-jobs it spawned without yielding its thread starves the workers it is waiting on and can deadlock. The alternative is help-while-wait, where the waiting thread runs other pending tasks itself. This is what the in-tree scheduler does, which is why it needs no fiber.

Box3D ships an in-tree scheduler (src/scheduler.h) used by the test suite and samples. You can adapt it or use it directly:

#include "scheduler.h"
b3Scheduler* scheduler = b3CreateScheduler(workerCount);
worldDef.workerCount = workerCount;
worldDef.enqueueTask = b3SchedulerEnqueueTask;
worldDef.finishTask = b3SchedulerFinishTask;
worldDef.userTaskContext = scheduler;
b3WorldId worldId = b3CreateWorld(&worldDef);
// ... simulate ...
b3DestroyWorld(worldId);
b3DestroyScheduler(scheduler);
void b3DestroyWorld(b3WorldId worldId)
Destroy a world.

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.

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 during b3World_Step()

Caution: Do not write to the Box3D world from multiple threads

It is safe to do ray-casts, shape-casts, and overlap tests from multiple threads outside of b3World_Step(). Generally any read-only operation is safe to do multithreaded outside of b3World_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.