An introduction to rigid-body physics

The 5 familiar features

A rigid body is a type of collision object that exhibits some familiar features of real-world objects, including:

  • rigidity: a fixed shape and

  • inertia: resistance to changes of motion.

The PhysicsRigidBody constructor allows you to specify the new body’s mass and collision shape. Together, these properties determine its moment of inertia:

CollisionShape ballShape = new SphereCollisionShape(1f);
float ballMass = 2f;
// create a rigid body
PhysicsRigidBody ball = new PhysicsRigidBody(ballShape, ballMass);
// calculate its moment of inertia (in local coordinates, diagonal entries only)
Vector3f inertia = Vector3f.UNIT_XYZ.divide(ball.getInverseInertiaLocal(null));

By default, rigid bodies also exhibit 3 other features of real-world objects:

  • They are dynamic: mobile, with their motion determined by forces, torques, and impulses.

  • They are subject to gravity: a continual downward force proportional to their mass.

  • They exhibit contact response: powerful forces prevent them from intersecting other bodies.

However, these last 3 features are optional in Libbulletjme and can easily be disabled.

HelloRigidBody

HelloRigidBody (also in Kotlin) is a SPORT app that demonstrates the 5 familiar features of rigid bodies.

Things to notice while running the app:

  1. Two balls, both falling slowly under the influence of gravity.

  2. The simulation runs at 1/10th normal speed.

  3. The ball on the right receives an impulse that pushes it leftward.

  4. After the impulse ends, the ball’s inertia keeps it moving leftward.

  5. The balls collide, generating contact forces that alter the balls' motions but not their shapes.

    1. The ball on the right loses half its horizontal velocity.

    2. The ball on the left begins moving leftward as it falls.

Configuring rigid bodies

Static rigid bodies

Not all rigid bodies are dynamic. To create a static (non-moving) rigid body, specify mass=0 in the constructor. Unlike a dynamic body, a static one is unaffected by forces, torques, and impulses, as if its mass were infinite.

Java’s static keyword is completely unrelated to physics.

HelloStaticBody (also in Kotlin) is a SPORT app that combines static and dynamic rigid bodies. Things to notice while running the app:

  1. Two balls: the top one dynamic, the bottom one static.

  2. The balls collide, generating contact forces that alter the path of the dynamic ball.

  3. The static ball stays fixed in place, unaffected by gravity and contact forces.

  4. In SPORT, dynamic rigid bodies are conventionally colored magenta, while non-dynamic ones are shown in gray.

Position

Every rigid body occupies a position in space, described by 2 properties:

  • the location of the body’s center and

  • the body’s orientation: the rotation of its (local) axes relative to those of the physics space.

Each property can be configured separately:

body.setPhysicsLocation(new Vector3f(5f, 1f, 0f));
body.setPhysicsRotation(new Quaternion(0.5f, 0.5f, 0.5f, 0.5f));
Once a dynamic or static body has been initialized, it shouldn’t be repositioned in this manner.

The center of a dynamic body should be its center of mass, the point around which it tends to spin, and its local axes should correspond to the principal axes of its mass distribution.

Unlike a dynamic body, a static body allows complete flexibility in the choice of center and local axes.

Kinematic flag

In addition to static and dynamic rigid bodies, there’s a third type.

Kinematic bodies share some properties with both static and dynamic ones. Like dynamic bodies, kinematic ones can move. However, they are unaffected by forces, torques, and impulses. Their movement is dictated by application logic that may, if desired, reposition them before each simulation step.

In the presence of dynamic bodies, a kinematic body acts like an irresistible battering ram.

There’s no direct way to instantiate a kinematic rigid body. The usual way is to instantiate a static or dynamic body and then convert it to kinematic using rigidBody.setKinematic(true).

HelloKinematics (also in Kotlin) is a SPORT app that combines kinematic and dynamic rigid bodies. Things to notice while running the app:

  1. Two balls: the top one dynamic, the bottom one kinematic.

  2. The kinematic ball orbits a fixed point in space.

  3. The balls collide, generating contact forces that affect the motion of the dynamic ball.

  4. The kinematic ball continues orbiting, unaffected by gravity and contact forces.

Contact response

