Ball vs Ball Collision Revisited

I know there are tons of posts dealing with ball vs ball collision and I have looked over any I could find, however none of them seem to work exactly like I want and I have been looking over examples for weeks now.
I have a script I modified from this site
(http://www.krazydad.com/bestiary/bestiary_superball.html)
that is pretty close but there is one problem I have yet to solve. Here is my modified code:
This part just updates the balls position based on their vx and vy variables. It gets called on a timer:


public function moveBalls():void {
 var loopLength=ballArray.length-1;
 for (var count:int=0; count<=loopLength; count++) {
  var currentBall=ballArray[count];
  var currentBallRadius=currentBall.ballRadius;
  currentBall.tempX = currentBall.x;
  currentBall.tempY = currentBall.y;
  currentBall.tempX += currentBall.vx;
  currentBall.tempY += currentBall.vy;
  if (currentBall.tempY-currentBallRadius<topEdge) {
   currentBall.tempY = topEdge+currentBallRadius;
   currentBall.vy *= -1;
  }
  else if (currentBall.tempY+currentBallRadius>bottomEdge) {
   currentBall.tempY = bottomEdge-currentBallRadius;
   currentBall.vy *= -1;
  }
  if (currentBall.tempX-currentBallRadius<leftEdge) {
   currentBall.tempX = leftEdge+currentBallRadius;
   currentBall.vx *= -1;
  }
  else if (currentBall.tempX+currentBallRadius>rightEdge) {
   currentBall.tempX = rightEdge-currentBallRadius;
   currentBall.vx *= -1;
  }
 }
 for (count=0; count<=loopLength; count++) {
  currentBall=ballArray[count];
  for (var subCount:int=count+1; subCount<=loopLength; subCount++) {
   var currentSubBall=ballArray[subCount];
   var xdiff:Number = currentSubBall.tempX-currentBall.tempX;
   var ydiff:Number = currentSubBall.tempY-currentBall.tempY;
   var dist:Number = Math.sqrt(xdiff*xdiff+ydiff*ydiff);
   if ((dist<=(currentBallRadius+currentSubBall.ballRadius)) && (!currentBall.isCollide ||!currentSubBall.isCollide) ) {
    currentBall.isCollide=true;
    currentSubBall.isCollide=true;
    doCollisionNew(currentBall,currentSubBall,dist,xdiff,ydiff);
   }
   else {
    currentBall.isCollide=false;
    currentSubBall.isCollide=false;
   }
  }
 }
 updateBalls();
}

Next we actually update the balls real position:


function updateBalls():void {
 var loopLength=ballArray.length-1;
 for (var count:int=0; count<=loopLength; count++) {
  var currentBall=ballArray[count];
  currentBall.x = currentBall.tempX;
  currentBall.y = currentBall.tempY;
 }
}

Ok, and here is the real part I’m having problems with. It gets called when there is a collision:


function doCollisionNew(b1:BreakerBall, b2:BreakerBall,dist,xdiff,ydiff):void {
 var mag1 = Math.sqrt(b1.vx*b1.vx+b1.vy*b1.vy);
 var mag2 = Math.sqrt(b2.vx*b2.vx+b2.vy*b2.vy);
 if ( dist==0) {
  var angle = Math.atan2(ydiff,xdiff);
  var cosa = Math.cos(angle);
  var sina = Math.sin(angle);
  var diff = ((b1.ballRadius+b2.ballRadius)-dist)/2;
  var cosd = cosa*diff;
  var sind = sina*diff;
  b1.tempX -= cosd;
  b1.tempY -= sind;
  b2.tempX += cosd;
  b2.tempY += sind;
  b2.vx=-b1.vx;
  b2.vy=-b1.vy;
  b1.vx=-b2.vx;
  b1.vy=-b2.vy;
 }
 else {
  b2.vx = (mag1*xdiff/dist);
  b1.vx = -(mag2*xdiff/dist);
  b2.vy = (mag1*ydiff/dist);
  b1.vy = -(mag2*ydiff/dist);
 }
}

Most of it is the same as jbum’s script (http://www.krazydad.com/bestiary/bestiary_superball.html) except I added the part within the if statement ( if (dist==0) {…} ). This is kind of a hack, because I found if the balls are moving fast enough where they end up on top of each other, the vx/vy comes back as NaN and the balls end up stuck in the top left corner of the screen. Probably not the “right” way to do things, but it seems to work ok for my purposes.
Alright, so what I’m looking for is a random number of balls bouncing around the screen and colliding against each other in the simplest way possible. Everything works fine with this code, however if two balls start at the same Y position and travel down and toward each other, they collide wrong. Instead of moving outward from each other and continuing down, instead they just get stuck bouncing back and forth straight on x. Here is an example of this where ball1 starts at x=30, y=50 and ball2 starts at x=230, y=50. The first ball has vx=5, vy=5 and the second ball is vx=-5, vy=5.
http://metalblend.com/ballTest/proto-2a.html
This should work more like this:
http://metalblend.com/ballTest/proto-2b.html
The problem I see is that if the first balls vy is equal to the second balls vy then the variable ydiff is equal to zero. This causes this


b2.vy = (mag1*ydiff/dist);
b1.vy = -(mag2*ydiff/dist);

to set both balls vy to zero, making them ‘stuck’ on the x plane.
I can get it to work right (shown in the second link) using this script for the collision calculation instead:


function doCollision(b1:BreakerBall, b2:BreakerBall, dist:Number, xdiff:Number, ydiff:Number):void {
 var angle = Math.atan2(ydiff,xdiff);
 var cosa = Math.cos(angle);
 var sina = Math.sin(angle);
 var vx1p = cosa*b1.vx+sina*b1.vy;
 var vy1p = cosa*b1.vy-sina*b1.vx;
 var vx2p = cosa*b2.vx+sina*b2.vy;
 var vy2p = cosa*b2.vy-sina*b2.vx;
 var P = vx1p+vx2p;
 var V = vx1p-vx2p;
 vx1p = (P-V)/2;
 vx2p = V+vx1p;
 b1.vx = cosa*vx1p-sina*vy1p;
 b1.vy = cosa*vy1p+sina*vx1p;
 b2.vx = cosa*vx2p-sina*vy2p;
 b2.vy = cosa*vy2p+sina*vx2p;
 var diff = ((b1.ballRadius+b2.ballRadius)-dist)/2;
 var cosd = cosa*diff;
 var sind = sina*diff;
 b1.tempX -= cosd;
 b1.tempY -= sind;
 b2.tempX += cosd;
 b2.tempY += sind;
}

There are a couple problems with using the second script though. One of the main problems, is that I would like all the balls to stay at a constant speed after they collide. This is probably more of a “fake” collision, but that is what I am looking for. With this second function if you start all balls out with the same speed like vx=5,vy=5 for all balls, after bouncing for a bit, some start to move slower and some start to move faster.
Here is an example of this problem (using the more complex script):
http://metalblend.com/ballTest/proto-2c.html
The first script (jbum’s) doesn’t do this (they stay at a constant speed):
http://metalblend.com/ballTest/proto-2d.html
Also this second function has much more calculations to it and I’d rather keep it simple to avoid overhead and frame rate problems when dealing with a lot of balls bouncing. As well, this script is too confusing for me to understand for the most part :).
So…if anyone knows an easy way to make the first script work right, without the problem of getting stuck on the x plane, MUCH APPRECIATED :).
Oh,and the source file’s I’m playing with can be downloaded here:
http://metalblend.com/ballTest/ballTest.zip

Whichever function you choose to use, you can clamp the velocity to a single value by storing the magnitude of the ball’s velocity in a variable. Once you have resolved the collision, multiply the new velocity’s unit vector by the magnitude of the original velocity.


// during initialization, set the ball's magnitude (length)
ball.len = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);

// after collision detection and response

// find new velocity's magnitude
var currentLength:Number = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.by);

// find new velocity's unit vector
var dx = ball.vx / currentLength;
var dy = ball.vy / currentLength;

// set the ball's velocity to the new velocity's unit vector multiplied by the old velocity's magnitude
ball.vx = dx * ball.len;
ball.by = dy * ball.len;

Even with this method, you will still have sticky occurances due to your functions not incorporating a time step over which to test for collisions. After the nearest collision is resolved, you still have some distance your velocity takes the ball before the next collision check, so it could go inside the ball it just hit, or another ball in that distance. So your loop needs to recurse till the entire velocity is used. If you have multiple simultaneous collisions, it gets a bit trickier.

Cool, thanks for the help. I’ll probably still go with the smaller script to reduce overhead and just throw in a hack that doesn’t allow y velocity to be zero. I could actually use your code somewhere else though, so thanks!

Wow, that’s complex… Useful information though, thanks!