The preceding chapters leads us to the following sketch of balls bouncing off each other very realistically.
And the whole sketch takes exactly 100 lines of code1.
const SKETCH_WIDTH = 600;
const SKETCH_HEIGHT = 300;
const NUM_BALLS = 8;
const BALLS = [];
function wrap(n, min, max) {
var divisor = max - min;
n = n - min;
var modulo = ((n % divisor) + divisor) % divisor;
return modulo + min;
}
function areColliding(ballA, ballB) {
if (ballA.pos.dist(ballB.pos) < ballA.r + ballB.r) return true;
return false;
}
function elastic_collision(m, n, v) {
// m = mass1, n = mass2, v = velocity1. Velocity2 MUST BE 0
var x = v * (m - n) / (m + n);
var y = v + x;
return [x, y];
}
function handleCollision(ball1, ball2) {
// Change to a new FoR in which ball2 is at rest
var vel_adj = ball2.vel.copy();
ball1.vel.sub(vel_adj);
ball2.vel.sub(vel_adj);
// Now change to a new FoR in which ball2 is at the origin
var pos_adj = ball2.pos.copy();
ball1.pos.sub(pos_adj);
ball2.pos.sub(pos_adj);
// Rotate frame of reference so the point of collision is on x axis and > 0
var angle_adj = ball1.pos.heading();
ball1.pos.rotate(-angle_adj);
ball1.vel.rotate(-angle_adj);
// Handle the collision in the new FoR
let [v1f, v2f] = elastic_collision(ball1.mass, ball2.mass, ball1.vel.x);
ball1.vel.x = v1f;
ball2.vel.x = v2f;
// Remove overlap
ball1.pos.x = ball2.r + ball1.r + 0.01;
// Switch back to the original FoR
ball1.pos.rotate(angle_adj);
ball1.vel.rotate(angle_adj);
ball2.vel.rotate(angle_adj);
ball1.pos.add(pos_adj);
ball2.pos.add(pos_adj);
ball1.vel.add(vel_adj);
ball2.vel.add(vel_adj);
}
class Ball {
constructor(id, x, y, vx, vy, r) {
this.pos = createVector(x, y);
this.vel = createVector(vx, vy);
this.r = r;
this.id = id;
this.mass = PI * sq(r);
this.color = "grey";
}
energy() {
return 0.5 * this.mass * sq(this.vel.mag());
}
momentum() {
return p5.Vector.mult(this.vel, this.mass/100);
}
update() {
this.pos.add(this.vel);
var x = wrap(this.pos.x, -this.r, width+this.r);
var y = wrap(this.pos.y, -this.r, height+this.r);
this.pos.set(x, y);
}
show() {
push();
fill(this.color);
strokeWeight(2);
circle(this.pos.x, this.pos.y, this.r * 2);
pop();
}
}
function restart() {
for (let i = 0; i < NUM_BALLS; i++)
BALLS[i] = new Ball(i, i*40, i*40, random(-3,3), random(-1,1), random(15,30));
}
function setup() {
can = createCanvas(SKETCH_WIDTH, SKETCH_HEIGHT);
restart();
}
function draw() {
fill("white");
rect(0,0,width,height);
// Move every ball
for (let ball of BALLS)
ball.update();
// Check for and handle collisions
for (let i = 0; i < BALLS.length; i++)
for (let j = i+1; j < BALLS.length; j++)
if (areColliding(BALLS[i], BALLS[j]))
handleCollision(BALLS[i], BALLS[j]);
// Draw every ball
for (let ball of BALLS)
ball.show();
}
At this point we have achieved what we set out to do, but there are many questions I have left unanswered, such as:
- Inelastic collisions
- Collisions in three dimensions
- Optimising the code
- Collisions between other shapes
In the next chapters of this site I will tackle some of these topics, but we’ve achieved a lot so far with just a few simple functions and an understanding of the laws of conservation of energy and momentum, and the idea of reference frames.
- The JavaScript police will probably object to my omission of { } in some of the for and if blocks. It may not be best practice and I think I can make an argument for doing it, but here is not the place :-). ↩︎