When physics simulation detects a collision between 2 bodies that both have contact response, it applies contact forces.

To disable contact response for a particular rigid body, use rigidBody.setContactResponse(false).

HelloContactResponse (also in Kotlin) is a SPORT app that demonstrates contact response. Things to notice while running the app:

  1. The ball falls until it collides with the gray (static) box, which provides a contact force to halt its motion and counteract gravity.

  2. Press E to disable the ball’s contact response.

  3. Afterwards, the box no longer exerts any force on the ball. Gravity takes over, and the ball falls through the box.

  4. In SPORT, non-responsive rigid bodies are shown in yellow.

This documentation assumes a keyboard with the "US" (QWERTY) layout. On keyboards with other layouts, keys may be labeled differently.

Velocity

Every dynamic body has a velocity that quantifies its motion as of the end of the last simulation step (and the start of the next).

More precisely, it has 2 velocities: linear velocity and angular velocity, both represented as 3-D vectors. The magnitude and direction of the linear velocity vector quantify the speed and direction at which the body’s center is traveling through space (if at all). The magnitude and direction of the angular velocity vector quantify the rate and axis direction of the body’s spinning motion (if any).

Both velocities of a static body are zero.
Both velocities of a kinematic body are undefined.

To alter the velocities of a dynamic rigid body, use its setLinearVelocity() and setAngularVelocity() methods.

Built-in forces

Many real-world phenomena can be modeled as forces acting on rigid bodies.

You can apply custom forces, impulses, and torques using the following 6 methods:

  • applyCentralForce(Vector3f)

  • applyCentralImpulse(Vector3f)

  • applyForce(Vector3f force, Vector3f offset)

  • applyImpulse(Vector3f impulse, Vector3f offset)

  • applyTorque(Vector3f)

  • applyTorqueImpulse(Vector3f)

However, some forces are so commonplace that they are "built into" rigid-body simulation:

  • drag forces:

    • damping

  • gravity

  • contact forces:

    • restitution

    • friction

Damping

In the absence of external forces, inertia would keep the velocities of a dynamic body constant. In the real world, however, we’re accustomed to seeing unpowered moving objects eventually come to rest. This behavior is often caused by drag forces (such as air resistance) that increase with speed.

To simulate drag forces, each rigid body has damping, which quantifies how quickly its motion decays to zero, assuming the body is dynamic.

More precisely, each body has 2 damping parameters: linear damping and angular damping, each of which ranges from zero (no drag) to one (motion ceases immediately). Linear damping damps the linear velocity, and angular damping damps the angular velocity.

Accessors are provided for both parameters, separately and together:

rigidBody.setAngularDamping(0.5f);  // default=0
rigidBody.setLinearDamping(0.2f);   // default=0
// or alternatively:
float linearDamping = 0.5f;
float angularDamping = 0.2f;
rigidBody.setDamping(linearDamping, angularDamping);

HelloDamping (also in Kotlin) is a SPORT app that demonstrates damping. Things to notice while running the app:

  1. 4 cubes initially share the same linear and angular velocities.

  2. The top 2 have constant linear velocities, evidence of no linear damping.

  3. The left 2 have constant angular velocities, evidence of no angular damping.

  4. The linear velocities of the bottom 2 cubes decay quickly to zero due to strong linear damping.

  5. The angular velocities of the right 2 cubes decay quickly to zero due to strong angular damping.

Gravity

In the real world, we’re accustomed to seeing unsupported objects fall. This behavior is caused by gravity, a downward force that’s proportional to mass (and thus causes a constant acceleration).

To simulate this phenomenon, each body has a gravity vector that quantifies the acceleration, assuming the body is dynamic. To configure a body’s gravity, use setGravity(accelerationVector).

If following the Y-up axes convention, the X and Z components of the vector should be zero, and its Y component should be negative.

To disable gravity for a particular rigid body, use rigidBody.setGravity(Vector3f.ZERO).

When a body is added to a physics space, the gravity of the space typically gets applied to it, replacing any previously configured gravity.

To disable gravity for a particular physics space and all bodies in it, use physicsSpace.setGravity(Vector3f.ZERO).

To protect a rigid body from gravity changes caused by the space to which it’s added, use rigidBody.setProtectGravity(true).

