How Contact Friction and Restitution Parameters Affect Material Response in Box3D

Box3D models material interaction through surface materials attached to shapes, where the friction coefficient controls sliding resistance using geometric mean mixing and restitution determines bounce intensity using maximum value selection, both of which can be customized via callbacks or tuned per-shape.

The erincatto/box3d physics engine defines material behavior through the b3SurfaceMaterial structure attached to every shape. These materials specify how objects interact when they collide, with the contact solver in src/contact_solver.c applying these properties to calculate physical responses. The two primary parameters—friction and restitution—determine tangential resistance and energy conservation during impacts.

Surface Material Properties

Every shape in Box3D carries a b3SurfaceMaterial containing friction and restitution values. You define these when creating shapes or modify them later using the dedicated setter functions.

Set per-shape material parameters using b3Shape_SetFriction and b3Shape_SetRestitution:

b3ShapeId shape = b3Shape_CreateSphere(world, 0.5f);
b3Shape_SetFriction(shape, 0.3f);      // Low-friction material
b3Shape_SetRestitution(shape, 0.7f);   // Bouncy material

These values are stored in the shape definition within src/shape.c and retrieved during contact generation to determine how the shape interacts with other bodies.

Default Mixing Rules

When two shapes collide, Box3D must combine their individual material properties into a single contact value. The engine applies specific mixing strategies by default, implemented in src/physics_world.c.

Friction Mixing: Geometric Mean

Friction uses the geometric mean of the two contacting materials:

// Default implementation in src/physics_world.c (lines 54-58)
float b3DefaultFrictionCallback(float a, uint64_t idA,
                                float b, uint64_t idB) {
    return sqrtf(a * b);
}

This sqrt(frictionA * frictionB) approach ensures that contacting a high-friction surface with a low-friction surface produces moderate resistance, preventing extreme values from dominating.

Restitution Mixing: Maximum Value

Restitution uses the maximum value of the two materials:

// Default implementation in src/physics_world.c (lines 60-64)
float b3DefaultRestitutionCallback(float a, uint64_t idA,
                                   float b, uint64_t idB) {
    return (a > b) ? a : b;
}

The max(restitutionA, restitutionB) rule means that if either object is bouncy, the contact will exhibit bounce characteristics.

Contact Solver Implementation

The mixed values directly influence impulse calculations in src/contact_solver.c. During the constraint solving phase, these parameters scale the forces applied to resolve collisions.

Friction in the Solver

The mixed friction value determines the magnitude of the tangential impulse that resists sliding. The solver uses contactConstraint->friction to scale the lateral force calculations, meaning higher friction values increase the resistance required to slide objects past each other.

Restitution in the Solver

Restitution governs the normal impulse when objects separate. The solver calculates bounce as:

impulse = -cp->normalMass * (vn + restitution * cp->relativeVelocity);

Here, vn represents the normal relative velocity. The restitution coefficient scales the velocity-based bounce term—values near 1.0 preserve nearly all impact velocity, while 0.0 eliminates bounce entirely.

Custom Mixing via Callbacks

Box3D allows complete customization of material interaction through callback functions defined in b3WorldDef. Replace the default mixing behavior by providing your own functions when creating the world.

Define custom friction and restitution callbacks:

float MyFrictionCallback(float a, uint64_t idA,
                         float b, uint64_t idB)
{
    // Linear blend based on material IDs
    return (idA < idB) ? a : b;
}

float MyRestitutionCallback(float a, uint64_t idA,
                            float b, uint64_t idB)
{
    // Average restitution with clamping
    float avg = 0.5f * (a + b);
    return (avg > 0.5f) ? 0.5f : avg;
}

// Apply when creating the world
b3WorldDef def = b3DefaultWorldDef();
def.frictionCallback    = MyFrictionCallback;
def.restitutionCallback = MyRestitutionCallback;
b3WorldId worldId = b3CreateWorld(&def);

These callbacks receive both material values and their associated user IDs, enabling material-specific logic such as anisotropic friction or energy-preserving restitution models.

Restitution Threshold Control

Box3D includes a restitution threshold to prevent jitter from low-energy contacts. When the normal relative velocity falls below this threshold, the solver ignores restitution even if materials are bouncy.

Configure the threshold at runtime:

// Adjust threshold to ignore tiny bounces
float current = b3World_GetRestitutionThreshold(worldId);
b3World_SetRestitutionThreshold(worldId, 0.1f);

In src/contact_solver.c, the solver reads this value before applying the bounce term:

float threshold = context->world->restitutionThreshold;
if (vn < -threshold) {
    // Apply restitution
}

This prevents micro-jitter when objects settle against each other while preserving high-velocity bounces.

Summary

  • Surface materials store friction and restitution per-shape via b3Shape_SetFriction and b3Shape_SetRestitution in src/shape.c.
  • Default mixing uses geometric mean (sqrt(a*b)) for friction and maximum value for restitution, defined in src/physics_world.c.
  • Contact solver applies friction to tangential impulses and restitution to normal bounce calculations in src/contact_solver.c.
  • Custom callbacks allow user-defined mixing strategies through worldDef.frictionCallback and worldDef.restitutionCallback.
  • Restitution threshold filters low-velocity bounces via b3World_SetRestitutionThreshold to improve simulation stability.

Frequently Asked Questions

What is the default friction mixing function in Box3D?

Box3D uses the geometric mean of the two contacting materials' friction values: sqrt(frictionA * frictionB). This implementation resides in b3DefaultFrictionCallback within src/physics_world.c. The geometric mean prevents extreme values from dominating when materials with different friction coefficients collide.

How do I make specific materials bouncier in Box3D?

Set higher restitution values (closer to 1.0) using b3Shape_SetRestitution(shape, 0.8f). Since the default mixing rule selects the maximum restitution value between two contacting materials, a high restitution value on either shape will make the contact bouncy. For global control, implement a custom restitutionCallback in b3WorldDef to define how material values combine.

Can I use different friction for different material pairs?

Yes. Provide a custom frictionCallback function when creating the world via b3WorldDef.frictionCallback. Your callback receives both friction values and their material IDs, allowing you to implement pair-specific rules such as ice-on-metal versus rubber-on-concrete interactions. The default geometric mean mixing can be replaced with any algorithm your callback implements.

Why do small collisions not bounce in Box3D?

Box3D employs a restitution threshold to prevent jitter. If the normal relative velocity of a contact is below the value set by b3World_SetRestitutionThreshold, the solver ignores the restitution coefficient and treats the collision as inelastic. This threshold, read in src/contact_solver.c, ensures stable stacking and resting contacts without micro-bouncing artifacts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →