6
\$\begingroup\$

I am doing a simple experiment in canvas using Javascript in which some balls will be thrown on the screen with some initial velocity and then they will bounce on colliding with each other or with the walls.

I managed to do the collision with walls perfectly but now the problem is with the collision with other balls. I am using the following code for it:

//Check collision between two bodies
function collides(b1, b2) {
    //Find the distance between their mid-points
    var dx = b1.x - b2.x,
        dy = b1.y - b2.y,
        dist = Math.round(Math.sqrt(dx*dx + dy*dy));

    //Check if it is a collision
    if(dist <= (b1.r + b2.r)) {
        //Calculate the angles 
        var angle = Math.atan2(dy, dx),
            sin = Math.sin(angle),
            cos = Math.cos(angle);

        //Calculate the old velocity components
        var v1x = b1.vx * cos,
            v2x = b2.vx * cos,
            v1y = b1.vy * sin,
            v2y = b2.vy * sin;

        //Calculate the new velocity components
        var vel1x = ((b1.m - b2.m) / (b1.m + b2.m)) * v1x + (2 * b2.m / (b1.m + b2.m)) * v2x,
            vel2x = (2 * b1.m / (b1.m + b2.m)) * v1x + ((b2.m - b1.m) / (b2.m + b1.m)) * v2x,
            vel1y = v1y,
            vel2y = v2y;

        //Set the new velocities
        b1.vx = vel1x;
        b2.vx = vel2x;
        b1.vy = vel1y;
        b2.vy = vel2y;
    }
}

You can see the experiment here. The problem is, some balls overlap each other and stick together while some of them rebound perfectly. I don't know what is causing this issue. Here's my balls object if that matters:

