web-dev-qa-db-ja.com

多角形が凸面、非凸面、または複雑であるかどうかを効率的に判断するにはどうすればよいですか?

XFillPolygon のmanページから:

  • shapeComplexの場合、パスは自己交差する場合があります。パス内の連続する一致点は自己交差として扱われないことに注意してください。

  • shapeConvexの場合、ポリゴン内のすべてのポイントペアについて、それらを結ぶ線分はパスと交差しません。クライアントが認識している場合、Convexを指定するとパフォーマンスが向上します。凸状ではないパスにConvexを指定した場合、グラフィックスの結果は定義されません。

  • shapeNonconvexの場合、パスは自己交差しませんが、形状は完全に凸ではありません。クライアントが認識している場合、Complexの代わりにNonconvexを指定すると、パフォーマンスが向上する場合があります。自己交差パスにNonconvexを指定した場合、グラフィックスの結果は未定義です。

Fill XFillPolygonでパフォーマンスの問題が発生しています。manページが示唆しているように、最初に行うべきステップは、ポリゴンの正しい形状を指定することです。私は現在、Complexを安全な側に使用しています。

ポリゴン(一連の座標によって定義される)が凸面、非凸面、または複雑であるかどうかを判断するための効率的なアルゴリズムはありますか?

42
hhafez

ギフトラッピングアルゴリズムよりもはるかに簡単にできます。特定の境界のないポイントのセットがあり、凸包を見つける必要がある場合、これは良い答えです。

対照的に、ポリゴンが自己交差しておらず、連続するポイントが境界を形成するリスト内のポイントのセットで構成される場合を考えます。この場合、ポリゴンが凸面かどうかを判断するのがはるかに簡単です(また、角度を計算する必要もありません)。

ポリゴンのエッジの各連続ペア(ポイントの各トリプレット)について、昇順でポイントを指すエッジによって定義されるベクトルの外積の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])を使用してください。


多角形が自己交差している場合、その指向角がすべて同じ方向であっても 凸性の技術的定義に失敗します この場合、上記のアプローチでは正しい結果が得られません。

104
Jason S

Stackoverflowでは、受け入れられた回答を削除することはできませんが、チェックアウト Rory Daultonの回答 と言います。

23
Eugene Yokota

この質問は、「凸多角形の決定」を検索する際のBingまたはGoogleの最初のアイテムになりました。ただし、十分な答えはありません。

(削除済み) @ EugeneYokotaによる回答は、順序付けられていないポイントのセットを凸多角形にできるかどうかをチェックすることで機能します、しかしそれはOPが求めたものではありません。彼は、与えられた多角形が凸面かどうかをチェックする方法を求めました。 (コンピュータサイエンスの「ポリゴン」は、通常、[ XFillPolygon documentation ]のように、2Dポイントの順序付き配列として定義されます。 )また、この場合のギフトラッピングアルゴリズムは、nポイントに対してO(n^2)の時間複雑性を持ちます。これは、この問題を解決するために実際に必要なものよりもはるかに大きいですが、効率的なアルゴリズム。

@ JasonSの答え、および彼のアイデアに従う他の答えは、 スターポリゴン を受け入れます 五a星 または@zennaのコメントの1つとして。ただし、スターポリゴンは凸とは見なされません。 @plasmacelがコメントで述べているように、これはポリゴンが自己交差していないという事前知識がある場合に使用するのに適したアプローチですが、その知識がない場合は失敗する可能性があります。

@ Sekhatの答えは正しいですが、O(n^2)の時間の複雑さもあるため、非効率的です。

@ LorenPechtelの追加回答ここで彼女の編集は最高のものですが、あいまいです。

最適な複雑さを持つ正しいアルゴリズム

ここで紹介するアルゴリズムは、O(n)の時間複雑さを持ち、ポリゴンが凸面かどうかを正しくテストし、投げたすべてのテストに合格します。アイデアは、各辺の方向と連続する辺間の方向の符号付き変化に注意して、多角形の辺を横断することです。ここで「符号付き」とは、左方向が正、右方向が負(またはその逆)、直進がゼロであることを意味します。これらの角度は、-pi(排他的)とpi(包括的)の間に正規化されます。 合計これらすべての方向変更角度(別名偏向角度)together凸多角形ではプラスまたはマイナス1ターン(つまり360度)になりますが、星のような多角形(または自己交差ループ)は異なる合計( n * 360 度、nは全体的に回転し、すべての偏向角が同じ符号のポリゴンの場合)。したがって、方向変更角度の合計がプラスまたはマイナス1ターンであることを確認する必要があります。また、方向変更角度がすべて正またはすべて負であり、逆(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
18
Rory Daulton

次のJava function/methodは 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;
}

アルゴリズムは、頂点が順序付けられている限り(時計回りまたは反時計回り)動作することが保証されており、自己交差するエッジはありません(つまり、 単純なポリゴン に対してのみ動作します)。

14
Uri Goren

以下は、ポリゴンがconvexかどうかを確認するテストです。

ポリゴンに沿った3点の各セットを検討します。すべての角度が180度以下の場合、凸多角形になります。各角度を計算するときは、合計(180-角度)も維持します。凸多角形の場合、これは合計360です。

このテストは、O(n)時間で実行されます。

また、ほとんどの場合、この計算は一度だけ実行して保存できるものであることに注意してください。ほとんどの場合、常に変化することなく動作するポリゴンのセットがあります。

7
Loren Pechtel

多角形が凸面であるかどうかをテストするには、多角形のすべてのポイントが各線と同じ高さかそれより後ろにある必要があります。

以下に例を示します。

enter image description here

3
Sekhat

@ RoryDaultonによる回答 は私には最適のようですが、角度の1つが正確に0の場合はどうでしょうか?そのようなEdgeケースがTrueを返すようにしたい場合があります。その場合は、行の「<=」を「<」に変更します。

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)) )

2番目のアサートは、元の回答で失敗します。すべきですか?私のユースケースでは、そうではなかったほうがいいと思います。

3
nickthecoder

私は両方のアルゴリズムを実装しました。@ UriGorenによって投稿されたアルゴリズム(わずかな改善-整数演算のみ)とJavaの@RoryDaultonからのアルゴリズムです。ポリゴンが閉じているため、いくつかの問題が発生しました。そのため、両方のアルゴリズムは、2番目が凸である場合に2番目を凹と見なしていました。そこで、このような状況を防ぐために変更しました。私のメソッドは、ベースインデックス(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;
}

そして今、ウリゴーレンから

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

この方法は、頂点が順序付けられている(時計回りまたはカウンター)と仮定して、単純なポリゴン(自己交差するエッジなし)で機能します。

頂点の配列の場合:

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)
1
Uri Goren

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
0
Peter K.T. Yu