Javascript Collisions not working

Viewed 91

For some reason my code isn't working. It should be a collision simulation but the balls are just sticking together and I can't seem to figure out why. I have been using the equation from https://en.wikipedia.org/wiki/Elastic_collision#Two-dimensional_collision_with_two_moving_objects and as far as I can see I have copied it perfectly, I have even added brackets to make sure the order of operation is correct, but still no luck

Here is my code:

var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d");

function Ball(){
    this.pos = {
        "x": Math.random() * canvas.width,
        "y": Math.random() * canvas.height
    };
    this.vel = {
        "x": Math.random() - 0.5,
        "y": Math.random() - 0.5
    };
    this.r = 16;
    this.colliding = false;
    this.m = 1;
}

function mag(v){
    return Math.sqrt((v.x * v.x) + (v.y * v.y));
}

function dir(v){
    return Math.atan2(v.y, v.x);
}

function dist(a, b){
    var dx = b.x - a.x,
        dy = b.y - a.y;
    return Math.sqrt(dx * dx + dy * dy);
}

var balls = [];
for(var i = 0; i < 10; i++){
    balls.push(new Ball());
}

setInterval(function(){
    for(var i = 0; i < balls.length; i++){
        balls[i].pos.x += balls[i].vel.x;
        balls[i].pos.y += balls[i].vel.y;

        if(balls[i].pos.x + balls[i].r > canvas.width){
            balls[i].pos.x = canvas.width - balls[i].r;
            balls[i].vel.x *= -1;
        }
        if(balls[i].pos.x < balls[i].r){
            balls[i].pos.x = balls[i].r;
            balls[i].vel.x *= -1;
        }
        if(balls[i].pos.y + balls[i].r > canvas.height){
            balls[i].pos.y = canvas.height - balls[i].r;
            balls[i].vel.y *= -1;
        }
        if(balls[i].pos.y < balls[i].r){
            balls[i].pos.y = balls[i].r;
            balls[i].vel.y *= -1;
        }

        balls[i].colliding = false;
    }

    for(var i = 0; i < balls.length; i++){
        for(var j = i + 1; j < balls.length; j++){
            if(mag(balls[i].vel) < 0){
                break;
            }

            if(dist(balls[i].pos, balls[j].pos) < balls[i].r + balls[j].r){
                balls[i].colliding = true;

                var contact = Math.atan2(balls[j].pos.y - balls[i].pos.y, balls[j].pos.x - balls[i].pos.x);

                balls[i].vel.x = ((((mag(balls[i].vel) * Math.cos(dir(balls[i].vel) - contact) * (balls[i].m - balls[j].m)) + (2 * balls[j].m * mag(balls[j].vel) * Math.cos(dir(balls[j].vel) - contact))) / (balls[i].m + balls[j].m)) * Math.cos(contact)) + (mag(balls[i].vel) * Math.sin(dir(balls[i].vel) - contact) * Math.cos(contact + (Math.PI / 2)));
                balls[i].vel.y = ((((mag(balls[i].vel) * Math.cos(dir(balls[i].vel) - contact) * (balls[i].m - balls[j].m)) + (2 * balls[j].m * mag(balls[j].vel) * Math.cos(dir(balls[j].vel) - contact))) / (balls[i].m + balls[j].m)) * Math.sin(contact)) + (mag(balls[i].vel) * Math.sin(dir(balls[i].vel) - contact) * Math.sin(contact + (Math.PI / 2)));

                balls[j].vel.x = ((((mag(balls[j].vel) * Math.cos(dir(balls[j].vel) - contact) * (balls[j].m - balls[i].m)) + (2 * balls[i].m * mag(balls[i].vel) * Math.cos(dir(balls[i].vel) - contact))) / (balls[j].m + balls[i].m)) * Math.cos(contact)) + (mag(balls[j].vel) * Math.sin(dir(balls[j].vel) - contact) * Math.cos(contact + (Math.PI / 2)));
                balls[j].vel.y = ((((mag(balls[j].vel) * Math.cos(dir(balls[j].vel) - contact) * (balls[j].m - balls[i].m)) + (2 * balls[i].m * mag(balls[i].vel) * Math.cos(dir(balls[i].vel) - contact))) / (balls[j].m + balls[i].m)) * Math.sin(contact)) + (mag(balls[j].vel) * Math.sin(dir(balls[j].vel) - contact) * Math.sin(contact + (Math.PI / 2)));
            }
        }
    }

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for(var i = 0; i < balls.length; i++){
        ctx.beginPath();

        if(balls[i].colliding){
            ctx.fillStyle = "#f00";
        }else{
            ctx.fillStyle = "#0f0";
        }


        ctx.arc(balls[i].pos.x, balls[i].pos.y, balls[i].r, 0, 2 * Math.PI);
        ctx.fill();
    }
}, 16);
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <canvas id="canvas" width="640" height="480"></canvas>
        <script src="main.js"></script>
    </body>
