P04: Physics Simulation with Constraints

P04: Physics Simulation with Constraints

Build a 2D physics engine that simulates rigid body dynamics with constraints (springs, joints, collisions)โ€”requiring you to solve systems of linear equations each frame.


Overview

Attribute Value
Difficulty Advanced
Time Estimate 3-4 weeks
Language C (recommended) or Python
Prerequisites Basic physics intuition, matrix operations
Primary Book โ€œGame Physics Engine Developmentโ€ by Ian Millington

Learning Objectives

By completing this project, you will:

  1. See vectors as physical state - Position, velocity, and acceleration as mathematical objects
  2. Master Ax = b solvers - Gaussian elimination and iterative methods
  3. Understand projections geometrically - Collision response via vector projection
  4. Grasp matrix conditioning - Why some systems are โ€œhardโ€ to solve
  5. Apply null spaces and least squares - Handle over/under-constrained systems
  6. Experience numerical stability - When math works on paper but fails in code

Theoretical Foundation

Part 1: Vectors as Physical State

Position, Velocity, Acceleration

Every physics simulation begins with vectors representing physical quantities:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    PHYSICS VECTORS                             โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                โ”‚
โ”‚    Position (p)     Velocity (v)     Acceleration (a)         โ”‚
โ”‚    "where"          "how fast"       "how much speeding up"   โ”‚
โ”‚                                                                โ”‚
โ”‚    p = [x]          v = dp/dt        a = dv/dt = dยฒp/dtยฒ      โ”‚
โ”‚        [y]                                                     โ”‚
โ”‚                                                                โ”‚
โ”‚    For a 2D point:                                             โ”‚
โ”‚    p = (3, 5) means 3 units right, 5 units up                 โ”‚
โ”‚    v = (1, 0) means moving right at 1 unit/second             โ”‚
โ”‚    a = (0, -9.8) means accelerating downward (gravity)        โ”‚
โ”‚                                                                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Physics Vectors

Newtonโ€™s Second Law in Vector Form

F = ma

Force vector โ†’ acceleration vector โ†’ velocity change โ†’ position change

For multiple forces:
a = (1/m) ร— ฮฃF = (1/m) ร— (F_gravity + F_spring + F_collision + ...)

Each force is a vector; we sum them.

The State Vector

For a system of n particles, collect all positions and velocities:

State vector S:

    [pโ‚]     position of particle 1
    [pโ‚‚]     position of particle 2
S = [...]
    [vโ‚]     velocity of particle 1
    [vโ‚‚]     velocity of particle 2
    [...]

For n particles in 2D: S has 4n components (2 position + 2 velocity each)

This state vector evolves over time according to physics equations.


Part 2: Numerical Integration

The Euler Method

The simplest integration scheme:

v_new = v_old + a ร— ฮ”t
p_new = p_old + v_old ร— ฮ”t

Or more accurately (semi-implicit Euler):
v_new = v_old + a ร— ฮ”t
p_new = p_old + v_new ร— ฮ”t  โ† use new velocity

Euler is simple but unstable - errors accumulate, springs explode.

Why Integration is Tricky

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    INTEGRATION ERROR                           โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                โ”‚
โ”‚    True path                   Euler approximation             โ”‚
โ”‚                                                                โ”‚
โ”‚       โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ                    โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ               โ”‚
โ”‚      โ•ฑ        โ•ฒ                  โ•ฑ              โ•ฒ              โ”‚
โ”‚     โ•ฑ          โ•ฒ                โ•ฑ                โ•ฒ             โ”‚
โ”‚    โ•ฑ            โ•ฒ              โ•ฑ                  โ•ฒ            โ”‚
โ”‚                                      โ†‘                         โ”‚
โ”‚                           Each step overshoots                 โ”‚
โ”‚                           Error accumulates                    โ”‚
โ”‚                                                                โ”‚
โ”‚   For springs/pendulums: Energy grows โ†’ explosion!            โ”‚
โ”‚                                                                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Integration Error

Better Methods: Verlet Integration

Position-based method that conserves energy better:

p_new = 2ร—p_current - p_old + a ร— ฮ”tยฒ

Velocity is implicit: v โ‰ˆ (p_new - p_old) / (2ร—ฮ”t)

Advantages:
- Second-order accurate (error โˆ ฮ”tยฒ)
- Energy-conserving (symplectic)
- Simple to implement

RK4 (Runge-Kutta 4th Order)

More accurate but expensive:

kโ‚ = f(t, y)
kโ‚‚ = f(t + ฮ”t/2, y + ฮ”tร—kโ‚/2)
kโ‚ƒ = f(t + ฮ”t/2, y + ฮ”tร—kโ‚‚/2)
kโ‚„ = f(t + ฮ”t, y + ฮ”tร—kโ‚ƒ)

y_new = y + (ฮ”t/6) ร— (kโ‚ + 2kโ‚‚ + 2kโ‚ƒ + kโ‚„)

For this project, semi-implicit Euler or Verlet is sufficient.


Part 3: Collision Detection and Response

Detecting Collisions

For circles, collision detection is simple:

Circle A: center pA, radius rA
Circle B: center pB, radius rB

Distance: d = ||pA - pB||
Collision: d < rA + rB

Penetration depth: ฮด = (rA + rB) - d
Collision normal: n = (pB - pA) / d

For boxes, polygons, etc., it gets more complex (SAT, GJK).

Collision Response via Projection

The key linear algebra insight: collision response is projection!

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                 COLLISION RESPONSE                             โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                โ”‚
โ”‚    Before collision:                                           โ”‚
โ”‚                                                                โ”‚
โ”‚         v (incoming velocity)                                  โ”‚
โ”‚          โ†˜                                                     โ”‚
โ”‚           โ†˜                                                    โ”‚
โ”‚    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ surface (normal n)                      โ”‚
โ”‚                                                                โ”‚
โ”‚    Decompose v into components:                                โ”‚
โ”‚                                                                โ”‚
โ”‚    v_normal = (v ยท n) ร— n     (component along normal)         โ”‚
โ”‚    v_tangent = v - v_normal   (component along surface)        โ”‚
โ”‚                                                                โ”‚
โ”‚    For elastic bounce:                                         โ”‚
โ”‚    v_new = v_tangent - e ร— v_normal  (e = coefficient of      โ”‚
โ”‚                                        restitution)            โ”‚
โ”‚                                                                โ”‚
โ”‚    For e = 1 (perfectly elastic):                              โ”‚
โ”‚    v_new = v - 2(v ยท n)n      (reflection formula)            โ”‚
โ”‚                                                                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Collision Response - Vector Projection

The math:

Reflection: v_reflected = v - 2(v ยท n)n

This is the formula for reflecting a vector across a plane!
The dot product measures how much v points into the surface.
Multiplying by n and subtracting reverses that component.

Impulse-Based Collision

For realistic physics, we compute impulses (instantaneous momentum changes):

For two colliding bodies A and B:

Relative velocity: v_rel = vA - vB
Normal component:  v_n = v_rel ยท n

If v_n > 0: bodies separating, no collision response needed

Impulse magnitude: j = -(1 + e) ร— v_n / (1/mA + 1/mB)

Apply impulse:
vA_new = vA + (j/mA) ร— n
vB_new = vB - (j/mB) ร— n

Part 4: Constraints and Solving Ax = b

What is a Constraint?

A constraint is a restriction on how bodies can move:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    TYPES OF CONSTRAINTS                        โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                โ”‚
โ”‚    Distance constraint (rod/rope):                             โ”‚
โ”‚    ||pA - pB|| = L   (maintain fixed distance)                 โ”‚
โ”‚                                                                โ”‚
โ”‚    Point constraint (pin joint):                               โ”‚
โ”‚    pA = pB           (two points coincide)                     โ”‚
โ”‚                                                                โ”‚
โ”‚    Angle constraint (hinge limit):                             โ”‚
โ”‚    ฮธ_min โ‰ค ฮธ โ‰ค ฮธ_max (rotation bounded)                        โ”‚
โ”‚                                                                โ”‚
โ”‚    Contact constraint (no penetration):                        โ”‚
โ”‚    (pA - pB) ยท n โ‰ฅ 0  (inequality constraint)                  โ”‚
โ”‚                                                                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Types of Constraints

Constraint Equations

Most constraints can be written as: C(x) = 0

For a distance constraint between particles at pโ‚ and pโ‚‚:

C = ||pโ‚ - pโ‚‚||ยฒ - Lยฒ  = 0

Taking the derivative (for velocity-level constraint):

ฤŠ = 2(pโ‚ - pโ‚‚) ยท (vโ‚ - vโ‚‚) = 0

This says: relative velocity must be perpendicular to the connecting line.

The Jacobian Matrix

For multiple constraints on multiple bodies, we form the Jacobian:

C = [Cโ‚]      Jacobian J = [โˆ‚Cโ‚/โˆ‚xโ‚  โˆ‚Cโ‚/โˆ‚xโ‚‚  ...]
    [Cโ‚‚]                   [โˆ‚Cโ‚‚/โˆ‚xโ‚  โˆ‚Cโ‚‚/โˆ‚xโ‚‚  ...]
    [...]                  [  ...      ...     ...]

The constraint becomes: J ร— v = 0  (or = -baumgarte ร— C for stability)

