10
\$\begingroup\$

After this question, I need some more help.

How can I find out which side of a rectangle a collision came from and react accordingly?

rectangles being collided with from all sides

The blue arrows are the paths that some circular objects would follow if before and after colliding with the box.

How can I calculate this?

\$\endgroup\$

5 Answers 5

8
\$\begingroup\$

Since this is based on your other question I'll give a solution for when the rectangle is axis-aligned.

First, you build up you current object's rectangle with the following values:

int boxLeft = box.X;
int boxRight = boxLeft + box.Width;
int boxTop = box.Y;
int boxBottom = boxTop + box.Height;

Next, you must have the old object's position (which you can store on each object or simply pass to a function) to create the old object's rectangle (when it was not colliding):

int oldBoxLeft = box.OldX;
int oldBoxRight = oldBoxLeft + box.Width;
int oldBoxTop = box.OldY;
int oldBoxBottom = oldBoxTop + box.Height;

Now, to know where the collision was from, you must find the side where the old position was not in the collision area and where its new position is. Because, when you think of it, this is what happens when you collide: a side that was not colliding enters an other rectangle.

Here is how you could do so (these functions assume that there is a collision. They should not be called if there is no collision):

 bool collidedFromLeft(Object otherObj)
{
    return oldBoxRight < otherObj.Left && // was not colliding
           boxRight >= otherObj.Left;
}

Rince and repeat.

bool collidedFromRight(Object otherObj)
{
    return oldBoxLeft >= otherObj.Right && // was not colliding
           boxLeft < otherObj.Right;
}

bool collidedFromTop(Object otherObj)
{
    return oldBoxBottom < otherObj.Top && // was not colliding
           boxBottom >= otherObj.Top;
}

bool collidedFromBottom(Object otherObj)
{
    return oldBoxTop >= otherObj.Bottom && // was not colliding
           boxTop < otherObj.Bottom;
}

Now, for the actual use with the collision response from the other question:

if (collidedFromTop(otherObj) || collidedFromBottom(otherObj))
    obj.Velocity.Y = -obj.Velocity.Y;
if (collidedFromLeft(otherObj) || collidedFromRight(otherObj))
    obj.Velocity.X = -obj.Velocity.X;

Again, this may not be the best solution but that's the way I usually go for collision detection.

\$\endgroup\$
2
  • \$\begingroup\$ Once again, you were right! ;D Thanks... (send me more postcards of your board next time... ^___^) \$\endgroup\$
    – NemoStein
    Commented Jun 20, 2011 at 20:50
  • \$\begingroup\$ Ahhh sadly I didn't know what I could have used it for.. maybe next time! \$\endgroup\$ Commented Jun 21, 2011 at 1:31
7
\$\begingroup\$

As the question is partially identical to this question, I will re-use some parts of my answer in order to try answering your question.


Lets define a context and some variables to make the following explanation more understandable. The representation form that we will use here probably doesn't fit the form of your own datas but it should be simpler to understand this way (indeed you can use the following methods using others kind of representations once you understand the principle)

So, we will consider an Axis Aligned Bounding Box (or an Oriented Bounded Box) and a moving Entity.

  • The Bounding Box is composed of 4 sides, and we will define each one as:
    Side1 = [x1,y1,x2,y2] (two points [x1,y1] and [x2,y2])

  • The moving Entity is defined as a velocity vector (position+speed):
    a position [posX, posY] and a speed [speedX, speedY].


You can determine which side of an AABB/OBB is hit by a vector using the following method:

  • 1/ Find the intersection points between the infinite lines passing though the four sides of the AABB and the infinite line passing through the entity position (pre-collision) which use the entity speed vector as slop. (You can either find a collision point or an undefined number, which corresponds to parallels or overlapping lines)

  • 2/ Once you know the intersection points (if they exist) you can search for those that are into the segment bounds.

  • 3/ Finally, if there is still multiple points on the list (the velocity vector can pass through multiple sides), you can search for the closest point from the entity origin using the magnitudes of the vector from the intersection to the entity origin.

Then you can determine the angle of collision using a simple dot product.

  • 4/ Find the angle between of collision using a dot product of the entity (probably a ball?) vector with the hit side vector.

----------

