【问题标题】:Help with this algorithm帮助这个算法
【发布时间】:2011-03-27 10:46:50
【问题描述】:

我有一个算法可以判断一个点是否在多边形内。

 int CGlEngineFunctions::PointInPoly(int npts, float *xp, float *yp, float x, float y)
 {
     int i, j, c = 0;
     for (i = 0, j = npts-1; i < npts; j = i++) {
         if ((((yp[i] <= y) && (y < yp[j])) ||
             ((yp[j] <= y) && (y < yp[i]))) &&
             (x < (xp[j] - xp[i]) * (y - yp[i]) / (yp[j] - yp[i]) + xp[i]))
             c = !c;
     }
     return c;
 }

我唯一的问题是它假设了一个奇怪的缠绕规则。我的意思是,如果多边形是自相交的,那么它会被认为是“空”的某些部分将返回为假。即使它自相交,我也需要它,多边形内的任何东西都会返回 true。

谢谢

【问题讨论】:

  • 也许你可以重新排列点,这样你绘制它们的顺序就不会自相交?
  • 不,我的应用程序是矢量图形,它允许不同种类的缠绕规则
  • 该算法基于Jordon曲线定理。基本上有 2 个空间与有界对象相关联 - 进或出。从所讨论的点投射光线将与边界相交奇数或偶数次。如果它是奇数则该点在边界内,如果它是偶数则该点在边界外。此外,这个特定版本的算法假设基于投射的光线是水平的这一事实进行了简单的优化。

标签: c++ c algorithm


【解决方案1】:

小心:这个答案是错误的。我现在没有时间修复它,但请查看 cmets。

这会将光线从该点投射到无穷远,并检查与每个多边形边缘的交点。每次找到交叉点时,都会切换标志 c

c = !c;

所以偶数个交叉点意味着偶数个切换,所以c最后会是0。奇数个交叉点表示奇数个切换,因此c 将为 1。

如果发生 any 交叉点,您想要设置 c 标志:

c = 1;

为了更好的衡量,您可以完全消除c,并提前终止:

 int CGlEngineFunctions::PointInPoly(int npts, float *xp, float *yp, float x, float y)
 {
     int i, j;
     for (i = 0, j = npts-1; i < npts; j = i++) {
         if ((((yp[i] <= y) && (y < yp[j])) ||
             ((yp[j] <= y) && (y < yp[i]))) &&
             (x < (xp[j] - xp[i]) * (y - yp[i]) / (yp[j] - yp[i]) + xp[i]))
             return 1;
     }
     return 0;
 }

【讨论】:

  • 并通过返回true/false 来修复设计并将返回类型更改为bool。此外,npts(因此ij)应该是无符号的,因为有符号值没有意义。将其更改为size_t。最后,一个更 C++ 类型的接口将是一个模板函数,它将迭代器带到第一个和最后一个点(或者甚至更好,只是包装在一个类中的点的集合)。但我认为这是一次更大的重新设计。
  • 感谢 GMan 的提示,我有一个更加模板化和 C++ 版本的它,我只是想展示它以使其更容易理解
  • 哦,从编码的风格来看,我只是假设这是C......我猜第一行我并没有仔细看。
  • @Jex,如果该点位于整个多边形的左侧,我相信这个算法会失败。
  • @Mark:最好是加/减 1,这取决于 (x,y) 和无穷大之间的线是从左到右(从头到尾看)还是从右到左穿过边缘。然后奇数规则由 (c % 2) 计算,而零绕组规则是 (c != 0)。 (几乎)两个规则的计算相同!
【解决方案2】:

将您的原始算法翻译成英文:您正在确定点右侧的多边形段数是偶数还是奇数。如果它是偶数(包括零),那么你的观点在外面,如果它是奇怪的,你的观点在里面。这意味着如果右侧有两条线段,左侧也有两条线段,则该点考虑在多边形内。

你需要做的是改变算法,让它检查两边的段;如果点的两侧都有线段,则该点在多边形内。

 int CGlEngineFunctions::PointInPoly(int npts, float *xp, float *yp, float x, float y) 
 { 
     int i, j;
     bool hasSegmentLeft = false;
     bool hasSegmentRight = false;
     for (i = 0, j = npts-1; i < npts; j = i++) { 
         if ((((yp[i] <= y) && (y < yp[j])) || 
             ((yp[j] <= y) && (y < yp[i]))))
         {
             if (x < (xp[j] - xp[i]) * (y - yp[i]) / (yp[j] - yp[i]) + xp[i])
             {
                 hasSegmentRight = true;
                 if (hasSegmentLeft)  // short circuit early return
                     return true;
             }
             else
             {
                 hasSegmentLeft = true;
                 if (hasSegmentRight)  // short circuit early return
                     return true;
             }
     } 
     return hasSegmentLeft && hasSegmentRight; 
 }

附: for 语句构造是处理循环到开头的循环列表的一种非常聪明的方法;我以前从没见过。

【讨论】:

  • 如果 U 形多边形的点位于 U 形的碗中(在多边形之外,但在其凸包内),该怎么办?您会在两侧找到交点,但该点不在多边形内。
猜你喜欢
  • 2011-09-27
  • 2012-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-02
相关资源
最近更新 更多