boolean rotatingSystem = true;
float systemRotation = PI/10;
float systemRotOffset = 0;
float systemRotInc = PI/160;
float systemRotMultiplier = PI/320;
balls = new Ball[maxShapes][maxBalls];
colours = new color[maxShapes];
for (int s = 0; s < maxShapes; s++)
for (int b = 0; b < maxBalls; b++)
PVector pos = new PVector(50, 25);
PVector vel = new PVector(random(-v, v), random(-v, v));
balls[s][b] = new Ball(pos, vel, 6, 0);
colours[s] = color(random(96, 192), random(96, 192), random(96, 192));
edges = new VectorLine[2];
edges[0] = new VectorLine(new PVector(width/4, 0), width/2, 1);
edges[1] = new VectorLine(new PVector(0, 0), width, 1);
if (reflect) edges[1].rotation = TWO_PI / (2 * numWedges);
else edges[1].rotation = TWO_PI / numWedges;
outerRing = new Ball(new PVector(0, 0), new PVector(0, 0), width/2 - border, 192);
ellipse(width/2, height/2, width - 2*border, height - 2*border);
for (int s = 0; s < numShapes; s++)
for (int b = 0; b < numBalls; b++)
for (int e = 0; e < 2; e++)
balls[s][b].lineBounce(edges[e]);
doRingCollision(balls[s][b], outerRing);
drawWedges(colours[s], balls[s]);
systemRotOffset += systemRotInc;
systemRotation += abs(sin(systemRotOffset)) * systemRotMultiplier;
systemRotation %= TWO_PI;
if (keyCode == 'm' || keyCode == 'M') numShapes = min(numShapes + 1, maxShapes);
if (keyCode == 'f' || keyCode == 'F') numShapes = max(numShapes - 1, minShapes);
if(keyCode == 'w' || keyCode == 'W')
numWedges = max(numWedges - 1, minWedges);
correctBallPositions(edges[1].rotation);
if (keyCode == 'n' || keyCode == 'N')
numWedges = min(numWedges + 1, maxWedges);
correctBallPositions(edges[1].rotation);
if(keyCode == 'r' || keyCode == 'R')
correctBallPositions(edges[1].rotation);
if(keyCode == 'c' || keyCode =='C') numBalls = min(numBalls + 1, maxBalls);
if(keyCode == 'p' || keyCode =='P') numBalls = max(numBalls - 1, minBalls);
if(keyCode == 's' || keyCode == 'S') rotatingSystem = !rotatingSystem;
PVector gravity = new PVector(0, 0.1);
Ball(PVector position, PVector velocity, float radius, color colour)
this.position = position;
this.velocity = velocity;
ellipse(position.x, position.y, 2*radius, 2*radius);
void lineBounce(VectorLine vLine)
PVector lineStart = vLine.getOneEnd();
PVector lineEnd = vLine.getOtherEnd();
PVector theLine = new PVector(lineStart.x - lineEnd.x, lineStart.y - lineEnd.y);
PVector start2ball = new PVector(position.x - lineStart.x, position.y - lineStart.y);
float dpStart = theLine.dot(start2ball);
PVector end2ball = new PVector(lineEnd.x - position.x, lineEnd.y - position.y);
float dpEnd = theLine.dot(end2ball);
PVector lineNormal = new PVector(-(lineStart.y - lineEnd.y), lineStart.x - lineEnd.x);
float ballDOTnormal = start2ball.dot(lineNormal);
if(start2ball.mag() < radius + vLine.lineWidth/2)
Ball lineStartBall = new Ball(lineStart, new PVector(0, 0), vLine.lineWidth, color(0));
doRingCollision(this, lineStartBall);
if(end2ball.mag() < radius + vLine.lineWidth/2)
Ball lineEndBall = new Ball(lineEnd, new PVector(0, 0), vLine.lineWidth, color(0));
doRingCollision(this, lineEndBall);
else if(abs(ballDOTnormal) < radius + vLine.lineWidth/2)
doLineCollision(this, vLine);
VectorLine(PVector position, float lineLength, float lineWidth)
this.position = position;
this.lineLength = lineLength;
this.lineWidth = lineWidth;
this.rotation = rotation;
this.rotationInc = rotationInc;
if(pivotType == "FLIPPER")
translate(position.x, position.y);
rect(0, -lineWidth/2, lineLength, lineWidth);
ellipse(0, 0, lineWidth, lineWidth);
ellipse(lineLength, 0, lineWidth, lineWidth);
translate(position.x, position.y);
rect(-lineLength/2, -lineWidth/2, lineLength, lineWidth);
ellipse(-lineLength/2, 0, lineWidth, lineWidth);
ellipse(lineLength/2, 0, lineWidth, lineWidth);
position = new PVector(mouseX, mouseY);
float dx = position.x - mouseX;
float dy = position.y - mouseY;
float mouseAngle = atan2(dy, dx);
if(pivotType == "FLIPPER")
float xbit = position.x - cos(rotation) * lineLength/2;
float ybit = position.y - sin(rotation) * lineLength/2;
return new PVector(xbit, ybit);
if(pivotType == "FLIPPER")
float xbit = position.x + cos(rotation) * lineLength;
float ybit = position.y + sin(rotation) * lineLength;
return new PVector(xbit, ybit);
float xbit = position.x + cos(rotation) * lineLength/2;
float ybit = position.y + sin(rotation) * lineLength/2;
return new PVector(xbit, ybit);
void doBallCollision(Ball ballA, Ball ballB)
float xbit = ballA.position.x - ballB.position.x;
float ybit = ballA.position.y - ballB.position.y;
PVector vjc = new PVector(xbit, ybit);
PVector jcn = new PVector(ybit, -xbit);
float distance = vjc.mag();
if(distance < ballA.radius + ballB.radius)
float vjcDot = vjc.dot(vjc);
float jcnDot = jcn.dot(jcn);
float avelDOTvjc = ballA.velocity.dot(vjc);
PVector aVel_vjc = PVector.mult(vjc, (avelDOTvjc / vjcDot));
float bvelDOTvjc = ballB.velocity.dot(vjc);
PVector bVel_vjc = PVector.mult(vjc, (bvelDOTvjc / vjcDot));
float avelDOTjcn = ballA.velocity.dot(jcn);
PVector aVel_jcn = PVector.mult(jcn, (avelDOTjcn / jcnDot));
float bvelDOTjcn = ballB.velocity.dot(jcn);
PVector bVel_jcn = PVector.mult(jcn, (bvelDOTjcn / jcnDot));
float absVel = aVel_vjc.mag() + bVel_vjc.mag();
float overlap = (ballA.radius + ballB.radius) - distance;
PVector aVel_overlap = PVector.mult(ballA.velocity, (overlap / absVel));
PVector bVel_overlap = PVector.mult(ballB.velocity, (overlap / absVel));
ballA.position.sub(aVel_overlap);
ballB.position.sub(bVel_overlap);
float sumMass = ballA.mass + ballB.mass;
float dMass = ballA.mass - ballB.mass;
PVector firstBitA = PVector.mult(aVel_vjc, dMass);
PVector secondBitA = PVector.mult(bVel_vjc, (2*ballB.mass));
PVector topLineA = PVector.add(firstBitA, secondBitA);
PVector newAvjc = PVector.div(topLineA, sumMass);
PVector firstBitB = PVector.mult(bVel_vjc, -dMass);
PVector secondBitB = PVector.mult(aVel_vjc, (2*ballA.mass));
PVector topLineB = PVector.add(firstBitB, secondBitB);
PVector newBvjc = PVector.div(topLineB, sumMass);
ballA.velocity.x = newAvjc.x + aVel_jcn.x;
ballA.velocity.y = newAvjc.y + aVel_jcn.y;
ballB.velocity.x = newBvjc.x + bVel_jcn.x;
ballB.velocity.y = newBvjc.y + bVel_jcn.y;
void doLineCollision(Ball ball, VectorLine vLine)
PVector lineStart = vLine.getOneEnd();
PVector lineEnd = vLine.getOtherEnd();
PVector start2ball = new PVector(ball.position.x - lineStart.x, ball.position.y - lineStart.y);
PVector normalToLine = new PVector(-(lineStart.y - lineEnd.y), lineStart.x - lineEnd.x);
normalToLine.normalize();
PVector theLine = new PVector(lineStart.x - lineEnd.x, lineStart.y - lineEnd.y);
float ballDOTline = start2ball.dot(theLine);
float lineDOTline = theLine.dot(theLine);
PVector ballProjectedOntoLine = PVector.mult(theLine, ballDOTline/lineDOTline);
PVector projectionAddedToStart = PVector.add(lineStart, ballProjectedOntoLine);
float ballDOTnormal = start2ball.dot(normalToLine);
PVector offsetFromLine = PVector.mult(normalToLine, ballDOTnormal);
offsetFromLine.normalize();
offsetFromLine.mult(ball.radius + vLine.lineWidth/2);
ball.position = PVector.add(projectionAddedToStart, offsetFromLine);
PVector incidence = new PVector(-ball.velocity.x, -ball.velocity.y);
float incidenceDOTline = incidence.dot(normalToLine);
PVector temp = PVector.mult(normalToLine, (2*incidenceDOTline));
ball.velocity = PVector.sub(temp, incidence);
void doRingCollision(Ball ball, Ball ring)
float dx = ring.position.x - ball.position.x;
float dy = ring.position.y - ball.position.y;
float distance = sqrt(dx*dx + dy*dy);
if((ring.radius < distance + ball.radius) && (distance < ring.radius + ball.radius))
float angle = atan2(dy, dx);
float xbit = ring.position.x - ring.radius * cos(angle);
float ybit = ring.position.y - ring.radius * sin(angle);
PVector tangentPosition = new PVector(xbit, ybit);
VectorLine tangent = new VectorLine(tangentPosition, 10, 1);
tangent.rotation = angle + HALF_PI;
doLineCollision(ball, tangent);
void drawWedges(color colour, Ball[] balls)
fill(getColour(balls[0]), 128);
translate(width/2, height/2);
for (int w = 0; w < 2*numWedges; w++)
rotate(w * TWO_PI / (2*numWedges));
rotate((w+1) * TWO_PI / (2*numWedges));
fill(getColour(balls[0]), 128);
translate(width/2, height/2);
for (int w = 0; w < numWedges; w++)
rotate(w * TWO_PI/numWedges);
void drawShapes(Ball[] theseBalls)
for (int b = 0; b < numBalls; b++)
curveVertex(theseBalls[b].position.x, theseBalls[b].position.y);
for (int b = 0; b < numBalls; b++)
curveVertex(theseBalls[b].position.x, theseBalls[b].position.y);
color getColour(Ball thisBall)
float greenbit = map(thisBall.position.x, 0, width/2, 0, 255);
float bluebit = abs(map(thisBall.position.y, 0, height/4, -255, 255));
float redbit = map(dist(0, 0, thisBall.position.x, thisBall.position.y), 0, width/2, 255, 0);
return color(redbit, greenbit, bluebit);
void correctBallPositions(float oldAngle)
if (reflect) edges[1].rotation = TWO_PI / (2 * numWedges);
else edges[1].rotation = TWO_PI / numWedges;
float newAngle = edges[1].rotation;
for (int s = 0; s < maxShapes; s++)
for (int b = 0; b < maxBalls; b++)
float distFromCentre = dist(0, 0, balls[s][b].position.x, balls[s][b].position.y);
float ballAngle = atan2(balls[s][b].position.y, balls[s][b].position.x);
float mappedAngle = map(ballAngle, 0, oldAngle, 0, newAngle);
balls[s][b].position.x = distFromCentre * cos(mappedAngle);
balls[s][b].position.y = distFromCentre * sin(mappedAngle);