PhysicsMechanicscommon4 min read
Rigid Body Dynamics
push off-centre and it both moves and spins
A rigid body has mass and a moment of inertia. Forces and impulses at a point change both its linear and angular velocity; contacts bounce and slide.
The red solid arrow is the strike impulse hitting the white box off-centre; short red arrows flash at contact points with the floor and walls. The yellow dashed line is velocity and the light-grey arc with an arrowhead is angular velocity (longer sweep = faster spin). Slide the impulse offset to 0 for pure translation, and change restitution and friction.
What it is
A rigid body is an object that never deforms, so its state is just a position, an orientation and their rates of change: linear velocity v and angular velocity ω. Mass m resists changes to v; the moment of inertia I resists changes to ω and depends on how the mass is spread around the centre of mass (a long plank spins more slowly than a compact cube of the same mass).
A force through the centre of mass only translates the body. Move the point of application sideways and part of it becomes torque τ = r × F, so the same push now also spins the body. Games mostly work with impulses J = F·Δt (an instant change of momentum), which is how hits, explosions and contacts are applied.
Each step, a simulation integrates with semi-implicit Euler (update the velocities first, then move using the new velocities), then resolves contacts: find the touching points, apply a normal impulse so they stop approaching, add restitution e for bounce, and clamp a tangential friction impulse to μ times the normal impulse. Run it at a fixed timestep so results do not depend on frame rate.
- physics
- torque
- impulse
- inertia
- restitution
- friction
Key formulas
- Linear and angular Newton's law
In 2D the cross product is a scalar: rx·Fy − ry·Fx.
- Impulse at a point
- Moment of inertia of a box
- Semi-implicit Euler
- Contact normal impulse
v_rel = v + ω × r is the velocity of the touching point, n̂ the contact normal.
Rules of thumb
- Step physics at 60–120 Hz with a fixed
dt; never feed a raw, variable frame delta to the solver. - Restitution
e: 0 = clay, 0.3–0.5 = wood or crates, 0.8+ = rubber ball. Below a closing speed of ~0.5 m/s treat it as 0 so stacks can rest. - Friction
μ: 0.05 ice, 0.4–0.6 wood or stone, 0.9+ rubber on concrete. - Iterate contacts 4–10 times per step (sequential impulses) and clamp the accumulated normal impulse to ≥ 0 rather than each partial one.
- Keep mass ratios between touching bodies under about 10:1, or stacks get soft and jittery.
Implementation
interface Body { x: number; y: number; angle: number vx: number; vy: number; omega: number; mass: number; inertia: number }
const cross = (ax: number, ay: number, bx: number, by: number) => ax * by - ay * bx
// r = hit point − centre of mass (world space)export function applyImpulse(b: Body, jx: number, jy: number, rx: number, ry: number) { b.vx += jx / b.mass b.vy += jy / b.mass b.omega += cross(rx, ry, jx, jy) / b.inertia}
export function step(b: Body, dt: number, g = -9.81) { b.vy += g * dt // velocity first... b.x += b.vx * dt // ...then position with the new velocity b.y += b.vy * dt b.angle += b.omega * dt}r = contactPoint - body.centrevRel = body.v + cross(body.omega, r) // velocity of the touching pointvn = dot(vRel, n)if vn < 0: // approaching e = (vn < -0.5) ? restitution : 0 jn = -(1 + e) * vn / (1/m + cross(r, n)^2 / I) applyImpulse(body, jn * n, r) vt = dot(body.v + cross(body.omega, r), tangent) jt = clamp(-vt / (1/m + cross(r, tangent)^2 / I), -mu*jn, mu*jn) applyImpulse(body, jt * tangent, r)body.position += n * penetration // positional correctionCommon mistakes
- Applying a hit at the centre of mass. It looks floaty because nothing ever spins; use the actual hit point.
- Using a sphere's inertia for every shape. A wrong
Imakes boxes spin too easily or barely at all. - Updating position before velocity (explicit Euler). Orbits and bounces gain energy and blow up.
- Restitution of 1 with friction and multiple contacts, then wondering why boxes never settle. Add a bounce threshold and let resting contacts use
e = 0. - Mixing world-space and local-space
rvectors when computing torque.
Prompt
Write a minimal 2D rigid-body solver in TypeScript (no physics library) for oriented boxes, rendered with Three.js. Store position, angle, linear velocity, angular velocity ω, mass m and inertia I = m(w²+h²)/12. Implement applyImpulse(J, r) (v += J/m, ω += (r×J)/I with the 2D cross product), semi-implicit Euler integration at a fixed 120 Hz step, and floor/wall contacts: find penetrating corners, solve an accumulated non-negative normal impulse, then restitution (skip it below 0.4 m/s closing speed so boxes can rest), then Coulomb friction clamped to μ·jn, then push the box out of the plane. Write unit tests: an impulse through the centre of mass gives ω = 0, an impulse above the centre pushing right spins clockwise, and kinetic energy never increases in a contact with e ≤ 1.In learning paths
- Collision & physics engine basicsStep 9 of 9
Related
- Balancing Act: Torque and Leversforce times lever arm decides which way it turns
- Fixed Timestepphysics ticks at a steady rate, rendering just blends between ticks
- Projectile Motionconstant sideways speed, steadily changing vertical speed
- Tunneling & Continuous Collisionfast objects skip over thin walls between two ticks
- Dot and Cross Productone number for alignment, one vector for perpendicular
Further reading
Updated 2026-11-01
