Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Collision response - Entity still slightly overlaps when moving  (Read 4052 times)

0 Members and 1 Guest are viewing this topic.

Raincode

  • Full Member
  • ***
  • Posts: 118
    • View Profile
Hi dear community,

I have added collision response for a dynamic entity colliding with a static one (at least that's what I spontaneously chose to call them). By static I mean it doesn't move and by dynamic I mean that it can move.

If I run into a static object and let go of the controls, my entity is positioned nicely at the outer bounds of the static once. (for reference see attachment)
However, if I, after colliding with the static object, continue to move towards the static object (which causes the dynamic object to slide btw), the dynamic one is further in than the outside bounds of the static object (for reference see attachment) Once I stop moving, it immediately jumps back to the correct position.

I think this is a bug (in my game of course!), and it rather annoys me. I mean, it still fulfills its purpose as preventing the player from running through a brick wall, but I believe it isn't correct.

I can't figure out what I have to change in my moving/collision response mechanics to solve this problem. I would be very thankful if someone could help me out and give me some hints.

I checked out this thread: http://en.sfml-dev.org/forums/index.php?topic=13766.0 and I really loved the little example program, but I couldn't really use it to fix my problem.

Another question I have, however it is even less related to SFML, is whether my method of collision response is appropriate. I have read quite many blog posts, tutorials and StackOverflow posts, yet frankly I only understood half of them at best, and from what I picked up, my method was once described (except that I don't use a collision normal (yep, don't exactly know what that is either) to determine the axis, but some ugly if-statements). Please consider this a low priority question, if it is not linked to my problem.

Here is the response code (added comments for hopefully clarification):
// pair is a pair of colliding Entities
else if (matches_categories(pair, Category::DynamicBody, Category::StaticBody)) {
            // dynamic as in movable (e.g. a car), static as in non-movable (e.g. a wall)
                        auto& dynamicEnt = static_cast<Entity&>(*pair.first);
                        auto& staticEnt = static_cast<Entity&>(*pair.second);

                        auto dynamicBounds = dynamicEnt.getBoundingRect();
                        auto staticBounds = staticEnt.getBoundingRect();

                        // since the origins are centered, I need additional offset
                        // so setPosition() puts it to the outside of the static Entity
                        const float offset = dynamicBounds.width / 2.f;

                        sf::Vector2f position = dynamicEnt.getPosition();

                        // in some situations the dynamic entity  would jump around (to the corners mostly), adding
                        // this tolerance value fixed the problem
                        float tol = 5;

                        if (bottom(dynamicBounds) > staticBounds.top + tol && dynamicBounds.top < bottom(staticBounds) - tol) {
                // the collision if happening along the X-Achsis
                //     dynamicEnt.setVelocity(0, dynamicEnt.getVelocity().y);
                                if (position.x > staticEnt.getPosition().x)
                // collision happened to the right of the static entity
                                        position.x = right(staticBounds) + offset;
                                else
                                    // collision happened to the left of the static entity
                                        position.x = staticBounds.left - offset;
                        }
                        else if (right(dynamicBounds) > staticBounds.left && dynamicBounds.left < right(staticBounds)) {
                // the collision is happending along the Y-Achsis
                //     dynamicEnt.setVelocity(dynamicEnt.getVelocity().x, 0);
                                if (position.y > staticEnt.getPosition().y)  
               // collision happened at rhe bottom of the static entity
                                        position.y = bottom(staticBounds) + offset;
                                else
                                    // collision happened at the top of the static entity
                                        position.y = staticBounds.top - offset;
                        }
                        dynamicEnt.setPosition(position);
 

My update of the world is something like this (broken down):
update
    set player velocity (handle input)
    handle collisions
    update entities (e.g. move player by player velocity * dt)
    set player velocity to (0, 0)
 

My input handling for movement is done like this (the actual direction is determined by rotation):
if direction Forward
    entity.setVelocity(0, -mech.get_max_speed());
else if direction Backward
    entity.setVelocity(0, mech.get_max_speed());
};
 

I have created a minimal example. I know the positioning isn't perfect from each side, but it illustrates what I have described quite nicely:
#include <SFML/Graphics.hpp>

