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.