Where v is the velocity vector of all bodies.

Solving for Constraint Forces

To satisfy constraints, we need forces. The constraint force ฮป:

Total acceleration: a = Mโปยน(F_external + Jแต€ฮป)

Constraint: J ร— a = b  (where b comes from the constraint)

Substituting:
J ร— Mโปยน ร— Jแต€ ร— ฮป = b - J ร— Mโปยน ร— F_external

This is a system:  A ร— ฮป = b

Where A = J ร— Mโปยน ร— Jแต€ (the constraint mass matrix)

This is the heart of constraint solving: solve Ax = b to find ฮป, then apply forces.


Part 5: Solving Systems of Linear Equations

The General Problem

A ร— x = b

Given: A (nร—n matrix), b (n vector)
Find:  x (n vector)

For physics:

  • A encodes how constraints couple to each other
  • b encodes how much the constraints are violated
  • x (ฮป) gives the correction forces

Gaussian Elimination

The classic direct method:

Step 1: Forward elimination (create upper triangular form)
Step 2: Back substitution (solve from bottom up)

Example:
[2  1  1 | 8]     [2  1  1 | 8]     [2  1  1 | 8]
[4  3  3 | 20] โ†’  [0  1  1 | 4] โ†’   [0  1  1 | 4]
[8  7  9 | 46]    [0  3  5 | 14]    [0  0  2 | 2]

Back substitute: z = 1, y = 3, x = 2

Complexity: O(nยณ) - too slow for large systems every frame!

LU Decomposition

Factor A once, solve quickly for different b:

A = L ร— U  (Lower triangular ร— Upper triangular)

Solve: L ร— y = b  (forward substitution)
Then:  U ร— x = y  (back substitution)

Both steps are O(nยฒ).

Useful when A doesnโ€™t change (same constraints, different forces).

Iterative Methods: Gauss-Seidel

For constraint solving, iterative methods work well:

def gauss_seidel(A, b, x, iterations):
    for _ in range(iterations):
        for i in range(n):
            sigma = sum(A[i][j] * x[j] for j in range(n) if j != i)
            x[i] = (b[i] - sigma) / A[i][i]
    return x

Why iterative works for physics:

  • Constraints are local (sparse matrix)
  • Approximate solution is often good enough
  • We solve many times per second (converges over frames)

Projected Gauss-Seidel (PGS)

For inequality constraints (contacts), clamp solutions:

def pgs(A, b, x, lo, hi, iterations):
    for _ in range(iterations):
        for i in range(n):
            delta = (b[i] - sum(A[i][j] * x[j] for j in range(n))) / A[i][i]
            x[i] = clamp(x[i] + delta, lo[i], hi[i])
    return x

This handles: โ€œbodies can push apart but not pull togetherโ€


Part 6: Matrix Conditioning and Numerical Stability

What is Condition Number?

The condition number ฮบ(A) measures how sensitive Ax = b is to perturbations:

If ฮบ(A) is small (close to 1): well-conditioned, stable
If ฮบ(A) is large: ill-conditioned, small errors โ†’ large errors

ฮบ(A) = ||A|| ร— ||Aโปยน|| = ฯƒ_max / ฯƒ_min  (ratio of singular values)

Why Conditioning Matters for Physics

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                ILL-CONDITIONED SYSTEMS                         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                โ”‚
โ”‚    Scenario: Very stiff springs mixed with soft springs        โ”‚
โ”‚                                                                โ”‚
โ”‚    Stiff spring: k = 10000                                     โ”‚
โ”‚    Soft spring:  k = 1                                         โ”‚
โ”‚                                                                โ”‚
โ”‚    Constraint matrix eigenvalues: 10000 and 1                  โ”‚
โ”‚    Condition number: ~10000                                    โ”‚
โ”‚                                                                โ”‚
โ”‚    Result: Gauss-Seidel converges slowly                       โ”‚
โ”‚            Numerical precision issues                          โ”‚
โ”‚            Simulation may become unstable                      โ”‚
โ”‚                                                                โ”‚
โ”‚    Fixes:                                                      โ”‚
โ”‚    - Scale units to reduce spread                              โ”‚
โ”‚    - Use regularization (add small diagonal)                   โ”‚
โ”‚    - Use higher precision (double instead of float)            โ”‚
โ”‚    - More iterations                                           โ”‚
โ”‚                                                                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Ill-Conditioned Systems

Numerical Stability Tips

  1. Use double precision for constraint solving
  2. Add bias/regularization: A + ฮตI instead of A
  3. Warm starting: Use previous frameโ€™s solution as initial guess
  4. Scale appropriately: Keep values in reasonable ranges

Part 7: Special Matrix Structures

Sparse Matrices

Physics matrices are typically sparse (mostly zeros):

For a chain of particles connected by springs:

[ร—  ร—  0  0  0  0]    ร— = non-zero
[ร—  ร—  ร—  0  0  0]    0 = zero
[0  ร—  ร—  ร—  0  0]
[0  0  ร—  ร—  ร—  0]    Each particle only interacts with neighbors.
[0  0  0  ร—  ร—  ร—]
[0  0  0  0  ร—  ร—]

Store only non-zeros โ†’ memory efficient
Skip zero multiplications โ†’ faster solving

Symmetric Positive Definite (SPD)

Many physics matrices are SPD:

  • Symmetric: A = Aแต€
  • Positive definite: xแต€Ax > 0 for all x โ‰  0

SPD matrices have:

  • All positive eigenvalues
  • Cholesky factorization: A = LLแต€ (faster than LU)
  • Guaranteed convergence for iterative methods

Block Structure

For rigid bodies, matrices have block structure:

[Mโ‚  0   0 ]   [vโ‚]   [Fโ‚]
[0   Mโ‚‚  0 ] ร— [vโ‚‚] = [Fโ‚‚]
[0   0   Mโ‚ƒ]   [vโ‚ƒ]   [Fโ‚ƒ]

Each Mแตข is a 3ร—3 (or 2ร—2 for 2D) mass/inertia matrix.
Block-diagonal structure allows efficient solving.

Part 8: Least Squares and Overdetermined Systems

When Ax = b Has No Exact Solution

If we have more constraints than degrees of freedom:

3 constraints, 2 unknowns:
[1  0]       [3]
[0  1] ร— x = [4]
[1  1]       [8]   โ† inconsistent!

No x satisfies all three equations exactly.

The Least Squares Solution

Minimize ย  Ax - b ย  ยฒ:
x* = (Aแต€A)โปยน Aแต€ b  (the normal equations)

This finds x that minimizes the sum of squared residuals.

For physics: โ€œsatisfy constraints as well as possibleโ€

Underdetermined Systems (Null Space)

More unknowns than constraints:

1 constraint, 2 unknowns:
[1  1] ร— x = [5]

Infinite solutions: x = [5-t, t] for any t

The null space of A contains all directions of "free motion"
that don't violate constraints.

For physics: A system with slack has infinite valid configurations.


Part 9: Putting It Together - The Physics Loop

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    PHYSICS SIMULATION LOOP                             โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                         โ”‚
โ”‚  For each time step:                                                   โ”‚
โ”‚  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€                                                   โ”‚
โ”‚                                                                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚ 1. APPLY EXTERNAL FORCES                                         โ”‚   โ”‚
โ”‚  โ”‚    F_total = F_gravity + F_user_input + F_air_resistance + ...   โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                     โ”‚                                   โ”‚
โ”‚                                     โ–ผ                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚ 2. INTEGRATE (predict new positions/velocities)                  โ”‚   โ”‚
โ”‚  โ”‚    v_predicted = v + (F/m) ร— ฮ”t                                  โ”‚   โ”‚
โ”‚  โ”‚    p_predicted = p + v_predicted ร— ฮ”t                            โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                     โ”‚                                   โ”‚
โ”‚                                     โ–ผ                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚ 3. DETECT COLLISIONS                                             โ”‚   โ”‚
โ”‚  โ”‚    Find all overlapping/intersecting pairs                       โ”‚   โ”‚
โ”‚  โ”‚    Compute collision points, normals, depths                     โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                     โ”‚                                   โ”‚
โ”‚                                     โ–ผ                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚ 4. BUILD CONSTRAINT SYSTEM                                       โ”‚   โ”‚
โ”‚  โ”‚    Assemble Jacobian J from all constraints                      โ”‚   โ”‚
โ”‚  โ”‚    Form A = J Mโปยน Jแต€  and  b = -J v - bias ร— C                  โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                     โ”‚                                   โ”‚
โ”‚                                     โ–ผ                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚ 5. SOLVE FOR CONSTRAINT FORCES                                   โ”‚   โ”‚
โ”‚  โ”‚    Solve A ฮป = b using Gauss-Seidel or PGS                       โ”‚   โ”‚
โ”‚  โ”‚    (possibly with warm starting from previous frame)             โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                     โ”‚                                   โ”‚
โ”‚                                     โ–ผ                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚ 6. APPLY CONSTRAINT IMPULSES                                     โ”‚   โ”‚
โ”‚  โ”‚    v = v + Mโปยน Jแต€ ฮป                                              โ”‚   โ”‚
โ”‚  โ”‚    (constraints now satisfied)                                   โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                     โ”‚                                   โ”‚
โ”‚                                     โ–ผ                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚ 7. UPDATE STATE                                                  โ”‚   โ”‚
โ”‚  โ”‚    p = p + v ร— ฮ”t  (with corrected velocity)                     โ”‚   โ”‚
โ”‚  โ”‚    Store for next frame                                          โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                                                         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Physics Simulation Loop


