Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

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();
    }
}
share|improve this question
    
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)). –  akled Jul 8 '12 at 13:27
    
@Bane this didn't changed anything. Also, condition should be <= not >=. Thanks anyway –  Kushagra Jul 8 '12 at 13:35
add comment

4 Answers

up vote 4 down vote accepted

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;
  }
}
share|improve this answer
    
Thanks for this! This made my collisions much better. But the only problem I was having is sill there i.e overlapping of the balls. I don't know how to prevent the balls from overlapping each other. You can check it here cssdeck.com/t/view/dcebfocu/8 –  Kushagra Jul 8 '12 at 16:31
    
when you detect a collision of the balls, move the balls 1 pixel away from the boundary of your main ball after changing the velocities to bounce it back. If you dont do this, the collission will be detected in the next frame which will bounce the other ball inside the first ball –  Arsalan00 Jul 8 '12 at 16:44
    
I solved this problem using this var absV = Math.abs(vel0.x) + Math.abs(vel1.x), overlap = (ball0.rad + ball1.rad) - Math.abs(pos0.x - pos1.x); pos0.x += vel0.x / absV * overlap; pos1.x += vel1.x / absV * overlap; where absV is the absolute velocity of one ball with respect to the other ball –  Arsalan00 Jul 8 '12 at 16:49
    
Well, that don't work for large number of balls but it works for a low number of balls. Check here. I think the problem is the addition of balls on the canvas. They are added randomly and hence some of them already overlap each other at the time of their addition. Is there any way I can prevent this? –  Kushagra Jul 8 '12 at 17:11
    
that should work for large number of balls, and will also solve the second problem. Using the code I provided in the comment, the new balls, if they overlap during creation, will automatically be seperated in the next frame. For large number of balls and cases where multiple balls overlap, this might take a few frames to seperate the balls –  Arsalan00 Jul 8 '12 at 17:18
show 5 more comments

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.

share|improve this answer
    
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. –  Kushagra Jul 8 '12 at 13:37
add comment

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.

share|improve this answer
add comment

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.

share|improve this answer
    
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. –  alexpinho98 Mar 28 '13 at 10:55
    
Hi alex, you can click on the edit link at the bottom of your answer to complement it as needed. –  petervaz Mar 28 '13 at 13:55
    
What do you mean by complementing? –  alexpinho98 Mar 30 '13 at 1:04
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.