To simulate a non-uniform gravitational field, update the gravity of each body before each simulation step. HelloNonUniformGravity (also in Kotlin) is a SPORT app that demonstrates this technique. The planet orbits a black hole whose location is indicated by colored arrows.

The planet’s path varies somewhat from orbit to orbit. This is partly due to inaccuracies of single-precision arithmetic.

Restitution

When responsive rigid bodies collide, contact forces come into play, altering their velocities. These forces are split into 2 components: restitution and friction.

Restitution is a force parallel to the contact normal. Its strength hints at what the bodies might be made out of.

If both bodies were made of hard, springy steel, they might separate without loss of kinetic energy, after undergoing what’s called a perfectly elastic collision. If, on the other hand, both bodies were made of soft, sticky clay, they might cling together, dissipating kinetic energy and undergoing what’s called a perfectly inelastic collision.

In reality, no collision is perfectly elastic. Elasticity is quantified by a coefficient of restitution, which ranges from zero (perfectly inelastic) to one (perfectly elastic).

In simulation, collisions are inelastic by default. (We saw this in HelloRigidBody.java.) Each rigid body has a restitution parameter, which defaults to zero. For each collision, the coefficient of restitution is calculated by multiplying the parameters of the colliding bodies.

To simulate a perfectly elastic collision, set the restitution parameters of both bodies to one:

rigidBodyA.setRestitution(1f); // default=0
rigidBodyB.setRestitution(1f);

The Newton’s Cradle demo demonstrates perfectly elastic collisions. At startup, the simulation is paused. Press . to start the simulation, or to pause it while it’s running.

Friction

While restitution models contact forces parallel to the contact normal, friction models contact forces orthogonal to the contact normal.

Each rigid body has a friction parameter (which defaults to 0.5). This parameter hints at the body’s surface characteristics. To configure the parameter, use setFriction(parameter). Reducing a body’s friction parameter makes it more slippery (think wet ice). Increasing it yields better traction (think sandpaper or dry rubber).

For each collision, a coefficient of friction is calculated by multiplying the parameters of the colliding bodies.

Rigid-body factors

All forces, torques, and impulses acting on dynamic rigid bodies are multiplied by factors that can be configured for each body.

For instance, to prevent a body from rotating:

body.setAngularFactor(new Vector3f(0f, 0f, 0f)); // default=(1, 1, 1)
body.setAngularVelocity(new Vector3f(0f, 0f, 0f));

Rigid-body factors can also be used to simulate physics in 2 dimensions. For instance, one might constrain a body to rotate only around axes parallel to the Z axis and translate only in directions parallel to the X-Y plane:

body.setAngularFactor(new Vector3f(0f, 0f, 1f)); // default=(1, 1, 1)
body.setLinearFactor(new Vector3f(1f, 1f, 0f));  // default=(1, 1, 1)
Factors are defined in terms of world (physics-space) axes, not the body’s local axes.

Deactivation

It’s common for physics simulations to reach a steady state in which the some or all bodies have stopped moving. If a dynamic rigid body doesn’t move for 2 seconds, the simulator may automatically deactivate it to reduce CPU consumption.

To prevent a body from being deactivated, a certain amount of movement, either linear or angular, needs to occur every 2 seconds. Accessors are provided for these thresholds:

float linearThreshold = 0.5f; // default=0.8
float angularThreshold = 0.2f; // default=1
rigidBody.setSleepingThresholds(linearThreshold, angularThreshold);
Sleeping is synonym for deactivation.

To disable deactivation globally (for all rigid bodies), use PhysicsBody.setDeactivationEnabled(false).

To disable deactivation for a particular rigid body, use physicsSpace.setEnableSleep(false).

To test whether a body is deactivated, use rigidBody.isActive().

To globally change the deactivation latency to 5 seconds:

PhysicsBody.setDeactivationDeadline(5f); // default=2

Deactivated bodies won’t be simulated (and won’t move) unless/until they get reactivated. Reactivation occurs when:

  • a new contact is added (due to a collision),

  • a custom force, torque, or impulse is applied, or

  • the rigidBody.activate() method is invoked.

To reactivate all bodies in a particular physics space, use physicsSpace.activateAll(true).

