Problem with AABB Collision Resolution

AABB Collision Detection is easy.

You simply set up the sides of a square or rectangle, and see if they overlap with the sides of another square or rectangle, like in my detection function here:

bool Player::checkCollision(Solid a)
{
if (topSide < a.getBottomSide()
&& bottomSide > a.getTopSide()
&& leftSide < a.getRightSide()
&& rightSide > a.getLeftSide()) return true; else return false;
}

If the player object has a collision with the solid object, return true, otherwise, return false.

Simple enough.

My problem is not with the detection part. It is with the resolution part.

I have tried various ways, but I can not seem to separate the X and Y collision resolutions.

For instance, if my player square collides with the left side of a solid square, I set its position to the left of the square, so it is not overlapping anymore. But, if you press up while you are colliding with the left side of the solid square, it executes the code for handling the resolution when you collide with the bottom side of the square object. Hence, it pushes the player downward.

I have tried MANY different variations of my code, even separating the horizontal and vertical resolution functions, and I still keep getting the same problem over and over again.

How do you keep the two resolutions different, so that they don't interfere with each other?

This is what I currently have:

void Player::moveHorizontally(Solid a)
{
if (Keyboard::isKeyPressed(Keyboard::Right)) velX = 0.02;
else if (Keyboard::isKeyPressed(Keyboard::Left)) velX = -0.02;
else velX = 0;

float prevX = x;
x+=velX;

if (checkCollision(a)) //if colliding with a solid object
{
x = prevX;
if (x > a.getX()) x+=0.01; //move the player a touch, so you are no longer colliding, and can move again
else if (x < a.getX()) x-=0.01;
}
}

void Player::moveVertically(Solid a)
{
if (Keyboard::isKeyPressed(Keyboard::Up)) velY = -0.02;
else if (Keyboard::isKeyPressed(Keyboard::Down)) velY = 0.02;
else velY = 0;

float prevY = y;
y+=velY;

if (checkCollision(a)) //if colliding with a solid object
{
y = prevY;
if (y > a.getY()) y+=0.01; //move the player a touch, so you are no longer colliding, and can move again
else if (y < a.getY()) y-=0.01;
}
}

I want to be able to have the player square press up against the left side of a solid square object, and when I press up, it doesn't push the player object downward.

I even tried checking the directional movement of the player (if velX < 0, then the player is moving left, etc.)

I have been struggling with this issue for so long, and I have done extensive research on Google. Most results seem to tell people to deal with other versions of collision detection, such as Swift AABB, separating-axis theorem, etc.

There is not a lot of information on the actual resolution of collisions. Plenty on detection, but resolution, not so much.

Any help would be greatly appreciated.

Thank you for your time and your help.

~Adam
bool Player::checkCollision(Solid a)
{
if (topSide < a.getBottomSide()
&& bottomSide > a.getTopSide()
&& leftSide < a.getRightSide()
&& rightSide > a.getLeftSide()) return true; else return false;
}

If the player object has a collision with the solid object, return true, otherwise, return false.

Simple enough.


I don't think you have this "simple enough" concept quite right.

According to your logic, you only have a collision if my top is less than the other bottom AND my bottom is greater than the other top AND my left is less than the other right AND my right is greater than the other left. Assuming are talking about using the coordinate system of a screen where the Y value increases as you go down, the only way a collision can occur is my object is completely contained within the other object.

If, or instance, my left side slightly overlaps the right side of the other object such that only (leftSide < a.getRightSide) is true, your checkCollision function will return false, because none of the other conditions are true.

I think you need to revisit your assumptions.
You may be interested in seeing how other libraries do axis-aligned rectangle collision.

https://github.com/SFML/SFML/blob/master/include/SFML/Graphics/Rect.inl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2019 Laurent Gomila (laurent@sfml-dev.org)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
//    you must not claim that you wrote the original software.
//    If you use this software in a product, an acknowledgment
//    in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
//    and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////

template <typename T>
bool Rect<T>::intersects(const Rect<T>& rectangle, Rect<T>& intersection) const
{
    // Rectangles with negative dimensions are allowed, so we must handle them correctly

    // Compute the min and max of the first rectangle on both axes
    T r1MinX = std::min(left, static_cast<T>(left + width));
    T r1MaxX = std::max(left, static_cast<T>(left + width));
    T r1MinY = std::min(top, static_cast<T>(top + height));
    T r1MaxY = std::max(top, static_cast<T>(top + height));

    // Compute the min and max of the second rectangle on both axes
    T r2MinX = std::min(rectangle.left, static_cast<T>(rectangle.left + rectangle.width));
    T r2MaxX = std::max(rectangle.left, static_cast<T>(rectangle.left + rectangle.width));
    T r2MinY = std::min(rectangle.top, static_cast<T>(rectangle.top + rectangle.height));
    T r2MaxY = std::max(rectangle.top, static_cast<T>(rectangle.top + rectangle.height));

    // Compute the intersection boundaries
    T interLeft   = std::max(r1MinX, r2MinX);
    T interTop    = std::max(r1MinY, r2MinY);
    T interRight  = std::min(r1MaxX, r2MaxX);
    T interBottom = std::min(r1MaxY, r2MaxY);

    // If the intersection is valid (positive non zero area), then there is an intersection
    if ((interLeft < interRight) && (interTop < interBottom))
    {
        intersection = Rect<T>(interLeft, interTop, interRight - interLeft, interBottom - interTop);
        return true;
    }
    else
    {
        intersection = Rect<T>(0, 0, 0, 0);
        return false;
    }
}

You can simplify this because you don't actually need to calculate the intersection rectangle itself. This code also allows for rectangles with negative dimensions, so if you don't need that, it can be simplified further.

You can test the validity of your collision function with independent tests, not requiring manual testing with a GUI or similar.
e.g.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void test_myCollisionFunction()
{
    Rect<float> rect1 ( ... width height x y ... );
    Rect<float> rect2 ( ... width height x y ... );
    
    bool expected = false; // example: expected to not be colliding for this test
    if (rect1.intersects(rect2) == expected)
    {
        cout << __func__ << " passed\n";
    }
    else
    {
        cout << __func__ << " failed\n";
    }
}

(Or you could use a unit testing framework, but one thing at a time.)
Last edited on
what I do for things like this is set a code, here, an enum will work {none, left on left, left on right, left on top, left on bottom, right on left, right on right, right on top, right on bottom, top on left .... blah blah}
and when you detect a collision, you set the appropriate state (you should know which edges hit at the moment of detection).

this serves 2 purposes: the enum is set so that no collison is 0 which is boolean false, and everything else is true, so if you want a boolean collided or not, you have it right there. If you need details, you can check which code it is. Depending on what you want to do with this info, you can use a lookup table (or a switch statement will do that for you if it can) to do whatever (eg cout << stringwords[enumvalue] ... ; )
Last edited on
Thank you doug4, Ganado, and jonnin for all of that information. doug4, I will definitely re-evaluate my code there. I took that code from some tutorials on AABB collision detection that I found. Ganado, thank you for the library information. I'll definitely take a closer look at that. jonnin, I will try using your method to see if I can get it to work.

I clearly need to start from the beginning again and see if I can program this the right way.

Thank you guys for the information. I appreciate it. :)
Topic archived. No new replies allowed.