function Ball() {
    //Random Positions
    this.x = 50 + Math.random() * W;
    this.y = 50 + Math.random() * H;

    //Random radii
    this.r = 15 + Math.random() * 30;
    this.m = this.r;

    //Random velocity components 
    this.vx = 1 + Math.random() * 4;
    this.vy = 1 + Math.random() * 4;

    //Random shade of grey color
    this.c = Math.round(Math.random() * 200);

    this.draw = function() {
        ctx.beginPath();
        ctx.fillStyle = "rgb(" + this.c + ", " + this.c + ", " + this.c + ")";
        ctx.arc(this.x, this.y, this.r, 0, Math.PI*2, false);
        ctx.fill();
        ctx.closePath();
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ You don't need to use the square root to calculate if they are colliding. It's very inefficient, just do this: if (dx*dx + dy*dy >= (r1 + r2)*(r1 + r2)). \$\endgroup\$
    – jcora
    Jul 8, 2012 at 13:27
  • \$\begingroup\$ @Bane this didn't changed anything. Also, condition should be <= not >=. Thanks anyway \$\endgroup\$
    – Kushagra
    Jul 8, 2012 at 13:35

4 Answers 4

5
\$\begingroup\$

I was working on something similar, in which balls will be colliding and rebounding at different angles and velocities. I found the solution in a book for ActionScript animation. Explaining it will take a lot of time and effort and honestly I cant really explain this since I've myself forgotten how most of this works. Please try your best to understand what this code is doing and if you need further assistance, ask me and I'll try my best to explain.

function rotate(x, y, sin, cos, reverse) {
    return {
        x: (reverse) ? (x * cos + y * sin) : (x * cos - y * sin),
        y: (reverse) ? (y * cos - x * sin) : (y * cos + x * sin)
    };
}

function checkCollision (ball0, ball1) {
  var dx = ball1.x – ball0.x,
      dy = ball1.y – ball0.y,
      dist = Math.sqrt(dx * dx + dy * dy);

  //collision handling code here
  if (dist < ball0.radius + ball1.radius) {
    //calculate angle, sine, and cosine
    var angle = Math.atan2(dy, dx),
        sin = Math.sin(angle),
        cos = Math.cos(angle), 

        //rotate ball0's position
        pos0 = {x: 0, y: 0}, //point

        //rotate ball1's position
        pos1 = rotate(dx, dy, sin, cos, true),

        //rotate ball0's velocity
        vel0 = rotate(ball0.vx, ball0.vy, sin, cos, true),

        //rotate ball1's velocity
        vel1 = rotate(ball1.vx, ball1.vy, sin, cos, true),

        //collision reaction
        vxTotal = vel0.x – vel1.x;
    vel0.x = ((ball0.mass - ball1.mass) * vel0.x + 2 * ball1.mass * vel1.x) /
             (ball0.mass + ball1.mass);
    vel1.x = vxTotal + vel0.x;

    //update position
    pos0.x += vel0.x;
    pos1.x += vel1.x;

    //rotate positions back
    var pos0F = rotate(pos0.x, pos0.y, sin, cos, false),
        pos1F = rotate(pos1.x, pos1.y, sin, cos, false);

    //adjust positions to actual screen positions
    ball1.x = ball0.x + pos1F.x;
    ball1.y = ball0.y + pos1F.y;
    ball0.x = ball0.x + pos0F.x;
    ball0.y = ball0.y + pos0F.y;

    //rotate velocities back
    var vel0F = rotate(vel0.x, vel0.y, sin, cos, false),
        vel1F = rotate(vel1.x, vel1.y, sin, cos, false);
    ball0.vx = vel0F.x;
    ball0.vy = vel0F.y;
    ball1.vx = vel1F.x;
    ball1.vy = vel1F.y;
  }
}
\$\endgroup\$
0
1
\$\begingroup\$

I believe your answer is due to the fact your circles are still in contact with each other and that the last line of the first function you post continues to update their positions to positions that are within the boundaries of each other. This will cause the collision routine to continue to keep them stuck where they are. At slower speeds the first function updates their velocity and position away from each other allowing them to move away as their positions no longer overlap.

\$\endgroup\$
1
  • \$\begingroup\$ I don't understand this, the last lines of the function just sets the new velocity vectors of the balls so that they can move in new directions. \$\endgroup\$
    – Kushagra
    Jul 8, 2012 at 13:37
0
\$\begingroup\$

A simple solution that I have implemented before is when you detect a collision between objects do some simple math to pull the apart from one another before doing the collision calculation. So if two spheres collide, separate them by the sum of their radii and by the angle they collided then do your calculation.

\$\endgroup\$
0
\$\begingroup\$

To resolve the colision i made a Vector class with some methods like: 'add()', 'sub()', 'scale()', 'get_angle()', 'get_length()', 'set_angle()', 'set_length()' and 'norm_reflect()'.

The last one might be a little tricky so i'll show you how i did it:

def norm_reflect(self, vec1):

    if vec1.get_angle() >= 0:    # if angle of vector is positive then add 90 to it

        return self.set_angle(vec1.get_angle() - self.get_angle() + 90)

    else:                        # else, the angle is negative so you subtract 90 from it

        return self.set_angle(vec1.get_angle() - self.get_angle() - 90)

To do the collision now its just simple:

1- See if they collide

2- If they do calculate how much you need to move them so they don't collide

dist.set_length(- dist.get_length() + total_radius).scale(0.5) 

3- Then move one circle one way and the other the other way

4- Then make the velocities like this:

b1.speed = b2.speed.add(b1.speed.norm_reflect(dist.set_angle(dist.get_angle() + 90)))
b2.speed = b1.speed.add(b2.speed.norm_reflect(dist.set_angle(dist.get_angle() + 90)))

PS: You can add things like elasticity to make it more realistic, i didn't put them here because it would get to messy.

\$\endgroup\$
3
  • \$\begingroup\$ Sorry i just realised that in step 4 you need to add this to each line: '.scale(0.5)'. It does the average of the two speeds. \$\endgroup\$ Mar 28, 2013 at 10:55
  • 1
    \$\begingroup\$ Hi alex, you can click on the edit link at the bottom of your answer to complement it as needed. \$\endgroup\$
    – petervaz
    Mar 28, 2013 at 13:55
  • \$\begingroup\$ What do you mean by complementing? \$\endgroup\$ Mar 30, 2013 at 1:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .