Bouncing balls in a heptagon · glm-5.2
Show 20 numbered balls (1 to 20) dropping from the center of a heptagon that spins 360 degrees every 5 seconds. The balls are affected by gravity and friction, collide with each other, and bounce realistically off the rotating walls; show each ball's spin via the rotation of its number. Implement all collision detection yourself.
transcript · 4 messages
Show 20 numbered balls (1 to 20) dropping from the center of a heptagon that spins 360 degrees every 5 seconds. The balls are affected by gravity and friction, collide with each other, and bounce realistically off the rotating walls; show each ball's spin via the rotation of its number. Implement all collision detection yourself.
thinking
Let me build this. A heptagon (7-sided polygon) spinning 360° every 5 seconds. Balls drop from center, gravity, friction, ball-ball collisions, ball-wall collisions with rotating walls, and each ball has spin shown by rotating its number.
Let me write a self-contained index.html.
Key physics:
- Heptagon centered at canvas center, some radius.
- Balls spawn at center periodically, 20 of them.
- Gravity pulls down.
- Friction/damping.
- Ball-ball collision: elastic with friction transferring spin.
- Ball-wall: wall is rotating, so wall has velocity at contact point = ω × r. Compute relative velocity, reflect normal component with restitution, apply tangential friction which affects both ball velocity and ball spin.
Ball spin: angular velocity, number rotates with it.
Let me think about the rotating wall collision. The heptagon vertices rotate over time. For a ball, find distance to each edge. If ball penetrates an edge (inside the polygon but the wall is the boundary — balls should stay inside). Actually balls bounce inside the heptagon (it's a container). So walls are the edges, balls bounce off from inside.
For each edge, compute the inward normal. Ball center should be on the inner side. If distance from ball center to edge line (signed, positive inside) < radius, then collision. Push ball out along normal, and apply collision response accounting for wall velocity.
Wall velocity at contact point: the heptagon rotates with angular velocity ω about center. A point on the wall at position p has velocity ω × (p - center) (in 2D, perpendicular).
Let me code this.
Canvas setup, requestAnimationFrame loop.
Let me write it.
Heptagon: 7 vertices. angle = i * 2π/7 + rotation. radius R.
Edge from vertex i to vertex i+1. Inward normal: for a convex polygon with vertices in order, the inward normal... Let me compute. For edge from A to B, direction d = B-A. Normal n = (-d.y, d.x) or (d.y, -d.x). Need the one pointing inward (toward center). Pick the one whose dot with (center - midpoint) > 0.
Ball-wall collision response:
- contact point ~ ball center - n * radius (n is inward normal, so wall is at ball center + (-n)*radius... wait). Let me define n = inward normal (pointing into polygon). Wall surface, ball is inside. If ball penetrates, ball center is too close to wall, distance from edge < radius. The wall is on the outside. So ball should be pushed inward: push ball center by (radius - dist) along n.
- Wall point velocity at contact: the contact point on the wall is ball_center - n * radius (the point on wall closest). Actually the contact point is approximately ball_center - n*radius (projected to edge). Wall velocity v_wall = ω × r where r = contact_point - center. In 2D with ω scalar (z), v = ω * (-r.y, r.x).
- Relative velocity of ball w.r.t. wall: v_rel = v_ball - v_wall.
- Normal component: v_n = v_rel · n. If v_n < 0 (moving into wall), reflect: v_n_new = -restitution * v_n.
- Tangent: t = perpendicular to n. v_t = v_rel · t. Friction reduces v_t: v_t_new = v_t * (1 - friction). The change in tangential velocity affects ball spin via torque.
Update ball velocity: v_ball = v_wall + v_n_new * n + v_t_new * t.
Spin update: The tangential impulse changes angular velocity. Impulse along tangent J_t = (v_t_new - v_t) * mass (in terms of ball). The torque = -J_t * radius (sign depends). Change in angular velocity: Δω = (friction_coef * impulse * radius) / moment_of_inertia. For a solid sphere/disc I = 0.5*m*r². This gives realistic spin transfer.
Let me simplify:
Δω = -2 * (v_t_new - v_t) / radius (for disc, the no-slip condition gives this). Actually the standard formula: relative tangential velocity at contact = v_t - ω_ball * radius (surface velocity due to spin). Friction tries to reduce this. Let me incorporate ball spin into relative velocity too.
This is getting complex but let me do a reasonable version.
Surface velocity of ball at contact due to spin: v_spin = ω_ball × (-n) ... the contact point relative to ball center is -n*radius (pointing outward to wall). Surface velocity = ω_ball (z) × (-n*radius) = ω_ball * radius * (n.y, -n.x)... let me just compute. For angular velocity ω (z-axis, positive = counterclockwise), velocity at point r (relative to center) = ω * (-r.y, r.x).
Contact point relative to ball center: r_contact = -n * radius (outward toward wall). Surface velocity = ω_ball * (-r_contact.y, r_contact.x) = ω_ball * (n.y * radius, -n.x * radius) = ω_ball * radius * (n.y, -n.x).
Tangent direction t: let's define t = (n.y, -n.x) (90° clockwise rotation of n, or counterclockwise? n=(1,0)→t=(0,-1) which is clockwise). The surface velocity tangential component = ω_ball * radius (along t).
Total relative tangential velocity at contact (ball surface vs wall): v_t_rel = (v_ball - v_wall)·t - ω_ball * radius.
Friction impulse reduces |v_t_rel|. Let J be friction impulse magnitude along t (positive in t direction).
After impulse:
- ball linear: v_ball += (J/m) * t
- ball angular: ω_ball += (J * radius / I) (torque from force at contact; sign: force in +t direction at contact point r=-n*radius produces torque = r × F = (-n*radius) × (J*t). In 2D z-component: r.x*F.y - r.y*F.x. With n=(1,0), t=(0,-1), r=(-radius,0), F=(0,-J): r.x*F.y - r.y*F.x = (-radius)*(-J) - 0 = radius*J. So torque = radius*J, positive → ω increases. Hmm but physically friction should oppose slip.)
Let me just do: compute v_t_rel, apply friction coefficient, clamp.
Simpler approach that looks good:
- v_n = v_rel · n (with v_rel = v_ball - v_wall). If v_n < 0:
- v_ball -= (1 + restitution) * v_n * n (reflect normal, since adding (1+e)*(-v_n)*n... let me: v_ball.n becomes -e * v_n).
v_ball = v_ball - (1+e) * v_n * n. ✓ (because v_n is negative, -(1+e)*v_n is positive along n, pushing inward).
- Tangential: t = (n.y, -n.x). v_t = v_rel · t - ω_ball * radius.
- Apply friction: reduce v_t by factor, or clamp by friction_coef * |normal impulse|.
- normal impulse magnitude = (1+e) * |v_n| * m.
- max friction impulse = friction * normal_impulse.
- desired impulse to stop slip = -v_t * m (approx, ignoring rotation) ... but rotation matters.
- Let me solve properly: impulse J along t.
New v_t_rel' = (v_ball + (J/m)t - v_wall)·t - (ω_ball + J*radius/I)*radius
= v_t + J/m - J*radius²/I
= v_t + J*(1/m - radius²/I)
For disc I = 0.5*m*radius², so radius²/I = 2/m. Thus 1/m - 2/m = -1/m.
v_t_rel' = v_t - J/m.
To zero slip: J = v_t * m. Clamp |J| ≤ friction * normal_impulse.
- Apply: v_ball += (J/m)*t, ω_ball += J*radius/I = J*radius/(0.5 m radius²) = 2J/(m*radius).
This is realistic. Let me use mass=1, radius varies.
Let me just set all balls same radius for simplicity, mass=1.
Ball-ball collision: standard elastic with restitution. For spin transfer between balls, similar but contact normal is between centers. I'll add some friction-based spin transfer.
Let me write the code now.
Spawn: 20 balls, drop from center one at a time? "dropping from the center" — spawn all at center, maybe staggered. Let me spawn them with small delays so they don't all overlap. Actually dropping from center means each ball starts at center. Let me spawn them with a slight time gap and tiny random velocity.
I'll spawn one every ~0.3 seconds until 20, then maintain 20 (or just spawn 20 total). Let me spawn 20 balls over time, each from center, and once 20 exist, stop spawning. But balls might escape? They stay in container. Good.
Let me write it.
Numbers 1-20 on balls, each ball's number rotates by ball's spin angle.
Colors: cycle through palette.
Let me code.
```js
const canvas, ctx;
const W, H;
const center = {x: W/2, y: H/2};
const heptRadius = min(W,H)*0.42;
const omega = 2*Math.PI/5; // rad/s
let rotation = 0;
const gravity = 900; // px/s²
const restitution_wall = 0.8;
const friction_wall = 0.2;
const restitution_ball = 0.85;
const friction_ball = 0.15;
const ballRadius = 18;
const linearDamping = 0.02; // air friction per second-ish
```
Balls: {x,y,vx,vy,angle,omega,number,color}
Collision detection ball-ball: for each pair, dist < 2*radius.
Let me handle substeps for stability: fixed timestep dt with multiple substeps.
I'll run physics with fixed dt=1/120, accumulator.
Let me write the full thing.
For wall collision, I need heptagon edges at current rotation. Vertices:
v_i = center + R*(cos(rotation + i*2π/7), sin(rotation + i*2π/7)).
For each edge (v_i, v_{i+1}), compute inward normal and distance.
Let me write helper.
Drawing: draw heptagon outline (rotated), draw balls with number rotated by angle.
Let me code it all now.
For number rendering: save, translate to ball center, rotate by ball.angle, draw circle, draw number at center, restore. Fill circle with color, number text white/black.
Let me write.
Spawn timing: spawn a ball every 250ms until 20. Each spawns at center with small random velocity downward-ish.
Actually to avoid initial huge overlap explosions, spawn with small downward velocity.
Let me finalize code.
I'll also add a slight visual: trail or glow? Keep simple.
Let me write the file./workspace/index.html (11488 bytes)Successfully wrote 11488 bytes to /workspace/index.html
I built the app in `/workspace/index.html`. It's fully self-contained (no external resources). What it does: **Scene** - A heptagon (7 sides) centered on screen, rotating 360° every 5 seconds (`OMEGA_HEPT = 2π/5`). - 20 numbered balls (1–20) spawn from the center, one every ~0.22s, each with a small random velocity. **Physics (all hand-rolled)** - Fixed-timestep integration at 120 Hz with an accumulator for stability. - Gravity, plus air damping on linear velocity and angular damping on spin. - **Ball–ball collisions**: detects overlap via distance, resolves positional correction, applies a normal impulse with restitution, and a tangential friction impulse that transfers spin between balls (using moment of inertia `I = ½mr²`). - **Ball–rotating-wall collisions**: for each heptagon edge, computes the inward normal, signed distance, penetration, then the wall's contact-point velocity due to rotation `v = ω × r`. The collision response is done in the wall's frame (relative velocity), reflecting the normal component with restitution and applying friction that both reduces tangential slip and changes ball spin via the torque relation. A second wall pass keeps balls contained under fast spin. - Each ball tracks its own angular velocity; the number is drawn rotated by the accumulated angle, with a small radial tick so spin is visible even when the number reads symmetric. **Rendering** - Canvas with DPR scaling, radial-gradient background, gradient-shaded balls with shadow, highlighted heptagon edges and vertex dots. Open `index.html` in a browser to watch it run.
TAKE IT TO CODE
A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.
RUN
EVALUATION
Numbered balls drop under gravity into a spinning heptagon, collide, and settle realistically; input has minimal extra effect.
Vision grade · anthropic/claude-sonnet-5
MEASUREMENTS
Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.