plinko-game

Physics Engine Implementation

Overview

This document describes the custom physics engine implementation for the Plinko game, based on advanced computational dynamics principles. The engine replaces Matter.js with a lightweight, purpose-built solution that implements Semi-Implicit Euler integration, fixed timestep accumulation, impulse-based collision resolution, and spatial partitioning.

Mathematical Foundations

1. Rigid Body Dynamics

Each rigid body in the simulation is represented by a state vector:

S = {r, v, θ, ω}

Where:

The derivative of this state follows Newton’s laws:

dS/dt = {v, a, ω, α}

Where:

2. Semi-Implicit Euler Integration

The engine uses Semi-Implicit Euler (also called Symplectic Euler) integration, which is superior to Explicit Euler for physics simulation:

Explicit Euler (UNSTABLE):

x_{t+Δt} = x_t + v_t * Δt
v_{t+Δt} = v_t + a_t * Δt

Semi-Implicit Euler (STABLE):

v_{t+Δt} = v_t + a_t * Δt
x_{t+Δt} = x_t + v_{t+Δt} * Δt  // Use NEW velocity

Key Advantage: Semi-Implicit Euler is symplectic, meaning it conserves the phase-space volume. This prevents energy from exploding (like Explicit Euler) or damping (like Implicit Euler). The system energy oscillates around the true value but remains bounded.

3. Fixed Timestep Accumulator

Variable timesteps cause numerical instability and non-deterministic behavior. The engine implements a fixed timestep accumulator:

accumulator += frameTime;
while (accumulator >= fixedDeltaTime) {
    step(fixedDeltaTime);  // Fixed Δt = 1/60 seconds
    accumulator -= fixedDeltaTime;
}
alpha = accumulator / fixedDeltaTime;  // For interpolation

Benefits:

4. Impulse-Based Collision Resolution

Traditional force-based collision handling can fail for stiff constraints (like hard walls). The engine uses impulse-based resolution:

Impulse Definition:

J = ∫ F dt = Δp = m * Δv

Therefore:

Δv = J/m

Collision Response Algorithm:

  1. Detect collision: Find contact point and normal
  2. Calculate relative velocity at contact:
    v_rel = v_B - v_A
    v_normal = v_rel · n
    
  3. Calculate impulse magnitude:
    j = -(1 + e) * v_normal / (1/m_A + 1/m_B)
    

    Where e is restitution (bounciness)

  4. Apply impulse:
    v_A = v_A - (j * n) / m_A
    v_B = v_B + (j * n) / m_B
    
  5. Apply friction (tangential impulse):
    t = perpendicular(n)
    j_friction = min(μ * j, -v_tangent * (1/m_A + 1/m_B))
    

5. Spatial Partitioning

Naive collision detection is O(N²). With N=100 objects, that’s 4,950 checks per frame. The engine uses Spatial Hashing to reduce this to ~O(N).

Hash Function:

H(x, y) = (x * p1 + y * p2) | 0

Where p1 = 73856093 and p2 = 19349663 are large primes.

Algorithm:

  1. Divide world into uniform grid cells (e.g., 50×50 pixels)
  2. Hash each body into cells it overlaps
  3. Only check collisions within same cells
  4. Result: ~O(N) average case

Physics Parameters

Gravity

gravity = { x: 0, y: 980 }  // pixels/second²

At canvas scale (~100 pixels/meter): 980 pixels/s² ≈ 9.8 m/s² (Earth gravity)

Material Properties

Pegs (Hard Plastic):

Balls (Metal):

These values were chosen to match real-world physics:

Performance Characteristics

Metric Value Notes
Collision Detection O(N) average Spatial hash with 50px cells
Integration O(N) Linear in number of bodies
Frame Budget 16.6ms 60 FPS target
Typical Bodies 50-100 Pegs + active balls
Memory ~2KB/body Lightweight JS objects

Comparison with Matter.js

Feature Custom Engine Matter.js
Integration Semi-Implicit Euler Verlet-like
Timestep Fixed (accumulator) Variable
Collision Impulse-based Constraint solver
Size ~21KB ~500KB
Complexity Simple Complex
Determinism Guaranteed Good
Performance Excellent Good

Validation

The engine has been validated through:

  1. Energy Conservation: Balls maintain consistent behavior across runs
  2. Stability: No explosions or tunneling at 60 FPS
  3. Determinism: Same random seed produces identical results
  4. Performance: Maintains 60 FPS with 100+ bodies
  5. Realism: Matches expected physical behavior

References

Code Structure

physics-engine.js
├── Vec2                 // 2D vector mathematics
├── RigidBody           // Body state and properties
├── SpatialHash         // Broad-phase collision detection
└── PhysicsEngine       // Main simulation loop
    ├── update()        // Fixed timestep accumulator
    ├── step()          // Semi-Implicit Euler integration
    ├── detectCollisions() // Broad + narrow phase
    └── resolveCollision() // Impulse-based resolution

Future Enhancements

Potential improvements (not currently implemented):