Project Specification

What Youโ€™re Building

A 2D physics simulator that:

  1. Simulates particles/circles under gravity
  2. Handles collisions with walls and other objects
  3. Supports spring/distance constraints
  4. Supports joint/pin constraints
  5. Renders in real-time

Minimum Requirements

  • Particle system with gravity
  • Semi-implicit Euler or Verlet integration
  • Circle-circle collision detection and response
  • Circle-wall collision (axis-aligned boundaries)
  • Distance constraints (springs/rods)
  • Simple constraint solver (Gauss-Seidel)
  • Real-time rendering (SDL2 or similar)

Stretch Goals

  • Rigid body rotation (not just particles)
  • Friction in collisions
  • Multiple joint types (hinges, sliders)
  • Stacking and piles of objects
  • Continuous collision detection
  • Broad-phase acceleration (spatial hashing)

Solution Architecture

Data Structures

typedef struct {
    Vec2 position;
    Vec2 velocity;
    float mass;
    float inv_mass;  // 1/mass, or 0 for static objects
    float radius;
} Particle;

typedef struct {
    int particle_a;
    int particle_b;
    float rest_length;
    float stiffness;
} DistanceConstraint;

typedef struct {
    int particle_a;
    int particle_b;
    Vec2 normal;
    float penetration;
} Contact;

typedef struct {
    Particle* particles;
    int num_particles;
    DistanceConstraint* constraints;
    int num_constraints;
    Contact* contacts;
    int num_contacts;
    Vec2 gravity;
    float dt;
} PhysicsWorld;

Module Structure

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                           main.c                                โ”‚
โ”‚  - Initialize world                                             โ”‚
โ”‚  - Main loop: update physics, render                           โ”‚
โ”‚  - Handle input                                                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                               โ”‚
        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
        โ–ผ                      โ–ผ                      โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   vec.c      โ”‚      โ”‚  physics.c   โ”‚      โ”‚  render.c    โ”‚
โ”‚              โ”‚      โ”‚              โ”‚      โ”‚              โ”‚
โ”‚ vec2_add     โ”‚      โ”‚ apply_forces โ”‚      โ”‚ draw_circle  โ”‚
โ”‚ vec2_sub     โ”‚      โ”‚ integrate    โ”‚      โ”‚ draw_line    โ”‚
โ”‚ vec2_scale   โ”‚      โ”‚ detect_collisions โ”‚ โ”‚ draw_world   โ”‚
โ”‚ vec2_dot     โ”‚      โ”‚ solve_constraints  โ”‚ โ”‚              โ”‚
โ”‚ vec2_length  โ”‚      โ”‚ step_world   โ”‚      โ”‚              โ”‚
โ”‚ vec2_normalizeโ”‚     โ”‚              โ”‚      โ”‚              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                               โ”‚
        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
        โ–ผ                      โ–ผ                      โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  collision.c โ”‚      โ”‚  constraint.cโ”‚      โ”‚  solver.c    โ”‚
โ”‚              โ”‚      โ”‚              โ”‚      โ”‚              โ”‚
โ”‚ circle_circleโ”‚      โ”‚ distance_initโ”‚      โ”‚ gauss_seidel โ”‚
โ”‚ circle_wall  โ”‚      โ”‚ distance_solveโ”‚     โ”‚ build_matrix โ”‚
โ”‚ find_contactsโ”‚      โ”‚ contact_solveโ”‚      โ”‚ solve_system โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Physics Engine Code Architecture

