Home Games Shader Sandbox

Game Dev Mechanics: Rigid Body Physics — How It Works

Drag to orbit • Click to launch a ball • Up to 20 balls
Balls: 10 / 20

When a crate tumbles off a shelf in Half-Life 2, when cars crumple in Wreckfest, or when a ragdoll slumps to the ground in The Last of Us, rigid body physics is doing the work. It is one of the core systems in game development, responsible for the tactile sense that a game world obeys real physical laws.

A rigid body is a solid object that does not deform under applied forces. Unlike cloth or soft-body physics where vertices flex and stretch, a rigid body maintains its exact shape; only position and orientation change. This simplification makes the math tractable while producing convincingly realistic motion. In this article we will cover integration, collision detection, and most importantly, impulse-based collision response, the algorithm at the heart of every major physics engine.

The State of a Rigid Body

A rigid body in 3D requires six degrees of freedom to fully describe its state at any moment:

  • Position $\vec{x}$: world-space coordinates of the center of mass
  • Linear velocity $\vec{v}$: how fast the center of mass is moving
  • Orientation $q$: stored as a unit quaternion to avoid gimbal lock
  • Angular velocity $\vec{\omega}$: rotation axis and rate in radians per second

We also store fixed physical properties: mass $m$ and moment of inertia $I$. Since rigid bodies do not deform, these are constant in body-space. Forces produce linear acceleration and torques produce angular acceleration via Newton's second law:

$$\vec{a} = \frac{\vec{F}}{m} \qquad \vec{\alpha} = I^{-1}\vec{\tau}$$

In game simulations, gravity is applied as a constant acceleration each frame, while collision responses use instantaneous impulses that directly modify velocity.

Numerical Integration

Physics simulations are initial value problems: given state at time $t$, compute state at $t + \Delta t$. The choice of integrator determines stability and energy conservation behavior.

Explicit Euler: What Not to Use

The naive approach updates position from current velocity, then velocity from current acceleration. It is energy-adding: simulations gain energy over time and eventually blow up. Springs oscillate with ever-increasing amplitude. Avoid it for anything beyond a quick prototype.

Semi-Implicit (Symplectic) Euler

One change in ordering fixes this: update velocity first, then use the new velocity to update position.

$$\vec{v}_{n+1} = \vec{v}_n + \vec{a}_n \cdot \Delta t$$ $$\vec{x}_{n+1} = \vec{x}_n + \vec{v}_{n+1} \cdot \Delta t$$

This makes the integrator symplectic: it conserves a modified version of total energy over time, so springs stay bounded and orbits do not spiral inward. Box2D, Bullet, and the demo above all use it. The tradeoff is first-order accuracy, meaning small timesteps are needed for precision.

Fixed Timestep

Physics should always run at a fixed timestep, decoupled from render frame rate. You accumulate delta time and step the simulation in fixed increments. This ensures deterministic, stable behavior regardless of frame rate spikes.

const FIXED_DT = 1 / 60;
let accumulator = 0;

function loop(timestamp) {
  const dt = Math.min((timestamp - lastTime) / 1000, 0.05);
  lastTime = timestamp;
  accumulator += dt;

  while (accumulator >= FIXED_DT) {
    physicsStep(FIXED_DT);
    accumulator -= FIXED_DT;
  }

  render();
  requestAnimationFrame(loop);
}

Collision Detection

Before responding to a collision we must detect it. Detection is split into two phases:

Broad phase: quickly eliminate pairs that obviously cannot collide using bounding volumes (AABBs, bounding spheres) and spatial structures like BVH trees or spatial hashing. This reduces the $O(n^2)$ problem to a manageable candidate set.

Narrow phase: run exact tests on candidate pairs. For spheres of radii $r_A$, $r_B$ at positions $\vec{x}_A$, $\vec{x}_B$, a collision exists when:

$$|\vec{x}_B - \vec{x}_A| \lt r_A + r_B$$

For convex polyhedra, the GJK algorithm computes minimum distance between shapes efficiently. Concave shapes are decomposed into convex hulls first.

The narrow phase must also output the contact manifold: the collision normal $\hat{n}$ (unit vector from $A$ to $B$ at the contact), the contact point $\vec{p}$, and the penetration depth $d$. These three quantities drive the entire collision response.