Puzzling behavior may occur if a deactivated body is:

  • supported by another body that then gets removed,

  • supported by another body that then has its contact response disabled, or

  • driven by a motorized physics joint.

The deactivated body will seem to be "stuck" because the events listed above do not, by themselves, reactivate it.

HelloDeactivation (also in Kotlin) is a SPORT app that demonstrates deactivation. Things to notice while running the app:

  1. The upper (dynamic) box falls until it collides with the lower (static) box, which provides a contact force to halt its motion and counteract gravity.

  2. About 2 seconds after the upper box stops moving, it gets deactivated.

  3. In SPORT, deactivated rigid bodies are conventionally colored gray.

  4. After the application removes the lower box, the upper box doesn’t resume falling. Due to deactivation, it appears to be "stuck".

Continuous collision detection

A common issue with discrete-time physics simulation involves a fast-moving dynamic body passing through a thin obstacle without any collision being detected. The issue arises because the body can pass from one side of the obstacle to the other in a single simulation step. The dynamic body doesn’t intersect the obstacle after any step, so no collision is detected and no contact forces are simulated.

To some extent, this issue could be mitigated by reducing the time step. But since CPU consumption is inversely proportional to the time step, this approach quickly becomes inefficient.

To solve this issue, the simulator offers continuous collision detection (CCD), an algorithm for detecting collisions that occur between simulation steps. CCD substitutes a sphere for the collision shape of the fast-moving body, sweeps that sphere forward along the body’s projected path, and performs detailed collision tests on any potential obstacles found during the sweep.

Because CCD involves extra computation, it’s disabled by default. Since it’s only necessary for fast-moving bodies, it’s enabled only when a body’s distance traveled per simulation step exceeds a threshold. To enable CCD for a particular rigid body, set its activation threshold to a positive value using rigidBody.setCcdMotionThreshold(distancePerTimeStep).

To obtain the best possible results from CCD, tune both the motion threshold and the size of the swept sphere. Here’s a heuristic that works well for many situations:

if (rigidBody.isDynamic()) {
    CollisionShape shape = rigidBody.getCollisionShape();
    float radius = shape.maxRadius();
    rigidBody.setCcdMotionThreshold(radius);
    rigidBody.setCcdSweptSphereRadius(radius);
}

HelloCcd (also in Kotlin) is a SPORT app that demonstrates CCD. Things to notice while running the app:

  1. The 2 balls have the same size, mass, initial height, and initial velocity.

  2. The ball with CCD enabled (on the left) sticks the landing on the platform.

  3. The control ball (on the right) falls through the platform, passing from one side to the other in a single simulation step.

By default, CCD tests for both dynamic-dynamic collisions and dynamic-static ones. For some applications (such as simulation of fast-moving ragdolls), testing for dynamic-dynamic collisions is undesirable. You can disable dynamic-dynamic CCD by invoking physicsSpace.setCcdWithStaticOnly(true).

Summary

  • Rigid bodies simulate familiar features of real-world objects.

  • There are 3 kinds: static, kinematic, and dynamic …​

Static Kinematic Dynamic

Movement

prior to first simulation step only: setPhysicsLocation() setPhysicsRotation()

setPhysicsLocation() setPhysicsRotation()

applyCentralForce() applyCentralImpulse() applyForce() applyImpulse() applyTorque() applyTorqueImpulse() setAngularVelocity() setLinearVelocity()

Affected by forces, impulses, and torques?

No.

No.

Yes.

Typical uses

Non-moving objects such as floors, posts, terrain, and walls

Application-controlled objects such as airships and elevators

Physics-controlled objects such as balls, bricks, and ragdolls

How to configure

setMass(0f) setKinematic(false)

setKinematic(true)

setMass(positive) setKinematic(false)

  • The properties of rigid bodies include: shape, mass, moment of inertia, location, orientation, velocities (linear and angular), damping, gravity, restitution, friction, sleeping thresholds, CCD threshold, and swept-sphere radius.

  • Contact response is an optional feature.

  • If a dynamic rigid body moves too slowly, it might get automatically deactivated after 2 seconds.

  • Continuous collision detection solves the problem of fast-moving dynamic bodies passing through thin obstacles.

  • Continuous collision detection is disabled by default.