Simulation Loop Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                      PHYSICS STEP IMPLEMENTATION                       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                         โ”‚
โ”‚  void physics_step(PhysicsWorld* world) {                              โ”‚
โ”‚                                                                         โ”‚
โ”‚      // 1. Apply external forces (gravity)                             โ”‚
โ”‚      for each particle p:                                              โ”‚
โ”‚          p.velocity += world.gravity * world.dt                        โ”‚
โ”‚                                                                         โ”‚
โ”‚      // 2. Predict positions (integrate)                               โ”‚
โ”‚      for each particle p:                                              โ”‚
โ”‚          p.position += p.velocity * world.dt                           โ”‚
โ”‚                                                                         โ”‚
โ”‚      // 3. Detect collisions                                           โ”‚
โ”‚      world.num_contacts = 0                                            โ”‚
โ”‚      detect_particle_collisions(world)                                 โ”‚
โ”‚      detect_wall_collisions(world)                                     โ”‚
โ”‚                                                                         โ”‚
โ”‚      // 4. Solve constraints iteratively                               โ”‚
โ”‚      for (int iter = 0; iter < NUM_ITERATIONS; iter++) {               โ”‚
โ”‚          // Solve distance constraints                                 โ”‚
โ”‚          for each constraint c:                                        โ”‚
โ”‚              solve_distance_constraint(c, world.particles)             โ”‚
โ”‚                                                                         โ”‚
โ”‚          // Solve contact constraints                                  โ”‚
โ”‚          for each contact c:                                           โ”‚
โ”‚              solve_contact(c, world.particles)                         โ”‚
โ”‚      }                                                                  โ”‚
โ”‚                                                                         โ”‚
โ”‚      // 5. Derive velocities from position changes (for Verlet)        โ”‚
โ”‚      for each particle p:                                              โ”‚
โ”‚          p.velocity = (p.position - p.old_position) / world.dt        โ”‚
โ”‚  }                                                                      โ”‚
โ”‚                                                                         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Physics Step Implementation


Phased Implementation Guide

Phase 1: Basic Particle Simulation (Days 1-4)

Goal: Particles falling under gravity

Tasks:

  1. Implement Vec2 math library
  2. Create Particle structure
  3. Implement semi-implicit Euler integration
  4. Add gravity
  5. Render particles with SDL2

Implementation:

void integrate_particles(PhysicsWorld* world) {
    float dt = world->dt;
    for (int i = 0; i < world->num_particles; i++) {
        Particle* p = &world->particles[i];
        // Apply gravity
        p->velocity = vec2_add(p->velocity, vec2_scale(world->gravity, dt));
        // Update position
        p->position = vec2_add(p->position, vec2_scale(p->velocity, dt));
    }
}

Verification:

  • Particles should fall downward
  • Velocity should increase over time
  • Position change should match v ร— ฮ”t

Phase 2: Wall Collisions (Days 5-7)

Goal: Particles bounce off screen boundaries

Tasks:

  1. Detect when particle exits bounds
  2. Compute collision normal
  3. Implement reflection formula
  4. Add coefficient of restitution

Implementation:

void collide_with_walls(Particle* p, float width, float height, float restitution) {
    // Left wall
    if (p->position.x - p->radius < 0) {
        p->position.x = p->radius;
        p->velocity.x = -p->velocity.x * restitution;
    }
    // Right wall
    if (p->position.x + p->radius > width) {
        p->position.x = width - p->radius;
        p->velocity.x = -p->velocity.x * restitution;
    }
    // Similarly for top/bottom
}

Verification:

  • Particles bounce off walls
  • Restitution = 1: energy conserved
  • Restitution = 0.5: each bounce loses energy

Phase 3: Particle-Particle Collisions (Days 8-11)

Goal: Particles collide with each other

Tasks:

  1. Detect circle-circle overlap
  2. Compute collision normal and penetration
  3. Implement impulse-based response
  4. Handle position correction

Implementation:

void detect_circle_collision(Particle* a, Particle* b, Contact* contact) {
    Vec2 delta = vec2_sub(b->position, a->position);
    float dist = vec2_length(delta);
    float min_dist = a->radius + b->radius;

    if (dist < min_dist) {
        contact->normal = vec2_scale(delta, 1.0f / dist);
        contact->penetration = min_dist - dist;
        contact->valid = true;
    } else {
        contact->valid = false;
    }
}

void resolve_collision(Particle* a, Particle* b, Contact* c, float restitution) {
    Vec2 rel_vel = vec2_sub(a->velocity, b->velocity);
    float vel_along_normal = vec2_dot(rel_vel, c->normal);

    // Don't resolve if separating
    if (vel_along_normal > 0) return;

    // Compute impulse
    float j = -(1 + restitution) * vel_along_normal;
    j /= a->inv_mass + b->inv_mass;

    Vec2 impulse = vec2_scale(c->normal, j);
    a->velocity = vec2_add(a->velocity, vec2_scale(impulse, a->inv_mass));
    b->velocity = vec2_sub(b->velocity, vec2_scale(impulse, b->inv_mass));

    // Position correction (prevent sinking)
    float correction = c->penetration / (a->inv_mass + b->inv_mass) * 0.8f;
    a->position = vec2_sub(a->position, vec2_scale(c->normal, a->inv_mass * correction));
    b->position = vec2_add(b->position, vec2_scale(c->normal, b->inv_mass * correction));
}

Verification:

  • Particles bounce off each other
  • Momentum is conserved
  • No permanent interpenetration

Phase 4: Distance Constraints (Days 12-15)

Goal: Connect particles with springs/rods