float right(const sf::FloatRect& rect) {
    return rect.left + rect.width;
}
float bottom(const sf::FloatRect& rect) {
    return rect.top + rect.height;
}

void check_handle_collision(sf::RectangleShape& dynamic,
                            const sf::RectangleShape& stat)  
{
    auto dynamicBounds = dynamic.getGlobalBounds();
    auto staticBounds = stat.getGlobalBounds();
    if(dynamicBounds.intersects(staticBounds)) {
        const float offset = dynamicBounds.width / 2.f;

        sf::Vector2f position = dynamic.getPosition();

        float tol = 2;

        if (bottom(dynamicBounds) > staticBounds.top + tol && dynamicBounds.top < bottom(staticBounds) - tol) {
            if (position.x > stat.getPosition().x)
                position.x = right(staticBounds) + offset;
            else
                position.x = staticBounds.left - offset;
        }
        else if (right(dynamicBounds) > staticBounds.left && dynamicBounds.left < right(staticBounds)) {
            if (position.y > stat.getPosition().y)
                position.y = bottom(staticBounds) + offset;
            else
                position.y = staticBounds.top - offset;
        }
        dynamic.setPosition(position);
    }
}

int main() {
    sf::RenderWindow window(sf::VideoMode(640, 480), "Minimal example");

    sf::RectangleShape dynamic(sf::Vector2f(64, 64));
    dynamic.setFillColor(sf::Color::Transparent);
    dynamic.setOutlineColor(sf::Color::Green);
    dynamic.setOutlineThickness(1);
    dynamic.setOrigin(dynamic.getGlobalBounds().width / 2.f,
                      dynamic.getGlobalBounds().height / 2.f);
    dynamic.setPosition(100, 100);

    sf::RectangleShape stat = dynamic; // short for static
    stat.setOutlineColor(sf::Color::Blue);
    stat.setPosition(sf::Vector2f(window.getSize()) / 2.f);

    sf::Clock clock;
        sf::Time timeSinceLastUpdate = sf::Time::Zero;
    const sf::Time TimePerFrame = sf::seconds(1.f / 60.f);
    const float speed = 200.f;
    sf::Vector2f vel(0, 0);

        while (window.isOpen()) {
                sf::Time dt = clock.restart();
                timeSinceLastUpdate += dt;

                while (timeSinceLastUpdate > TimePerFrame) {
                        timeSinceLastUpdate -= TimePerFrame;
            sf::Vector2f vel(0, 0);

                        sf::Event event;
                        while(window.pollEvent(event)) {
                if(event.type == sf::Event::Closed)
                    window.close();
                        }

                        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
                            vel.y += -speed;
            if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
                            vel.y += speed;
            if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
                            vel.x += -speed;
            if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
                            vel.x += speed;

                        check_handle_collision(dynamic, stat);

                        dynamic.move(vel * TimePerFrame.asSeconds());
                }

                window.clear();
                window.draw(stat);
                window.draw(dynamic);
                window.display();
        }
}
 

Sorry for the long post, I just really wanted to describe things precisely, and provide sufficient material.

dabbertorres

  • Hero Member
  • *****
  • Posts: 505
    • View Profile
    • website/blog
Re: Collision response - Entity still slightly overlaps when moving
« Reply #1 on: May 30, 2015, 10:24:46 pm »
You're moving dynamic with it's velocity after you handle the collision. So it's position gets updated relative to that. Handle the collision after moving, I bet that'd fix it.

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Collision response - Entity still slightly overlaps when moving
« Reply #2 on: May 31, 2015, 12:03:50 am »
The way dabbertorres mentioned already, you can just swap these two lines:
                        check_handle_collision(dynamic, stat);

                        dynamic.move(vel * TimePerFrame.asSeconds());
to become:
                        dynamic.move(vel * TimePerFrame.asSeconds());

                        check_handle_collision(dynamic, stat);
and that will fix the problem you described (I assume that your actual code tests for vertical collision too.)

