代码之家  ›  专栏  ›  技术社区  ›  Eddie

需要使用分离轴定理实现碰撞检测的帮助

  •  5
  • Eddie  · 技术社区  · 15 年前

    因此,经过数小时的搜索和阅读,我发现使用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是子弹边缘的向量列表,小行星边缘是相似的,BullVerts和小行星是[i]的。显然,顶点是各个子弹或小行星的每个顶点的向量列表。

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

    5 回复  |  直到 15 年前
        1
  •  2
  •   Eddie    15 年前

    结果证明我对定理的数学理解是完全正确的。相反,问题在于我没有将多边形的中心点包括在垂直向量中。

    感谢大家抽出时间来。

        2
  •  0
  •   Keith Randall    15 年前

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

    你的小行星是方形的吗?如果是这样,那么边界框测试将始终是精确的,并且 realCollision collision 应该始终相同。如果不是,那你就不是在建造 asteroidEdges 正确-你需要迭代顶点的数量,而不是4个。

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

        3
  •  0
  •   Dave Kilian    15 年前

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

    看起来你正在创建一个小行星克隆体,在这种情况下,你会期望子弹被旋转,但是这个代码总是把子弹当作是完全直立的。那是你的问题吗?

        4
  •  0
  •   dash-tom-bang    15 年前

    有助于发现问题的是要使子弹成为一个重点。它可能会说明代码的其他部分的问题。另外,如果你的观点发生了碰撞,但子弹没有,你会看到一些具体的东西。

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

        5
  •  0
  •   user168715    15 年前

    除了整个偏移量,这是错误的,算法的其余部分 似乎 好啊。你试过通过追踪找出问题所在吗?

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

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