0
\$\begingroup\$

I'm working on a small SFML engine, and i'm trying to implement continuous collisions. It's working great, but the corners are getting detected as collisions and i can't figure out why. I already searched around a lot but couldn't find anything looking like my issue:

Video of the issue

    float Collider::sweptAABB(Collider &other, float dt, sf::Vector2f& normal)
{
    float xInvEntry, yInvEntry;
    float xInvExit, yInvExit;

    sf::Vector2f vel = m_velocity + (m_acceleration * dt);

    //if (!broadCheck(other, dt))
    //    return 1.0f;

    // find the distance between the objects on the near and far sides for both x and y
    if (vel.x > 0.0f) {
        xInvEntry = other.getTopLeft().x - (getTopLeft().x + getGlobalBounds().width);
        xInvExit = (other.getTopLeft().x + other.getGlobalBounds().width) - getTopLeft().x;
    } else {
        xInvEntry = (other.getTopLeft().x + other.getGlobalBounds().width) - getTopLeft().x;
        xInvExit = other.getTopLeft().x - (getTopLeft().x + getGlobalBounds().width);
    }

    if (vel.y > 0.0f) {
        yInvEntry = other.getTopLeft().y - (getTopLeft().y + getGlobalBounds().height);
        yInvExit = (other.getTopLeft().y + other.getGlobalBounds().height) - getTopLeft().y;
    } else {
        yInvEntry = (other.getTopLeft().y + other.getGlobalBounds().height) - getTopLeft().y;
        yInvExit = other.getTopLeft().y - (getTopLeft().y + getGlobalBounds().height);
    }

    // find time of collision and time of leaving for each axis (if statement is to prevent divide by zero)
    float xEntry, yEntry;
    float xExit, yExit;

    if (vel.x == 0.0f) {
        xEntry = -std::numeric_limits<float>::infinity();
        xExit = std::numeric_limits<float>::infinity();
    } else {
        xEntry = xInvEntry / vel.x;
        xExit = xInvExit / vel.x;
    }

    if (vel.y == 0.0f) {
        yEntry = -std::numeric_limits<float>::infinity();
        yExit = std::numeric_limits<float>::infinity();
    } else {
        yEntry = yInvEntry / vel.y;
        yExit = yInvExit / vel.y;
    }

    // find the earliest/latest times of collision
    float entryTime = std::max(xEntry, yEntry);
    float exitTime = std::min(xExit, yExit);

    // if there was no collision
    if (entryTime > exitTime || xEntry < 0.0f && yEntry < 0.0f || xEntry > 1.0f || yEntry > 1.0f) {
        normal = sf::Vector2f(0, 0);
        return 1.0f;
    } else {
        // calculate normal of collided surface
        if (xEntry > yEntry) {
            if (xInvEntry < 0.0f) {
                normal = sf::Vector2f(1.0f, 0.0f);
            } else {
                normal = sf::Vector2f(-1.0f, 0.0f);
            }
        } else {
            if (yInvEntry < 0.0f) {
                normal = sf::Vector2f(0.0f, 1.0f);
            } else {
                normal = sf::Vector2f(0.0f, -1.0f);
            }
        }

        // return the time of collision
        return entryTime;
    }
}

void Collider::resolveCollision(Collider &other, float dt)
{
    sf::Vector2f normal;
    float collisionTime = sweptAABB(other, dt, normal);
    sf::Vector2f vel = (m_velocity) + (m_acceleration * dt);

    if (collisionTime < 1.0f && math::length(vel) != 0.0f) {
        setFillColor(sf::Color(0, 255, 0, 50));
    } else {
        setFillColor(sf::Color(255, 0, 0, 50));
    }


    float remainingTime = 1.0f - collisionTime;
    if (collisionTime == 0) {
        float dotprod = (vel.x * normal.y + \
            vel.y * normal.x) * remainingTime;
        m_velocity.x = dotprod * normal.y;
        m_velocity.y = dotprod * normal.x;
        setPosition(getPosition() + (m_velocity));
    } else {
        m_velocity = vel;
        setPosition(getPosition() + m_velocity * collisionTime);
    }

    if (m_acceleration.x == 0 && m_acceleration.y == 0)
        m_velocity *= 0.95f;
    m_acceleration = sf::Vector2f(0, 0);

    m_sweptBox.setPosition(getTopLeft());
}
\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

