2つの長方形の交差を検出するアルゴリズム?


143

2つの長方形が交差しているかどうかを検出するアルゴリズムを探しています(1つは任意の角度で、もう1つは垂直/水平線のみで)。

一方のコーナーがもう一方のALMOSTにあるかどうかのテストは機能します。長方形が十字のような形状を形成する場合、失敗します。

垂直線の特別なケースを必要とする線の勾配を使用しないことは良い考えのようです。


コーナーチェックに追加した場合、2番目の長方形が角度付き長方形の境界(長方形)の内側にあるかどうかを確認します。
ウェスP

これは何語でやるの?Javaにはこれを可能にする組み込みクラスがあるからです。
Martijn

グラフィックAPIとほとんどのGUIライブラリ(swingなど)にはこれが実装されていると思います。
l_39217_l 2008

それらは重なり合うが四角形の内側に角がない場合を見逃す可能性があります
FlorianBöschSep

1
この質問は、stackoverflow.com / questions / 306316 / とほぼ同じです。ただし、これは特にC ++向けのソリューションを探しています。受け入れられた答えも非常にシンプルで簡単です。
シルバーゴンザレス

回答:


162

標準的な方法は、分離軸テストを実行することです(その上でGoogle検索を実行します)。

要するに:

  • 2つのオブジェクトを区切る線が見つかれば、2つのオブジェクトは交差しません。たとえば、オブジェクト/オブジェクトのすべてのポイントは、線の異なる側にあります。

面白いのは、2つの長方形のすべてのエッジをチェックするだけで十分なことです。長方形が重ならない場合、エッジの1つが分離軸になります。

2Dでは、スロープを使用せずにこれを行うことができます。エッジは、2つの頂点間の差として簡単に定義されます。たとえば、

  edge = v(n) - v(n-1)

90度回転させると、これに垂直になります。2Dでは、これは次のように簡単です。

  rotated.x = -unrotated.y
  rotated.y =  unrotated.x

したがって、三角法や勾配は含まれていません。ベクトルをユニット長に正規化する必要もありません。

