【问题标题】:Need help with implementing collision detection using the Separating Axis Theorem在使用分离轴定理实现碰撞检测方面需要帮助
【发布时间】:2010-06-01 02:07:24
【问题描述】:

所以,经过几个小时的谷歌搜索和阅读,我发现使用 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 和 asteroids[i].vertices 显然是各个子弹或小行星的每个顶点的向量列表。

老实说,我不是在寻找代码更正,而是在寻找新的眼睛。

【问题讨论】:

  • 到底是什么问题? realCollision 总是假的吗?你的边界框测试有效吗?我没有看到任何明显的东西,您应该将碰撞检测分离到一个单独的方法中,以便您可以对其进行单元测试。
  • 边界框碰撞有效,但 realCollision 几乎总是以 false 告终。
  • 用最新的代码更新了,又读了一篇文章,跟上重点。

标签: c++ collision-detection vector


【解决方案1】:

事实证明,我对这个定理的数学理解非常好。相反,问题在于我没有在顶点向量中包含多边形的中心点。

感谢大家的宝贵时间。

【讨论】:

    【解决方案2】:

    您添加了这个错误的vectOffset 部分 - 您的小行星和子弹的坐标系是相同的,对吧? (如果边界框测试有效,则必须如此。)

    你的小行星是正方形的吗?如果是这样,那么边界框测试将始终是精确的,realCollisioncollision 应该始终相同。如果不是,那么您没有正确构建 asteroidEdges - 您需要迭代顶点数,而不是 4 个。

    但是说真的,让这个代码成为一个单独的方法并为它编写一个单元测试,这是我可以运行你的代码来看看发生了什么的唯一方法。

    【讨论】:

    • 小行星都是4个顶点,但不是正方形。
    【解决方案3】:

    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));

    看起来您正在创建一个小行星克隆,在这种情况下,您希望子弹旋转,但此代码始终将子弹视为完全直立。会不会是你的问题?

    【讨论】:

    • 我没有想到,我会看看我能做什么。
    • 从头开始,子弹永远不会旋转。
    • 嗯,在这种情况下,我找不到您的代码有任何问题,除了我(如 Keith)不明白 vectOffset 应该做什么。您是否尝试过注释掉 float offset = ... 行和之后的两行?
    • 我已经删除了偏移量,它什么也没做。这是一个今天到期的项目,所以从这一点上我真的无能为力。幸运的是,这是一个 10 年级的 trig 课程,而不是会注意到碰撞检测差异的程序员课程(真的,我只需要解释这个定理,但展示它也会很好)。
    【解决方案4】:

    可能有助于找到问题的方法是让项目符号成为一个要点。它可能会说明代码其他部分的问题。另外,如果你的观点发生了碰撞,但子弹没有发生碰撞,你会得到一些具体的东西来看看。

    换句话说,简化您的问题,直到出现解决方案。 ;)

    【讨论】:

      【解决方案5】:

      除了有问题的整个偏移量之外,算法的其余部分似乎还可以。您是否尝试过跟踪以发现问题?

      顺便说一句,有几个风格怪癖使代码难以一目了然:

      • 为什么到处都是指针,而不是在堆栈上分配所有这些临时 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);
      }
      

      【讨论】:

      • object1 是顶点向量还是边向量?
      • 顶点。从中减去连续的顶点来构造边(Vector2D edge = object1[(i+1)%nume] - object1[i];)
      • 另外,如果不清楚,最后使用的方法是 isColliding,通过传递两个顶点列表(在您的情况下,每个顶点四个)。 checkCollisionOneSided 只是一个辅助方法。
      • 是的,看起来像。我基本上重新实现了你的代码,但是以一种自包含的方式,以便我可以编译和测试它。您可以尝试在游戏中同时调用我的代码和您的代码,并比较答案;如果他们不同意某个输入,您有一个具体的测试用例,您可以手动检查。如果他们都同意并且游戏仍然存在错误,您可能会查看其他代码中的错误(例如,边界框或中心未正确计算。)
      • 是的,我现在正在添加您的功能。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多