class Simulation {
constructor(width, height) {
this.width = width;
this.height = height;
this.balls = [];
}
addBall(radius, fill = "#444444") {
const diameter = radius * 2;
const minX = diameter;
const minY = diameter;
const maxX = this.width - diameter;
const maxY = this.height - diameter;
const x = Math.random() * (maxX - minX) + minX;
const y = Math.random() * (maxY - minY) + minY;
const direction = Math.random() * Math.PI * 2;
this.balls.push(new Ball(x, y, radius, direction, ballSpeed, fill));
}
update() {
for (const ball of this.balls) {
ball.move();
}
}
testForWalls() {
for (const ball of this.balls) {
if (ball.x + ball.radius > this.width) {
ball.dx *= -1;
ball.x = this.width - ball.radius;
continue;
}
// too far left
if (ball.x - ball.radius < 0) {
ball.dx *= -1;
ball.x = ball.radius;
continue;
}
// too far down
if (ball.y + ball.radius > this.height) {
ball.dy *= -1;
ball.y = this.height - ball.radius;
continue;
}
// too far up
if (ball.y - ball.radius < 0) {
ball.dy *= -1;
ball.y = ball.radius;
}
}
}
checkForCollisionsIndexed() {
// create our new index
const index = new Flatbush(this.balls.length);
// add them
for (const ball of this.balls) {
index.add(...ball.bbox);
}
// prep the index
index.finish();
for (const first of this.balls) {
const collisions = index.search(...first.bbox);
for (const idx of collisions) {
const second = this.balls[idx];
if (first === second) continue;
if (first.isCollidingWith(second)) {
this.collide(first, second);
}
}
}
}
checkForCollisions() {
// this could be much more efficient, but you need ~1,000+ balls for it
// to start to matter, so I kept it simple
for (const first of this.balls) {
for (const second of this.balls) {
// skip if same ball
if (first === second) continue;
if (first.isCollidingWith(second)) {
this.collide(first, second);
}
}
}
}
collide(first, second) {
// first we determine if they're moving toward each other
// this allows overlaps going in opposite directions to "resolve"
// and not stick to each other
const dx = first.x - second.x;
const dy = first.y - second.y;
const vx = second.dx - first.dx;
const vy = second.dy - first.dy;
const dot = dx * vx + dy * vy;
// if going toward each other, bounce 'em
if (dot > 0) {
const cr = first.mass + second.mass;
const dr = first.mass - second.mass;
// determine our new velocities
const vx1 = (first.dx * dr + 2 * second.mass * second.dx) / cr;
const vy1 = (first.dy * dr + 2 * second.mass * second.dy) / cr;
const vx2 = (second.dx * -dr + 2 * first.mass * first.dx) / cr;
const vy2 = (second.dy * -dr + 2 * first.mass * first.dy) / cr;
// set them
first.dx = vx1;
first.dy = vy1;
second.dx = vx2;
second.dy = vy2;
// trigger a move for both so they stop overlapping immediately
first.move();
second.move();
}
}
render(context) {
for (const ball of this.balls) {
context.beginPath();
context.fillStyle = ball.fill;
context.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
context.fill();
context.closePath();
}
}
}