Tasks:

  1. Define distance constraint structure
  2. Compute constraint violation
  3. Implement iterative position correction
  4. Visualize constraints

Implementation:

void solve_distance_constraint(DistanceConstraint* c, Particle* particles) {
    Particle* a = &particles[c->particle_a];
    Particle* b = &particles[c->particle_b];

    Vec2 delta = vec2_sub(b->position, a->position);
    float current_length = vec2_length(delta);
    float error = current_length - c->rest_length;

    if (current_length < 0.0001f) return;  // Avoid division by zero

    // Correction direction
    Vec2 correction = vec2_scale(delta, error / current_length);

    // Apply correction based on inverse masses
    float total_inv_mass = a->inv_mass + b->inv_mass;
    if (total_inv_mass < 0.0001f) return;  // Both static

    a->position = vec2_add(a->position, vec2_scale(correction, a->inv_mass / total_inv_mass));
    b->position = vec2_sub(b->position, vec2_scale(correction, b->inv_mass / total_inv_mass));
}

Verification:

  • Connected particles maintain distance
  • Multiple iterations improve constraint satisfaction
  • Chain of particles behaves like rope

Phase 5: Building the Constraint Solver (Days 16-19)

Goal: Unified constraint solving system

Tasks:

  1. Abstract constraint interface
  2. Build constraint Jacobian
  3. Implement Gauss-Seidel solver
  4. Handle mixed constraint types

Implementation:

// Generic constraint solver (simplified)
void solve_constraints(PhysicsWorld* world, int iterations) {
    for (int iter = 0; iter < iterations; iter++) {
        // Distance constraints
        for (int i = 0; i < world->num_constraints; i++) {
            solve_distance_constraint(&world->constraints[i], world->particles);
        }

        // Contact constraints
        for (int i = 0; i < world->num_contacts; i++) {
            solve_contact(&world->contacts[i], world->particles);
        }
    }
}

Verification:

  • All constraints satisfied (within tolerance)
  • Stable with many constraints
  • Convergence improves with iterations

Phase 6: Complex Scenes (Days 20-23)

Goal: Build interesting simulations

Tasks:

  1. Create chain/rope scene
  2. Create bridge scene
  3. Create cloth-like grid
  4. Add user interaction (drag, spawn)

Scenes to implement:

void create_rope_scene(PhysicsWorld* world) {
    // Chain of particles with distance constraints
    for (int i = 0; i < 10; i++) {
        add_particle(world, i * 30, 100, 5);
        if (i > 0) {
            add_distance_constraint(world, i-1, i, 30);
        }
    }
    // Pin first particle
    world->particles[0].inv_mass = 0;  // Static
}

void create_soft_body_scene(PhysicsWorld* world) {
    // Grid of particles with structural and shear constraints
    int grid_size = 5;
    for (int y = 0; y < grid_size; y++) {
        for (int x = 0; x < grid_size; x++) {
            add_particle(world, 200 + x * 30, 100 + y * 30, 5);
        }
    }
    // Add constraints (structural, shear, bend)
    // ...
}

Verification:

  • Rope swings realistically
  • Soft body maintains shape but deforms
  • No explosions or jitter

Phase 7: Polish and Optimization (Days 24-28)

Goal: Make it robust and fast

Tasks:

  1. Add friction to collisions
  2. Implement warm starting
  3. Add broad-phase collision detection
  4. Profile and optimize

Friction:

void apply_friction(Particle* a, Particle* b, Contact* c, float friction) {
    Vec2 rel_vel = vec2_sub(a->velocity, b->velocity);
    Vec2 tangent = vec2_sub(rel_vel, vec2_scale(c->normal, vec2_dot(rel_vel, c->normal)));
    float tangent_speed = vec2_length(tangent);

    if (tangent_speed < 0.001f) return;

    tangent = vec2_scale(tangent, 1.0f / tangent_speed);
    float jt = -vec2_dot(rel_vel, tangent) / (a->inv_mass + b->inv_mass);

    // Clamp to Coulomb friction
    jt = clamp(jt, -friction * c->normal_impulse, friction * c->normal_impulse);

    Vec2 friction_impulse = vec2_scale(tangent, jt);
    a->velocity = vec2_add(a->velocity, vec2_scale(friction_impulse, a->inv_mass));
    b->velocity = vec2_sub(b->velocity, vec2_scale(friction_impulse, b->inv_mass));
}

Verification:

  • Objects stack without sliding
  • System remains stable under stress
  • 60 FPS with 100+ objects

Testing Strategy

