Нужна помощь в реализации обнаружения столкновений с использованием теоремы о разделяющей оси - PullRequest
5 голосов
/ 01 июня 2010

Итак, после нескольких часов поиска и поиска в Google, я обнаружил, что основной процесс обнаружения столкновения с использованием SAT:

for each edge of poly A
    project A and B onto the normal for this edge
    if intervals do not overlap, return false
end for

for each edge of poly B
    project A and B onto the normal for this edge
    if intervals do not overlap, return false
end for

Однако, так как я пытаюсь реализовать это в коде, я просто не могу заставить его обнаруживать столкновение. Мой текущий код выглядит следующим образом:

for (unsigned int i = 0; i < asteroids.size(); i++) {
    if (asteroids.valid(i)) {
        asteroids[i]->Update();

        // Player-Asteroid collision detection
        bool collision = true;
        SDL_Rect asteroidBox = asteroids[i]->boundingBox;

        // Bullet-Asteroid collision detection
        for (unsigned int j = 0; j < player.bullets.size(); j++) {
            if (player.bullets.valid(j)) {
                Bullet b = player.bullets[j];

                collision = true;
                if (b.x + (b.w / 2.0f) < asteroidBox.x - (asteroidBox.w / 2.0f)) collision = false;
                if (b.x - (b.w / 2.0f) > asteroidBox.x + (asteroidBox.w / 2.0f)) collision = false;
                if (b.y - (b.h / 2.0f) > asteroidBox.y + (asteroidBox.h / 2.0f)) collision = false;
                if (b.y + (b.h / 2.0f) < asteroidBox.y - (asteroidBox.h / 2.0f)) collision = false;

                if (collision) {
                    bool realCollision = false;

                    float min1, max1, min2, max2;

                    // Create a list of vertices for the bullet
                    CrissCross::Data::LList<Vector2D *> bullVerts;
                    bullVerts.insert(new Vector2D(b.x - b.w / 2.0f, b.y + b.h / 2.0f));
                    bullVerts.insert(new Vector2D(b.x - b.w / 2.0f, b.y - b.h / 2.0f));
                    bullVerts.insert(new Vector2D(b.x + b.w / 2.0f, b.y - b.h / 2.0f));
                    bullVerts.insert(new Vector2D(b.x + b.w / 2.0f, b.y + b.h / 2.0f));
                    // Create a list of vectors of the edges of the bullet and the asteroid
                    CrissCross::Data::LList<Vector2D *> bullEdges;
                    CrissCross::Data::LList<Vector2D *> asteroidEdges;
                    for (int k = 0; k < 4; k++) {
                        int n = (k == 3) ? 0 : k + 1;
                        bullEdges.insert(new Vector2D(bullVerts[k]->x - bullVerts[n]->x,
                                                bullVerts[k]->y - bullVerts[n]->y));
                        asteroidEdges.insert(new Vector2D(asteroids[i]->vertices[k]->x - asteroids[i]->vertices[n]->x,
                                                    asteroids[i]->vertices[k]->y - asteroids[i]->vertices[n]->y));
                    }

                    Vector2D *vectOffset = new Vector2D(asteroids[i]->center.x - b.x, asteroids[i]->center.y - b.y);

                    for (unsigned int k = 0; k < asteroidEdges.size(); k++) {
                        Vector2D *axis = asteroidEdges[k]->getPerpendicular();
                        axis->normalize();
                        min1 = max1 = axis->dotProduct(asteroids[i]->vertices[0]);
                        for (unsigned int l = 1; l < asteroids[i]->vertices.size(); l++) {
                            float test = axis->dotProduct(asteroids[i]->vertices[l]);
                            min1 = (test < min1) ? test : min1;
                            max1 = (test > max1) ? test : max1;
                        }
                        min2 = max2 = axis->dotProduct(bullVerts[0]);
                        for (unsigned int l = 1; l < bullVerts.size(); l++) {
                            float test = axis->dotProduct(bullVerts[l]);
                            min2 = (test < min2) ? test : min2;
                            max2 = (test > max2) ? test : max2;
                        }
                        float offset = axis->dotProduct(vectOffset);
                        min1 += offset;
                        max1 += offset;
                        delete axis; axis = NULL;
                        float d0 = min1 - max2;
                        float d1 = min2 - max1;
                        if ( d0 > 0 || d1 > 0 ) {
                            realCollision = false;
                            break;
                        } else {
                            realCollision = true;
                        }
                    }

                    if (realCollision) {
                        for (unsigned int k = 0; k < bullEdges.size(); k++) {
                            Vector2D *axis = bullEdges[k]->getPerpendicular();
                            axis->normalize();
                            min1 = max1 = axis->dotProduct(asteroids[i]->vertices[0]);
                            for (unsigned int l = 1; l < asteroids[i]->vertices.size(); l++) {
                                float test = axis->dotProduct(asteroids[i]->vertices[l]);
                                min1 = (test < min1) ? test : min1;
                                max1 = (test > max1) ? test : max1;
                            }
                            min2 = max2 = axis->dotProduct(bullVerts[0]);
                            for (unsigned int l = 1; l < bullVerts.size(); l++) {
                                float test = axis->dotProduct(bullVerts[l]);
                                min2 = (test < min2) ? test : min2;
                                max2 = (test > max2) ? test : max2;
                            }
                            float offset = axis->dotProduct(vectOffset);
                            min1 += offset;
                            max1 += offset;
                            delete axis; axis = NULL;
                            float d0 = min1 - max2;
                            float d1 = min2 - max1;
                            if ( d0 > 0 || d1 > 0 ) {
                                realCollision = false;
                                break;
                            } else {
                                realCollision = true;
                            }
                        }
                    }
                    if (realCollision) {
                        player.bullets.remove(j);

                        int numAsteroids;
                        float newDegree;
                        srand ( j + asteroidBox.x );
                        if ( asteroids[i]->degree == 90.0f ) {
                            if ( rand() % 2 == 1 ) {
                                numAsteroids = 3;
                                newDegree = 30.0f;
                            } else {
                                numAsteroids = 2;
                                newDegree = 45.0f;
                            }
                            for ( int k = 0; k < numAsteroids; k++)
                                asteroids.insert(new Asteroid(asteroidBox.x + (10 * k), asteroidBox.y + (10 * k), newDegree));
                        }
                        delete asteroids[i];
                        asteroids.remove(i);
                    }
                    while (bullVerts.size()) {
                        delete bullVerts[0];
                        bullVerts.remove(0);
                    }
                    while (bullEdges.size()) {
                        delete bullEdges[0];
                        bullEdges.remove(0);
                    }
                    while (asteroidEdges.size()) {
                        delete asteroidEdges[0];
                        asteroidEdges.remove(0);
                    }

                    delete vectOffset; vectOffset = NULL;
                }
            }
        }
    }
}

