2d ball collision

Associate
Joined
2 Aug 2004
Posts
564
Location
Adelaide
I've written an objective-c routine that works well with ball-ball collisions. However, having an issue with ball-rectangle collisions, i.e. a simple barrier (not external walls).

The detection is simple, but the what happens after detection is causing the problem. A bounce off the horizontal or vertical surfaces are handled fine but when the collision occurs near the corners the ball becomes static and won't move.

In pseudo code it goes like this:

Code:
for (balls in ballarray)
     if ball intersects barrier
          reverse velocity vector
          move back so not touching 
          reverse velocity vector (return to original vector)
    
          if ball is hitting horizontal surface
               reverse y of velocity vector
          else
               reverse x of velocity vector
     end
end

The first part handles the situation where a ball is moving fast enough to enter the rectangle before the collision check happens by moving the ball back in time to a point just before it collided. The second part performs a simple test to check if the ball is striking a the horizontal or vertical side of the rectangle and then changing the x or y component of the velocity vector as necessary.

Here is the method:

Code:
for (Ball *aBall in ballArray){
        if (CGRectIntersectsRect(aBall.getRect, barrier.position))
        {
            //move ball back until not touching barrier
            aBall.velocity = CGPointMake(aBall.velocity.x*-1.0f, aBall.velocity.y*-1.0f);
            while (CGRectIntersectsRect(barrier.position,aBall.getRect)) {
                [aBall simpleUpdate];
            }
            //return velocity of ball to previous value
            aBall.velocity = CGPointMake(aBall.velocity.x*-1.0f, aBall.velocity.y*-1.0f);
            
            //cx is midpoint of barrier on x axis
            NSInteger cx = barrier.position.origin.x+(barrier.position.size.width/2);
            if (abs(aBall.position.x-cx)<(barrier.position.size.width/2)){
                //must be on horzontal surface
                aBall.velocity = CGPointMake(aBall.velocity.x, aBall.velocity.y*-0.9f);
            } else {
                //must be vertical surface
                aBall.velocity = CGPointMake(aBall.velocity.x*-0.9f, aBall.velocity.y);
            }
        //}
        }
        NSLog(@"Velocity x=%f y=%f",aBall.velocity.x, aBall.velocity.y);

Any ideas?
 
Hmm, so when it hits the corner it's velocity is / should be zero in theory?

Code:
// After we check to see if it's hit a horizontal or vertical surface,
// check to see if it's got stuck....
if (aBall.velocity == Vector.Zero) // Or whatever compares it to (0.0f, 0.0f).
{
    // It's got stuck....
    // Apply some random movement code or maybe last know velocity here.
}



Actually, maybe something else to try is to make it so it can handle corners in theory... I think, xD

Code:
    // cx is midpoint of barrier on x axis
    NSInteger cx = barrier.position.origin.x+(barrier.position.size.width/2);
    if (abs(aBall.position.x-cx)<(barrier.position.size.width/2))
    {
        // Hit horizontal...
        aBall.velocity = CGPointMake(aBall.velocity.x, aBall.velocity.y*-0.9f);
    } 

    // cy is midpoint of barrier on y axis
    NSInteger cy = barrier.position.origin.y+(barrier.position.size.height/2);
    if (abs(aBall.position.y - cy) < (barrier.position.size.height / 2))
    {
        //must be vertical surface
        aBall.velocity = CGPointMake(aBall.velocity.x*-0.9f, aBall.velocity.y);
     }

I can't work out why it's sticking.... but the above code simply changes the else to an if so it checks both sides so in theory, if it's near a corner or such... it should reverse the velocity in both axis giving an angle direction. No idea if any of that would work for your problem, but it's stuff I'd try out.
 
Thanks for that but still the same. If I remove the lines that backtrack the ball out of the rectangle the problem goes but that creates its own problem as fast moving balls can end up stuck inside the rectangle.
 
Ah ok, erm only thing I can think of... and no idea if it'd make that much of a difference as I don't know your code base, but

First call is,
Code:
CGRectIntersectsRect(aBall.getRect, barrier.position)

and the second is,
Code:
CGRectIntersectsRect(barrier.position,aBall.getRect)

Although I assume that you have multiple arguments for the same function etc xD, just seems odd to me that they've swapped for essentially the same functionality.
 
I had changed round the first and second objects being passed to the CGRectIntersectsRect method to see if it made a difference and it didn't. It returns true or false if there is an intersection and the order does not appear to matter.

The ball-ball collision management is a lot more complex but works, really annoying that the ball-rectangle collision, that should be straight forward, is giving me problems.
 
Solved.

As I thought, it was simple but as is the norm I couldn't see it and needed a nights sleep for it to become clear.

I was being lazy and treating the rebound from a rectangle as either a vertical or horizontal collision.

rebound.jpg


This worked fine except for when a ball came close to the corner. At that point it would stop and not behave as it should. I tried all sorts of things to work round this rather than tackle the obvious problem.

I have now split the rectangle into three distinct rebound zones, horizontal, vertical and corner.

rebounda.jpg


If a ball is in the a horizontal rebound zone the y component of the velocity is reversed. If in the vertical zone the x component is reversed. Finally if at a corner both the x and y components are reversed.

Very obvious now but sometimes when you are looking at a problem for too long you don't see the obvious.

Here is the code. The tests for the corner could be brought into a single if but for clarity I have kept them seperate.

Code:
for (Ball *aBall in ballArray){
        for (Barrier *aBarrier in barrierArray){
            if (CGRectIntersectsRect(aBall.getRect, aBarrier.position))
            {
                //move ball back until not touching barrier
                aBall.velocity = CGPointMake(aBall.velocity.x*-1.0f, aBall.velocity.y*-1.0f);
                while (CGRectIntersectsRect(aBarrier.position,aBall.getRect)) {
                    [aBall simpleUpdate];
                }
                //return velocity of ball to previous value
                aBall.velocity = CGPointMake(aBall.velocity.x*-1.0f, aBall.velocity.y*-1.0f);
                
                //work out the bounds of the barrier
                CGPoint tl = CGPointMake(aBarrier.position.origin.x, aBarrier.position.origin.y);
                CGPoint tr = CGPointMake(aBarrier.position.origin.x + aBarrier.position.size.width, aBarrier.position.origin.y);
                CGPoint bl = CGPointMake(aBarrier.position.origin.x, aBarrier.position.origin.y + aBarrier.position.size.height);
                CGPoint br = CGPointMake(aBarrier.position.origin.x + aBarrier.position.size.width, aBarrier.position.origin.y + aBarrier.position.size.height);
                
                if ((aBall.position.y>tl.y)&&(aBall.position.y<br.y)) {
                    //this has been a vertical collision
                    aBall.velocity = CGPointMake(aBall.velocity.x*-0.9f, aBall.velocity.y);
                }
                if ((aBall.position.x>tl.x)&&(aBall.position.x<tr.x)) {
                    //this has been a horizontal collision
                    aBall.velocity = CGPointMake(aBall.velocity.x, aBall.velocity.y*-0.9f);
                }
                if ((aBall.position.x<tl.x)&&(aBall.position.y<tl.y)) {
                    //collision at top left corner
                    aBall.velocity = CGPointMake(aBall.velocity.x*-0.9f, aBall.velocity.y*-0.9f);
                }
                if ((aBall.position.x<bl.x)&&(aBall.position.y>bl.y)) {
                    //collision at bottom left corner
                    aBall.velocity = CGPointMake(aBall.velocity.x*-0.9f, aBall.velocity.y*-0.9f);
                }
                if ((aBall.position.x>br.x)&&(aBall.position.y>br.y)) {
                    //collision at bottom right corner
                    aBall.velocity = CGPointMake(aBall.velocity.x*-0.9f, aBall.velocity.y*-0.9f);
                }
                if ((aBall.position.x>tr.x)&&(aBall.position.y<tr.y)) {
                    //collision at top right corner
                    aBall.velocity = CGPointMake(aBall.velocity.x*-0.9f, aBall.velocity.y*-0.9f);
                }
                            
            }
        }
    }
 
That sounds like more of a workaround, there's no inherent issue with using only 2 surfaces.

I think the problem is multiple collisions occurring in a single frame and those collisions being handled in the wrong order. For example, the ball collides with the top and then the left, but the collision with the left is handled before the collision with the top.
 
I don't think it is a workaround. The ball should only be in contact with one surface for any given collision, it's not like the situation where the ball is inside the rectangle and therefore could strike a vertical and horizontal surface at the same time.

Splitting the rebound zone for the rectangle into 3 zones rather than 2 seems to be logical as there are 3 different outcomes possible, i.e. a change in the velocity vector x component, change in the velocity vector y component and change in both the x and y of the velocity vector.

Still open to a different strategy if you have one.
 
Ah, I see, your first image made me think the ball was inside, though you had said otherwise. You're right that the corner does need to be handled separately.

I think there is still a small issue; you seem to be treating the ball as a rectangle in the corner collisions.
 
I thought you may have picked it up wrong from the diagrams :). I was cheating a bit by using the CGRectIntersectsRect method to test for collision, which as you rightly said will treat the ball as a rectangle. This is fine for the vertical or horizontal vertices of a rectangle but causes a problem for the corners. The collision test between the balls and rectangles has been changed to:

Code:
-(bool)collision:(int)circleX:(int) circleY:(int)radius:(int)squareX:(int)squareY:(int)width:(int)height{	
    int distance = 0;	
    if(circleX < squareX) 
        distance += pow(circleX - squareX,2);	
    else 
        if (circleX > squareX + width) distance += pow(circleX - squareX - width, 2);	
            if(circleY < squareY) 
                distance += pow(circleY - squareY,2);	
            else if (circleY > squareY + height) 
                distance += pow(circleY - squareY - height, 2);		
    if( distance <= pow(radius, 2))		
        return true; 	
    else			
        return false;
}

This has been converted from a C++ routine I found and works well.
 
Thanks for that, the paper makes for some interesting reading. For the app I'm developing my collision result routines are now working as required but I'm always open to some optimisation ideas.
 
Back
Top Bottom