How Joint Motors in Box3D Drive Rotational and Linear Motion with Force Limits

Box3D joint motors compute constraint impulses to achieve target velocities, then clamp those impulses using user-defined force limits scaled by the time step, ensuring physically stable actuation.

Box3D, the open-source 3D physics engine by Erin Catto, implements motor-driven joints that actively propel rigid bodies while respecting Newtonian force constraints. These joint motors in Box3D operate through a three-stage solver process that converts desired velocities into impulsive forces, making them essential for robotics, vehicle simulations, and mechanical actuators.

How Joint Motors Compute Constrained Motion

The motor architecture is shared across MotorJoint, RevoluteJoint, and PrismaticJoint, with all implementations following a consistent three-phase pattern inside the velocity solver.

Stage 1: Target Velocity Specification

Users specify desired motion through API setters that store target velocities in the joint structure. For the 6-DOF MotorJoint, you set linear and angular velocities independently.

In src/motor_joint.c, lines 16-22 handle linear velocity storage, while lines 30-36 store angular velocity targets:

// Linear velocity target (line 16-22)
void b3MotorJoint_SetLinearVelocity(b3JointId jointId, b3Vec3 velocity)

// Angular velocity target (line 30-36)  
void b3MotorJoint_SetAngularVelocity(b3JointId jointId, b3Vec3 omega)

For single-axis joints, RevoluteJoint uses b3RevoluteJoint_SetMotorSpeed (line 90-96 in src/revolute_joint.c) to set rotational velocity, while PrismaticJoint uses b3PrismaticJoint_SetMotorSpeed (line 96-102 in src/prismatic_joint.c) for linear motion along the joint axis.

Stage 2: Spring-Damping Correction

Optionally, MotorJoint applies soft spring forces before velocity correction. When linearHertz or angularHertz are non-zero, the solver resolves spring constraints that pull bodies toward target relative poses.

This occurs in src/motor_joint.c starting at line 302, where the angular spring block (lines 302-322) and linear spring calculations compute restorative impulses based on the specified frequency and damping ratio.

Stage 3: Impulse Limiting with Force Caps

After spring resolution, the solver computes the impulse required to achieve the target velocity, then clamps it to respect user-defined force limits. This clamping converts Newton limits (or N·m for torque) into impulse limits by multiplying by the time step context->h.

In src/motor_joint.c, lines 94-108 handle linear velocity impulse clamping:

float maxImpulse = context->h * joint->maxVelocityForce;
b3Vec3 oldImpulse = joint->linearVelocityImpulse;
joint->linearVelocityImpulse = b3Add(oldImpulse, impulse);

if (b3LengthSquared(joint->linearVelocityImpulse) > maxImpulse*maxImpulse) {
    joint->linearVelocityImpulse = b3MulSV(maxImpulse,
                                         b3Normalize(joint->linearVelocityImpulse));
}

The angular equivalent appears at lines 128-144, using maxVelocityTorque instead of maxVelocityForce. This pattern ensures the motor never exceeds the specified force budget, regardless of how large the velocity error becomes.

Motor Joint Types and APIs

Box3D provides three distinct motor implementations optimized for different degrees of freedom.

MotorJoint: 6-DOF Linear and Angular Control

MotorJoint simultaneously drives translation and rotation with separate force and torque limits. Setters for these limits appear in src/motor_joint.c at lines 44-70:

  • b3MotorJoint_SetMaxVelocityForce (line 58): Caps linear motor force in Newtons
  • b3MotorJoint_SetMaxVelocityTorque (line 44): Caps angular motor torque in Newton-meters

This joint type maintains four distinct limit values: maxVelocityForce, maxVelocityTorque, maxSpringForce, and maxSpringTorque.

RevoluteJoint: Rotational Motors with Torque Limits

RevoluteJoint provides single-axis rotation using motor speed and maximum torque parameters. The implementation in src/revolute_joint.c exposes:

  • b3RevoluteJoint_SetMotorSpeed (lines 90-96): Sets target angular velocity in radians per second
  • b3RevoluteJoint_SetMaxMotorTorque (lines 105-112): Sets the torque limit in N·m

The solver clamps the accumulated motorImpulse variable to stay within [-maxMotorTorque * h, maxMotorTorque * h].

PrismaticJoint: Linear Motors with Force Limits

PrismaticJoint constrains motion to a single axis while allowing motorized translation. Found in src/prismatic_joint.c, the API includes:

  • b3PrismaticJoint_SetMotorSpeed (lines 96-102): Target linear velocity along the joint axis
  • b3PrismaticJoint_SetMaxMotorForce (lines 111-118): Maximum force in Newtons

Like RevoluteJoint, the solver applies the same impulse-clamping pattern to ensure the motor force never exceeds the user-defined cap.

Code Examples

Example 1: 6-DOF MotorJoint with Velocity and Force Limits

b3WorldId worldId = b3CreateWorld(&worldDef);
b3BodyId bodyA = b3CreateBody(worldId, &bodyDefA);
b3BodyId bodyB = b3CreateBody(worldId, &bodyDefB);

/* Create motor joint */
b3MotorJointDef mjDef = b3DefaultMotorJointDef();
mjDef.base.bodyIdA = bodyA;
mjDef.base.bodyIdB = bodyB;
b3JointId mjId = b3CreateMotorJoint(worldId, &mjDef);

/* Target velocities */
b3MotorJoint_SetLinearVelocity(mjId, (b3Vec3){1.0f, 0.0f, 0.0f});   // 1 m/s along X
b3MotorJoint_SetAngularVelocity(mjId, (b3Vec3){0.0f, 0.5f, 0.0f}); // 0.5 rad/s about Y

/* Force/torque limits */
b3MotorJoint_SetMaxVelocityForce(mjId, 200.0f);   // ≤ 200 N
b3MotorJoint_SetMaxVelocityTorque(mjId, 150.0f); // ≤ 150 N·m

/* Optional spring softening */
b3MotorJoint_SetLinearHertz(mjId, 5.0f);
b3MotorJoint_SetLinearDampingRatio(mjId, 0.7f);

Example 2: RevoluteJoint Rotation with Torque Limit

b3RevoluteJointDef rjDef = b3DefaultRevoluteJointDef();
rjDef.base.bodyIdA = bodyA;
rjDef.base.bodyIdB = bodyB;
b3JointId rjId = b3CreateRevoluteJoint(worldId, &rjDef);

b3RevoluteJoint_EnableMotor(rjId, true);
b3RevoluteJoint_SetMotorSpeed(rjId, 2.0f);          // 2 rad/s
b3RevoluteJoint_SetMaxMotorTorque(rjId, 80.0f);       // ≤ 80 N·m

Example 3: PrismaticJoint Linear Motion with Force Limit

b3PrismaticJointDef pjDef = b3DefaultPrismaticJointDef();
pjDef.base.bodyIdA = bodyA;
pjDef.base.bodyIdB = bodyB;
b3JointId pjId = b3CreatePrismaticJoint(worldId, &pjDef);

b3PrismaticJoint_EnableMotor(pjId, true);
b3PrismaticJoint_SetMotorSpeed(pjId, 3.0f);          // 3 m/s along axis
b3PrismaticJoint_SetMaxMotorForce(pjId, 120.0f);     // ≤ 120 N

Summary

  • Box3D joint motors use a three-stage solver: target velocity specification, optional spring-damping, and impulse clamping with force limits.
  • Force limits are converted to impulse limits by multiplying by the simulation time step (context->h), ensuring consistent behavior across different timestep sizes.
  • MotorJoint provides full 6-DOF control with separate linear force and angular torque caps, while RevoluteJoint and PrismaticJoint offer single-axis rotation and translation motors respectively.
  • All motor implementations in src/motor_joint.c, src/revolute_joint.c, and src/prismatic_joint.c share the same fundamental impulse-clamping architecture for stable physics simulation.

Frequently Asked Questions

How do force limits convert to impulse limits in Box3D?

Box3D converts force limits to impulse limits by multiplying the user-specified maximum force (in Newtons) or torque (in N·m) by the simulation time step context->h. This occurs in src/motor_joint.c at lines 94-108 for linear forces and lines 128-144 for angular torques, ensuring the accumulated impulse never exceeds what the force limit could produce in a single timestep.

What is the difference between MotorJoint and RevoluteJoint motors?

MotorJoint is a 6-DOF constraint that can simultaneously drive linear and angular velocities with separate force and torque limits, as implemented in src/motor_joint.c. RevoluteJoint is a single-degree-of-freedom rotational hinge that only drives angular velocity around one axis using b3RevoluteJoint_SetMotorSpeed and b3RevoluteJoint_SetMaxMotorTorque, as found in src/revolute_joint.c lines 90-112.

Can spring drives and velocity motors operate simultaneously?

Yes. In src/motor_joint.c, the solver first resolves spring constraints (lines 302-322) to pull bodies toward target poses, then applies velocity motor constraints (lines 85-112) to drive relative motion. The force limits maxSpringForce and maxVelocityForce are evaluated independently, allowing both soft positioning and hard velocity control to coexist.

Where are motor parameters stored in the Box3D source?

Motor parameters reside in joint-specific structures defined in the source files. src/motor_joint.c stores maxVelocityForce and maxVelocityTorque in the b3MotorJoint struct (setters at lines 44-70), while src/revolute_joint.c stores motorSpeed and maxMotorTorque in b3RevoluteJoint (lines 90-112). These values persist across simulation steps and are accessed during the b3SolveMotorJoint or corresponding solve functions.

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 →