More details:

  • 1/ Find intersections

    • a / Determine the infinite lines ( Ax + Bx = D ) using their parametric forms ( P(t) = Po + tD ).

      Point origin: Po = [posX, posY]
      Direction vector: D = [speedX, speedY]

      A = D.y = speedY
      B = -D.x = -speedX
      D = (Po.x * D.y) - (Po.y * D.x) = (posXspeedY) - (posYspeedX)

      Ax + By = D <====> (speedYx) + (-speedXy) = (posXspeedY) - (posYspeedX)

      I used the entity point values to illustrate the method, but this is exactly the same method to determine the 4 side infinite lines of the bounding box (Use Po = [x1,y1] and D = [ x2-x1 ; y2-y1 ] instead).

    • b / Next, to find the intersection of two infinite lines we can solve the following system:

      A1x + B1x = D1 <== Line passing through the entity point with the speed vector as slop.
      A2x + B2x = D2 <== One of the lines passing through the AABB sides.

      which yields the following coordinates for the interception:

      Interception x = ((B2*D1) - (B1*D2)) / ((A1*B2) - (A2*B1))
      Interception y = ((A1*D2) - (A2*D1)) / ((A1*B2) - (A2*B1))

      If the denominator ((A1*B2) - (A2*B1)) equals zero, then the both lines are parallels or overlapping, otherwise you should find an intersection.

  • 2/ Test for the segment bounds. As this is straightforward to verify, there is no needs for more details.

  • 3/ Search for the closest point. If there is still multiple points on the list, we can find which side is the closest to the entity origin point.

    • a / Determine the vector going from the intersection point to the entity origin point

      V = Po - Int = [Po.x - Int.x ; Po.y - Int.y ]

    • b / Compute the vector magnitude

      ||V|| = sqrt( V.x² + V.y² )

    • c / find the smallest one.
  • 4/ Now that you know which side will be hit, you can determine the angle using a dot product.

    • a / Let S = [ x2-x1 ; y2-y1 ] be the side vector that will be hit and E = [ speedX ; speedY ] be the entity velocity vector.

      Using the vector dot product rule we know that

      S·E = SxEx + SyEy
      and
      S·E = ||S|| ||E|| cos θ

      So we can determine θ by manipulating a bit this equation...

      cos θ = (S·E) / (||S|| ||E||)

      θ = acos( (S·E) / (||S|| ||E||) )

      with

      S·E = Sx * Ex + Sy * Ey
      ||S|| = sqrt( Sx² + Sy² )
      ||E|| = sqrt( Ex² + Ey² )


Note: As I said in the other question thread, this is probably not the most efficient nor the most simple way to do it, this is just what's come into mind, and some part of the maths could maybe help.

I didn't verified with a concrete OBB example (I did with an AABB) but it should works too.

\$\endgroup\$
6
\$\begingroup\$

A simple way is to resolve the collision, then translate the moving object's collision box by one pixel in each direction in turn and see which ones result in collision.

If you want to do it "properly" and with rotated collision shapes or arbitrary polygons, I suggest reading up on the Separating Axis Theorem. Metanet software (the folks who made the N game) for instance have an awesome dev article on SAT. They also discuss the physics involved.

\$\endgroup\$
2
\$\begingroup\$

One way would be to rotate the world around your rectangle. "The world" in this case is only the objects you care about: the rectangle, and the ball. You rotate the rectangle around its center until its bounds are aligned with the x-/y-axes, then you rotate the ball by the same amount.

The important point here is that you rotate the ball around the rectangle's center, not its own.

Then you can easily test for collision just like you would with any other non-rotated rectangle.


Another option is to treat the rectangle as four distinct line-segments, and test for collision with each of them separately. This allows you to test for collision and figure out which side was collided at the same time.

\$\endgroup\$
1
\$\begingroup\$

I used fixed angles in my calculations, but this should help you some

void Bullet::Ricochet(C_Rect *r)
{
    C_Line Line;
    //the next two lines are because I detected 
    // a collision in my main loop so I need to take a step back.

    x = x + ceil(speed * ((double)fcos(itofix(angle)) / 65536));
    y = y + ceil(speed * ((double)fsin(itofix(angle)) / 65536));
    C_Point Prev(x,y);

    //the following checks our position to all the lines will give us
    // an answer which line we will hit due to no lines
    // with angles > 90 lines of a rect always shield the other lines.

    Line = r->Get_Closest_Line(Prev);    
    int langle = 0;
    if(!Line.Is_Horizontal())   //we need to rotate the line to a horizontal position
    {
        langle = Line.Get_Point1().Find_Fixed_Angle(Line.Get_Point2());
        angle = angle - langle;  //to give us the new angle of approach
    }
    //at this point the line is horizontal and the bullet is ready to be fixed.
    angle = 256 - angle;
    angle += langle;
}
\$\endgroup\$

You must log in to answer this question.

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