ポイントがラインの片側または反対側にあるかどうかをテストする場合は、ドット積を使用できます。サインはあなたがどちら側にいるのかを教えてくれます:

  // rotated: your rotated edge
  // v(n-1) any point from the edge.
  // testpoint: the point you want to find out which side it's on.

  side = sign (rotated.x * (testpoint.x - v(n-1).x) + 
               rotated.y * (testpoint.y - v(n-1).y);

次に、長方形Bのエッジに対して長方形Aのすべての点をテストします。逆も同様です。分離エッジが見つかった場合、オブジェクトは交差しません(Bの他のすべてのポイントがテスト対象のエッジの反対側にある場合-下の図を参照)。分離エッジが見つからない場合は、長方形が交差しているか、1つの長方形が他の長方形に含まれています。

このテストは、凸状のポリゴンで機能します。

修正:分離エッジを特定するには、1つの長方形のすべてのポイントを他の各エッジに対してテストするだけでは不十分です。Aのすべての点がEの同じ半平面にあるため、候補エッジE(下)は分離エッジとして識別されます。ただし、Bの頂点Vb1およびVb2は分離エッジではありません。その半平面にもあります。それがそうでなかったならば、それは分離エッジであったにちがいない http://www.iassess.com/collision.png


2
このアルゴリズムはすべてのケースで機能するわけではありません。2番目の長方形を最初の長方形に対して45度回転させて配置し、対角線に沿ってオフセットして、上記の交差テストを満たすが交差しないようにすることができます。
Skizz 21:22

6
スキズ、8つすべてのエッジを確認します。オブジェクトが交差しない場合、8つのエッジのいずれかがオブジェクト分離します。あなたのケースを示す画像を投稿してみませんか?私は..あなたに軸を表示することができます
ニルスPipenbrinck

2
私の間違い、それはその状態に対処します。
2008

2
画像は現在死んでいます(2012年
John Dvorak

2
これを視覚化するのに苦労したので、参照されている画像がどのように見えるかを再現しました。imgur.com/bNwrzsv
Rjdlee

16

基本的に次の図を見てください。


2つのボックスが衝突すると、ラインAとBが重なります。

これはX軸とY軸の両方で実行する必要があり、四角形が衝突するためには両方が重なり合う必要があることに注意してください。

質問に答えるgamasutra.comの良い記事があります(写真は記事からのものです)。5年前に同様のアルゴリズムを実行しましたが、後でここに投稿するには、コードスニペットを見つける必要があります

修正:分離軸定理は、分離軸が存在する場合(つまり、図に示されいる投影が重複しない場合)、2つの凸形状重複しないと述べています。したがって、「分離軸が存在します」=>「オーバーラップなし」。これは二重の意味合いではないので、逆を結論付けることはできません。


1
明らかに、2つの正方形(0,0,1,1)と(0,3,1,4)は重なりませんが、x軸上のそれらの投影は完全に重なります。両方のテストが必要です。組み合わせは十分です。
MSalters

18
xとyの投影がオーバーラップするだけでは不十分です。たとえば、長方形[(0,0)、(0,3)、(3,3)、(3,0)]と[(2,5)、 (5,2)、(7,4)、(4,7)]。
ジョエル、

4
私はGöの@Joelに同意します。この方法では、長方形が重ならないケースが多数ありますが、投影された半径はxとyの両方で一致しません。
スコッティT

5
この答えは間違いではありませんが、誤解を招きます。2つのボックスが衝突すると、ラインAとBが重なります。線AおよびBの重複は、2つのボックスが、または衝突してもしなくてもよい場合:ことも事実である
マットが燃える

7
@floater:私はそれがだと言うでしょうだけではなく、間違った、しかしさらに悪いである、誤解を招きます。
BlueRaja-ダニープフルフフト

4

m_pGladiatorの答えは正しいです。 分離軸テストは、長方形の重なりを検出する最も簡単で標準的な方法です。投影間隔が重ならない線を分離軸と呼びます。Nils Pipenbrinckの解法は一般的すぎます。ドット積を使用して、1つの形状が完全にもう一方の端の片側にあるかどうかをチェックします。このソリューションは、実際にはnエッジの凸多角形を誘発する可能性があります。ただし、2つの長方形には最適化されていません。

m_pGladiatorの答えの重要な点は、両方の軸(xとy)で2つの長方形の投影をチェックする必要があるということです。2つの投影が重なり合っている場合、これらの2つの長方形が重なり合っていると言えます。したがって、m_pGladiatorの回答に対する上記のコメントは間違っています。

単純な状況では、2つの長方形が回転されない場合は、構造を持つ長方形を提示します。

struct Rect {
    x, // the center in x axis
    y, // the center in y axis
    width,
    height
}

長方形A、BにrectA、rectBという名前を付けます。

    if Math.abs(rectA.x - rectB.x) < (Math.abs(rectA.width + rectB.width) / 2) 
&& (Math.abs(rectA.y - rectB.y) < (Math.abs(rectA.height + rectB.height) / 2))
    then
        // A and B collide
    end if

2つの長方形のいずれかが回転している場合、x軸とy軸へのそれらの投影を決定するためにいくつかの努力が必要になる場合があります。構造体RotatedRectを次のように定義します。

struct RotatedRect : Rect {
    double angle; // the rotating angle oriented to its center
}

違いは、幅が少し異なる点です。rectAの場合はwidthA '、rectBの場合はMath.sqrt(rectA.width*rectA.width + rectA.height*rectA.height) * Math.cos(rectA.angle) widthB'です。Math.sqrt(rectB.width*rectB.width + rectB.height*rectB.height) * Math.cos(rectB.angle)

    if Math.abs(rectA.x - rectB.x) < (Math.abs(widthA' + widthB') / 2) 
&& (Math.abs(rectA.y - rectB.y) < (Math.abs(heightA' + heightB') / 2))
    then
        // A and B collide
    end if

GDC(ゲーム開発会議2007)PPTを参照できます。www.realtimecollisiondetection.net/ pubs / GDC07_Ericson_Physics_Tutorial_SAT.ppt


負の幅を処理するために、「Math.abs(rectA.width + rectB.width)」のMath.abs()が必要なのはなぜですか?
AlexWien 2013

分離軸は必ずしもコンパスの方向ではなく、任意の角度を持つことができます。
Ben Voigt 2014

回転していない長方形rectA(x = 0、y = 0、width = 1、height = 1)とrectB(x = 2、y = 0、width = 100、height = 1)は交差していませんが、メソッドはそれらを示しています交差する。私は何か間違ったことをしていますか?
鏡サッシャRosylight 2017年

4

Cocoaでは、selectedArea rectが回転したNSViewのフレームrectと交差するかどうかを簡単に検出できます。ポリゴンを計算する必要さえありません、法線など。これらのメソッドをNSViewサブクラスに追加するだけです。たとえば、ユーザーがNSViewのスーパービュー上の領域を選択してから、selectedArea rectを渡してメソッドDoesThisRectSelectMeを呼び出します。API convertRect:がその仕事をします。NSViewをクリックして選択すると、同じトリックが機能します。その場合は、以下のように単にhitTestメソッドをオーバーライドします。API convertPoint:その仕事をします;-)

- (BOOL)DoesThisRectSelectMe:(NSRect)selectedArea
{
    NSRect localArea = [self convertRect:selectedArea fromView:self.superview];

    return NSIntersectsRect(localArea, self.bounds);
}


- (NSView *)hitTest:(NSPoint)aPoint
{
    NSPoint localPoint = [self convertPoint:aPoint fromView:self.superview];
    return NSPointInRect(localPoint, self.bounds) ? self : nil;
}

2
このコードは、画面に対して正方形の長方形でのみ機能します。それは些細なケースです。想定しているのは、画面に対してまたは互いに90度の角度でない長方形を扱っているということです。
Duncan C

私がアプリケーションで確認して使用したように、そのコードは回転したどの長方形でも機能します。回転角度に関係なく。
Leonardo

これはアルゴリズムについては説明していませんが、すでにそれを使用しているライブラリについて言及しているだけです。
Ben Voigt 14

2

1つの長方形からの線のいずれかが他からの線のいずれかと交差するかどうかを確認します。単純な線分の交差は簡単にコーディングできます。

さらに速度が必要な場合は、ラインセグメントの交差(スイープライン)の高度なアルゴリズムがあります。http://en.wikipedia.org/wiki/Line_segment_intersectionを参照してください


4
気をつけて!ある長方形が別の長方形を完全に囲む場合を忘れないでください
ピタロウ2008

2

1つの解決策は、No Fit Polygonと呼ばれるものを使用することです。このポリゴンは、2つのポリゴンから(概念的には一方を他方の周りにスライドさせることによって)計算され、相対オフセットが与えられたときにポリゴンがオーバーラップする領域を定義します。このNFPを取得したら、2つのポリゴンの相対オフセットで指定されたポイントで包含テストを実行するだけです。この包含テストは迅速かつ簡単ですが、最初にNFPを作成する必要があります。

Webで「適合しないポリゴン」を検索して、凸面ポリゴンのアルゴリズムを見つけることができるかどうかを確認します(凹面ポリゴンがある場合は、さらに複雑になります)。何も見つからない場合は、ハワードドットJドットでGmailドットコムにメールしてください。


1

考えられるすべてのケースに対応できると思います。次のテストを行います。

  1. 長方形1の頂点のいずれかが長方形2の内側にあること、およびその逆を確認します。他の長方形の内側にある頂点を見つけると、それらが交差して検索を停止したと結論付けることができます。これは、完全に他の中にある1つの長方形を処理します。
  2. 上記のテストが決定的でない場合は、1つの長方形の各線と他の長方形の各線の交点を見つけます。交点が見つかったら、対応する4点によって作成された仮想長方形の内側にあるかどうかを確認します。そのような点が見つかると、それらは交差して検索を停止すると結論付けます。

上記の2つのテストでfalseが返された場合、これらの2つの長方形は重なりません。


0

Javaを使用している場合、Shapeインターフェースのすべての実装には、長方形をとる交差メソッドがあります。


残念ながら、私はC#を使用しています。RectangleクラスにはContains()メソッドがありますが、これは回転していない四角形専用です。
user20493 2008

intersects()メソッドは、交差ではなくブール値を返すため、ほとんど役に立ちません。
ZZ 5

0

さて、ブルートフォースメソッドは、水平方向の四角形のエッジをウォークし、エッジに沿った各ポイントをチェックして、それが他の四角形の上にあるかどうかを確認します。

数学的な答えは、両方の長方形の各エッジを表す方程式を作成することです。これで、長方形Aの4本の線のいずれかが長方形Bの線のいずれかと交差するかどうかを簡単に見つけることができます。これは、単純な(高速)線形方程式ソルバーです。

-アダム


2
方程式の問題は、無限の勾配を持つ垂直線がある場合です。
user20493 2008

すべてのソリューションにコーナーケースがあります。
アダムデービス

2
1つの正方形が完全にもう一方を囲んでいます。
Oliver Hallam、

0

角度を付けた長方形の各辺と、軸合わせされた長方形の各辺との交点を見つけることができます。これを行うには、各辺が置かれている無限直線の方程式(つまり、基本的にv1 + t(v2-v1)およびv'1 + t '(v'2-v'1))を見つけ、これらの2つの方程式が等しい場合(それらが平行である場合は、それをテストできます)、tを解いて線を満たし、その点が2つの頂点間の線分にあるかどうか、つまり0 <= tが真であるかどうかをテストします<= 1および0 <= t '<= 1。

ただし、これは、1つの四角形が他の四角形を完全に覆っている場合には適用されません。どちらかの四角形の4つの点すべてが他の四角形の内側にあるかどうかをテストすることでカバーできます。


0

これは、この問題の3Dバージョンに対して私が行うことです。

2つの長方形を方程式P1およびP2で記述される平面としてモデル化し、次にP1 = P2を記述して、平面が平行(交差なし)または同じ平面内にある場合は存在しない交差線方程式から導きます。その場合、0 = 0になります。その場合は、2D長方形交差アルゴリズムを使用する必要があります。

次に、両方の長方形の平面にあるその線が両方の長方形を通過するかどうかを確認します。もしそうなら、あなたは2つの長方形の交差を持っています、そうでなければあなたはそうではありません(またはそうすべきではありません、私は私の頭のコーナーケースを逃したかもしれません)。

線が同じ平面の長方形を通過するかどうかを確認するには、線と長方形の辺の2点の交点を見つけ(線の方程式を使用してそれらをモデル化)、交点がにあることを確認します範囲。

それは数学的な説明ですが、残念ながら私には上記を行うコードがありません。


平面の交差線が見つかった場合、その一部が両方の長方形内に存在することを確認する必要があります。
Lee Louviere、2013年

0

分離軸テストを使用するよりもわずかに速いテストを実行する別の方法は、いずれかの長方形(任意に選択された)の各頂点で巻き数アルゴリズム(象限でのみ- 恐ろしく遅い角度合計ではない)を使用することです。頂点のいずれかにゼロ以外の曲がり番号がある場合、2つの長方形が重なります。

このアルゴリズムは、分離軸テストよりも多少時間がかかりますが、エッジが2つの象限を横切る場合に半平面テストしか必要ないため、高速です(分離軸法を使用した最大32のテストとは対照的)。

このアルゴリズムには、任意のポリゴン(凸面または凹面)のオーバーラップをテストするために使用できるという利点があります。私の知る限り、アルゴリズムは2D空間でのみ機能します。


3
私は間違っているかもしれませんが、ある長方形の頂点が別の長方形の内側にあるかどうかを確認するだけではありませんか?はいの場合、長方形が内部に頂点なしでオーバーラップする可能性があるため、それだけでは不十分です。
sinelaw、2011年

長方形でできますか?どうやって?2つの長方形が交差するためには、長方形の1つの少なくとも1つの頂点が他の長方形上になければなりません。
Duncan C

@DuncanC:はい、できます。反例はクロスであり、元の質問にもリストされていました。
Ben Voigt 14

@BenVoigtこれは非常に古いスレッドですが、あなたは完全に正しいです。
Duncan C 14

0

他に何か不足しているのはなぜこれをそんなに複雑にするのですか?

(x1、y1)と(X1、Y1)が四角形の角である場合、交差を見つけるには、次のようにします。

    xIntersect = false;
    yIntersect = false;
    if (!(Math.min(x1, x2, x3, x4) > Math.max(X1, X2, X3, X4) || Math.max(x1, x2, x3, x4) < Math.min(X1, X2, X3, X4))) xIntersect = true;
    if (!(Math.min(y1, y2, y3, y4) > Math.max(Y1, Y2, Y3, Y4) || Math.max(y1, y2, y3, y4) < Math.min(Y1, Y2, Y3, Y4))) yIntersect = true;
    if (xIntersect && yIntersect) {alert("Intersect");}

3
あなたは彼が任意の角度で回転させられることを望んでいるのを見逃しています。
Robotbugs 2013年

0

私はそれを次のように実装しました:

bool rectCollision(const CGRect &boundsA, const Matrix3x3 &mB, const CGRect &boundsB)
{
    float Axmin = boundsA.origin.x;
    float Axmax = Axmin + boundsA.size.width;
    float Aymin = boundsA.origin.y;
    float Aymax = Aymin + boundsA.size.height;

    float Bxmin = boundsB.origin.x;
    float Bxmax = Bxmin + boundsB.size.width;
    float Bymin = boundsB.origin.y;
    float Bymax = Bymin + boundsB.size.height;

    // find location of B corners in A space
    float B0x = mB(0,0) * Bxmin + mB(0,1) * Bymin + mB(0,2);
    float B0y = mB(1,0) * Bxmin + mB(1,1) * Bymin + mB(1,2);

    float B1x = mB(0,0) * Bxmax + mB(0,1) * Bymin + mB(0,2);
    float B1y = mB(1,0) * Bxmax + mB(1,1) * Bymin + mB(1,2);

    float B2x = mB(0,0) * Bxmin + mB(0,1) * Bymax + mB(0,2);
    float B2y = mB(1,0) * Bxmin + mB(1,1) * Bymax + mB(1,2);

    float B3x = mB(0,0) * Bxmax + mB(0,1) * Bymax + mB(0,2);
    float B3y = mB(1,0) * Bxmax + mB(1,1) * Bymax + mB(1,2);

    if(B0x<Axmin && B1x<Axmin && B2x<Axmin && B3x<Axmin)
        return false;
    if(B0x>Axmax && B1x>Axmax && B2x>Axmax && B3x>Axmax)
        return false;
    if(B0y<Aymin && B1y<Aymin && B2y<Aymin && B3y<Aymin)
        return false;
    if(B0y>Aymax && B1y>Aymax && B2y>Aymax && B3y>Aymax)
        return false;

    float det = mB(0,0)*mB(1,1) - mB(0,1)*mB(1,0);
    float dx = mB(1,2)*mB(0,1) - mB(0,2)*mB(1,1);
    float dy = mB(0,2)*mB(1,0) - mB(1,2)*mB(0,0);

    // find location of A corners in B space
    float A0x = (mB(1,1) * Axmin - mB(0,1) * Aymin + dx)/det;
    float A0y = (-mB(1,0) * Axmin + mB(0,0) * Aymin + dy)/det;

    float A1x = (mB(1,1) * Axmax - mB(0,1) * Aymin + dx)/det;
    float A1y = (-mB(1,0) * Axmax + mB(0,0) * Aymin + dy)/det;

    float A2x = (mB(1,1) * Axmin - mB(0,1) * Aymax + dx)/det;
    float A2y = (-mB(1,0) * Axmin + mB(0,0) * Aymax + dy)/det;

    float A3x = (mB(1,1) * Axmax - mB(0,1) * Aymax + dx)/det;
    float A3y = (-mB(1,0) * Axmax + mB(0,0) * Aymax + dy)/det;

    if(A0x<Bxmin && A1x<Bxmin && A2x<Bxmin && A3x<Bxmin)
        return false;
    if(A0x>Bxmax && A1x>Bxmax && A2x>Bxmax && A3x>Bxmax)
        return false;
    if(A0y<Bymin && A1y<Bymin && A2y<Bymin && A3y<Bymin)
        return false;
    if(A0y>Bymax && A1y>Bymax && A2y>Bymax && A3y>Bymax)
        return false;

    return true;
}

行列mBは、B空間の点をA空間の点に変換する任意のアフィン変換行列です。これには、単純な回転と平行移動、回転とスケーリング、フルアフィンワープが含まれますが、パースペクティブワープは含まれません。

それは可能な限り最適ではないかもしれません。速度は大きな問題ではありませんでした。しかし、それは私にとってはうまくいくようです。


0

以下は、受け入れられた回答のMATLAB実装です。

function olap_flag = ol(A,B,sub)

%A and B should be 4 x 2 matrices containing the xy coordinates of the corners in clockwise order

if nargin == 2
  olap_flag = ol(A,B,1) && ol(B,A,1);
  return;
end

urdl = diff(A([1:4 1],:));
s = sum(urdl .* A, 2);
sdiff = B * urdl' - repmat(s,[1 4]);

olap_flag = ~any(max(sdiff)<0);

0

これは従来の方法で、線ごとに行き、線が交差しているかどうかを確認します。これはMATLABのコードです。

C1 = [0, 0];    % Centre of rectangle 1 (x,y)
C2 = [1, 1];    % Centre of rectangle 2 (x,y)
W1 = 5; W2 = 3; % Widths of rectangles 1 and 2
H1 = 2; H2 = 3; % Heights of rectangles 1 and 2
% Define the corner points of the rectangles using the above
R1 = [C1(1) + [W1; W1; -W1; -W1]/2, C1(2) + [H1; -H1; -H1; H1]/2];
R2 = [C2(1) + [W2; W2; -W2; -W2]/2, C2(2) + [H2; -H2; -H2; H2]/2];

R1 = [R1 ; R1(1,:)] ;
R2 = [R2 ; R2(1,:)] ;

plot(R1(:,1),R1(:,2),'r')
hold on
plot(R2(:,1),R2(:,2),'b')


%% lines of Rectangles 
L1 = [R1(1:end-1,:) R1(2:end,:)] ;
L2 = [R2(1:end-1,:) R2(2:end,:)] ;
%% GEt intersection points
P = zeros(2,[]) ;
count = 0 ;
for i = 1:4
    line1 = reshape(L1(i,:),2,2) ;
    for j = 1:4
        line2 = reshape(L2(j,:),2,2) ;
        point = InterX(line1,line2) ;
        if ~isempty(point)
            count = count+1 ;
            P(:,count) = point ;
        end
    end
end
%%
if ~isempty(P)
    fprintf('Given rectangles intersect at %d points:\n',size(P,2))
    plot(P(1,:),P(2,:),'*k')
end

関数InterXは、https://in.mathworks.com/matlabcentral/fileexchange/22441-curve-intersections?focused = 5165138&tab = functionからダウンロードできます


0

2つの四角形がある場合、私は独自の単純な方法を持っています。

R1 =(min_x1、max_x1、min_y1、max_y1)

R2 =(min_x2、max_x2、min_y2、max_y2)

次の場合にのみ重複します。

オーバーラップ=(max_x1> min_x2)および(max_x2> min_x1)および(max_y1> min_y2)および(max_y2> min_y1)

3Dボックスに対しても実行できますが、実際には任意の数の次元で機能します。


0

他の答えでも十分に述べられているので、疑似コードのワンライナーを追加します。

!(a.left > b.right || b.left > a.right || a.top > b.bottom || b.top > a.bottom);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.