All projects

Just for fun

Polygon Bouncer

Balls bouncing under gravity inside a rectangle or a regular polygon with 3 to 12 sides. Written in TypeScript on an HTML5 canvas, with substepped collision detection against the walls and between the balls.

  • Collisions
  • Generative

Tap or click inside the boundary to add a ball. Use the panel to change the boundary, gravity, restitution and friction.

© Hosein Gholami

Tap or click inside the boundary to add a ball. Lighter balls are faster.

Bouncer

Share of the normal speed kept in a bounce; 1 is perfectly elastic.
Coulomb friction: each bounce removes at most μ × the normal impulse from the sliding speed.
Equal discs, same restitution as the walls.
Balls0 / 300
Mean speed0 px/s

Features

  • Real-time physics simulation with gravity and collision detection
  • A rectangle filling the canvas or a regular polygon with 3–12 sides; the walls follow the canvas when the window resizes
  • Adjustable gravity, restitution and wall friction
  • Fixed 240 Hz physics step, so 60 Hz and 120 Hz screens show the same motion
  • Elastic ball–ball collisions; tap to add a ball, and random spawns keep clear of the walls and of other balls

Technical Implementation

  • Custom physics engine with collision resolution
  • Walls as half-planes: a ball is inside a convex polygon when it is inside every edge, so even a very fast ball cannot tunnel out
  • Substeps sized so no ball moves more than half its radius per substep
  • Responsive canvas with device pixel ratio support
  • Coulomb wall friction: the tangential impulse is at most μ times the normal impulse, so friction does not depend on the step size

Code Highlights

Physics Engine Core

// One substep: gravity, motion, then each wall as a half-plane n·p >= c
for (const b of balls) {
  b.vy += g * h;
  b.x += b.vx * h;
  b.y += b.vy * h;
}
for (let k = 0; k < edges; k++) {
  const d = nx[k] * b.x + ny[k] * b.y - c[k];   // signed distance to edge k
  if (d >= b.r) continue;
  b.x += (b.r - d) * nx[k];                      // push back inside
  b.y += (b.r - d) * ny[k];
  const vn = b.vx * nx[k] + b.vy * ny[k];
  if (vn >= 0) continue;                         // already separating
  const jn = -(1 + e) * vn;                      // normal impulse
  // ...reflect the normal speed, and take at most mu * jn off the sliding speed
}

Project Impact

This project demonstrates advanced JavaScript techniques and real-time graphics programming concepts. It showcases the ability to create engaging, interactive experiences using web technologies, combining mathematical algorithms with smooth visual presentation.