如何测试点是否在2D整数坐标中的凸多边形内?

时间:2009-07-13 14:02:41

标签: geometry polygon hittest

多边形作为Vector2I对象列表(2维,整数坐标)给出。如何测试给定点是否在内?我在网上找到的所有实现都失败了一些微不足道的反例。写一个正确的实现似乎很难。语言并不重要,因为我会自己移植它。

9 个答案:

答案 0 :(得分:23)

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

你可以用十字产品轻松检查(因为它与段和点之间形成的角度的余弦成正比,带有正号的那些将位于右侧,而那些带有负号的位于左侧)。

以下是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 = x_product(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 x_product(a, b):
    return a[0]*b[1]-a[1]*b[0]

答案 1 :(得分:14)

Ray Casting或Winding方法是此问题最常见的方法。有关详细信息,请参阅Wikipedia article

另外,请查看this page以获取C语言中详细记录的解决方案。

答案 2 :(得分:7)

如果多边形是凸的,那么在C#中,以下实现&#34; test if always on same side&#34;方法,并且最多在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 < polygon.Count - 1 ? i + 1 : 0;

        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 :(得分:3)

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

答案 4 :(得分:3)

除了我发现我必须翻译多边形以使你测试的点与原点相同时,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
}

答案 5 :(得分:2)

我知道的方式是这样的。

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

然后对于此多边形中的每一行,检查它们是否相交。

它们相交的线数总和给你内部与否。

如果是奇数:里面

如果是偶数:外面

答案 6 :(得分:1)

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

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

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

答案 7 :(得分:1)

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

要使用C#代码公开此内容,

save.execute

行列式演算,

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

答案 8 :(得分:0)

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

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