web-dev-qa-db-de.com

Algorithmus zur Erkennung des Schnittpunkts zweier Rechtecke?

Ich suche nach einem Algorithmus, um zu erkennen, ob sich zwei Rechtecke schneiden (eines in einem beliebigen Winkel, das andere nur mit vertikalen/horizontalen Linien).

Testen, ob eine Ecke von einer in der anderen ALMOST funktioniert. Es schlägt fehl, wenn die Rechtecke eine kreuzförmige Form bilden.

Es scheint eine gute Idee zu sein, die Verwendung von Steigungen der Linien zu vermeiden, was spezielle Fälle für vertikale Linien erfordern würde.

136
user20493

Die Standardmethode wäre, den Trennachsen-Test durchzuführen (eine Google-Suche danach durchführen).

Zusamenfassend:

  • Zwei Objekte schneiden sich nicht, wenn Sie eine Linie finden, die die beiden Objekte voneinander trennt. z.B. Die Objekte/alle Punkte eines Objekts befinden sich auf verschiedenen Seiten der Linie.

Der Spaß ist, dass es ausreichend ist, alle Kanten der beiden Rechtecke zu überprüfen. Wenn sich die Rechtecke nicht überlappen, ist eine der Kanten die Trennachse.

In 2D können Sie dies tun, ohne Steigungen zu verwenden. Eine Kante wird einfach als die Differenz zwischen zwei Scheitelpunkten definiert, z.

  Edge = v(n) - v(n-1)

Sie können einen Winkel dazu erhalten, indem Sie ihn um 90 ° drehen. In 2D ist dies einfach als:

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

Also keine Trigonometrie oder Steigungen. Das Normalisieren des Vektors auf die Einheitenlänge ist ebenfalls nicht erforderlich.

Wenn Sie testen möchten, ob sich ein Punkt auf der einen oder anderen Seite der Linie befindet, können Sie einfach das dot-Produkt verwenden. Das Schild zeigt Ihnen, auf welcher Seite Sie sich befinden:

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

Testen Sie nun alle Punkte des Rechtecks ​​A gegen die Kanten des Rechtecks ​​B und umgekehrt. Wenn Sie eine Trennkante finden, überschneiden sich die Objekte nicht (vorausgesetzt, alle anderen Punkte in B befinden sich auf der anderen Seite der Kante, auf die getestet wird - siehe Zeichnung unten). Wenn Sie keinen Trennrand finden, schneiden sich entweder die Rechtecke oder ein Rechteck ist im anderen enthalten.

Der Test funktioniert übrigens mit beliebigen konvexen Polygonen. 

Amendment: Um eine Trennkante zu identifizieren, reicht es nicht aus, alle Punkte eines Rechtecks ​​gegeneinander zu testen. Die Kandidatenkante E (unten) würde als solche als Trennkante identifiziert, da sich alle Punkte in A in derselben Halbebene von E befinden. Es ist jedoch keine Trennkante, da die Scheitelpunkte Vb1 und Vb2 von B sind auch in dieser halben Ebene. Es wäre nur ein trennender Rand gewesen, wenn das nicht der Fall gewesen wäre http://www.iassess.com/collision.png

151

Schauen Sie sich grundsätzlich folgendes Bild an: 


 

Wenn die beiden Kästchen kollidieren, überlappen sich die Linien A und B.

Beachten Sie, dass dies sowohl auf der X- als auch auf der Y-Achse erfolgen muss, und dass beide überlappen müssen, damit die Rechtecke kollidieren.

Es gibt einen guten Artikel in gamasutra.com , der die Frage beantwortet (das Bild stammt aus dem Artikel) ... Ich habe vor fünf Jahren einen ähnlichen Algorithmus verwendet und muss mein Code-Snippet finden, um es später hier zu posten

Abänderung: Der Satz der Trennachse besagt, dass zwei konvexe Formen nicht überlappen, wenn eine Trennachse vorhanden ist (d. H. Eine, bei der die gezeigten Projektionen nicht überlappen). Also "Eine Trennachse existiert" => "Keine Überlappung". Dies ist keine Zwei-Implikation, so dass Sie das Gegenteil nicht abschließen können. 

15
m_pGladiator

In Cocoa können Sie leicht erkennen, ob das selectedArea-Rect den Frame-Rect des gedrehten NSView schneidet. Sie müssen nicht einmal Polygone, Normalen und solche berechnen. Fügen Sie diese Methoden einfach zu Ihrer NSView-Unterklasse hinzu. __ Zum Beispiel wählt der Benutzer einen Bereich im NSView-Übersichtsfenster aus und ruft dann die Methode DoesThisRectSelectMe auf, die den selectedArea rect übergibt. Die API convertRect: erledigt diesen Job. Der gleiche Trick funktioniert, wenn Sie auf das NSView klicken, um es auszuwählen. In diesem Fall überschreiben Sie einfach die hitTest-Methode wie unten beschrieben. Die API convertPoint: erledigt diesen Job ;-)

- (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;
}
4
Leonardo

die Antwort von m_pGladiator ist richtig und ich bevorzuge es .Achsentest trennen ist die einfachste Methode, um eine Überlappung von Rechtecken zu erkennen. Eine Linie, für die sich die Projektionsintervalle nicht überschneiden, nennen wir Trennachse. Nils Pipenbrincks Lösung ist zu allgemein. Verwenden Sie dot product, um zu prüfen, ob sich eine Form ganz auf der einen Seite des Randes der anderen befindet. Diese Lösung könnte tatsächlich zu n-Kanten konvexen Polygonen führen. Es ist jedoch nicht für zwei Rechtecke optimiert.

der kritische Punkt in der Antwort von m_pGladiator ist, dass wir die Projektion von zwei Rechtecken auf beiden Achsen (x und y) überprüfen sollten. Wenn sich zwei Projektionen überlappen, können wir sagen, dass sich diese beiden Rechtecke überlappen. Die obigen Kommentare zur Antwort von m_pGladiator sind also falsch.

wenn zwei Rechtecke nicht gedreht werden, zeigen wir in der einfachen Situation ein Rechteck mit der Struktur:

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

wir nennen Rechteck A, B mit 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

wenn eines der beiden Rechtecke gedreht wird, kann es erforderlich sein, die Projektion auf die X- und Y-Achsen zu bestimmen. Definieren Sie struct RotatedRect wie folgt:

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

der Unterschied ist, dass die Breite jetzt etwas anders ist: widthA 'für rectA: Math.sqrt(rectA.width*rectA.width + rectA.height*rectA.height) * Math.cos(rectA.angle) widthB' für rectB: 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

Könnte sich auf eine GDC (Game Development Conference 2007) beziehen PPT www.realtimecollisiondetection.net/pubs/GDC07_Ericson_Physics_Tutorial_SAT.ppt

3
tristan

Prüfen Sie, ob eine der Linien eines Rechtecks ​​eine der anderen Linien schneidet. Der Schnittpunkt für Naive-Liniensegmente lässt sich leicht codieren.

Wenn Sie mehr Geschwindigkeit benötigen, gibt es erweiterte Algorithmen für den Schnittpunkt von Liniensegmenten (Sweep-Line). Siehe http://en.wikipedia.org/wiki/Line_segment_intersection

2
Louis Brandy

Eine Lösung ist die Verwendung eines No Fit Polygon. Dieses Polygon wird aus den beiden Polygonen berechnet (konzeptionell durch Verschieben) und definiert den Bereich, für den sich die Polygone aufgrund ihres relativen Versatzes überlappen. Sobald Sie dieses NFP haben, müssen Sie einfach einen Einschlusstest mit einem Punkt durchführen, der durch den relativen Versatz der beiden Polygone angegeben wird. Dieser Inklusionstest ist schnell und einfach, aber Sie müssen zuerst das NFP erstellen.

Suchen Sie im Web nach No Fit Polygon und finden Sie heraus, ob Sie einen Algorithmus für konvexe Polygone finden können (es wird VIEL komplexer, wenn Sie konkave Polygone haben). Wenn Sie nichts finden können, senden Sie mir eine E-Mail an howard dot J dot

2
Howard May

Ich denke, es wird sich um alle möglichen Fälle kümmern. 

  1. Prüfen Sie, ob die Ecken des Rechtecks ​​1 innerhalb des Rechtecks ​​2 liegen und umgekehrt. Immer wenn Sie einen Scheitelpunkt finden, der sich innerhalb des anderen Rechtecks ​​befindet, können Sie daraus schließen, dass sie sich schneiden und die Suche beenden. Dies sorgt für ein Rechteck, das vollständig in dem anderen liegt.
  2. Wenn der obige Test nicht eindeutig ist, finden Sie die Schnittpunkte jeder Linie eines Rechtecks ​​mit jeder Linie des anderen Rechtecks. Sobald ein Schnittpunkt gefunden wurde, prüfen Sie, ob er sich innerhalb des imaginären Rechtecks ​​befindet, das von den entsprechenden 4 Punkten erstellt wird. Wenn immer ein solcher Punkt gefunden wird, schließen Sie, dass sie sich schneiden und die Suche beenden.

Wenn die obigen 2 Tests falsch sind, überlappen sich diese beiden Rechtecke nicht.

1
John Smith

Wenn Sie Java verwenden, haben alle Implementierungen der Shape-Schnittstelle eine intersects -Methode, die ein Rechteck annimmt. 

0
Brendan Cashman

Entweder fehlt mir etwas anderes, warum das so kompliziert wird?

wenn (x1, y1) und (X1, Y1) Ecken der Rechtecke sind, dann suchen Sie nach einem Schnittpunkt:

    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");}
0
user1517108

Nun, die Brute-Force-Methode besteht darin, die Kanten des horizontalen Rechtecks ​​zu durchlaufen und jeden Punkt entlang des Randes zu überprüfen, um zu sehen, ob er auf das andere Rechteck fällt oder darin liegt.

Die mathematische Antwort besteht darin, Gleichungen zu bilden, die jeden Rand beider Rechtecke beschreiben. Jetzt können Sie einfach feststellen, ob eine der vier Linien des Rechtecks ​​A eine der Linien des Rechtecks ​​B schneidet, die ein einfacher (schneller) Gleichungslöser sein sollte.

-Adam

0
Adam Davis

Sie könnten den Schnittpunkt jeder Seite des abgewinkelten Rechtecks ​​mit jeder Seite der Achse ausrichten. Tun Sie dies, indem Sie die Gleichung der unendlichen Linie finden, auf der jede Seite liegt (dh v1 + t(v2-v1) und v'1 + t '(v'2-v'1)) Punkt, an dem sich die Linien treffen, indem Sie nach t suchen, wenn diese beiden Gleichungen gleich sind (wenn sie parallel sind, können Sie dies testen) und dann testen, ob dieser Punkt auf dem Liniensegment zwischen den beiden Scheitelpunkten liegt, dh stimmt es 0 <= t <= 1 und 0 <= t '<= 1.

Dies gilt jedoch nicht für den Fall, dass ein Rechteck das andere vollständig bedeckt. Das können Sie abdecken, indem Sie testen, ob alle vier Punkte eines der Rechtecke innerhalb des anderen Rechtecks ​​liegen. 

0
HenryR

Dies ist, was ich für die 3D - Version dieses Problems tun würde:

Modellieren Sie die beiden Rechtecke als durch die Gleichung P1 und P2 beschriebene Ebenen, schreiben Sie dann P1 = P2 und leiten Sie daraus die Schnittliniengleichung ab, die nicht existiert, wenn die Ebenen parallel sind (kein Schnittpunkt) oder in derselben Ebene liegen In diesem Fall erhalten Sie 0 = 0. In diesem Fall müssen Sie einen Schnittalgorithmus für 2D-Rechtecke verwenden.

Dann würde ich sehen, ob diese Linie, die sich in der Ebene beider Rechtecke befindet, durch beide Rechtecke verläuft. Wenn dies der Fall ist, haben Sie eine Kreuzung von 2 Rechtecken, sonst tun Sie es nicht (oder sollten nicht, ich habe möglicherweise einen Eckfall in meinem Kopf übersehen).

Um herauszufinden, ob eine Linie durch ein Rechteck in derselben Ebene verläuft, würde ich die zwei Schnittpunkte der Linie und die Seiten des Rechtecks ​​ermitteln (mit Liniengleichungen modellieren) und dann sicherstellen, dass die Schnittpunkte in liegen Angebot.

Das sind die mathematischen Beschreibungen, leider habe ich keinen Code dafür.

0
freespace

Hier ist eine Matlab-Implementierung der akzeptierten Antwort:

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
Jed

Dies ist die herkömmliche Methode, gehen Sie Zeile für Zeile und prüfen Sie, ob sich die Linien schneiden. Dies ist der Code in 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

die Funktion InterX kann heruntergeladen werden von: https://in.mathworks.com/matlabcentral/fileexchange/22441-curve-intersections?focused=5165138&tab=function

Eine andere Möglichkeit, den Test durchzuführen, der etwas schneller ist als der Trennachsentest, besteht darin, den Algorithmus der Wicklungszahlen (nur für Quadranten - nicht Winkelsummierung, der fürchterlich langsam ist) an jedem Scheitelpunkt eines Rechtecks ​​(beliebig) zu verwenden gewählt). Wenn einer der Scheitelpunkte eine Wicklungsnummer ungleich Null hat, überlappen sich die beiden Rechtecke.

Dieser Algorithmus ist etwas langatmiger als der Trennachsentest, er ist jedoch schneller, da nur ein halber Test erforderlich ist, wenn Kanten zwei Quadranten kreuzen (im Gegensatz zu bis zu 32 Tests, die das Trennachsenverfahren verwenden).

Der Algorithmus hat den weiteren Vorteil, dass er zum Testen der Überlappung von any polygon (konvex oder konkav) verwendet werden kann. Meines Wissens funktioniert der Algorithmus nur im 2D-Raum.

0
Mads

Ich habe eine einfachere Methode, wenn wir zwei Rechtecke haben:

R1 = (min_x1, max_x1, min_y1, max_y1)

R2 = (min_x2, max_x2, min_y2, max_y2)

Sie überlappen sich nur dann, wenn:

Überlappung = (max_x1> min_x2) und (max_x2> min_x1) und (max_y1> min_y2) und (max_y2> min_y1)

Sie können es auch für 3D-Boxen tun, tatsächlich funktioniert es für eine beliebige Anzahl von Dimensionen.

0
BitFarmer

In anderen Antworten wurde genug gesagt, also füge ich einfach Pseudocode-Einzeiler hinzu:

!(a.left > b.right || b.left > a.right || a.top > b.bottom || b.top > a.bottom);
0
Przemek

Ich habe es so umgesetzt:

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

Die Matrix mB ist eine beliebige affine Transformationsmatrix, die Punkte im B-Raum in Punkte im A-Raum umwandelt. Dies umfasst einfache Rotation und Translation, Rotation plus Skalierung und vollständige affine Warps, jedoch keine perspektivischen Warps.

Es ist möglicherweise nicht so optimal wie möglich. Geschwindigkeit war keine große Sorge. Es scheint jedoch für mich ok zu funktionieren.

0
Robotbugs