bullEdges - это список векторов краев пули, asteroidEdges аналогичен, а bullVerts и астероиды [i] .vertices, очевидно, являются списками векторов каждой вершины для соответствующей пули или астероида.

Честно говоря, я не ищу исправления кода, просто свежий взгляд.

Ответы [ 5 ]

2 голосов
/ 02 июня 2010

Оказывается, мое математическое понимание теоремы было совершенно в порядке. Вместо этого проблема заключалась в том, что я не включал центральные точки многоугольников в векторы вершин.

Спасибо всем за уделенное время.

0 голосов
/ 02 июня 2010

Помимо всего смещения, которое глючит, остальная часть алгоритма кажется ОК. Вы пытались отследить его, чтобы определить проблему?

Кстати, есть несколько стилистических изысков, которые затрудняют чтение кода с первого взгляда:

  • Почему указатели повсюду, вместо размещения всех этих временных Vector2D в стеке?
  • Почему CrissCross::Data::LList вместо "старого доброго" std::vector?
  • Наверняка Vector2D имеет перегруженный оператор -?

Вот краткая и самодостаточная реализация алгоритма. Я несколько проверил это, но не даю никаких гарантий:

#include <vector>
#include <limits>

using namespace std;

class Vector2D
{
public:
  Vector2D() : x(0), y(0) {}
  Vector2D(double x, double y) : x(x), y(y) {}

  Vector2D operator-(const Vector2D &other) const
  {
    return Vector2D(x - other.x, y - other.y);
  }

  double dot(const Vector2D &other) const
  {
    return x * other.x + y*other.y;
  }

  Vector2D perp() const
  {
    return Vector2D(-y, x);
  }

  double x,y;
};

bool checkCollisionOneSided(vector<Vector2D> &object1, vector<Vector2D> &object2)
{
  int nume = object1.size();
  for(int i=0; i<nume; i++)
    {
      Vector2D edge = object1[(i+1)%nume] - object1[i];
      Vector2D normal = edge.perp();

      double min1 = numeric_limits<double>::infinity();
      double min2 = min1;
      double max1 = -numeric_limits<double>::infinity();
      double max2 = max1;

      for(int j=0; j<object1.size(); j++)
    {
      double dot = normal.dot(object1[j]);
      min1 = std::min(min1, dot);
      max1 = std::max(max1, dot);
    }
      for(int j=0; j<object2.size(); j++)
    {
      double dot = normal.dot(object2[j]);
      min2 = std::min(min2, dot);
      max2 = std::max(max2, dot);
    }

      if(min2 > max1 || min1 > max2)
    return false;
    }
  return true;
}

bool isColliding(vector<Vector2D> &object1, vector<Vector2D> &object2)
{
  return checkCollisionOneSided(object1, object2) && checkCollisionOneSided(object2, object1);
}
0 голосов
/ 01 июня 2010

bullVerts.insert(new Vector2D(b.x - b.w / 2.0f, b.y + b.h / 2.0f)); bullVerts.insert(new Vector2D(b.x - b.w / 2.0f, b.y - b.h / 2.0f)); bullVerts.insert(new Vector2D(b.x + b.w / 2.0f, b.y - b.h / 2.0f)); bullVerts.insert(new Vector2D(b.x + b.w / 2.0f, b.y + b.h / 2.0f));

Похоже, вы создаете клон астероидов, и в этом случае вы ожидаете, что пуля будет вращаться, но этот код всегда обрабатывает пулю, как если бы она была полностью вертикальной. Может ли это быть вашей проблемой?

0 голосов
/ 01 июня 2010

Что-то, что может помочь найти проблему, состоит в том, чтобы сделать точку пули. Это может осветить проблемы с другими частями вашего кода. Плюс, тогда, если ваша точка столкнется, но пуля не даст, вы увидите что-то конкретное.

Другими словами, упрощайте вашу проблему, пока не появится решение. ;)

0 голосов
/ 01 июня 2010

Вы добавили эту vectOffset часть, что неправильно - системы координат ваших астероидов и пуль одинаковы, верно? (Должно быть, если тест ограничительной рамки работает.)

Ваши квадраты астероидов? Если это так, то тест ограничивающего прямоугольника всегда будет точным, а realCollision и collision всегда должны быть идентичными. Если нет, значит, вы неправильно строите asteroidEdges - вам нужно перебирать количество вершин, а не 4.

А если серьезно, сделайте этот код отдельным методом и напишите для него модульный тест, это единственный способ запустить ваш код, чтобы увидеть, что происходит.

...