On a separate note, when you use outlines on shapes, the zero position (0, 0) is the corner of the shape, not the outline (the outline extends outwards from the shape). To allow for this, you can either use negative outline values to extend inwards instead, or you will need to add .left to .width and .top to .height thus:
        dynamic.setOrigin((dynamic.getGlobalBounds().width + dynamic.getGlobalBounds().left) / 2.f,
                (dynamic.getGlobalBounds().height + dynamic.getGlobalBounds().top) / 2.f);
Although, I would prefer local bounds here instead.

p.s. When testing your code, I added the following code after the keyboard input to keep the speed consistent, even during diagonal movement:
                        if ((vel.x > 0.01f || vel.x < -0.01f) && (vel.y > 0.01f || vel.y < -0.01f))
                                vel *= 0.707f;
You're welcome  :P
« Last Edit: May 31, 2015, 12:10:45 am by Hapax »
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

Raincode

  • Full Member
  • ***
  • Posts: 118
    • View Profile
Re: Collision response - Entity still slightly overlaps when moving
« Reply #3 on: May 31, 2015, 12:13:52 am »
Hi,

uhm, I guess this was a dead simple solution, at least the described problem doesn't occur anymore...

I was thinking way too complicated, I should have tried that by myself. I was just sort of following the book with my design, but now that I think about it, this type of collision code should go where the other function 'adapt_player_pos()' lies, which keeps the player from moving outside of the world bounds. Quite logic actually..

In a nutshell, this is what I have now currently:
handle collisions
update
handle body collisions  // stupid naming, I know
 

If I just moved the entire collision code behind the update function, things started happening like entire spritesheets of explosion animations popping up for a split second when they started playing... I'll just keep two seperate functions for know.

Anyway, thankyou for the wake-up call.

P.S.:
@hapax:
Thanks for the hint about the origin. I used negative OutlineThickness and it displays nicely on all sides equally now.
Forgive me, I simply overlooked correcting the diagonal movement, I was so occupied with that frustrating collision response  :P
« Last Edit: May 31, 2015, 12:21:09 am by Raincode »

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Collision response - Entity still slightly overlaps when moving
« Reply #4 on: May 31, 2015, 12:34:47 am »
The other option for the origin/outline problem is just to set the origin before you set the thickness as the bounds aren't aware of it at that time.

The problem with you collisions being split by the movement update is that the first collisions are still being tested before the update, which means that the update can cause it to collide without being tested. All the collisions should be after the update; they just need to play nice with each other - where one collision reponse leaves it should be acceptable for the next collision test.
It might also be an option to test both types of collision at once.

If collision detection and response is causing animation problems, the collision detection should take into consideration the animation and the response should be able to alter the animation to match what is expected. Probably...
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

SeriousITGuy

  • Full Member
  • ***
  • Posts: 123
  • Still learning...
    • View Profile
Re: Collision response - Entity still slightly overlaps when moving
« Reply #5 on: June 03, 2015, 10:24:35 am »
As I also ran into this problem I want to mention my solution, as I haven't seen it here yet.
The previous discribed solution is first move, than handle collision and correct position. This approach seems to work but is flawed anyway.
Better solution is to interpolate your movement before you actually move. You precalulate where your dynamic entity will be when moved by the amount of pixels it should move this given time step. If the current position + the move amount is going to intersect with the static entity move your dynamic entity only so far until it actually hits the static entity. If the pre-estimated movement will not intersect with the static entity, just move.
This approach is actually the base how a physics library handles collision, by interpolation. Downside: Performance will be slower, but physics are more accurate that way. Good physic libraries will let you set the number of interpolation-iterations for every entity, so you can make a good balance between performance and accuracy.

Cheers.

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Collision response - Entity still slightly overlaps when moving
« Reply #6 on: June 03, 2015, 08:17:28 pm »
I agree with you, SeriousITGuy.
The attempted move should be projected first and tested there.
If they 'would' collide, some options then are (in increasing degrees of complexity and therefore generally decreasing performance):
  • don't move there (don't move it at all)
  • don't move it exactly there but somewhere near where it isn't colliding
  • work out exactly where it should collide and move it there (SeriousITGuy's solution)
  • work out exactly where it would collide and then work out how much more energy would be left from the rebound and move is away by that amount
(most physically accurate way of these listed)
[/list]
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*