如何测试一个点是不是在二维整数坐标中的凸多边形内?

Posted

技术标签:

【中文标题】如何测试一个点是不是在二维整数坐标中的凸多边形内?【英文标题】:How to test if a point is inside of a convex polygon in 2D integer coordinates?如何测试一个点是否在二维整数坐标中的凸多边形内? 【发布时间】:2010-11-10 07:35:06 【问题描述】:

多边形以 Vector2I 对象列表的形式给出(二维,整数坐标)。我如何测试给定点是否在内部?我在网上找到的所有实现都因一些微不足道的反例而失败。编写正确的实现似乎真的很难。语言无关紧要,我会自己移植。

【问题讨论】:

一条评论。如果是面试问题,你应该得到一个 O(log n) 的解决方案,因为凸多边形是一个特例。使用二进制搜索以及 ufukgun 的答案中给出的想法。 这里的答案非常糟糕。 This article by Eric Haines 描述了许多方法,并提供了对众所周知的文本的参考。 Point in Polygon aka hit test的可能重复 【参考方案1】:

如果它是凸的,检查它的简单方法是该点位于所有线段的同一侧(如果以相同的顺序遍历)。

您可以使用点积轻松检查(因为它与线段和点之间形成的角度的余弦成正比,如果我们使用边缘的法线计算它,那些带有正号的将位于右侧和左侧带有负号的那些)。

这是 Python 中的代码:

RIGHT = "RIGHT"
LEFT = "LEFT"

def inside_convex_polygon(point, vertices):
    previous_side = None
    n_vertices = len(vertices)
    for n in xrange(n_vertices):
        a, b = vertices[n], vertices[(n+1)%n_vertices]
        affine_segment = v_sub(b, a)
        affine_point = v_sub(point, a)
        current_side = get_side(affine_segment, affine_point)
        if current_side is None:
            return False #outside or over an edge
        elif previous_side is None: #first segment
            previous_side = current_side
        elif previous_side != current_side:
            return False
    return True

def get_side(a, b):
    x = cosine_sign(a, b)
    if x < 0:
        return LEFT
    elif x > 0: 
        return RIGHT
    else:
        return None

def v_sub(a, b):
    return (a[0]-b[0], a[1]-b[1])

def cosine_sign(a, b):
    return a[0]*b[1]-a[1]*b[0]

【讨论】:

当有众所周知的解决方案时,将某些东西组合在一起几乎总是会错过一些边缘情况。 边缘上的点会发生什么?假设 k = 0,它应该给出 ZeroDivisionError。 @stefano 好吧,如果这是一种可能的情况,那么我们将不得不决定这意味着内部还是外部(边界是开放的还是封闭的) @fortran true,但您不认为在除以 abs(k) 之前进行测试以检查 k==0 是否是合适的,以避免错误吗? ^ 来自@jolly 的上述多边形渲染:wolframalpha.com/input/…【参考方案2】:

Ray Casting 或 Winding 方法是解决此问题的最常见方法。详情请见Wikipedia article。

另外,请查看 this page,了解 C 语言中记录良好的解决方案。

【讨论】:

对于整数坐标,wrf的代码sn -p就绰绰有余了。 这是最常见的...但是如果你已经知道多边形是凸的,像这种情况,fortran应该更快! @e-James C 解决方案的链接似乎已损坏【参考方案3】:

如果多边形是凸的,那么在C#中,下面实现了“test if always on same side”方法,并且最多在O(n个多边形点)处运行:

public static bool IsInConvexPolygon(Point testPoint, List<Point> polygon)

    //Check if a triangle or higher n-gon
    Debug.Assert(polygon.Length >= 3);

    //n>2 Keep track of cross product sign changes
    var pos = 0;
    var neg = 0;

    for (var i = 0; i < polygon.Count; i++)
    
        //If point is in the polygon
        if (polygon[i] == testPoint)
            return true;

        //Form a segment between the i'th point
        var x1 = polygon[i].X;
        var y1 = polygon[i].Y;

        //And the i+1'th, or if i is the last, with the first point
        var i2 = (i+1)%polygon.Count;

        var x2 = polygon[i2].X;
        var y2 = polygon[i2].Y;

        var x = testPoint.X;
        var y = testPoint.Y;

        //Compute the cross product
        var d = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1);

        if (d > 0) pos++;
        if (d < 0) neg++;

        //If the sign changes, then point is outside
        if (pos > 0 && neg > 0)
            return false;
    

    //If no change in direction, then on same side of all segments, and thus inside
    return true;

【讨论】:

对不起,如果这看起来有点迂腐,但如果列表的长度小于 3,你可能应该失败(甚至断言)。这是对多边形的测试,而不是查看是否点等于另一个点,或者一个点在一条线上。处理这些情况是一种很好的方式,可以让您日后感到非常头疼,因为您期望以一种方式进行的事情正在以另一种方式进行,而没有告诉您您做错了什么。此外,方法名称并不意味着它很好地涵盖了这些情况。 非常有帮助!如果这对任何人有帮助,我已经在另一个答案中修改并移植了该代码:***.com/a/48941131/516188 也许有人发现我的版本更清晰。 非常有帮助!我刚刚在我的自制游戏开发中测试了这个功能:Amiga 计算机的点击冒险。它开箱即用,转换成 C89,在旧的 68000 上编译和运行。谢谢! (C版在这里:github.com/ResistanceVault/rpage/blob/master/rpage/utils.c#L119)【参考方案4】:

openCV 中的 pointPolygonTest 函数“确定点是在轮廓内部、外部还是在边缘上”: http://docs.opencv.org/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=pointpolygontest#pointpolygontest

【讨论】:

OpenCV 是一个非常大的库。你真的不想仅仅为了这个而使用它。【参考方案5】:

fortran 的答案几乎对我有用,只是我发现我必须平移多边形,以便您正在测试的点与原点相同。这是我编写的用于完成这项工作的 javascript

function Vec2(x, y) 
  return [x, y]

Vec2.nsub = function (v1, v2) 
  return Vec2(v1[0]-v2[0], v1[1]-v2[1])

// aka the "scalar cross product"
Vec2.perpdot = function (v1, v2) 
  return v1[0]*v2[1] - v1[1]*v2[0]


// Determine if a point is inside a polygon.
//
// point     - A Vec2 (2-element Array).
// polyVerts - Array of Vec2's (2-element Arrays). The vertices that make
//             up the polygon, in clockwise order around the polygon.
//
function coordsAreInside(point, polyVerts) 
  var i, len, v1, v2, edge, x
  // First translate the polygon so that `point` is the origin. Then, for each
  // edge, get the angle between two vectors: 1) the edge vector and 2) the
  // vector of the first vertex of the edge. If all of the angles are the same
  // sign (which is negative since they will be counter-clockwise) then the
  // point is inside the polygon; otherwise, the point is outside.
  for (i = 0, len = polyVerts.length; i < len; i++) 
    v1 = Vec2.nsub(polyVerts[i], point)
    v2 = Vec2.nsub(polyVerts[i+1 > len-1 ? 0 : i+1], point)
    edge = Vec2.nsub(v1, v2)
    // Note that we could also do this by using the normal + dot product
    x = Vec2.perpdot(edge, v1)
    // If the point lies directly on an edge then count it as in the polygon
    if (x < 0)  return false 
  
  return true

【讨论】:

【参考方案6】:

我知道的方式是这样的。

您在多边形外的某处选择一个点,它可能远离几何体。 然后你从这一点画一条线。我的意思是你用这两个点创建一个线方程。

然后对于这个多边形中的每条线,检查它们是否相交。

相交线数的总和告诉你它是否在里面。

如果是奇数:在里面

如果是偶数:在外面

【讨论】:

我刚刚了解到:eJames 已经讲过的光线投射算法 我觉得你的解释很难理解......这条线的另一点是什么? 光线投射通常是一个糟糕的解决方案,它不能很好地处理投射光线靠近一侧的顶点附近的点。绕线规则更加稳健和快速,尤其是对于凸形 这个解决方案正是 WRF 的代码 sn-p 所做的。 "线的另一点是什么?"任何保证在多边形之外的点。例如:找到所有点的最小 x 和 y。选择 x-100,y-100 是多边形外的一个点。【参考方案7】:

您必须检查要测试的点是否保持其相对于凸多边形所有段的方向。如果是这样,它在里面。要对每个段执行此操作,请检查段向量的行列式是否为 AB 和点的向量是否为 AP 保留它的符号。如果行列式为零,则该点位于线段上。

要在 C# 代码中公开这一点,

  public bool IsPointInConvexPolygon(...)
  
     Point pointToTest = new Point(...);
     Point pointA = new Point(...);
     ....

     var polygon = new List<Point>  pointA, pointB, pointC, pointD ... ;
     double prevPosition = 0;
     // assuming polygon is convex.
     for (var i = 0; i < polygon.Count; i++)
     
        var startPointSegment = polygon[i];
        // end point is first point if the start point is the last point in the list
        // (closing the polygon)
        var endPointSegment = polygon[i < polygon.Count - 1 ? i + 1 : 0];
        if (pointToTest.HasEqualCoordValues(startPointSegment) ||
            pointToTest.HasEqualCoordValues(endPointSegment))
          return true;

        var position = GetPositionRelativeToSegment(pointToTest, startPointSegment, endPointSegment);
        if (position == 0) // point position is zero so we are on the segment, we're on the polygon.
           return true;

        // after we checked the test point's position relative to the first segment, the position of the point 
        // relative to all other segments must be the same as the first position. If not it means the point 
        // is not inside the convex polygon.
        if (i > 0 && prevPosition != position)
           return false;

        prevPosition = position;
     
     return true; 
  

行列式微积分,

  public double GetPositionRelativeToSegment(Point pointToTest, Point segmentStart, Point segmentEnd)
  
     return Math.Sign((pointToTest.X - segmentStart.X) * (segmentEnd.Y - segmentStart.Y) -
                (pointToTest.Y - segmentStart.Y) * (segmentEnd.X - segmentStart.X));
  

【讨论】:

【参考方案8】:

或者来自写这本书的人看到 - geometry page

特别是this page,他讨论了为什么缠绕规则通常比射线交叉更好。

编辑 - 抱歉,这不是写了出色书籍 Computational Geometry in C 的 Jospeh O'Rourke,而是 Paul Bourke,但仍然是非常非常好的几何算法来源。

【讨论】:

然后您引用的页面继续列出来自 WRF 的代码 sn-p。【参考方案9】:

这是我在项目中使用的版本。它非常优雅和简洁。适用于各种多边形。

http://www.eecs.umich.edu/courses/eecs380/HANDOUTS/PROJ2/InsidePoly.html

以下代码由 Randolph Franklin 编写,内部点返回 1,外部点返回 0。

int pnpoly(int npol, float *xp, float *yp, float x, float y)

  int i, j, c = 0;
  for (i = 0, j = npol-1; i < npol; 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;

【讨论】:

以上是关于如何测试一个点是不是在二维整数坐标中的凸多边形内?的主要内容,如果未能解决你的问题,请参考以下文章

已知一个多边形的所有顶点坐标,如果确定一点是不是在这个多边形内?

在百度地图上手绘了一个多边形,随便用鼠标点击地图获取改点坐标,并判断是不是属于在手绘的多边形内

如何判断点在一个区域内?用户绘制区域(射线法)判断点在多边形区域,报警区域

如何判断点在一个区域内?用户绘制区域(射线法)判断点在多边形区域,报警区域

急求在matlab判别点(x,y)是不是在凸多边形内 ,知道凸多边形的顶点坐标,做毕设卡着了,路过的救救呀,急求!

C语言,二维点按照x坐标大小排序题。