c++ SFML. Проблема с коллизией круга и квадратов

Проблема заключается в методе intersects. Суть в том, что все прекрасно работает ровно до того момента, пока круг не начинает идти в стену и влево/наверх. Круг перестает скользить вдоль стены на грани квадрата и следующего квадрата, будто бы уперся в невидимую стену. Я попробовал изменить проход по объектам(если раньше он начинал с первого объекта, то теперь с последнего) и направления блокировки инвертировались, то есть проблема как минимум частично заключается в этом. Как можно разрешить кругу скользить вдоль стен вне зависимости направления?

Код:

#include "math.h"
#include "SFML/Graphics.hpp"

sf::RenderWindow window;
sf::Event event;
sf::View camera;

const int amountOfTile = 32;
sf::RectangleShape tiles[amountOfTile][amountOfTile];
sf::CircleShape player;

void intersects(sf::CircleShape &player, sf::RectangleShape &rect) {
    bool leftIsBlocked = 0;
    bool rightIsBlocked = 0;
    bool topIsBlocked = 0;
    bool bottomIsBlocked = 0;

    float testX = player.getPosition().x;
    float testY = player.getPosition().y;

    //проверка на то, какие грани пересекает круг
    //изначально здесь были позиции точек квадрата, а не отдаление от центра. Однако ситуация в обоих случаях одинакова!
    if (player.getPosition().x < rect.getPosition().x - 16) { testX = rect.getPosition().x - 16; leftIsBlocked = 1; } //левая грань
    else if (player.getPosition().x > rect.getPosition().x + 16) { testX = rect.getPosition().x + 16; rightIsBlocked = 1; } //правая
    if (player.getPosition().y < rect.getPosition().y - 16) { testY = rect.getPosition().y - 16; topIsBlocked = 1; }  //верхняя
    else if (player.getPosition().y > rect.getPosition().y + 16) { testY = rect.getPosition().y + 16; bottomIsBlocked = 1; } //нижняя

    float distX = abs(player.getPosition().x - testX);
    float distY = abs(player.getPosition().y - testY);
    float distance = sqrt((distX * distX) + (distY * distY));

    //если дистанция меньше радиуса круга, то пришло время для коллизии
    if (distance <= 16) {
        //если какое либо булево значение активно, высчитывается расстояние до точки на грани квадрата и уже оно вычитывается/прибавляется к позиции игрока
        //предполагаю, что ошибка где то здесь, однако что бы я здесь не менял - не помогало
        if (leftIsBlocked) player.setPosition(player.getPosition().x - abs(16 - distance), player.getPosition().y);
        else if (rightIsBlocked) player.setPosition(player.getPosition().x + abs(16 - distance), player.getPosition().y);
        if (topIsBlocked) player.setPosition(player.getPosition().x, player.getPosition().y - abs(16 - distance));
        else if (bottomIsBlocked) player.setPosition(player.getPosition().x, player.getPosition().y + abs(16 - distance));
    }
}

