Reputation: 717
I'm having this problem while trying to detect collisions between two balls when one (or both) have very high velocity. I guess this a very common issue and I understand why it happens. My guess is that the solution will have to do with derivatives, I have already designed something but I don't want to "re-invent the wheel" if there is a known solution.
Anything that may enlight my path will be apretiated!
I did this very simple example. If both balls have a speed of, lets say, 1.5 or 3, they will collide. If we use something very higher, like 50, it will fail.
<html>
<head>
<title>Collision test</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const width = window.innerWidth;
const height = window.innerHeight-4;
const center = { x: width/2, y: height/2 };
canvas.width = width;
canvas.height = height;
// Ball Class Definition
class Ball {
constructor(x, y, mass, direction, speed, color) {
this.x = x;
this.y = y;
this.vx = (Math.cos(direction) * speed) || 0;
this.vy = (Math.sin(direction) * speed) || 0;
this.mass = mass || 1;
this.radius = mass * 3;
this.color = color || "#000000";
}
update() {
this.x += this.vx;
this.y += this.vy;
}
}
let speedA = 1.5;
let speedB = 1;
// Create two balls that will collide
let ballA = new Ball(center.x - 300, center.y, 3, Math.PI*2, speedA, "green");
let ballB = new Ball(center.x + 100, center.y, 2.2, Math.PI, speedB, "green");
// Main update/draw function
function draw() {
window.requestAnimationFrame(draw);
ctx.clearRect(0,0, width, height);
ballA.update();
ballB.update();
handleCollisions(ballA, ballB);
// Draw Ball A
ctx.beginPath();
ctx.arc(ballA.x, ballA.y, ballA.radius, 0, Math.PI * 2, false);
ctx.fillStyle = ballA.color;
ctx.fill();
// Draw Ball B
ctx.beginPath();
ctx.arc(ballB.x, ballB.y, ballB.radius, 0, Math.PI * 2, false);
ctx.fillStyle = ballB.color;
ctx.fill();
}
// Detect and handle collision
function handleCollisions(p1, p2) {
let xDist, yDist;
xDist = p1.x - p2.x;
yDist = p1.y - p2.y;
let distSquared = xDist*xDist + yDist*yDist;
//Check the squared distances instead of the the distances, same result, but avoids a square root.
if(distSquared <= (p1.radius + p2.radius)*(p1.radius + p2.radius)){
let xVelocity = p2.vx - p1.vx;
let yVelocity = p2.vy - p1.vy;
let dotProduct = xDist*xVelocity + yDist*yVelocity;
//Neat vector maths, used for checking if the objects moves towards one another.
if(dotProduct > 0){
let collisionScale = dotProduct / distSquared;
let xCollision = xDist * collisionScale;
let yCollision = yDist * collisionScale;
//The Collision vector is the speed difference projected on the Dist vector,
//thus it is the component of the speed difference needed for the collision.
let combinedMass = p1.mass + p2.mass;
let collisionWeightA = 2 * p2.mass / combinedMass;
let collisionWeightB = 2 * p1.mass / combinedMass;
p1.vx += collisionWeightA * xCollision;
p1.vy += collisionWeightA * yCollision;
p2.vx -= collisionWeightB * xCollision;
p2.vy -= collisionWeightB * yCollision;
}
}
}
draw();
</script>
</body>
</html>
I added this code to JSBin.
Upvotes: 2
Views: 1349
Reputation: 3473
You seem to understand the problem: the discrete sampling of your scene makes high speed projectiles go through or miss objects they should hit.
The naive way to solve this is to increase your frame rate. This becomes unfeasible in the end, because the faster the projectile, the more frames you need to make. Not recommended. What you need is some continuous collision detection method. For example Bullet uses this, and does not experience the problem you have.
The simplest thing I can think of is to create a cylinder from the current_position
to the last_position
with the same radius as the ball. Then, I would check for collisions with these cylinders. This would solve easy cases where a projectile hits a stationary target. For moving targets, you could start with the same thing as above (that is, you would compare cylinders with cylinders). In case of a collision, you need to recalculate the position of the collision objects at time of collision, and then see if they actually hit or not.
Upvotes: 2
Reputation: 299
The problem is that the collision detection is done using the distance between the locations of the balls, but if they are moving too fast, then they might "hop" right over each other and never be near enough for a collision to be detected.
One solution might be to calculate points along this hop, and their respective times, for each ball. Then, compare the list of points for each ball, and see if there is a time when the balls are close enough for a collision. In other words, interpolate their positions in between frames and check for collision at these interpolated positions. You have to be careful though because even though the balls might pass through a point close enough for collision, they need to do so at around the same time.
I am sure that there exists javascript frameworks or libraries specifically for games and physics if you do not want to take this on by yourself. I have not ever dealt with them, but Google should know.
Upvotes: 2