【问题标题】:Error, Implementing Winding Number Algorithm, (OpenGL, C++)错误,实现绕组数算法,(OpenGL,C++)
【发布时间】:2010-10-14 15:13:29
【问题描述】:

我正在尝试实现绕组数算法来测试一个点是否在另一个多边形内。虽然我的算法的结果是错误的并且不一致。我已经为此工作了很长时间,这已经变得有点痛苦了!

我基本上已经从笔记和网站上转换了伪代码,比如softsurfer.com

我成功检测到我的播放器和建筑对象边界框是否重叠。我将结果返回给一个结构,(BoxResult),它让我知道是否发生了碰撞并返回与它碰撞的框(下)

struct BoxResult{
bool collide;
Building returned;
};

void buildingCollision(){
int wn = 0;                         //winding number count
BoxResult detect = boxDetection();  //detect if any bounding boxes intersect
    if(detect.collide){ //If a bounding box has collided, excute Winding Number Algorithm.
        for(int i = 0; i < player.getXSize(); i++){
            Point p;
            p.x = player.getXi(i);
            p.y = player.getYi(i);
            wn = windingNum(detect.returned,p);
            cout << wn << endl;
            //Continue code to figure out rebound reaction
        }
    }
}

然后我测试建筑物和玩家之间的碰撞(下)。我已经尝试了 5 次不同的尝试和数小时的调试来了解错误发生的位置,但是我正在实施仅使用数学的最​​无效的方法(如下)。

      int windingNum(Building & b, Point & p){
int result = 0;             //Winding number is one, if point is in poly
float total;            //Counts the total angle between different vertexs
double wn;

    for(int i = 0; i <= b.getXSize()-1;i++){
    float acs, nom, modPV, modPV1, denom, angle;
        if(i ==  3){
                //Create the different points PVi . PVi+1
                Point PV, PV1;
                PV.x = (b.getXi(i) + wx) * p.x; 
                PV.y = (b.getYi(i) + wy) * p.y;
                PV1.x = (b.getXi(0) + wx) * p.x; 
                PV1.y = (b.getYi(0) + wy) * p.y;

                modPV = sqrt( (PV.x * PV.x) + (PV.y * PV.y));       //Get the modulus of PV
                modPV1 = sqrt( (PV1.x * PV1.x) + (PV1.y * PV1.y));  //Get modulus of PV1

                nom = (PV1.x * PV.x) + (PV1.y * PV.y);              //Dot product of PV and PV1
                denom = modPV * modPV1;     //denomintor of winding number equation
                angle = nom / denom;
                acs = acos(angle) * 180/PI;     //find the angle between the different points
                total = total + acs;        //add this angle, to the total angle count
            }
            if(i < 3){
                //Create the different points PVi . PVi+1
                Point PV, PV1;
                PV.x = (b.getXi(i) + wx) * p.x; 
                PV.y = (b.getYi(i) + wy) * p.y;
                PV1.x = (b.getXi(i+1) +wx) * p.x; 
                PV1.y = (b.getYi(i+1) +wy) * p.y;

                modPV = sqrt((PV.x * PV.x) + (PV.y * PV.y));        //Get the modulus of PV
                modPV1 = sqrt((PV1.x * PV1.x) + (PV1.y * PV1.y));   //Get modulus of PV1

                nom = (PV1.x * PV.x) + (PV1.y * PV.y);              //Dot product of PV and PV1
                denom = modPV * modPV1;     //denomintor of winding number equation
                angle = nom / denom;
                acs = acos(angle) * 180/PI;  //find the angle between the different points
                total = total + acs;        //add this angle, to the total angle count
                }
    }

    wn = total;
    if(wn < 360){
        result = 0;}
    if(wn == 360){
        result = 1;}

    return result;
}

由于我不明白的原因 acs = acos(angle) 总是返回 1.#IND000。 顺便说一句,你知道,我只是在另一个方块上测试算法,因此如果 i == 3 和 if i

如果你需要知道这些,wy 和 wx 是被翻译的世界坐标。从而将玩家移动到世界各地,例如为了让玩家向前移动,所有东西都用 wy 的负数转换。

此外,Building 对象看起来类似于以下结构:

struct Building {
vector<float> x;   //vector storing x co-ords
vector<float> y;   //vector storing y co-ords
float ymax, ymin, xmax, xmin //values for bounding box
vector<int> polygons; //stores the number points per polygon (not relevant to the problem)
}

如果有人可以提供帮助,我将不胜感激!我只是希望我能看到哪里出了问题! (我相信所有程序员都曾说过,哈哈)感谢您的阅读...

【问题讨论】:

    标签: c++ opengl graphics geometry


    【解决方案1】:

    计算 PV 和 PV1 模数的两条线不正确。他们应该是

    modPV  = sqrt(PV.x  * PV.x  + PV.y  * PV.y );
    modPV1 = sqrt(PV1.x * PV1.x + PV1.y * PV1.y);
    

    这样能解决问题吗?

    【讨论】:

    • 您好,这已经解决了有关计算不正确模数的问题,但是我仍然遇到其他问题。我已经对代码进行了编辑,并在第一篇文章中修改了我的代码副本。由于空间不足,我将在另一条评论中解释!
    • 出现两个主要问题,当点 p 在 x 轴上与建筑物 b 的边界框相交时,算法返回该点在 b 内。尽管有很大的差距。此外,在 y 轴上,它似乎只检测原点是否在建筑物 b 内。这没有意义!
    • PV 是从 P 到 V 的向量,不是吗?如果是这样,那么您需要从 p.x 中减去 (b.getXi(i) + wx) 等。顺便说一句,我刚刚注意到您可以在 denom 中保存一个 sqrt。只需将 PV 和 PV1 的平方和相乘,然后在最后取 sqrt。
    • 我已经尝试过,正如 Troubadour 所建议的那样,但这不起作用并且总是返回 0。绕组数公式:1/2PI * Acos 之和(PVi . PVi+1/ |PVi| |PVi +1|) 对不起,我不确定如何在此处写上求和符号。还有其他想法吗?
    • 嗯,softsurfer 上的描述确实说它是您需要的 signed 角度。记住 acos 只会给你一个介于 0 和 pi 之间的角度。此外,我对检查是否与 360 完全相等持怀疑态度。如果 ( abs(wn) >= 360 ) result = 1;因为结果被初始化为0?
    【解决方案2】:

    我可能不明白您的问题/问题,但这里有一个简单而可靠的多边形测试点:PNPOLY

    【讨论】:

    • 信不信由你,但我已经尝试在 poly test 中做到这一点!大声笑我将在下面对我的问题的另一个答案中发布代码......
    【解决方案3】:

    关于你的交叉数算法的实现,第一个明显的错误是你没有遍历所有边。你是个矮人。您应该循环到 i

    int ip1 = ( i + 1 )  % n;
    

    这当然也适用于您原始问题中的代码,以免您必须拥有两份代码。

    第二个是那个

    rem = cn % 1;
    

    没有效果。 softsurfer 上的代码很好,即

    rem = (cn&1);
    

    它试图通过测试最后一位是否设置来检测 cn 是奇数还是偶数。如果你想使用模运算符 % 进行相同的测试,那么你应该把它写成

    rem = cn % 2;
    

    因为将 cn 除以 2 的余数分配给 rem。

    除此之外,我还没有查看是否还有其他问题。

    【讨论】:

    • 啊,我知道我犯了一些愚蠢的错误。我已经按照您的建议进行了更正,但是 if 语句永远不会评估为真。我将在另一个答案中再次发布我的代码,并进行新的更改。再次感谢您发现这些错误,非常感谢!
    【解决方案4】:

    我已经放弃了缠绕数字代码,它真的让我着迷!如果有人确实找到了解决方案,我仍然会非常感激。我现在正在尝试使用交叉数算法进行多点检测。我将伪代码保存在 cmets 中,再次来自 softsurfer....

    int cn_PnPoly( Point P, Building & b, int n )
    {
        int    cn = 0;    // the crossing number counter
        int     rem = 0;
        vector<float>x;
        vector<float>y;
        x.swap(b.getX());
        y.swap(b.getY());
        //// loop through all edges of the polygon
        //for (int i=0; i<n; i++) {    // edge from V[i] to V[i+1]
        //   if (((V[i].y <= P.y) && (V[i+1].y > P.y))    // an upward crossing
        //    || ((V[i].y > P.y) && (V[i+1].y <= P.y))) { // a downward crossing
        //        // compute the actual edge-ray intersect x-coordinate
        //        float vt = (float)(P.y - V[i].y) / (V[i+1].y - V[i].y);
        //        if (P.x < V[i].x + vt * (V[i+1].x - V[i].x)) // P.x < intersect
        //            ++cn;   // a valid crossing of y=P.y right of P.x
        //    }
        //}
        //return (cn&1);    // 0 if even (out), and 1 if odd (in)
    
    
            // loop through all edges of the polygon
        for (int i=0; i<n-1; i++) {    // edge from V[i] to V[i+1]
            if (((y.at(i) <= P.y) && (y.at(i+1) > P.y))    // an upward crossing
                || ((y.at(i) > P.y) && (y.at(i+1) <= P.y))) { // a downward crossing
                // compute the actual edge-ray intersect x-coordinate
                    float vt = (float)(P.y - y.at(i)) / (y.at(i+1) - y.at(i));
                    if (P.x < x.at(i) + vt * (x.at(i+1) - x.at(i))) // P.x < intersect
                    ++cn;   // a valid crossing of y=P.y right of P.x
            }
        }
        rem = cn % 1;
        return (rem);    // 0 if even (out), and 1 if odd (in)
    }
    

    这总是返回零,我不确定为什么!?!我是否错误地转换了算法?测试点的方向是否重要(即顺时针,逆时针)?

    【讨论】:

    • 我只能说你真的应该试着去理解这个算法。完成后,您将能够调试代码。毕竟,这只是基本几何+常识。
    【解决方案5】:

    我已尝试按照 audris 的建议实施 PNPOLY。然而,这给出了一些有趣的结果。 下面是原始的 C 代码,下面是我为我的应用程序转换的代码......

    原始C代码...

    int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
    {
      int i, j, c = 0;
      for (i = 0, j = nvert-1; i < nvert; j = i++) {
        if ( ((verty[i]>testy) != (verty[j]>testy)) &&
         (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
           c = !c;
      }
      return c;
    }
    

    我的代码....

    其中 wx 和 wy 是全局坐标。

    int pnpoly(int nvert, vector<float> vertx, vector<float> verty, float testx, float testy)
    {
      int i, j, c = 0;
      for (i = 0, j = nvert-1; i < nvert; j = i++) {
          if ( (( (verty.at(i)+wy) > testy) != ( (verty.at(j)+wy) >testy)) &&
            (testx < ((vertx.at(j)+wx) - (vertx.at(i)+wx) ) * (testy- (verty.at(i)+wy) ) / ( (verty.at(j)+wy) - (verty.at(i)+wy)) + (vertx.at(i)+wx)) )
           c++;
      }
      return c;
    }
    

    我正在针对 2D 方形建筑测试玩家对象。这也会返回奇怪的结果,当我达到底线(xmin,ymin 到 xmax,ymin)它工作正常。如果我击中两边的 ethier(xmin,ymin 到 xmin,ymax 或 xmax,ymin 到 xmax,ymax),则仅当玩家距离起点点太远时它才会返回 1。同样在玩家进入边界框的一侧(xmin,ymin 到 xmin,ymax),尽管击中多边形,算法仍返回 2。在顶部,(xmin,ymax to xmax,ymax) 仅当玩家完全在多边形中时才返回 1。

    我还传递了来自 Building 对象的两个向量 x 和 y,向量大小为 int nvert。这可能与播放器对象的标题有关吗?在算法中是如何计算的?

    【讨论】:

      【解决方案6】:

      您已按照 Troubadour 对交叉数算法的建议进行了一些更改,但由于某种原因,if 语句永远不会返回 true。我发布的新代码如下。顺便说一句,再次感谢大家的回复:-)

      int cn_PnPoly( Point P, Building & b, int n )
      {
          int    cn = 0;    // the crossing number counter
          int     rem = 0;
          vector<float>x;
          vector<float>y;
          x.swap(b.getX());
          y.swap(b.getY());
          //// loop through all edges of the polygon
          //for (int i=0; i<n; i++) {    // edge from V[i] to V[i+1]
          //   if (((V[i].y <= P.y) && (V[i+1].y > P.y))    // an upward crossing
          //    || ((V[i].y > P.y) && (V[i+1].y <= P.y))) { // a downward crossing
          //        // compute the actual edge-ray intersect x-coordinate
          //        float vt = (float)(P.y - V[i].y) / (V[i+1].y - V[i].y);
          //        if (P.x < V[i].x + vt * (V[i+1].x - V[i].x)) // P.x < intersect
          //            ++cn;   // a valid crossing of y=P.y right of P.x
          //    }
          //}
          //return (cn&1);    // 0 if even (out), and 1 if odd (in)
      
      
              // loop through all edges of the polygon
          for (int i=0; i<n; i++) {    // edge from V[i] to V[i+1]
          int ip1 = (i +1) %n;
              if (((y.at(i) <= P.y) && (y.at(ip1) > P.y))    // an upward crossing
                  || ((y.at(i) > P.y) && (y.at(ip1) <= P.y))) { // a downward crossing
                  // compute the actual edge-ray intersect x-coordinate
                      float vt = (float)(P.y - y.at(i)) / (y.at(ip1) - y.at(i));
                      if (P.x < x.at(i) + vt * (x.at(ip1) - x.at(i))) // P.x < intersect
                      ++cn;   // a valid crossing of y=P.y right of P.x
              }
          }
          rem =  (cn&1);
          return (rem);    // 0 if even (out), and 1 if odd (in)
      }
      

      【讨论】:

        【解决方案7】:

        下面我更正了代码,我忘了添加世界坐标。又一个愚蠢的错误......

        int cn_PnPoly( Point P, Building & b, int n )
        {
            int    cn = 0;    // the crossing number counter
            int     rem = 0;
            vector<float>x;
            vector<float>y;
            x.swap(b.getX());
            y.swap(b.getY());
        
                // loop through all edges of the polygon
            for (int i=0; i<n; i++) {    // edge from V[i] to V[i+1]
            int ip1 = (i +1) %n;
                if (((  (y.at(i)+wy) <= P.y) && ( (y.at(ip1)+wy) > P.y))    // an upward crossing
                    || ((  (y.at(i)+wy) > P.y) && ( (y.at(ip1)+wy) <= P.y))) { // a downward crossing
                    // compute the actual edge-ray intersect x-coordinate
                        float vt = (float)(P.y - (y.at(i)+wy) ) / ( (y.at(ip1)+wy) - (y.at(i)+wy) );
                        if (P.x < (x.at(i)+wx) + vt * ( (x.at(ip1)+wx) - (x.at(i)+wx) )) // P.x < intersect
                        ++cn;   // a valid crossing of y=P.y right of P.x
                }
            }
            rem =  (cn&1);
            return (rem);    // 0 if even (out), and 1 if odd (in)
        }
        

        虽然这可以检测点何时在多边形中,但它不会考虑玩家的当前航向。

        如果这没有意义,在 2D 游戏中,我通过按世界坐标平移所有多边形来围绕玩家移动世界地图。这些是游戏中的wx和wy。 我还围绕一个标题变量旋转播放器。

        这些是在绘图函数中计算出来的,但是碰撞检测函数不考虑航向。为此,我只需将 Building 对象给出的 x 和 y 坐标乘以标题?不幸的是我不太擅长几何。

        【讨论】:

        • 对于每一帧,复制对象未转换的几何体,并将该副本转换为世界空间,就像渲染一样。将该转换后的副本也用于碰撞检测等。处理几何的函数可以假设它已经在世界空间中,即删除 wx/wy。
        • 啊,我想我明白了。因此,基本上对于 Building 对象的每个点,我都需要对其进行转换,其量与我转换玩家航向的量相同。应该没问题,我有代码可以在我的代码中进一步转换多边形。我将发布完成的解决方案(希望可以工作!)
        • 啊,我现在完全明白了!我已经完成了我的 2D 游戏,稍后将发布这两种算法的解决方案,并更简单地解释它们是如何工作的以及当我有时间时该怎么做。
        猜你喜欢
        • 2015-06-19
        • 1970-01-01
        • 2014-03-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-03
        • 1970-01-01
        相关资源
        最近更新 更多