</html>

1 Answers

For starters, I inverted the new velX and velY for ball j on collision, and your collision system seems to be working just fine. However if you look closely, you'll notice them getting stuck like you mentioned, that's because the balls can travel more than one pixel per tick, resulting in the ball traveling inside another, and thus the collisions will keep trying over and over until they are not inside. To prevent this you'll need to adjust the velX and velY if the distance is less than the combined radii.

var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d");

function Ball(){
    this.pos = {
        "x": Math.random() * canvas.width,
        "y": Math.random() * canvas.height
    };
    this.vel = {
        "x": Math.random() - 0.5,
        "y": Math.random() - 0.5
    };
    this.r = 16;
    this.colliding = false;
    this.m = 1;
}

function mag(v){
    return Math.sqrt((v.x * v.x) + (v.y * v.y));
}

function dir(v){
    return Math.atan2(v.y, v.x);
}

function dist(a, b){
    var dx = b.x - a.x,
        dy = b.y - a.y;
    return Math.sqrt(dx * dx + dy * dy);
}

var balls = [];
for(var i = 0; i < 10; i++){
    balls.push(new Ball());
}

setInterval(function(){
    for(var i = 0; i < balls.length; i++){
        balls[i].pos.x += balls[i].vel.x;
        balls[i].pos.y += balls[i].vel.y;

        if(balls[i].pos.x + balls[i].r > canvas.width){
            balls[i].pos.x = canvas.width - balls[i].r;
            balls[i].vel.x *= -1;
        }
        if(balls[i].pos.x < balls[i].r){
            balls[i].pos.x = balls[i].r;
            balls[i].vel.x *= -1;
        }
        if(balls[i].pos.y + balls[i].r > canvas.height){
            balls[i].pos.y = canvas.height - balls[i].r;
            balls[i].vel.y *= -1;
        }
        if(balls[i].pos.y < balls[i].r){
            balls[i].pos.y = balls[i].r;
            balls[i].vel.y *= -1;
        }

        balls[i].colliding = false;
    }

    for(var i = 0; i < balls.length; i++){
        for(var j = i + 1; j < balls.length; j++){
            if(mag(balls[i].vel) < 0){
                break;
            }

            if(dist(balls[i].pos, balls[j].pos) < balls[i].r + balls[j].r){
                balls[i].colliding = true;

                var contact = Math.atan2(balls[j].pos.y - balls[i].pos.y, balls[j].pos.x - balls[i].pos.x);

                balls[i].vel.x = ((((mag(balls[i].vel) * Math.cos(dir(balls[i].vel) - contact) * (balls[i].m - balls[j].m)) + (2 * balls[j].m * mag(balls[j].vel) * Math.cos(dir(balls[j].vel) - contact))) / (balls[i].m + balls[j].m)) * Math.cos(contact)) + (mag(balls[i].vel) * Math.sin(dir(balls[i].vel) - contact) * Math.cos(contact + (Math.PI / 2)));
                balls[i].vel.y = ((((mag(balls[i].vel) * Math.cos(dir(balls[i].vel) - contact) * (balls[i].m - balls[j].m)) + (2 * balls[j].m * mag(balls[j].vel) * Math.cos(dir(balls[j].vel) - contact))) / (balls[i].m + balls[j].m)) * Math.sin(contact)) + (mag(balls[i].vel) * Math.sin(dir(balls[i].vel) - contact) * Math.sin(contact + (Math.PI / 2)));

                balls[j].vel.x = -((((mag(balls[j].vel) * Math.cos(dir(balls[j].vel) - contact) * (balls[j].m - balls[i].m)) + (2 * balls[i].m * mag(balls[i].vel) * Math.cos(dir(balls[i].vel) - contact))) / (balls[j].m + balls[i].m)) * Math.cos(contact)) + (mag(balls[j].vel) * Math.sin(dir(balls[j].vel) - contact) * Math.cos(contact + (Math.PI / 2)));
                balls[j].vel.y = -((((mag(balls[j].vel) * Math.cos(dir(balls[j].vel) - contact) * (balls[j].m - balls[i].m)) + (2 * balls[i].m * mag(balls[i].vel) * Math.cos(dir(balls[i].vel) - contact))) / (balls[j].m + balls[i].m)) * Math.sin(contact)) + (mag(balls[j].vel) * Math.sin(dir(balls[j].vel) - contact) * Math.sin(contact + (Math.PI / 2)));
            }
        }
    }

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for(var i = 0; i < balls.length; i++){
        ctx.beginPath();

        if(balls[i].colliding){
            ctx.fillStyle = "#f00";
        }else{
            ctx.fillStyle = "#0f0";
        }


        ctx.arc(balls[i].pos.x, balls[i].pos.y, balls[i].r, 0, 2 * Math.PI);
        ctx.fill();
    }
}, 16);
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <canvas id="canvas" width="640" height="480"></canvas>
        <script src="main.js"></script>
    </body>
</html>

Related