An AABB check is to determine an intersection of axis aligned bounding boxes, i.e., they there is some kind of overlap of those shapes.

Intersection is true IF: The projection of box A onto the X axis overlaps the projection of box B on the same axis AND The projection of box A onto the Y axis overlaps the projection of box B on the same axis.

This is your rudimentary check, which in code would simply return a boolean value (true/false).

Clearly, this is not very useful, so we need a little more information. This information comes in the form of an intersection manifold, and contains an edge normal, and a degree of penetration.

This is done by determining the axis of least penetration and retaining that value and axis, until all axes have been tested. (this is called Minimum traversal vector, or MTV)

By this point, you have your manifold, and it can be used to determine how to resolve the collision.

It is at this point your code your code gets a little weird:

You have interspersed various lines of code that interrogate and compute velocity. You do not need this unless you are trying to do physics based resolutions, which you are not. Your code would be better looking something like:

bool Collider::sweptAABB(Collider &other, float& penetration, sf::Vector2f& normal)
{
    // X axis projection
    float aXmin = getTopLeft().x;
    float aXmax = getTopRight().x;
    float bXmin = other.getTopLeft().x;
    float bXmax = other.getTopRight().x;
    // Y axis projection
    float aYmin = getTopLeft().y;
    float aYmax = getBottomLeft().y;
    float bYmin = other.getTopLeft().y;
    float bYmax = other.getBottomLeft().y;

    // Check for non-intersection and early exit clause.
    if (aXmin > bXmax) return false;
    if (bXmin > aXmax) return false;
    
    if (aYmin > bYmax) return false;
    if (bYmin > aYmax) return false;
    
    // If we reach this point, then there is a collision. 
    // We now have to compute the manifold

    penetration = std::numeric_limits<float>::max();
    // Other collider on the right?
    if (bXmin < aXmax && aXmin < bXmax)
    {
        penetration = std::abs(bXmin - aXmax);
        normal = sf::Vector2f(-1.f, 0.f);
    }
    // Other collider on the left?
    if (aXmin < bXmax && bXmin < aXmax)
    {
        penetration = std::abs(aXmin - bXmax);
        normal = sf::Vector2f(1.f, 0.f);
    }
    // Repeat for Y axis
    // Other collider above?
    if (bYmin < aYmax && aYmin < bYmax)
    {
        float py = std::abs(bYmin - aYmax);
        if (py < penetration)
        {
            penetration = py;
            normal = sf::Vector2f(0.f, -1.f);
        }
    }
    // Other collider below?
    if (aXmin < bXmax && bXmin < aXmax)
    {
        py = std::abs(aXmin - bXmax);
        if (py < penetration)
        {
            penetration = py;
            normal = sf::Vector2f(0.f, 1.f);
        }
    }
    return true;
}

void Collider::resolveCollision(Collider &other)
{
    sf::Vector2f normal;
    float penetration = 0.0f;
    bool result = sweptAABB(other, penetration , normal);

    if (result) {
        setFillColor(sf::Color(0, 255, 0, 50));
        m_sweptBox.setPosition(getTopLeft() + normal * penetration);
    } else {
        setFillColor(sf::Color(255, 0, 0, 50));
    }        
}

As you can see, you do not need dt, acceleration, or velocity for simple collision response, but if you wanted it to be more physics based, then you could compute the normal force, which is outside the scope of this question.

Now, to finally answer your question: The reason your box stops, is because you act on an intersection depth of zero or greater. Simply greater than zero is sufficient.

\$\endgroup\$

You must log in to answer this question.

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