☄️ Bouncing Balls Simulation

☄️ How It Works

This is a physics simulation where colored balls bounce around a circular arena, competing for survival through an emergent line-crossing mechanic. Watch as balls move under realistic physics, creating colored tethers when they hit walls. The simulation ends when only one ball remains with intact lines—a beautiful demonstration of chaos, collision, and competition.

Each ball starts with random velocity and begins bouncing immediately. When a ball strikes the arena boundary, it spawns a colored line segment connecting the impact point to its center. These lines stay attached to the ball, moving as the ball continues its trajectory. The drama unfolds when balls cross each other's lines—any intersection destroys that line instantly. Once a ball has created lines but lost them all to crossings, it's eliminated. The last survivor wins.

You'll notice motion trails following each ball—these ethereal streaks are purely visual, showing recent paths without affecting the simulation. The elimination mechanic depends solely on the colored lines created at wall impacts. It's a hypnotic dance of geometry, momentum, and chance.

Physics Deep Dive

Gravitational Acceleration: The simulation applies a constant downward acceleration vector of 0.15 pixels per frame squared to each ball. This creates a parabolic trajectory pattern characteristic of projectile motion. The gravity constant was tuned to balance visual interest with reasonable simulation timescales—stronger gravity would cause rapid settling, while weaker gravity would result in floating behavior that diminishes wall collision frequency.

Velocity Damping (Air Resistance): Each frame, velocities are multiplied by 0.9995, modeling quadratic air drag proportional to velocity. This exponential decay prevents perpetual motion and creates gradual energy dissipation. The damping coefficient is deliberately weak to allow sustained bouncing—the half-life of velocity is approximately 1,386 frames. A minimum velocity threshold of 0.1 px/frame prevents numerical underflow and ensures balls maintain some motion even at low energy states.

Circular Boundary Collision Detection: The arena is a circle centered at canvas center with radius 300px (or 40% of minimum dimension on small screens). Collision detection uses Euclidean distance: when the following condition is met (where $r$ is ball radius and $R$ is arena radius), a collision is registered:

$$\sqrt{(x-x_0)^2 + (y-y_0)^2} + r > R$$

The collision normal is computed as the normalized unit vector from arena center to ball center:

$$\hat{n} = \frac{\text{ball} - \text{center}}{\|\text{ball} - \text{center}\|}$$

Elastic Wall Reflection: Upon wall collision, velocity is reflected across the boundary normal, implementing specular reflection:

$$\mathbf{v}' = \mathbf{v} - 2(\mathbf{v} \cdot \hat{n})\hat{n}$$

A restitution coefficient of 0.99 is applied to model slight energy loss: $\mathbf{v}_{\text{final}} = 0.99 \times \mathbf{v}'$. This near-perfect elasticity keeps the simulation energetic while gradually reducing chaos. The ball is repositioned to prevent overlap: $\text{position} = \text{center} + (R - r) \times \hat{n}$.

Line Generation Algorithm: Each wall collision spawns a line segment stored as {x1, y1, x2, y2, color}. The first endpoint $(x_1, y_1)$ is the wall impact point: $\text{impact} = \text{center} + R \times \hat{n}$. The second endpoint $(x_2, y_2)$ is the ball's center at collision. These lines are owned by the ball and transform with it—each frame, line endpoints update: $x_2^{\text{new}} = \text{ball}.x$, $y_2^{\text{new}} = \text{ball}.y$, maintaining the offset vector from wall to ball.

Ball-Ball Collision Physics: Inter-ball collisions use elastic collision equations in the collision frame. When two balls overlap ($\text{distance} < r_1 + r_2$), the collision axis is computed as the normalized separation vector:

$$\hat{u} = \frac{\text{ball}_2 - \text{ball}_1}{\|\text{ball}_2 - \text{ball}_1\|}$$

Velocities are decomposed into normal and tangential components. Normal components are exchanged: $v_{1n}' = v_{2n}$, $v_{2n}' = v_{1n}$, while tangential components remain unchanged. To prevent energy accumulation from numerical errors, post-collision speeds are averaged. Balls are separated to eliminate overlap.

Line Intersection Mathematics: Every frame, for each ball and each line, the simulation computes point-to-line-segment distance. For line segment AB and point P, it projects P onto the infinite line:

$$t = \frac{(P-A) \cdot (B-A)}{\|B-A\|^2}$$

If $0 \leq t \leq 1$, the closest point is on the segment: $C = A + t(B-A)$. The distance is $d = \|P-C\|$. If $d < \text{ball}_{\text{radius}}$, intersection is confirmed and the line is deleted. This $O(n \cdot m)$ algorithm ($n$ balls, $m$ lines) runs at 60 FPS thanks to canvas 2D context acceleration.

Elimination State Machine: Each ball tracks hasCreatedLines (boolean) and lines[] (array). Initially hasCreatedLines = false. On first wall collision, it becomes true. A ball is eligible for elimination when hasCreatedLines = true AND lines.length = 0. The simulation removes such balls via splice() from the active ball array. Single ball remaining triggers victory state. This creates a two-phase game: accumulation (balls create lines) and attrition (lines get destroyed).

Motion Trail Rendering: Each ball maintains a circular buffer of 15 position history entries. Every frame, the current position is pushed and the oldest is popped. Trails render as polylines with gradient transparency, where $i$ is the segment index:

$$\alpha(i) = 0.6 \times \frac{i}{15}$$

This creates a fade effect using CanvasRenderingContext2D's globalAlpha property. Trails are rendered before balls in the draw stack to ensure balls appear on top. These are purely decorative—they don't participate in collision or elimination logic.

Temporal Integration (Verlet-like): Position updates use semi-implicit Euler integration: $\mathbf{v} \mathrel{+}= \mathbf{a} \times dt$, $\mathbf{p} \mathrel{+}= \mathbf{v} \times dt$, with $dt = 1$ (normalized to frame time). This symplectic integrator conserves energy better than explicit Euler and prevents the simulation from exploding. The order matters: updating velocity before position reduces numerical drift and maintains stability across varying frame rates.

Color Generation: Ball colors are generated using HSL color space with evenly distributed hues:

$$\text{hue} = \frac{i}{\text{numBalls}} \times 360°$$

With saturation = 70% and lightness = 60%. This ensures high contrast between adjacent balls while avoiding oversaturated or too-dark colors. HSL is converted to RGB by the CSS engine. This systematic approach creates aesthetically pleasing, distinguishable palettes regardless of ball count.

Performance Optimizations: Canvas clears use clearRect() rather than full repaints. Collision detection employs early exits: if balls are far apart ($\text{distance} > 2 \times \text{max}_{\text{radius}}$), detailed collision math is skipped. The line crossing algorithm short-circuits when $t < 0$ or $t > 1$ (point not between line endpoints). requestAnimationFrame() synchronizes with display refresh, typically 60 Hz, preventing wasted computations during non-visible frames.