如何有效地确定多边形是凸的,非凸的还是复杂的?

时间:2009-01-23 05:16:49

标签: algorithm geometry polygon computational-geometry xlib

来自XFillPolygon的手册页:

  
      
  • 如果shape 复杂,则路径可能会自相交。请注意,路径中的连续重合点不会被视为自相交。

  •   
  • 如果shape 凸面,则对于多边形内的每对点,连接它们的线段不会与路径相交。如果客户已知,指定凸面可以提高性能。如果为非凸的路径指定凸面,则图形结果未定义。

  •   
  • 如果shape Nonconvex ,则路径不会自相交,但形状不是完全凸的。如果客户已知,指定 Nonconvex 而不是复杂可以提高效果。如果为自相交路径指定 Nonconvex ,则图形结果未定义。

  •   

我遇到填充XFillPolygon的性能问题,并且正如手册页所示,我要采取的第一步是指定多边形的正确形状。我目前正在使用复杂来保证安全。

是否有一种有效的算法来确定多边形(由一系列坐标定义)是凸面,非凸面还是复杂?

10 个答案:

答案 0 :(得分:102)

你可以比礼物包装算法更容易......当你有一组没有任何特定边界的点并且需要找到凸包时,这是一个很好的答案。

相反,考虑多边形不是自相交的情况,它由列表中的一组点组成,其中连续点形成边界。在这种情况下,更容易弄清楚多边形是否凸起(并且您也不必计算任何角度):

对于多边形的每个连续边对(每个点的三元组),计算由按指令递增顺序指向点的边所定义的向量的叉积的z分量。取这些载体的叉积:

 given p[k], p[k+1], p[k+2] each with coordinates x, y:
 dx1 = x[k+1]-x[k]
 dy1 = y[k+1]-y[k]
 dx2 = x[k+2]-x[k+1]
 dy2 = y[k+2]-y[k+1]
 zcrossproduct = dx1*dy2 - dy1*dx2

如果交叉积的z分量全部为正或全为负,则多边形为凸。否则多边形是非凸的。

如果有N个点,请确保计算N个交叉产品,例如:一定要使用三元组(p [N-2],p [N-1],p [0])和(p [N-1],p [0],p [1])。


如果多边形是自相交的,那么it fails the technical definition of convexity即使其定向角度都在同一方向,在这种情况下,上述方法也不会产生正确的结果。

答案 1 :(得分:23)

Stackoverflow不会让我删除已接受的答案,但我会结帐Rory Daulton's answer

答案 2 :(得分:14)

当您搜索"确定凸多边形时,此问题现在是Bing或Google中的第一个项目。"但是,没有一个答案是足够好的。

accepted answer by @EugeneYokota 通过检查无序点集是否可以制作凸多边形来工作,但这不是OP所要求的。他要求一种方法来检查给定的多边形是否凸起。 (A"多边形"在计算机科学中通常被定义为[在XFillPolygon documentation]中作为2D点的有序数组,连续点与一侧连接,最后一点连接到第一点。)此外,在这种情况下,礼品包装算法的O(n^2)点的时间复杂度为n - 这比解决此问题实际需要的要大得多,而问题要求有效算法

@JasonS's answer ,以及其后的其他答案,接受star polygons,例如pentagram或@ zenna' s注释,但星形多边形不被认为是凸的。如 @plasmacel在注释中注明,如果您事先知道多边形不是自相交的,那么这是一个很好的方法,但如果您没有这些知识,它可能会失败。

@Sekhat's answer 是正确的,但它的时间复杂度为O(n^2),因此效率低下。

编辑后,

@LorenPechtel's added answer 是最好的,但它很模糊。

具有最佳复杂度的正确算法

我在这里提出的算法具有O(n)的时间复杂度,正确地测试多边形是否是凸的,并且传递了我抛出的所有测试。想法是遍历多边形的边,注意每一边的方向和连续边之间的方向的有符号变化。 &#34;签名&#34;这里的意思是左边是正面,右边是负面(或反面),直线是零。这些角度归一化为在负-pi(不包括)和pi(包括)之间。 求和所有这些方向变化角度(又名偏转角度)在一起将导致正或负一转(即对于凸多边形,360 度),而星形多边形(或自交叉环)将具有不同的总和( n * 360 度,对于 n <对于所有偏转角度符号相同的多边形,/ em>整体转动。所以我们必须检查方向变化角度的总和是正负一转。我们还检查方向变化角度是全部是正的还是全部是负的而不是反转(pi弧度),所有点都是实际的2D点,并且没有连续的顶点是相同的。 (最后一点是有争议的 - 您可能希望允许重复顶点,但我更愿意禁止它们。)这些检查的组合捕获所有凸多边形和非凸多边形。

以下是Python 3的代码,它实现了算法并包含一些小的效率。由于注释行和簿记涉及避免重复点访问,代码看起来比实际更长。

TWO_PI = 2 * pi

