Angenommen, jedes Box-Objekt hat die Eigenschaften x, y, width, height und hat seinen Ursprung in der Mitte und weder die Objekte noch die Begrenzungsboxen drehen sich.
Angenommen, jedes Box-Objekt hat die Eigenschaften x, y, width, height und hat seinen Ursprung in der Mitte und weder die Objekte noch die Begrenzungsboxen drehen sich.
Antworten:
(C-ish Pseudocode - Sprachoptimierungen entsprechend anpassen)
bool DoBoxesIntersect(Box a, Box b) {
return (abs(a.x - b.x) * 2 < (a.width + b.width)) &&
(abs(a.y - b.y) * 2 < (a.height + b.height));
}
Auf Englisch: Überprüfen Sie auf jeder Achse, ob die Zentren der Kästchen so nah sind, dass sie sich schneiden. Wenn sie sich auf beiden Achsen schneiden, schneiden sich die Kästchen. Wenn nicht, dann nicht.
Sie können die <'s in <= ändern, wenn Sie die Kantenberührung als Schnittmenge zählen möchten. Wenn Sie eine bestimmte Formel nur für Kantenberührungen verwenden möchten, können Sie == nicht verwenden. Dadurch erfahren Sie, ob sich die Ecken berühren, und nicht, ob sich die Kanten berühren. Sie möchten etwas tun, das logisch äquivalent zu ist return DoBoxesIntersectOrTouch(a, b) && !DoBoxesIntersect(a, b)
.
Es ist erwähnenswert, dass Sie eine kleine, aber signifikante Geschwindigkeitssteigerung erzielen können, indem Sie die halbe Breite und die halbe Höhe zusätzlich zu (oder anstelle von) der vollen Breite und der vollen Höhe speichern. Andererseits ist es selten, dass der 2d-Bounding-Box-Schnittpunkt der Leistungsengpass ist.
abs(5 - 10) * 2 < (10 + 4)
=> 10 < 14
. Sie müssen einige einfache Anpassungen vornehmen, damit es mit der oberen linken Ecke und der Größe funktioniert.
Dies funktioniert für zwei Rechtecke, die an der X- und Y-Achse ausgerichtet sind.
Jedes Rechteck hat die Eigenschaften:
"left", die x-Koordinate seiner linken Seite,
"top", die y-Koordinate seiner oberen Seite,
"right", die x-Koordinate seiner rechten Seite,
"bottom", die y-Koordinate von seine Unterseite,
function IntersectRect(r1:Rectangle, r2:Rectangle):Boolean {
return !(r2.left > r1.right
|| r2.right < r1.left
|| r2.top > r1.bottom
|| r2.bottom < r1.top);
}
Beachten Sie, dass dies für ein Koordinatensystem vorgesehen ist, in dem die + y-Achse nach unten und die + x-Achse nach rechts zeigt (dh typische Bildschirm- / Pixelkoordinaten). Um dies an ein typisches kartesisches System anzupassen, bei dem + y nach oben gerichtet ist, würden die Vergleiche entlang der vertikalen Achsen umgekehrt, z.
return !(r2.left > r1.right
|| r2.right < r1.left
|| r2.top < r1.bottom
|| r2.bottom > r1.top);
Die Idee ist, alle möglichen Bedingungen zu erfassen, unter denen sich die Rechtecke nicht überlappen, und dann die Antwort zu negieren, um zu sehen, ob sie sich überlappen. Unabhängig von der Richtung der Achsen ist leicht zu erkennen, dass sich zwei Rechtecke nicht überlappen, wenn:
Die linke Kante von r2 ist weiter rechts als die rechte Kante von r1
________ ________
| | | |
| r1 | | r2 |
| | | |
|________| |________|
oder die rechte Kante von r2 ist weiter links als die linke Kante von r1
________ ________
| | | |
| r2 | | r1 |
| | | |
|________| |________|
oder die Oberkante von r2 liegt unter der Unterkante von r1
________
| |
| r1 |
| |
|________|
________
| |
| r2 |
| |
|________|
oder die Unterkante von r2 liegt über der Oberkante von r1
________
| |
| r2 |
| |
|________|
________
| |
| r1 |
| |
|________|
Die ursprüngliche Funktion und eine alternative Beschreibung der Funktionsweise finden Sie hier: http://tekpool.wordpress.com/2006/10/11/rectangle-intersection-determine-if-two-given-rectangles-intersect- einander oder nicht /
Wenn Sie objektorientierte Begrenzungsrahmen wünschen, versuchen Sie dieses Tutorial auf der Trennachse Theorem von Metanet: http://www.metanetsoftware.com/technique/tutorialA.html
SAT ist nicht die schnellste Lösung, aber relativ einfach. Sie versuchen, eine einzelne Linie (oder eine Ebene, wenn 3D) zu finden, die Ihre Objekte trennt. Wenn diese Linie existiert, ist es garantiert, parallel zur Kante einer Ihrer Boxen zu sein. Sie durchlaufen also alle Kanten, um zu prüfen, ob sie die Boxen trennt.
Dies funktioniert auch für achsenausgerichtete Rahmen, indem nur die x / y-Achse berücksichtigt wird.
Das obige DoBoxesIntersect ist eine gute paarweise Lösung. Wenn Sie jedoch viele Boxen haben, haben Sie immer noch ein O (N ^ 2) -Problem, und Sie werden möglicherweise feststellen, dass Sie zusätzlich etwas tun müssen, wie es Kaj meint. (In der 3D-Kollisionserkennungsliteratur wird davon ausgegangen, dass es sowohl einen breitphasigen als auch einen engphasigen Algorithmus gibt. Wir werden sehr schnell alle möglichen Überlappungspaare finden und dann etwas teureres, um zu prüfen, ob dies möglich ist Paare sind tatsächliche Paare.)
Der Breitbandalgorithmus, den ich zuvor verwendet habe, ist "Sweep-and-Prune". Für 2D pflegen Sie zwei sortierte Listen für den Anfang und das Ende jeder Box. Solange sich die Box nicht >> von Frame zu Frame bewegt, wird sich die Reihenfolge dieser Listen nicht wesentlich ändern. Sie können also die Blase- oder Einfügesortierung verwenden, um sie beizubehalten. Das Buch "Real-Time Rendering" enthält eine schöne Beschreibung der Optimierungen, die Sie durchführen können, aber es läuft in der breiten Phase auf die O (N + K) -Zeit hinaus, für N Boxen, von denen sich K überlappen, und mit hervorragender realer Welt Leistung, wenn Sie sich N ^ 2 Boolesche Werte leisten können, um zu verfolgen, welche Kästchenpaare sich von Bild zu Bild schneiden. Sie haben dann insgesamt O (N + K ^ 2) Zeit, was << O (N ^ 2) ist, wenn Sie viele Kästchen, aber nur wenige Überlappungen haben.
Eine Menge Mathe hier für ein sehr einfaches Problem. Nehmen wir an, wir haben 4 Punkte für ein rechtes, oberes, linkes, unteres, rechtes ...
Wenn Sie feststellen möchten, ob zwei Rechtecke kollidieren, müssen Sie nur prüfen, ob alle möglichen Extreme, die Kollisionen verhindern würden, erfüllt sind. Wenn Sie Grenzkollisionen einbeziehen möchten, MÜSSEN Sie die beiden Rechtecke kollidieren. Ersetzen Sie einfach die Zeichen> und < mit entsprechendem> = und = <.
struct aRect{
float top;
float left;
float bottom;
float right;
};
bool rectCollision(rect a, rect b)
{
return ! ( b.left > a.right || b.right < a.left || b.top < a.bottom || b.bottom > a.top);
}
Alternative Version der Antwort von ZorbaTHut:
bool DoBoxesIntersect(Box a, Box b) {
return (abs(a.x - b.x) < (a.width + b.width) / 2) &&
(abs(a.y - b.y) < (a.height + b.height) / 2);
}
Je nachdem, welches Problem Sie lösen möchten, ist es möglicherweise besser, den Überblick über Ihr Objekt zu behalten, während Sie es verschieben. Führen Sie also eine Liste mit sortierten x Start- und Endpositionen und eine Liste mit Start- und Endpositionen. Wenn Sie eine Menge Überlappungsprüfungen durchführen müssen und daher optimieren müssen, können Sie dies zu Ihrem Vorteil nutzen, da Sie sofort nachsehen können, wer links von Ihnen endet. Jeder, der links davon endet, kann beschnitten werden sofort. Gleiches gilt für oben, unten und rechts.
Die Buchhaltung kostet natürlich Zeit, daher eignet sie sich eher für Situationen mit wenigen sich bewegenden Objekten, aber vielen Überlappungsprüfungen.
Eine andere Option ist räumliches Hashing, bei dem Sie die Objekte basierend auf der ungefähren Position (Größe kann in mehrere Eimer aufgeteilt werden) in einen Eimer packen, aber nur, wenn es viele Objekte gibt, von denen sich aufgrund der Buchhaltungskosten relativ wenige pro Iteration bewegen.
Grundsätzlich hilft alles, was (n * n) / 2 vermeidet (wenn Sie Objekt a gegen b prüfen, müssen Sie b nicht offensichtlich gegen a prüfen), mehr als die Optimierung der Begrenzungsrahmenprüfungen. Wenn Bounding-Box-Checks einen Engpass darstellen, würde ich ernsthaft empfehlen, nach alternativen Lösungen für das Problem zu suchen.
Der Abstand zwischen den Mittelpunkten ist nicht der gleiche wie der Abstand zwischen den Ecken (wenn sich eine Box zum Beispiel in der anderen befindet). Im Allgemeinen ist diese Lösung die richtige (denke ich).
Abstand zwischen Zentren (zB x): abs(x1+1/2*w1 - x2+1/2*w2)
oder1/2 * abs(2*(x1-x2)+(w1-w2)
Mindestabstand ist 1/2 w1 + 1/2 w2 or 1/2 (w1+w2)
. die hälften stornieren also ..
return
ABS(2*(x1 - x2) + (w1-w2) ) < (w1+w2)) &&
ABS(2*(y1 - y2) + (h1-h2) ) < (h1+h2));
Hier ist meine Implementierung in Java unter der Annahme einer Zweierkomplementarchitektur . Wenn Sie keine Zweierkomplementierung haben, verwenden Sie stattdessen einen Standardfunktionsaufruf von Math.abs :
boolean intersects(IntAxisAlignedBox left, IntAxisAlignedBox right) {
return
(
lineDeltaFactor(left.min.x, left.max.x, right.min.x, right.max.x) |
lineDeltaFactor(left.min.y, left.max.y, right.min.y, right.max.y) |
lineDeltaFactor(left.min.z, left.max.z, right.min.z, right.max.z)
) == 0;
}
int lineDeltaFactor(int leftMin, int leftMax, int rightMin, int rightMax) {
final int
leftWidth = leftMax - leftMin,
rightWidth = rightMax - rightMin,
leftMid = leftMin + ((leftMax - leftMin) >> 1),
rightMid = rightMin + ((rightMax - rightMin) >> 1);
return (abs(leftMid - rightMid) << 1) / (leftWidth + rightWidth + 1);
}
int abs(int value) {
final int mask = value >> (Integer.SIZE - 1);
value ^= mask;
value += mask & 1;
return value;
}
Angenommen, ein halbwegs vernünftiger Compiler / LLVM inline erweitert diese Funktionen, um teures Stack-Jonglieren und V-Table-Lookups zu vermeiden. Dies schlägt bei Eingabewerten nahe den 32-Bit-Extremwerten (dh Integer.MAX_VALUE
und Integer.MIN_VALUE
) fehl .
Der schnellste Weg besteht darin, alle 4 Werte in einem einzigen Vektorregister zu kombinieren.
Speichern Sie die Boxen in einem Vektor mit folgenden Werten [ min.x, min.y, -max.x, -max.y ]
. Wenn Sie Boxen wie diese speichern, werden für den Schnittpunkttest nur 3 CPU-Anweisungen benötigt:
_mm_shuffle_ps
um die zweite Box neu zu ordnen, indem man die min und max Hälften spiegelt.
_mm_xor_ps
mit magischer Zahl _mm_set1_ps(-0.0f)
, um die Vorzeichen aller 4 Werte im zweiten Feld umzudrehen.
_mm_cmple_ps
Um alle 4 Werte miteinander zu vergleichen, werden die folgenden zwei Register verglichen:
[ a.min.x, a.min.y, -a.max.x, -a.max.y ] < [ b.max.x, b.max.y, -b.min.x, -b.min.y ]
Schließlich, falls erforderlich, _mm_movemask_ps
um das Ergebnis aus der Vektoreinheit in ein Skalarregister zu bekommen. Der Wert 0 bedeutet, dass sich die Kästchen schneiden. Wenn Sie mehr als 2 Boxen haben, ist dies nicht erforderlich. Lassen Sie die Werte in den Vektorregistern und verwenden Sie bitweise Operationen, um Ergebnisse aus mehreren Boxen zu kombinieren.
Sie haben weder eine Sprache noch eine Plattform angegeben, aber die Unterstützung für SIMD wie diese oder ähnliche ist in allen Plattformen und Sprachen verfügbar. Auf dem Handy hat ARM NEON SIMD mit sehr ähnlichen Sachen. .NET hat Vector128 im Namespace System.Runtime.Intrinsics und so weiter.