int main() {
    srand(time(0));
    window.create(sf::VideoMode(1000, 1000), "sfml");
    window.setFramerateLimit(60);

    //расстановка квадратов
    for (int i = 0; i < amountOfTile; ++i) {
        for (int j = 0; j < amountOfTile; ++j) {
            tiles[i][j].setSize(sf::Vector2f(32, 32));
            tiles[i][j].setOrigin(sf::Vector2f(16, 16));
            tiles[i][j].setFillColor(sf::Color::Red);
            tiles[i][j].setPosition(sf::Vector2f((i + 0) * 32, (j + 0) * 32));
            tiles[i][j].setOutlineThickness(1);
            tiles[i][j].setOutlineColor(sf::Color::Blue);
        }
    }

    //случайные стены-квадраты
    for (int i = 0; i < amountOfTile; ++i) {
        for (int j = 0; j < amountOfTile; ++j) {
            if (rand() % 100 > 90) {
                tiles[i][j].setFillColor(sf::Color::Green);
            }
        }
    }

    //настройка персонажа
    player.setRadius(16);
    player.setOrigin(sf::Vector2f(16, 16));
    player.setFillColor(sf::Color::White);
    player.setPosition(sf::Vector2f(256, 128));

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

            //resize window
            if (event.type == sf::Event::Resized) {
                sf::Vector2f cameraCenter = camera.getCenter();
                camera.setSize(event.size.width, event.size.height);
                camera.setCenter(cameraCenter);
            }
        }

        if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) player.move(0, -2.0f);
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) player.move(0, 2.0f);
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) player.move(-2.0f, 0);
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) player.move(2.0f, 0);

        //проверка всех возможных коллизий(в будущем проверяться будут ближайшие)
        for (int i = 0; i < amountOfTile; ++i) {
            for (int j = 0; j < amountOfTile; ++j) {
                if (tiles[i][j].getFillColor() == sf::Color::Green) {
                    intersects(player, tiles[i][j]);
                }
            }
        }
        
        camera.setCenter(player.getPosition());
        window.setView(camera);
        window.clear();

        //в будущем я засуну тайлы в вертексные массивы
        for (int i = 0; i < amountOfTile; ++i) {
            for (int j = 0; j < amountOfTile; ++j) {
                window.draw(tiles[i][j]);
            }
        }
        window.draw(player);
        window.display();
    }
    return 0;
}```

Ответы (1 шт):

Автор решения: Shuvi22

Короче проблема оказалась довольно неочевидной(как минимум для меня). Коллизия за раз проверяет один объект. То есть если объект пересекает два объекта, происходит коллизия сначала с одним объектом, а только потом со вторым - при этом результаты столкновения сразу переносятся в первый объект и если он более не пересекает второй объект - то все, коллизия произошла только с одним объектом. Это неправильно. Решением(одно из, скорее всего) является то, что нельзя двигать объект ровно до того момента, пока он не проверит себя с другими - то есть понадобится вектор сдвига, который будет применяться к основному объекту после всех проверок.

Исправленный код:

#include <iostream>
#include "math.h"
#include "SFML/Graphics.hpp"

sf::RenderWindow window;
sf::Event event;
sf::View camera;

const int amountOfTile = 32;
sf::RectangleShape tiles[amountOfTile][amountOfTile];
sf::CircleShape player;

void intersects(sf::CircleShape &player, sf::RectangleShape &rect, sf::Vector2f &vec) {
    bool leftIsBlocked = 0;
    bool rightIsBlocked = 0;
    bool topIsBlocked = 0;
    bool bottomIsBlocked = 0;

    float testX = player.getPosition().x;
    float testY = player.getPosition().y;

    if (player.getPosition().x < rect.getPosition().x - 16) { testX = rect.getPosition().x - 16; leftIsBlocked = 1; } //левая грань
    else if (player.getPosition().x > rect.getPosition().x + 16) { testX = rect.getPosition().x + 16; rightIsBlocked = 1; } //правая
    if (player.getPosition().y < rect.getPosition().y - 16) { testY = rect.getPosition().y - 16; topIsBlocked = 1; }  //верхняя
    else if (player.getPosition().y > rect.getPosition().y + 16) { testY = rect.getPosition().y + 16; bottomIsBlocked = 1; } //нижняя

    float distX = abs(player.getPosition().x - testX);
    float distY = abs(player.getPosition().y - testY);
    float distance = sqrt((distX * distX) + (distY * distY));

    if (distance <= 16) { //теперь здесь двигается НЕ ИГРОК, а вектор сдвига, поэтому для остальных объектов игрок не сдвинулся. И да, это работает не совсем правильно на краю объектов, но я уже не знаю, как это решить. Но для моих нужд этого должно хватить
        if (leftIsBlocked) vec.x -= abs(16 - distance);
        else if (rightIsBlocked) vec.x += abs(16 - distance);
        if (topIsBlocked) vec.y -= abs(16 - distance);
        else if (bottomIsBlocked) vec.y += abs(16 - distance);
    }
}

int main() {
    srand(time(0));
    window.create(sf::VideoMode(1000, 1000), "sfml");
    window.setFramerateLimit(60);

    for (int i = 0; i < amountOfTile; ++i) {
        for (int j = 0; j < amountOfTile; ++j) {
            tiles[i][j].setSize(sf::Vector2f(32, 32));
            tiles[i][j].setOrigin(sf::Vector2f(16, 16));
            tiles[i][j].setFillColor(sf::Color::Red);
            tiles[i][j].setPosition(sf::Vector2f((i + 0) * 32, (j + 0) * 32));
            tiles[i][j].setOutlineThickness(1);
            tiles[i][j].setOutlineColor(sf::Color::Blue);
        }
    }

    for (int i = 0; i < amountOfTile; ++i) {
        for (int j = 0; j < amountOfTile; ++j) {
            if (rand() % 100 > 90) {
                tiles[i][j].setFillColor(sf::Color::Green);
            }
        }
    }

    player.setRadius(16);
    player.setOrigin(sf::Vector2f(16, 16));
    player.setFillColor(sf::Color::White);
    player.setPosition(sf::Vector2f(256, 128));

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

            //resize window
            if (event.type == sf::Event::Resized) {
                sf::Vector2f cameraCenter = camera.getCenter();
                camera.setSize(event.size.width, event.size.height);
                camera.setCenter(cameraCenter);
            }
        }

        if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) player.move(0, -2.0f);
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) player.move(0, 2.0f);
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) player.move(-2.0f, 0);
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) player.move(2.0f, 0);

        sf::Vector2f vec; //вектор сдвига

        for (int i = 0; i < amountOfTile; ++i) {
            for (int j = 0; j < amountOfTile; ++j) {
                if (tiles[i][j].getFillColor() == sf::Color::Green) {
                    intersects(player, tiles[i][j], vec); //теперь здесь передается vec
                }
            }
        }
        player.setPosition(player.getPosition().x + vec.x, player.getPosition().y + vec.y); //применение вектора сдвига
        
        camera.setCenter(player.getPosition());
        window.setView(camera);
        window.clear();

        for (int i = 0; i < amountOfTile; ++i) {
            for (int j = 0; j < amountOfTile; ++j) {
                window.draw(tiles[i][j]);
            }
        }
        window.draw(player);
        window.display();
    }
    return 0;
}
→ Ссылка