Finding collision between two balls

1k Views Asked by At

QUESTION: Given the position, velocity, and acceleration of two balls (Ball#1 and Ball#2). Is there a way to figure out when they will collide?

(The acceleration is constant and caused by friction)

WARNING I'm looking for a function that can take an input of two balls and their properties and then return the time they will collide. I'm not looking for the solution of stepping forward a tiny amount every frame and checking for collision. I want to be able to predict collisions very fast.

WHY?: The purpose is to make a highly efficient and "perfect" 8-ball pool physics engine.

NOTE: I already have a collision response system in place. Calculate x/y point that 2 moving balls will collide. However this method breaks down whenever friction is involved.

WHAT I'VE TRIED: I've tried to solve this problem myself and I will do my best to explain my method below.

To start, I took some help from the kinematic equations and created a function that gives me the position of the balls at any specified time. f(t)=ball position at time t

Then I put the functions for the positions of each of the two balls into the distance formula. The result is a new function that can give me the distance between the balls at any specified time. d(t)=distance between balls

The next step would be to check when the distance is 2r.

so, d(t)=2r

However, The function d(t) is not a simple function. It's a quartic function. d(t) = at^4 + bt^3 + ct^2 + dt + e). This means that I would have to solve a quartic equation to find the point of collision.

I know how to solve quartic functions, but I would rather not. (my program (Scratch) has no way of dealing with complex numbers)

Welp, that's it. I'll be here if you have any questions.

1

There are 1 best solutions below

3
Justin On

If I'm understanding correctly you are just looking for how to implement friction into your physics engine. If you are solely looking for "when" they will collide then maybe this guys CodePen will help, otherwise continue on.

Like you said calculate the distance between two balls by getting the distance. If the distance is <= 2r then call a response function. In the response function get the vector between the two balls and normalize it. Calculate the relative speed of the balls and then set their x and y velocity accordingly. In the update function of the class I am multiplying the velocity by 0.993 which is just a number I picked and liked. You can change it to change how much friction there appears to be.

This example take mass into account if you feel you want to use it. For this example the mass is the same for all balls so it's really unnecessary but you may choose to make the cueball a different mass so I added it. This example only shoots the cueball in one direction to keep the code small.

Check out this wiki on Impulse Wikipedia Impulse and here's as good video on 2d collision using it YouTube

Just click anywhere on the canvas

let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 700;
let billiardBalls = [];

class Ball {
  constructor(x, y, color) {
    this.x = x;
    this.y = y;
    this.color = color;
    this.r = 10;
    this.vx = 0;
    this.vy = 0;
    this.mass = this.r * 0.25; //using 1/4th of the radius as mass
    this.speed = 0.5;
  }
  draw() {
    ctx.beginPath();
    ctx.fillStyle = this.color;
    ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
    ctx.fill();
  }
  update() {
    this.x += this.vx;
    this.y += this.vy;
    this.vx *= 0.993; //change to alter friction amount
    this.vy *= 0.993; //change to alter friction amount
    this.canvasCollision();
  }
  canvasCollision() {
    if (this.x - this.r <= 0 || this.x + this.r >= canvas.width) {
      this.vx *= -1;
    }
    if (this.y - this.r <= 0 || this.y + this.r >= canvas.height) {
      this.vy *= -1;
    }
  }
}

function createBalls() {
  for (let i = 0; i < 5; i++) {
    billiardBalls.push(new Ball(canvas.width / 2 + 40 - i * 20, 100, "black"));
  }
  for (let i = 0; i < 4; i++) {
    billiardBalls.push(new Ball(canvas.width / 2 + 30 - i * 20, 118, "black"));
  }
  for (let i = 0; i < 3; i++) {
    billiardBalls.push(new Ball(canvas.width / 2 + 20 - i * 20, 136, "black"));
  }
  for (let i = 0; i < 2; i++) {
    billiardBalls.push(new Ball(canvas.width / 2 + 10 - i * 20, 154, "black"));
  }
  billiardBalls.push(new Ball(canvas.width / 2, 172, "black"));
}
createBalls();

let cueBall = new Ball(canvas.width / 2, 450, "white");

function collisionDetection(obj1, obj2) {
  let dist = Math.hypot(obj1.x - obj2.x, obj1.y - obj2.y);
  //if collision is detected send it to the response function
  if (dist <= obj1.r + obj2.r) {
    collisionResponse(obj1, obj2, dist);
  }
}

function collisionResponse(obj1, obj2, dist) {
  //get the vector of the angle the balls collided and normalize it
  let vec = { x: obj2.x - obj1.x, y: obj2.y - obj1.y };
  let vecNorm = {
    x: vec.x / dist,
    y: vec.y / dist
  };
  //get the relative velocity between the balls
  let vecRelVelocity = { x: obj1.vx - obj2.vx, y: obj1.vy - obj2.vy };
  //calc speed after hit
  let speed = vecRelVelocity.x * vecNorm.x + vecRelVelocity.y * vecNorm.y;
  if (speed < 0) {
    return;
  }
  //change vx and vy for each object
  let J = (2 * speed) / (obj1.mass + obj2.mass);
  obj1.vx -= J * obj2.mass * vecNorm.x;
  obj1.vy -= J * obj2.mass * vecNorm.y;
  obj2.vx += J * obj1.mass * vecNorm.x;
  obj2.vy += J * obj1.mass * vecNorm.y;
}

canvas.addEventListener("click", (e) => {
  cueBall.vy -= 12; //change this to see the difference in how hard it hits
  //too fast can cause issues with collision detection
  //this is only for this simple example
});

function animate() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = "green";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  cueBall.draw();
  cueBall.update();
  for (let i = 0; i < billiardBalls.length; i++) {
    billiardBalls[i].draw();
    billiardBalls[i].update();
    collisionDetection(cueBall, billiardBalls[i]);
    for (let j = i + 1; j < billiardBalls.length; j++) {
      collisionDetection(billiardBalls[i], billiardBalls[j]);
    }
  }
  requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>