def is_convex_polygon(polygon):
    """Return True if the polynomial defined by the sequence of 2D
    points is 'strictly convex': points are valid, side lengths non-
    zero, interior angles are strictly between zero and a straight
    angle, and the polygon does not intersect itself.

    NOTES:  1.  Algorithm: the signed changes of the direction angles
                from one side to the next side must be all positive or
                all negative, and their sum must equal plus-or-minus
                one full turn (2 pi radians). Also check for too few,
                invalid, or repeated points.
            2.  No check is explicitly done for zero internal angles
                (180 degree direction-change angle) as this is covered
                in other ways, including the `n < 3` check.
    """
    try:  # needed for any bad points or direction changes
        # Check for too few points
        if len(polygon) < 3:
            return False
        # Get starting information
        old_x, old_y = polygon[-2]
        new_x, new_y = polygon[-1]
        new_direction = atan2(new_y - old_y, new_x - old_x)
        angle_sum = 0.0
        # Check each point (the side ending there, its angle) and accum. angles
        for ndx, newpoint in enumerate(polygon):
            # Update point coordinates and side directions, check side length
            old_x, old_y, old_direction = new_x, new_y, new_direction
            new_x, new_y = newpoint
            new_direction = atan2(new_y - old_y, new_x - old_x)
            if old_x == new_x and old_y == new_y:
                return False  # repeated consecutive points
            # Calculate & check the normalized direction-change angle
            angle = new_direction - old_direction
            if angle <= -pi:
                angle += TWO_PI  # make it in half-open interval (-Pi, Pi]
            elif angle > pi:
                angle -= TWO_PI
            if ndx == 0:  # if first time through loop, initialize orientation
                if angle == 0.0:
                    return False
                orientation = 1.0 if angle > 0.0 else -1.0
            else:  # if other time through loop, check orientation is stable
                if orientation * angle <= 0.0:  # not both pos. or both neg.
                    return False
            # Accumulate the direction-change angle
            angle_sum += angle
        # Check that the total number of full turns is plus-or-minus 1
        return abs(round(angle_sum / TWO_PI)) == 1
    except (ArithmeticError, TypeError, ValueError):
        return False  # any exception means not a proper convex polygon

答案 3 :(得分:13)

以下Java函数/方法是this answer中描述的算法的实现。

public boolean isConvex()
{
    if (_vertices.size() < 4)
        return true;

    boolean sign = false;
    int n = _vertices.size();

    for(int i = 0; i < n; i++)
    {
        double dx1 = _vertices.get((i + 2) % n).X - _vertices.get((i + 1) % n).X;
        double dy1 = _vertices.get((i + 2) % n).Y - _vertices.get((i + 1) % n).Y;
        double dx2 = _vertices.get(i).X - _vertices.get((i + 1) % n).X;
        double dy2 = _vertices.get(i).Y - _vertices.get((i + 1) % n).Y;
        double zcrossproduct = dx1 * dy2 - dy1 * dx2;

        if (i == 0)
            sign = zcrossproduct > 0;
        else if (sign != (zcrossproduct > 0))
            return false;
    }

    return true;
}

只要顶点有序(顺时针或逆时针),并且没有自相交边(即它仅适用于simple polygons),该算法可以保证工作。

答案 4 :(得分:5)

这是检查多边形是否的测试。

考虑沿多边形的每组三个点。如果每个角度都是180度或更小,则有一个凸多边形。当你弄清楚每个角度时,也保持一个总计(180 - 角度)。对于凸多边形,这将总计360。

此测试在O(n)时间内运行。

另请注意,在大多数情况下,您可以执行此计算并保存 - 大部分时间您都有一组要处理的多边形,并且不会一直在更改。

答案 5 :(得分:3)

要测试多边形是否为凸面,多边形的每个点都应与每条线相同或相同。

以下是一张示例图片:

enter image description here

答案 6 :(得分:3)

answer by @RoryDaulton  对我来说似乎是最好的,但如果其中一个角度恰好是0呢? 有些人可能希望这样的边缘情况返回True,在这种情况下,改变&#34;&lt; =&#34;到&#34;&lt;&#34;在行:

if orientation * angle < 0.0:  # not both pos. or both neg.

以下是我的测试用例,其中突出显示了该问题:

# A square    
assert is_convex_polygon( ((0,0), (1,0), (1,1), (0,1)) )

# This LOOKS like a square, but it has an extra point on one of the edges.
assert is_convex_polygon( ((0,0), (0.5,0), (1,0), (1,1), (0,1)) )

第二个断言在原始答案中失败了。应该是? 对于我的用例,我希望它没有。

答案 7 :(得分:1)

这个方法适用于简单的多边形(没有自相交的边),假设顶点是有序的(顺时针或反向)

对于顶点数组:

vertices = [(0,0),(1,0),(1,1),(0,1)]

以下python实施检查所有交叉产品的z组件是否具有相同的符号

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

def isConvex(vertices):
    if len(vertices)<4:
        return True
    signs= [zCrossProduct(a,b,c)>0 for a,b,c in zip(vertices[2:],vertices[1:],vertices)]
    return all(signs) or not any(signs)

答案 8 :(得分:1)

