int numBalls = 5; CradleBall[] balls; float spacing = 60; void setup() { size(800, 400); balls = new CradleBall[numBalls]; float startX = width / 2 - (numBalls - 1) * spacing / 2; for (int i = 0; i < numBalls; i++) { float x = startX + i * spacing; balls[i] = new CradleBall(x, 100, 150, 20); } // Pull back first ball balls[0].angle = radians(-45); } void draw() { background(150, 50, 50); // Green table for (int i = 0; i < numBalls; i++) { balls[i].update(); balls[i].display(); } // Detect collisions and transfer momentum for (int i = 0; i < numBalls - 1; i++) { CradleBall b1 = balls[i]; CradleBall b2 = balls[i + 1]; if (b1.isSwingingToward(b2) && b1.isTouching(b2)) { // Transfer angular velocity b2.angularVelocity = b1.angularVelocity; b1.angularVelocity = 0; } } } // --- Cradle Ball class with motion and collision --- class CradleBall { float anchorX, anchorY; float length; float r; float angle = 0; float angularVelocity = 0; float angularAcceleration = 0; float damping = 0.998; float gravity = 0.4; float ballX, ballY; CradleBall(float anchorX, float anchorY, float length, float r) { this.anchorX = anchorX; this.anchorY = anchorY; this.length = length; this.r = r; } void update() { angularAcceleration = (-gravity / length) * sin(angle); angularVelocity += angularAcceleration; angularVelocity *= damping; angle += angularVelocity; // Calculate actual position of the ball ballX = anchorX + sin(angle) * length; ballY = anchorY + cos(angle) * length; } void display() { stroke(180); line(anchorX, anchorY, ballX, ballY); fill(200); // Silver noStroke(); ellipse(ballX, ballY, r * 2, r * 2); } boolean isTouching(CradleBall other) { float d = dist(this.ballX, this.ballY, other.ballX, other.ballY); return d <= this.r * 2 * 0.98; // Allow a bit of overlap } boolean isSwingingToward(CradleBall other) { return (this.ballX < other.ballX && this.angularVelocity > 0) || (this.ballX > other.ballX && this.angularVelocity < 0); } }