Erstens ist die Antwort von Kevin Reid bei achsenausgerichteten Rechtecken die beste und der Algorithmus die schnellste.
Verwenden Sie zweitens für einfache Formen die Relativgeschwindigkeit (siehe unten) und den Satz der Trennachse für die Kollisionserkennung. Es wird Ihnen sagen , ob eine Kollision im Fall der linearen Bewegung geschieht (keine Rotation). Und wenn es eine Rotation gibt, brauchen Sie einen kleinen Zeitschritt, um genau zu sein. Nun zur Beantwortung der Frage:
Wie erkennt man im allgemeinen Fall, ob sich zwei konvexe Formen schneiden?
Ich gebe Ihnen einen Algorithmus, der für alle konvexen Formen und nicht nur für Sechsecke funktioniert.
Angenommen, X und Y sind zwei konvexe Formen. Sie kreuzen sich genau dann, wenn sie einen Punkt gemeinsam haben, dh es gibt einen Punkt x ∈ X und einen Punkt y ∈ Y, so dass x = y . Betrachtet man den Raum als Vektorraum, so bedeutet dies x - y = 0 . Und jetzt kommen wir zu diesem Minkowski-Geschäft:
Die Minkowski-Summe von X und Y ist die Menge von allem x + y für x ∈ X und y ∈ Y .
Ein Beispiel für X und Y
X, Y und ihre Minkowski-Summe, X + Y
Angenommen, (-Y) ist die Menge aller -y für y ∈ Y , dann gegeben den vorherigen Absatz, X und Y schneiden sich genau dann, wenn X + (-Y) 0 enthält , dh den Ursprung .
Nebenbemerkung: Warum schreibe ich X + (-Y) anstelle von X - Y ? Nun, weil es in der Mathematik eine Operation namens Minkowski-Differenz von A und B gibt, die manchmal mit X - Y geschrieben wird, aber nichts mit der Menge von allem zu tun hat x - y für x ∈ X und y ∈ Y (der echte Minkowski Unterschied ist etwas komplexer).
Wir möchten also die Minkowski-Summe von X und -Y berechnen und herausfinden, ob sie den Ursprung enthält. Der Ursprung ist im Vergleich zu anderen Punkten nicht besonders. Um festzustellen, ob sich der Ursprung innerhalb einer bestimmten Domäne befindet, verwenden wir einen Algorithmus, der uns mitteilen kann, ob ein bestimmter Punkt zu dieser Domäne gehört.
Die Minkowski-Summe von X und Y hat eine coole Eigenschaft: Wenn X und Y konvex sind, dann X + Y auch. Und festzustellen, ob ein Punkt zu einer konvexen Menge gehört, ist viel einfacher, als wenn diese Menge nicht konvex wäre.
Wir können unmöglich das gesamte x - y für x ∈ X und y ∈ Y berechnen, da es unendlich viele solcher Punkte x und y gibt. Da X , Y und X + Y also hoffentlich konvex sind, können wir einfach verwenden Die "äußersten" Punkte definieren die Formen X und Y , die ihre Eckpunkte sind, und wir erhalten die äußersten Punkte von X + Y und einige weitere.
Diese zusätzlichen Punkte sind von den äußersten Punkten von X + Y "umgeben", so dass sie nicht zur Definition der neu erhaltenen konvexen Form beitragen. Wir sagen, dass sie nicht die " konvexe Hülle " der Punktmenge definieren. Was wir also tun, ist, dass wir sie loswerden, um uns auf den endgültigen Algorithmus vorzubereiten, der uns sagt, ob der Ursprung innerhalb der konvexen Hülle liegt.
Der konvexe Rumpf von X + Y. Wir haben die "inneren" Eckpunkte entfernt.
Wir bekommen also
Ein erster, naiver Algorithmus
boolean intersect(Shape X, Shape Y) {
SetOfVertices minkowski = new SetOfVertices();
for (Vertice x in X) {
for (Vertice y in Y) {
minkowski.addVertice(x-y);
}
}
return contains(convexHull(minkowski), Vector2D(0,0));
}
Die Schleifen haben offensichtlich die Komplexität O (mn), wobei m und n die Anzahl der Eckpunkte jeder Form sind. Die minkoswki
Menge enthält höchstens mn Elemente. Der convexHull
Algorithmus hat eine Komplexität, die von dem von Ihnen verwendeten Algorithmus abhängt , und Sie können O (k log (k)) anstreben, wobei k die Größe der Menge von Punkten ist. In unserem Fall erhalten wir also O (mn log (mn) ) . Der contains
Algorithmus hat eine Komplexität, die linear mit der Anzahl der Kanten (in 2D) oder Flächen (in 3D) der konvexen Hülle ist. Dies hängt also wirklich von Ihren Ausgangsformen ab, ist jedoch nicht größer als O (mn) .
Ich lasse Sie nach dem contains
Algorithmus für konvexe Formen googeln , er ist ziemlich häufig. Ich kann es hier setzen, wenn ich die Zeit habe.
Aber es ist die Kollisionserkennung, die wir machen, damit wir das viel optimieren können
Wir hatten ursprünglich zwei Körper A und B, die sich während eines Zeitschritts dt ohne Drehung bewegten (was ich anhand Ihrer Bilder erkennen kann). Nennen wir v A und v B die jeweiligen Geschwindigkeiten von A und B , die während unseres Zeitschritts der Dauer dt konstant sind . Wir bekommen folgendes:
Und, wie Sie in Ihren Bildern hervorheben, streichen diese Körper in 3D durch Bereiche (oder Volumen), während sie sich bewegen:
und sie enden als A ' und B' nach dem Zeitschritt.
Um unseren naiven Algorithmus hier anzuwenden, müssten wir nur die überstrichenen Volumina berechnen. Aber das machen wir nicht.
In dem Bezugssystem B , B bewegt sich nicht (duh!). Und A hat eine bestimmte Geschwindigkeit in Bezug auf B , die Sie durch Berechnen von v A - v B erhalten (Sie können das Gegenteil tun , indem Sie die relative Geschwindigkeit von B im Bezugssystem von A berechnen ).
Von links nach rechts: Geschwindigkeiten im Basisbezugsrahmen; relative Geschwindigkeiten; Berechnung der relativen Geschwindigkeiten.
Wenn Sie B als unbeweglich in einem eigenen Referenzrahmen betrachten, müssen Sie nur das Volumen berechnen, das A durchläuft, wenn es sich während dt mit seiner relativen Geschwindigkeit v A - v B bewegt .
Dies verringert die Anzahl der Eckpunkte, die bei der Minkowski-Summenberechnung verwendet werden sollen (manchmal sehr).
Eine weitere mögliche Optimierung ist der Punkt, an dem Sie das Volumen berechnen, das von einem der Körper überstrichen wird, z. B. A. Sie müssen nicht alle Scheitelpunkte verschieben, aus denen A besteht. Nur diejenigen, die zu Kanten gehören (Flächen in 3D), deren äußere normale "Gesicht" die Richtung der Kehren. Sicherlich haben Sie das schon bemerkt, als Sie Ihre überstrichenen Flächen für die Quadrate berechnet haben. Anhand des Skalarprodukts mit der Abtastrichtung, die positiv sein muss, können Sie erkennen, ob eine Normale in die Abtastrichtung zeigt.
Die letzte Optimierung, die nichts mit Ihrer Frage nach Kreuzungen zu tun hat, ist in unserem Fall wirklich nützlich. Dabei werden die erwähnten Relativgeschwindigkeiten und die sogenannte Trennachsenmethode verwendet. Sicher weißt du es schon.
Angenommen, Sie kennen die Radien von A und B in Bezug auf ihre Massenschwerpunkte (dh den Abstand zwischen dem Massenschwerpunkt und dem am weitesten davon entfernten Scheitelpunkt) wie folgt:
Eine Kollision kann nur auftreten, wenn der Begrenzungskreis von A mit dem von B übereinstimmt . Wir sehen hier, dass dies nicht der Fall ist, und wie Sie dem Computer mitteilen, dass die Entfernung von C B zu I wie im folgenden Bild berechnet werden soll, und sicherstellen, dass sie größer als die Summe der Radien von A und B ist . Wenn es größer ist, keine Kollision. Wenn es kleiner ist, dann Kollision.
Dies funktioniert nicht sehr gut mit Formen, die ziemlich lang sind, aber im Fall von Quadraten oder anderen derartigen Formen ist es eine sehr gute Heuristik , Kollisionen auszuschließen .
Der auf B angewendete Trennachsensatz und das von A überstrichene Volumen zeigen jedoch an, ob die Kollision auftritt. Die Komplexität des zugeordneten Algorithmus ist linear mit der Summe der Anzahl der Eckpunkte jeder konvexen Form, aber es ist weniger magisch, wann der Zeitpunkt kommt, um die Kollision tatsächlich zu handhaben.
Unser neuer, besserer Algorithmus, der Schnittpunkte zur Erkennung von Kollisionen verwendet, aber immer noch nicht so gut ist wie der Satz der Trennachse, um tatsächlich zu sagen, ob eine Kollision auftritt
boolean mayCollide(Body A, Body B) {
Vector2D relativeVelocity = A.velocity - B.velocity;
if (radiiHeuristic(A, B, relativeVelocity)) {
return false; // there is a separating axis between them
}
Volume sweptA = sweptVolume(A, relativeVelocity);
return contains(convexHull(minkowskiMinus(sweptA, B)), Vector2D(0,0));
}
boolean radiiHeuristic(A, B, relativeVelocity)) {
// the code here
}
Volume convexHull(SetOfVertices s) {
// the code here
}
boolean contains(Volume v, Vector2D p) {
// the code here
}
SetOfVertices minkowskiMinus(Body X, Body Y) {
SetOfVertices result = new SetOfVertices();
for (Vertice x in X) {
for (Vertice y in Y) {
result.addVertice(x-y);
}
}
return result;
}