我实现了两种算法:@UriGoren发布的算法(只有一个很小的改进 - 只有整数数学)和来自@RoryDaulton的算法。我遇到了一些问题,因为我的多边形是关闭的,所以当它是凸的时,两种算法都将第二种算法视为凹面。所以我改变它以防止这种情况。我的方法也使用基本索引(可以是或不是0)。

这些是我的测试顶点:

// concave
int []x = {0,100,200,200,100,0,0};
int []y = {50,0,50,200,50,200,50};

// convex
int []x = {0,100,200,100,0,0};
int []y = {50,0,50,200,200,50};

现在算法:

private boolean isConvex1(int[] x, int[] y, int base, int n) // Rory Daulton
{
  final double TWO_PI = 2 * Math.PI;

  // points is 'strictly convex': points are valid, side lengths non-zero, interior angles are strictly between zero and a straight
  // angle, and the polygon does not intersect itself.
  // NOTES:  1.  Algorithm: the signed changes of the direction angles from one side to the next side must be all positive or
  // all negative, and their sum must equal plus-or-minus one full turn (2 pi radians). Also check for too few,
  // invalid, or repeated points.
  //      2.  No check is explicitly done for zero internal angles(180 degree direction-change angle) as this is covered
  // in other ways, including the `n < 3` check.

  // needed for any bad points or direction changes
  // Check for too few points
  if (n <= 3) return true;
  if (x[base] == x[n-1] && y[base] == y[n-1]) // if its a closed polygon, ignore last vertex
     n--;
  // Get starting information
  int old_x = x[n-2], old_y = y[n-2];
  int new_x = x[n-1], new_y = y[n-1];
  double new_direction = Math.atan2(new_y - old_y, new_x - old_x), old_direction;
  double angle_sum = 0.0, orientation=0;
  // Check each point (the side ending there, its angle) and accum. angles for ndx, newpoint in enumerate(polygon):
  for (int i = 0; i < n; i++)
  {
     // Update point coordinates and side directions, check side length
     old_x = new_x; old_y = new_y; old_direction = new_direction;
     int p = base++;
     new_x = x[p]; new_y = y[p];
     new_direction = Math.atan2(new_y - old_y, new_x - old_x);
     if (old_x == new_x && old_y == new_y)
        return false; // repeated consecutive points
     // Calculate & check the normalized direction-change angle
     double angle = new_direction - old_direction;
     if (angle <= -Math.PI)
        angle += TWO_PI;  // make it in half-open interval (-Pi, Pi]
     else if (angle > Math.PI)
        angle -= TWO_PI;
     if (i == 0)  // if first time through loop, initialize orientation
     {
        if (angle == 0.0) return false;
        orientation = angle > 0 ? 1 : -1;
     }
     else  // if other time through loop, check orientation is stable
     if (orientation * angle <= 0)  // not both pos. or both neg.
        return false;
     // Accumulate the direction-change angle
     angle_sum += angle;
     // Check that the total number of full turns is plus-or-minus 1
  }
  return Math.abs(Math.round(angle_sum / TWO_PI)) == 1;
}

现在来自Uri Goren

private boolean isConvex2(int[] x, int[] y, int base, int n)
{
  if (n < 4)
     return true;
  boolean sign = false;
  if (x[base] == x[n-1] && y[base] == y[n-1]) // if its a closed polygon, ignore last vertex
     n--;
  for(int p=0; p < n; p++)
  {
     int i = base++;
     int i1 = i+1; if (i1 >= n) i1 = base + i1-n;
     int i2 = i+2; if (i2 >= n) i2 = base + i2-n;
     int dx1 = x[i1] - x[i];
     int dy1 = y[i1] - y[i];
     int dx2 = x[i2] - x[i1];
     int dy2 = y[i2] - y[i1];
     int crossproduct = dx1*dy2 - dy1*dx2;
     if (i == base)
        sign = crossproduct > 0;
     else
     if (sign != (crossproduct > 0))
        return false;
  }
  return true;
}

答案 9 :(得分:0)

将Uri的代码改编成matlab。希望这可能有所帮助。

请注意,Uri的算法仅适用于 简单多边形!所以,一定要先测试多边形是否简单!

% M [ x1 x2 x3 ...
%     y1 y2 y3 ...]
% test if a polygon is convex

function ret = isConvex(M)
    N = size(M,2);
    if (N<4)
        ret = 1;
        return;
    end

    x0 = M(1, 1:end);
    x1 = [x0(2:end), x0(1)];
    x2 = [x0(3:end), x0(1:2)];
    y0 = M(2, 1:end);
    y1 = [y0(2:end), y0(1)];
    y2 = [y0(3:end), y0(1:2)];
    dx1 = x2 - x1;
    dy1 = y2 - y1;
    dx2 = x0 - x1;
    dy2 = y0 - y1;
    zcrossproduct = dx1 .* dy2 - dy1 .* dx2;

    % equality allows two consecutive edges to be parallel
    t1 = sum(zcrossproduct >= 0);  
    t2 = sum(zcrossproduct <= 0);  
    ret = t1 == N || t2 == N;

end