Impulse-Based Collision Response

This is the core of rigid body physics. When two bodies collide we apply an instantaneous impulse, a direct change to velocity, to prevent interpenetration and produce realistic bouncing.

An impulse $\vec{J}$ applied to a body changes its linear momentum directly: $\Delta\vec{v} = \vec{J}/m$. By Newton's third law, body $A$ receives $+j\hat{n}$ and body $B$ receives $-j\hat{n}$, where $j$ is a scalar we derive.

Deriving the Impulse Magnitude

We choose $j$ so that the post-collision relative velocity along $\hat{n}$ satisfies the coefficient of restitution $e$ (0 = no bounce, 1 = perfectly elastic). The relative velocity along the normal before collision:

$$v_{rel} = (\vec{v}_A - \vec{v}_B) \cdot \hat{n}$$

If $v_{rel} \gt 0$ the bodies are already separating; skip resolution. Otherwise we want $v'_{rel} = -e \cdot v_{rel}$. After applying the impulses, the new relative velocity becomes:

$$v'_{rel} = v_{rel} + j\left(\frac{1}{m_A} + \frac{1}{m_B}\right)$$

Setting this equal to $-e \cdot v_{rel}$ and solving for $j$:

$$j = \frac{-(1+e) \cdot v_{rel}}{\frac{1}{m_A} + \frac{1}{m_B}}$$

For general rigid bodies with rotation, angular velocity at the contact point adds moment of inertia terms to the denominator:

$$j = \frac{-(1+e) \cdot v_{rel}}{\frac{1}{m_A} + \frac{1}{m_B} + \frac{(\vec{r}_A \times \hat{n})^2}{I_A} + \frac{(\vec{r}_B \times \hat{n})^2}{I_B}}$$

where $\vec{r}_A$, $\vec{r}_B$ are vectors from each center of mass to the contact point. Once $j$ is known, velocity updates are trivial:

$$\vec{v}_A' = \vec{v}_A + \frac{j}{m_A}\hat{n} \qquad \vec{v}_B' = \vec{v}_B - \frac{j}{m_B}\hat{n}$$

Positional Correction

Even with perfect impulse resolution, numerical error causes objects to slowly sink into each other over many frames: interpenetration. The fix is a positional correction step that nudges overlapping objects apart along $\hat{n}$ proportional to their inverse masses. Lighter objects move more than heavier ones:

$$\vec{x}_A' = \vec{x}_A - \frac{\beta \cdot d}{m_A^{-1} + m_B^{-1}} \cdot m_A^{-1} \cdot \hat{n}$$ $$\vec{x}_B' = \vec{x}_B + \frac{\beta \cdot d}{m_A^{-1} + m_B^{-1}} \cdot m_B^{-1} \cdot \hat{n}$$

Here $\beta \approx 0.4$–$0.8$ controls correction aggressiveness. A small slop value (1–4 mm) is subtracted from $d$ first to prevent jitter at resting contacts.

Angular Dynamics

Rotation adds depth to the simulation. A torque $\vec{\tau} = \vec{r} \times \vec{F}$ at moment arm $\vec{r}$ produces angular acceleration $\vec{\alpha} = I^{-1}\vec{\tau}$. In 3D the inertia tensor $I$ is a $3 \times 3$ matrix describing mass distribution around each axis. For a solid sphere of radius $r$ it simplifies to the scalar $I = \frac{2}{5}mr^2$.

As the body rotates, $I$ must be re-expressed in world space each frame via the rotation matrix $R$: $I_{world} = R \cdot I_{body} \cdot R^T$. Orientation is integrated by converting angular velocity to a quaternion derivative each step, keeping $q$ normalized to prevent drift.

Practical Considerations

Iterative constraint solvers: Production engines like Box2D and Bullet do not resolve each collision independently. They frame all contacts as a constraint system and solve iteratively with Sequential Impulse (SI) or Projected Gauss-Seidel (PGS). This produces stable stacking and jointed structures that naive per-pair impulses cannot.