Unit Tests

  1. Vector operations:
    • Dot product of perpendicular vectors = 0
    • Normalized vector has length 1
    • Reflection formula is correct
  2. Collision detection:
    • Overlapping circles detected
    • Non-overlapping circles not detected
    • Normal points from A to B
  3. Integration:
    • Constant velocity โ†’ position increases linearly
    • Constant acceleration โ†’ velocity increases linearly
  4. Constraints:
    • Distance constraint maintains length
    • Constraint solver converges

Physical Validation

  1. Conservation of energy (elastic collision, no gravity)
  2. Conservation of momentum (any collision)
  3. Projectile motion (matches vโ‚€t + ยฝgtยฒ)
  4. Pendulum period (matches 2ฯ€โˆš(L/g))

Stress Tests

  1. Many particles: 100+ without slowdown
  2. Long chains: 50+ links stable
  3. Stacking: 10+ objects stable
  4. High speeds: No tunneling through walls

Common Pitfalls and Debugging

Explosion/Instability

Symptom: Particles fly off to infinity

Causes:

  • Time step too large
  • Forces computed incorrectly
  • Penetration not resolved

Fixes:

  • Reduce ฮ”t (try 1/120 instead of 1/60)
  • Add position correction
  • Clamp velocities to maximum

Sinking Objects

Symptom: Objects slowly fall through floors

Causes:

  • Collision detected but not resolved
  • Bias/slop too large
  • Not enough solver iterations

Fixes:

  • Add position correction after velocity correction
  • Reduce bias (e.g., 0.01 instead of 0.1)
  • Increase iterations (10-20)

Jitter/Vibration

Symptom: Objects vibrate in place

Causes:

  • Over-correction
  • Conflicting constraints
  • No damping

Fixes:

  • Add damping: v *= 0.99
  • Reduce correction factor
  • Warm start solver

Constraint Stretching

Symptom: Springs stretch too much

Causes:

  • Not enough solver iterations
  • Stiffness too low
  • Integration error

Fixes:

  • More iterations (20+)
  • Use position-based dynamics
  • Smaller time step

Tunneling

Symptom: Fast objects pass through walls

Causes:

  • Discrete collision detection
  • Large time step
  • Small obstacles

Fixes:

  • Continuous collision detection (CCD)
  • Limit max velocity
  • Sub-step physics

Extensions and Challenges

Beginner Extensions

  • Add mouse interaction (drag particles)
  • Create preset scenes (bridge, rope, dominos)
  • Add visual effects (trails, colors)

Intermediate Extensions

  • Implement friction
  • Add rigid body rotation
  • Create hinge joints
  • Implement spatial hashing for broad phase

Advanced Extensions

  • Continuous collision detection
  • 3D extension
  • Deformable bodies
  • Fluid simulation (SPH)

Real-World Connections

Game Engines

Every physics engine (Box2D, Bullet, PhysX) uses these concepts:

  • Constraint-based dynamics
  • Iterative solvers
  • Warm starting
  • Continuous collision detection

Robotics

Robot dynamics and control:

  • Forward/inverse kinematics
  • Constraint satisfaction
  • Force/torque computation

Computer Graphics

Physically-based animation:

  • Cloth simulation
  • Hair dynamics
  • Destruction effects

Engineering Simulation

Structural analysis:

  • Finite element methods
  • Stress/strain computation
  • Dynamic loading

Self-Assessment Checklist

Conceptual Understanding

  • I can explain why we need to solve Ax = b for physics
  • I understand the role of the Jacobian matrix
  • I can describe why iterative solvers work for constraints
  • I know what matrix conditioning means for stability
  • I understand the tradeoffs of different integration methods

Practical Skills

  • I implemented collision detection and response
  • I built a working constraint solver
  • My simulation runs stable at 60 FPS
  • I can create various constraint types
  • I can debug common physics bugs

Teaching Test

Can you explain to someone else:

  • How does impulse-based collision response work?
  • Why do constraints create systems of equations?
  • What makes a simulation stable vs. unstable?
  • How does Gauss-Seidel iteration work?

Resources

Primary

  • โ€œGame Physics Engine Developmentโ€ by Ian Millington
  • โ€œPhysics for Game Developersโ€ by David Bourg
  • Box2D source code - Reference implementation

Reference

  • โ€œReal-Time Collision Detectionโ€ by Christer Ericson
  • โ€œNumerical Recipes in Cโ€ by Press et al. - Equation solvers
  • Erin Cattoโ€™s GDC presentations - Box2D authorโ€™s talks

Online

  • Box2D documentation - Algorithm explanations
  • Gaffer On Games (gafferongames.com) - Physics articles
  • Two-Bit Coding YouTube - Physics engine tutorials

When you complete this project, youโ€™ll understand that physics simulation is applied linear algebraโ€”solving equations, projecting vectors, and navigating numerical challenges. Every game, every simulation, every robot uses these techniques.