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

Character Mover

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;
float radius
The radius of the hemispheres.
Definition types.h:1913
b3Vec3 center1
Local center of the first hemisphere.
Definition types.h:1907
b3Vec3 center2
Local center of the second hemisphere.
Definition types.h:1910
A solid capsule can be viewed as two hemispheres connected by a rectangle.
Definition types.h:1905
A 3D vector.
Definition math_functions.h:41

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:

  1. Compute a desired translation from input and physics (gravity, velocity).
  2. Call b3World_CastMover to find how far the mover can actually travel.
  3. Move the capsule by the returned fraction of the desired translation.
  4. Call b3World_CollideMover to gather all contact planes at the new position.
  5. Filter and assemble the planes into a b3CollisionPlane array.
  6. Call b3SolvePlanes to compute a corrected position delta.
  7. Apply the delta and call b3ClipVector to remove velocity components that push into surfaces.

Swept Motion: 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
);
bool b3MoverFilterFcn(b3ShapeId shapeId, void *context)
Used to filter shapes for shape casting character movers.
Definition types.h:1853
World id references a world instance. This should be treated as an opaque handle.
Definition id.h:38
b3Vec3 b3Pos
In single precision mode these types are the same.
Definition math_functions.h:90
The query filter is used to filter collisions between queries and shapes.
Definition types.h:1284
float b3World_CastMover(b3WorldId worldId, b3Pos origin, const b3Capsule *mover, b3Vec3 translation, b3QueryFilter filter, b3MoverFilterFcn *fcn, void *context)
Cast a capsule mover through the world.

This casts the capsule through the world and returns the fraction [0, 1] of translation 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.
Shape id references a shape instance. This should be treated as an opaque handle.
Definition id.h:53

b3World_CastMover is intended for movement, not for gathering contact information. Use b3World_CollideMover for that.

Contact Planes: b3World_CollideMover

b3WorldId worldId,
b3Pos origin, // mover and returned planes are relative to this
const b3Capsule* mover,
b3QueryFilter filter,
void* context
);
bool b3PlaneResultFcn(b3ShapeId shapeId, const b3PlaneResult *plane, int planeCount, void *context)
Used to collect collision planes for character movers.
Definition types.h:1849
void b3World_CollideMover(b3WorldId worldId, b3Pos origin, const b3Capsule *mover, b3QueryFilter filter, b3PlaneResultFcn *fcn, void *context)
Collide a capsule mover with the world, gathering collision planes that can be fed to b3SolvePlanes.

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.
The plane between a character mover and a shape.
Definition types.h:1800

b3PlaneResult carries the contact plane and a world-space contact point:

typedef struct b3PlaneResult {
b3Plane plane; // normal + offset: separation = dot(normal, p) - offset
b3Plane plane
Outward pointing plane.
Definition types.h:1802
b3Vec3 point
Closest point on the shape. May not be unique.
Definition types.h:1805
A plane.
Definition math_functions.h:113

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):

b3BodyId bodyId,
b3BodyPlaneResult* bodyPlanes,
int planeCapacity,
b3Pos origin,
const b3Capsule* mover,
b3QueryFilter filter,
b3WorldTransform bodyTransform
);
int b3Body_CollideMover(b3BodyId bodyId, b3BodyPlaneResult *bodyPlanes, int planeCapacity, b3Pos origin, const b3Capsule *mover, b3QueryFilter filter, b3WorldTransform bodyTransform)
Collide a character mover with a specific body using a specified body transform.
Body plane result for movers.
Definition types.h:1839
Body id references a body instance. This should be treated as an opaque handle.
Definition id.h:45
b3Transform b3WorldTransform
In single precision mode these types are the same.
Definition math_functions.h:93

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 into b3CollisionPlane entries and call:

typedef struct b3CollisionPlane {
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
b3Vec3 targetDelta,
int count
);
float pushLimit
Setting this to FLT_MAX makes the plane as rigid as possible.
Definition types.h:1818
bool clipVelocity
Indicates if b3ClipVector should clip against this plane. Should be false for soft collision.
Definition types.h:1824
b3Plane plane
The collision plane between the mover and some shape.
Definition types.h:1814
float push
The push on the mover determined by b3SolvePlanes. Usually in meters.
Definition types.h:1821
b3PlaneSolverResult b3SolvePlanes(b3Vec3 targetDelta, b3CollisionPlane *planes, int count)
Solves the position of a mover that satisfies the given collision planes.
These are collision planes that can be fed to b3SolvePlanes.
Definition types.h:1812
Result returned by b3SolvePlanes.
Definition types.h:1829

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);
b3Vec3 b3ClipVector(b3Vec3 vector, const b3CollisionPlane *planes, int count)
Clips the velocity against the given collision planes.

This removes the components of vector that push into any plane where clipVelocity 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);
b3Vec3 delta
The final relative translation.
Definition types.h:1831
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

The Mover sample in the samples application shows a complete implementation including acceleration, friction, jumping, and a pogo stick.