Sleeping: Bodies with very low velocity and no net force are put to sleep and excluded from the physics update. This saves significant CPU on large scenes and eliminates jitter in resting contacts.

Continuous Collision Detection (CCD): Fast-moving objects can tunnel through thin geometry in a single frame. CCD sweeps the shape's volume along its trajectory to catch intersections that discrete testing misses, which matters for bullets, fast vehicles, and thin walls.

Mass ratios: Large mass ratios (e.g. 1000:1) destabilize iterative solvers. Most engines clamp ratios or use specialized handling. Kinematic bodies (moved programmatically with infinite effective mass) are used for floors, walls, and moving platforms without participating in the solver.

Real-World Examples

  • Half-Life 2 (Havok): The gravity gun worked because rigid body physics made every crate and barrel feel genuinely weighty and interactive.
  • Kerbal Space Program: Articulated rigid body chains model rockets, with each stage connected by joints. Getting the physics right is central to what makes the game compelling.
  • Tears of the Kingdom: Nintendo's Havok integration lets players build contraptions from rigid bodies joined with the Ultrahand ability.
  • BeamNG.drive: Soft-body vehicles built from a lattice of rigid nodes connected by springs, taken as far as rigid body simulation can go.

The Core Algorithm in Code

Here is the complete sphere-sphere impulse resolution function, the same algorithm powering the demo above:

const RESTITUTION = 0.85;

function resolveSphereSphere(a, b) {
  const dx = b.pos.x - a.pos.x;
  const dy = b.pos.y - a.pos.y;
  const dz = b.pos.z - a.pos.z;
  const distSq = dx*dx + dy*dy + dz*dz;
  const minDist = a.radius + b.radius;

  if (distSq >= minDist * minDist || distSq < 1e-6) return;

  const dist = Math.sqrt(distSq);
  const nx = dx/dist, ny = dy/dist, nz = dz/dist;

  // Relative velocity along normal
  const rvn = (a.vel.x - b.vel.x)*nx
            + (a.vel.y - b.vel.y)*ny
            + (a.vel.z - b.vel.z)*nz;

  if (rvn > 0) return; // Already separating

  // Impulse scalar
  const j = -(1 + RESTITUTION) * rvn / (a.invMass + b.invMass);

  // Apply impulse
  a.vel.x += j * a.invMass * nx;
  a.vel.y += j * a.invMass * ny;
  a.vel.z += j * a.invMass * nz;
  b.vel.x -= j * b.invMass * nx;
  b.vel.y -= j * b.invMass * ny;
  b.vel.z -= j * b.invMass * nz;

  // Positional correction
  const overlap = minDist - dist;
  const corr = overlap / (a.invMass + b.invMass) * 0.45;
  a.pos.x -= corr * a.invMass * nx;
  a.pos.y -= corr * a.invMass * ny;
  a.pos.z -= corr * a.invMass * nz;
  b.pos.x += corr * b.invMass * nx;
  b.pos.y += corr * b.invMass * ny;
  b.pos.z += corr * b.invMass * nz;
}

For production use, consider Rapier (Rust/WASM, excellent for web games), Bullet (C++, used in Godot and many AAA titles), or Jolt Physics (C++, used in Horizon Forbidden West). These handle solver stability, joint constraints, and edge cases that a hand-rolled engine would need to address.

Conclusion

Rigid body physics gives game worlds their physical weight. The core algorithm (semi-implicit Euler integration, impulse-based collision response, positional correction) is compact and covers a wide range of scenarios well. Once you understand how $j = \frac{-(1+e)\cdot v_{rel}}{\sum m_i^{-1}}$ maps to bouncing spheres and tumbling crates, the physics engine behind any game becomes readable. The demo above simulates up to twenty spheres with full impulse collision resolution in real time; try toggling gravity and launching balls to see how energy distributes through the system.

Comments

Like this article? Consider supporting us

Your support helps us keep creating free game dev content, tutorials, and tools.

Free

$0 /month

Newsletter and public posts

  • Newsletter access
  • Public posts & updates
  • Community access

Studio Backer

$25 /month

Direct impact on development with your name in the credits

  • Everything in Supporter
  • Your name in game credits
  • Priority feature requests
  • Direct developer access
  • Monthly asset downloads