Es gibt viele Gründe, aus denen Sie eine benutzerdefinierte Physik-Engine erstellen möchten: Erstens, Ihre mathematischen, physischen und programmierten Fähigkeiten zu erlernen und zu verbessern, sind gute Gründe, ein solches Projekt zu versuchen. Zweitens kann eine angepasste Physik-Engine jede Art von technischen Effekt angehen, den der Ersteller mit der Fähigkeit erstellen kann. In diesem Artikel möchte ich Ihnen eine solide Einführung geben, wie Sie eine benutzerdefinierte Physik-Engine völlig neu erstellen können.
Physik bietet ein wunderbares Mittel, um einem Spieler zu ermöglichen, sich in ein Spiel einzutauchen. Es macht Sinn, dass die Beherrschung einer Physik-Engine für jeden Programmierer eine große Bereicherung darstellt. Optimierungen und Spezialisierungen können jederzeit aufgrund eines tiefen Verständnisses der inneren Funktionsweise der Physik-Engine vorgenommen werden.
Am Ende dieses Tutorials werden die folgenden Themen in zwei Dimensionen behandelt:
Hier ist eine kurze Demo:
Hinweis: Obwohl dieses Tutorial mit C ++ geschrieben wurde, sollten Sie in der Lage sein, in fast jeder Spieleentwicklungsumgebung dieselben Techniken und Konzepte anzuwenden.
Dieser Artikel beinhaltet eine ziemlich große Menge an Mathematik und Geometrie und in viel geringerem Umfang auch die eigentliche Kodierung. Ein paar Voraussetzungen für diesen Artikel sind:
Es gibt einige Artikel und Tutorials im Internet, einschließlich Tuts +, die Kollisionserkennung abdecken. Wenn ich das weiß, möchte ich das Thema sehr schnell durchgehen, da dieser Abschnitt nicht im Mittelpunkt dieses Artikels steht.
AABB (Axis Aligned Bounding Box) ist eine Box, deren vier Achsen mit dem Koordinatensystem ausgerichtet sind, in dem sie sich befindet. Dies bedeutet, dass es sich um eine Box handelt, die sich nicht drehen kann, und sie ist immer um 90 Grad (normalerweise am Bildschirm ausgerichtet) ausgerichtet. Im Allgemeinen wird es als "Begrenzungsrahmen" bezeichnet, da AABBs zum Binden anderer komplexerer Formen verwendet werden.
Ein Beispiel AABB.Die AABB einer komplexen Form kann als einfacher Test verwendet werden, um festzustellen, ob sich komplexere Formen in den AABB möglicherweise überschneiden. Bei den meisten Spielen wird die AABB jedoch als grundlegende Form verwendet und bindet eigentlich nichts anderes. Die Struktur Ihrer AABB ist wichtig. Es gibt verschiedene Möglichkeiten, eine AABB darzustellen, dies ist jedoch mein Favorit:
struct AABB Vec2 min; Vec2 max; ;
Mit diesem Formular kann eine AABB durch zwei Punkte dargestellt werden. Der minimale Punkt repräsentiert die unteren Grenzen der x- und y-Achse, und max repräsentiert die höheren Grenzen. Mit anderen Worten, sie repräsentieren die oberen linken und unteren rechten Ecken. Um zu erkennen, ob sich zwei AABB-Formen schneiden, müssen Sie ein grundlegendes Verständnis des Trennachsensatzes (SAT) haben..
Hier ein kurzer Test aus der Echtzeitkollisionserkennung von Christer Ericson, der den SAT verwendet:
bool AABBvsAABB (AABB a, AABB b) // Beenden Sie sich ohne Schnittpunkt, wenn sie entlang einer Achse getrennt gefunden werden, wenn (a.max.x < b.min.x or a.min.x > b.max.x) liefert false if (a.max.y < b.min.y or a.min.y > b.max.y) return false // Keine Trennachse gefunden, daher gibt es mindestens eine überlappende Achse, die true zurückgibt.
Ein Kreis wird durch einen Radius und einen Punkt dargestellt. So sollte Ihre Kreisstruktur aussehen:
struct Circle Schwimmerradius Vec-Position;
Es ist sehr einfach zu testen, ob sich zwei Kreise schneiden oder nicht: Nehmen Sie die Radien der beiden Kreise und addieren Sie sie, und prüfen Sie, ob diese Summe größer ist als der Abstand zwischen den beiden Kreisen.
Eine wichtige Optimierung, die hier vorgenommen werden muss, ist die Beseitigung der Notwendigkeit, den Quadratwurzeloperator zu verwenden:
float Abstand (Vec2 a, Vec2 b) return sqrt ((ax - bx) ^ 2 + (ay - by) ^ 2) bool CirclevsCircleUnoptimized (Kreis a, Kreis b) float r = a.radius + b.radius Rückkehr r < Distance( a.position, b.position ) bool CirclevsCircleOptimized( Circle a, Circle b ) float r = a.radius + b.radius r *= r return r < (a.x + b.x)^2 + (a.y + b.y)^2
Im Allgemeinen ist Multiplikation eine viel billigere Operation als die Quadratwurzel eines Werts.
Die Impulsauflösung ist eine besondere Art der Kollisionsauflösungsstrategie. Kollisionsauflösung ist der Vorgang, bei dem zwei Objekte, von denen festgestellt wird, dass sie sich überschneiden, so übernommen werden, dass sie sich nicht überschneiden.
Im Allgemeinen verfügt ein Objekt innerhalb einer Physikmaschine über drei Hauptfreiheitsgrade (in zwei Dimensionen): Bewegung in der xy-Ebene und Drehung. In diesem Artikel beschränken wir implizit die Rotation und verwenden nur AABBs und Circles. Der einzige Freiheitsgrad, den wir wirklich berücksichtigen müssen, ist die Bewegung entlang der xy-Ebene.
Durch das Auflösen erkannter Kollisionen beschränken wir die Bewegung so, dass sich Objekte nicht überschneiden können. Die Idee der Impulsauflösung ist die Verwendung eines Impulses (sofortige Änderung der Geschwindigkeit), um gefundene Objekte voneinander zu trennen. Zu diesem Zweck müssen Masse, Position und Geschwindigkeit jedes Objekts in gewisser Weise berücksichtigt werden: Wir möchten, dass große Objekte, die mit kleineren Objekten kollidieren, sich während der Kollision ein wenig bewegen und die kleinen Objekte wegfliegen. Wir möchten auch, dass sich Objekte mit unendlicher Masse überhaupt nicht bewegen.
Ein einfaches Beispiel für die Impulsauflösung.Um solche Effekte zu erzielen und der natürlichen Intuition des Verhaltens von Objekten zu folgen, verwenden wir starre Körper und einiges an Mathematik. Ein starrer Körper ist nur eine Form, die vom Benutzer (dh von Ihnen, dem Entwickler) definiert wird und implizit als nicht verformbar definiert ist. Sowohl AABBs als auch Kreise in diesem Artikel sind nicht verformbar und werden immer entweder AABB oder Circle sein. Kein Quetschen oder Dehnen erlaubt.
Durch die Arbeit mit starren Körpern können viele Berechnungen und Ableitungen stark vereinfacht werden. Aus diesem Grund werden in Spielesimulationen üblicherweise starre Körper verwendet, weshalb wir sie in diesem Artikel verwenden werden.
Angenommen, wir haben zwei Formen gefunden, die sich überschneiden, wie kann man die beiden tatsächlich trennen? Nehmen wir an, unsere Kollisionserkennung hat uns zwei wichtige Informationen geliefert:
Um einen Impuls auf beide Objekte anzuwenden und sie auseinander zu bewegen, müssen wir wissen, in welche Richtung sie gedrückt werden und um wie viel. Die Kollisionsnormal ist die Richtung, in der der Impuls angelegt wird. Die Eindringtiefe bestimmt (zusammen mit einigen anderen Dingen), wie groß ein Impuls sein wird. Dies bedeutet, dass der einzige Wert, für den gelöst werden muss, die Größe unseres Impulses ist.
Lassen Sie uns nun eine lange Wanderung unternehmen, um herauszufinden, wie wir diese Impulsgröße lösen können. Wir fangen mit unseren beiden Objekten an, die sich überschneiden:
Gleichung 1\ [V ^ AB = V ^ B - V ^ A \] Beachten Sie, dass Sie zum Erstellen eines Vektors von Position A nach Position B Folgendes tun müssen: Endpunkt - Startpunkt
. \ (V ^ AB \) ist die Relativgeschwindigkeit von A nach B. Diese Gleichung sollte in Form der Kollisionsnormalität \ (n \) ausgedrückt werden - das heißt, wir möchten die Relativgeschwindigkeit von A nach kennen B entlang der Richtung der Kollisionsnormalen:
\ [V ^ AB \ cdot n = (V ^ B - V ^ A) \ cdot n \]
Wir machen jetzt von dem Punktprodukt Gebrauch. Das Punktprodukt ist einfach; es ist die Summe der komponentenbezogenen Produkte:
Gleichung 3\ [V_1 = \ begin Matrix x_1 \\ y_1 \ Ende Matrix, V_2 = \ begin Matrix x_2 \\ y_2 \ Ende Matrix \\ V_1 \ cdot V_2 = x_1 * x_2 + y_2 * y_2 \ ]
Der nächste Schritt ist die Einführung des sogenannten Restitutionskoeffizient. Restitution ist ein Begriff, der Elastizität oder Schwung bedeutet. Jedes Objekt in Ihrer Physik-Engine wird als Dezimalwert dargestellt. Bei der Impulsberechnung wird jedoch nur ein Dezimalwert verwendet.
Um zu entscheiden, welche Restitution verwendet werden soll (gekennzeichnet durch \ (e \) für Epsilon), sollten Sie immer die niedrigste Restitution verwenden, die an der Kollision beteiligt ist, um intuitive Ergebnisse zu erzielen:
// Gegeben zwei Objekte A und B e = min (A.restitution, B.restitution)
Sobald \ (e \) erfasst ist, können wir es in unsere Gleichung setzen, um die Impulsgröße zu lösen.
Das Newtonsche Rückstellungsgesetz besagt Folgendes:
Gleichung 4\ [V '= e * V \]
Das heißt nur, dass die Geschwindigkeit nach einer Kollision gleich der Geschwindigkeit davor ist, multipliziert mit einer Konstanten. Diese Konstante stellt einen "Sprungfaktor" dar. In Anbetracht dessen wird es relativ einfach, die Restitution in unsere aktuelle Ableitung zu integrieren:
Gleichung 5\ [V ^ AB \ cdot n = -e * (V ^ B - V ^ A) \ cdot n \]
Beachten Sie, wie wir hier ein negatives Zeichen eingeführt haben. In Newtons Gesetz der Restitution geht \ (V '\), der resultierende Vektor nach dem Absprung, tatsächlich in die entgegengesetzte Richtung von V. Wie stellen wir also entgegengesetzte Richtungen in unserer Herleitung dar? Ein negatives Vorzeichen einführen.
So weit, ist es gut. Jetzt müssen wir diese Geschwindigkeiten unter dem Einfluss eines Impulses ausdrücken können. Hier ist eine einfache Gleichung zum Modifizieren eines Vektors durch einen Impulsskalar \ (j \) entlang einer bestimmten Richtung \ (n \):
Gleichung 6\ [V '= V + j * n \]
Hoffentlich macht die obige Gleichung Sinn, da es sehr wichtig ist zu verstehen. Wir haben einen Einheitsvektor \ (n \), der eine Richtung darstellt. Wir haben einen Skalar \ (j \), der angibt, wie lang unser \ (n \) -Vektor sein wird. Dann fügen wir unseren skalierten \ (n \) -Vektor zu \ (V \) hinzu, um \ (V '\) zu erhalten. Dies ist nur das Hinzufügen eines Vektors zu einem anderen, und wir können diese kleine Gleichung verwenden, um einen Impuls eines Vektors auf einen anderen anzuwenden.
Hier gibt es noch ein bisschen mehr Arbeit. Formal ist ein Impuls als Impulsänderung definiert. Momentum ist Masse * Geschwindigkeit
. Wenn wir das wissen, können wir einen Impuls darstellen, wie es formal definiert ist:
\ [Impuls = Masse * Geschwindigkeit \\ Geschwindigkeit = \ frac Impuls Masse \ daher V '= V + \ frac j * n Masse \]
Die drei Punkte in einem kleinen Dreieck (\ (\ deshalb \)) können als "deshalb" gelesen werden. Es wird verwendet, um zu zeigen, dass das Ding im Voraus verwendet werden kann, um zu schließen, dass das, was als Nächstes kommt, wahr ist.Bis jetzt sind gute Fortschritte gemacht worden! Wir müssen jedoch in der Lage sein, einen Impuls unter Verwendung von \ (j \) in zwei verschiedenen Objekten auszudrücken. Bei einer Kollision mit Objekt A und B wird A in die entgegengesetzte Richtung von B geschoben:
Gleichung 8\ [V '^ A = V ^ A + \ frac j * n Masse ^ A \\ V' ^ B = V ^ B - \ frac j * n Masse ^ B \]
Diese beiden Gleichungen werden A entlang des Richtungseinheitsvektors \ (n \) durch Impulsskalar (Betrag von \ (n \)) \ (j \) weg von B wegschieben..
Jetzt müssen Sie nur noch die Gleichungen 8 und 5 zusammenführen. Unsere resultierende Gleichung sieht etwa so aus:
Gleichung 9\ [(V ^ A - V ^ V + \ frac j * n Masse ^ A + \ frac j * n Masse ^ B) * n = -e * (V ^ B - V ^ A) \ cdot n \\ \ daher \\ (V ^ A - V ^ V + \ frac j * n Masse ^ A + \ frac j * n Masse ^ B) * n + e * (V ^ B - V ^ A) \ cdot n = 0 \]
Wenn Sie sich erinnern, bestand das ursprüngliche Ziel darin, unsere Größenordnung zu isolieren. Dies liegt daran, dass wir wissen, in welche Richtung die Kollision aufgelöst werden soll (angenommen durch die Kollisionserkennung), und haben nur noch die Größe dieser Richtung gelöst. Die Größe, die in unserem Fall unbekannt ist, ist \ (j \); Wir müssen \ (j \) isolieren und danach lösen.
Gleichung 10\ [(V ^ B - V ^ A) \ cdot n + j * (\ frac j * n Masse ^ A + \ frac j * n Masse ^ B) * n + e * ( V ^ B - V ^ A) \ cdot n = 0 \\ \ daher \\ (1 + e) ((V ^ B - V ^ A) \ cdot n) + j * (\ frac j * n Masse ^ A + \ frac j * n Masse ^ B) * n = 0 \\ \ daher \\ j = \ frac - (1 + e) ((V ^ B - V ^ A) \ cdot n) \ frac 1 Masse ^ A + \ frac 1 Masse ^ B \]
Wütend! Das war ziemlich viel Mathematik! Für jetzt ist alles vorbei. Es ist wichtig zu wissen, dass wir in der endgültigen Version von Gleichung 10 \ (j \) auf der linken Seite (unsere Stärke) haben und alles auf der rechten Seite alles bekannt ist. Dies bedeutet, dass wir einige Codezeilen schreiben können, um unseren Impulsskalar \ (j \) aufzulösen. Und Junge ist der Code viel lesbarer als mathematische Notation!
void ResolveCollision (Object A, Object B) // Relative Geschwindigkeit berechnen Vec2 rv = B.velocity - A.velocity // Relative Geschwindigkeit in Bezug auf den Normalrichtungs-Float berechnen velAlongNormal = DotProduct (rv, normal) // Nicht auflösen wenn sich die Geschwindigkeiten voneinander trennen, wenn (velAlongNormal> 0) zurückkehrt; // Berechne den Rückstellungsschwimmer e = min (A. Restitution, B. Restitution) // Berechne den Impulsskalarschwimmer j = - (1 + e) * velAlongNormal j / = 1 / A.mass + 1 / B.mass // Übernehmen Impuls Vec2 Impuls = j * normal A. Geschwindigkeit - = 1 / A. Masse * Impuls B. Geschwindigkeit + = 1 / B. Masse * Impuls
In dem obigen Codebeispiel sind einige wichtige Punkte zu beachten. Die erste Sache ist der Check in Zeile 10, if (VelAlongNormal> 0)
. Diese Überprüfung ist sehr wichtig. Dadurch wird sichergestellt, dass Sie eine Kollision nur dann lösen, wenn sich die Objekte aufeinander zu bewegen.
Wenn sich Objekte voneinander entfernen, möchten wir nichts tun. Dadurch wird verhindert, dass Objekte, die eigentlich nicht als Kollision betrachtet werden sollten, voneinander getrennt werden. Dies ist wichtig, um eine Simulation zu erstellen, die der menschlichen Intuition folgt, was während der Objektinteraktion geschehen soll.
Zweitens ist zu beachten, dass die inverse Masse ohne Grund mehrfach berechnet wird. Speichern Sie einfach Ihre inverse Masse in jedem Objekt und berechnen Sie diese einmalig vorab:
A.inv_mass = 1 / A.massViele Physik-Engines speichern tatsächlich keine Rohmasse. Physik-Engines speichern oftmals inverse Masse und inverse Masse allein. Es ist einfach so, dass die meiste Mathematik, die Masse beinhaltet, in der Form von vorliegt
1 / Masse
. Das letzte, was zu beachten ist, ist, dass wir unseren Impulsskalar \ (j \) intelligent über die beiden Objekte verteilen. Wir möchten, dass kleine Objekte von großen Objekten mit einem großen Anteil von \ (j \) abprallen und dass die Geschwindigkeit der großen Objekte um einen sehr kleinen Anteil von \ (j \) geändert wird..
Dazu können Sie Folgendes tun:
float mass_sum = A.mass + B.mass Floatverhältnis = A.mass / Masse_sum A.geschwindigkeit - = Verhältnis * Impulsverhältnis = B.mass / Masse_sum B.geschwindigkeit + = Verhältnis * Impuls
Es ist wichtig zu wissen, dass der obige Code dem entspricht ResolveCollision ()
Beispielfunktion von vor. Wie bereits erwähnt, sind inverse Massen in einer Physikmaschine sehr nützlich.
Wenn wir fortfahren und den Code verwenden, den wir bisher haben, laufen Objekte aufeinander und springen ab. Das ist großartig, obwohl was passiert, wenn eines der Objekte unendlich viel Masse hat? Nun, wir brauchen eine gute Möglichkeit, unendlich viele Masse in unserer Simulation darzustellen.
Ich schlage vor, Null als unendliche Masse zu verwenden - obwohl wir versuchen, die inverse Masse eines Objekts mit Null zu berechnen, haben wir eine Division durch Null. Um dieses Problem zu umgehen, führen Sie folgende Schritte aus, wenn Sie die inverse Masse berechnen:
if (A.mass == 0) A.inv_mass = 0, sonst A.inv_mass = 1 / A.mass
Ein Wert von Null führt zu korrekten Berechnungen während der Impulsauflösung. Das ist immer noch in Ordnung. Das Problem des Einsinkens von Objekten entsteht, wenn etwas aufgrund der Schwerkraft in ein anderes Objekt sinkt. Vielleicht trifft etwas mit geringer Restitution eine Wand mit unendlicher Masse und beginnt zu sinken.
Diese Senkung ist auf Gleitkommafehler zurückzuführen. Bei jeder Fließkommaberechnung wird aufgrund von Hardware ein kleiner Fließkommafehler eingeführt. (Weitere Informationen finden Sie in Google [Gleitpunktfehler IEEE754].) Mit der Zeit sammelt sich dieser Fehler in Positionsfehlern an, wodurch Objekte ineinander sinken.
Um diesen Fehler zu korrigieren, muss er berücksichtigt werden. Um diesen Positionsfehler zu korrigieren, zeige ich Ihnen eine Methode namens lineare Projektion. Die lineare Projektion reduziert die Durchdringung zweier Objekte um einen kleinen Prozentsatz. Dies wird ausgeführt, nachdem der Impuls angelegt wurde. Die Positionskorrektur ist sehr einfach: Bewegen Sie jedes Objekt entlang der Kollisionsnormalität \ (n \) um einen Prozentsatz der Eindringtiefe:
void PositionalCorrection (Objekt A, Objekt B) const float Prozent = 0,2 // normalerweise 20% bis 80% Vec2-Korrektur = PenetrationDepth / (A.inv_mass + B.inv_mass)) * percent * n A.position - = A.inv_mass * Korrektur B.Position + = B.inv_mass * Korrektur
Beachten Sie, dass wir das skalieren Eindringtiefe
durch die Gesamtmasse des Systems. Dies führt zu einer Positionskorrektur, die proportional zu unserer Masse ist. Kleine Objekte werden schneller weggeschoben als schwerere Objekte.
Bei dieser Implementierung gibt es ein kleines Problem: Wenn wir immer unseren Positionsfehler beheben, springen die Objekte hin und her, während sie aufeinander liegen. Um dies zu verhindern, muss etwas Spielraum gegeben werden. Wir führen nur eine Positionskorrektur durch, wenn die Durchdringung über einem beliebigen Schwellenwert liegt, der als "Slop" bezeichnet wird:
void PositionalCorrection (Objekt A, Objekt B) const float Prozent = 0.2 // normalerweise 20% bis 80% const float slop = 0.01 // normalerweise 0.01 bis 0.1 Vec2 Korrektur = max (Penetration - k_slop, 0.0f) / (A. inv_mass + B.inv_mass)) * percent * n A.position - = A.inv_mass * Korrektur B.position + = B.inv_mass * Korrektur
Dadurch können Objekte immer etwas eindringen, ohne dass die Positionskorrektur eintritt.
Das letzte Thema, das in diesem Artikel behandelt werden soll, ist die einfache Mannigfaltigkeit. EIN vielfältig In mathematischen Begriffen ist etwas so etwas wie "eine Sammlung von Punkten, die eine Fläche im Raum darstellt". Wenn ich mich jedoch auf den Begriff "Mannigfaltigkeit" beziehe, beziehe ich mich auf ein kleines Objekt, das Informationen über eine Kollision zwischen zwei Objekten enthält.
Hier ist ein typischer Mannigfaltigkeitsaufbau:
struct Manifold Objekt * A; Objekt * B; Schwimmerdurchdringung; Vec2 normal; ;
Bei der Kollisionserkennung sollten sowohl die Durchdringung als auch die Kollisionsnormale berechnet werden. Um diese Informationen zu finden, müssen die ursprünglichen Kollisionserkennungsalgorithmen oben in diesem Artikel erweitert werden.
Beginnen wir mit dem einfachsten Kollisionsalgorithmus: Kreis gegen Kreis. Dieser Test ist meistens trivial. Können Sie sich vorstellen, in welche Richtung die Kollision gelöst werden soll? Es ist der Vektor von Kreis A zu Kreis B. Dies kann erhalten werden, indem die Position von B von A subtrahiert wird.
Die Eindringtiefe bezieht sich auf die Radien der Kreise und den Abstand voneinander. Die Überlappung der Kreise kann durch Subtraktion der summierten Radien durch die Entfernung von jedem Objekt berechnet werden.
Hier ist ein vollständiger Beispielalgorithmus zum Erzeugen der Mannigfaltigkeit einer Kollision zwischen Kreis und Kreis:
bool CirclevsCircle (Manifold * m) // Ein paar Zeiger auf jedes Objekt setzen Objekt * A = m-> A; Objekt * B = m → B; // Vektor von A nach B Vec2 n = B-> pos - A-> pos float r = A-> radius + B-> radius r * = r wenn (n.LengthSquared ()> r) false // Circles zurückgibt kollidiert haben, berechnen Sie nun den Mannigfaltigkeits-Float d = n.Length () // führen Sie den tatsächlichen sqrt aus // Wenn der Abstand zwischen den Kreisen nicht Null ist, wenn (d! = 0) // Abstand ist Differenz zwischen Radius und Abstand m-> Durchdringung = r - d // Nutzen Sie unser d, da wir sqrt bereits innerhalb von Length () // Punkten von A nach B ausgeführt haben und ein Einheitsvektor c-> normal = t / d ist true // Kreise sind an derselben Position else // Wählen Sie zufällige (aber konsistente) Werte aus c-> Durchdringung = A-> Radius c-> normal = Vec (1, 0) geben Sie true zurück
Die bemerkenswertesten Dinge hier sind: Wir führen keine Quadratwurzeln aus, bis dies notwendig ist (Objekte werden als kollidierend befunden), und wir prüfen, ob sich die Kreise nicht exakt auf derselben Position befinden. Wenn sie sich auf derselben Position befinden, wäre unsere Entfernung Null, und wir müssen bei der Berechnung eine Division durch Null vermeiden t / d
.
Der AABB-zu-AABB-Test ist etwas komplexer als Circle vs Circle. Die Kollisionsnormale ist nicht der Vektor von A nach B, sondern eine Gesichtsnormale. Eine AABB ist eine Box mit vier Gesichtern. Jedes Gesicht hat ein normales Gesicht. Diese Norm repräsentiert einen Einheitsvektor, der senkrecht zum Gesicht steht.
Untersuchen Sie die allgemeine Gleichung einer Linie in 2D:
\ [ax + by + c = 0 \\ normal = \ begin bmatrix a \\ b \ end bmatrix \]
In der obigen Gleichung, ein
und b
sind der Normalvektor für eine Linie und der Vektor (a, b)
wird als normalisiert angenommen (Vektorlänge ist Null). Unsere Kollisionsnormalität (Richtung, um die Kollision aufzulösen) wird in Richtung einer der Gesichtsnormalen sein.
c
repräsentiert in der allgemeinen Gleichung einer Linie? c
ist der Abstand vom Ursprung. Dies ist sehr nützlich, um zu testen, ob sich ein Punkt auf der einen oder anderen Seite einer Linie befindet, wie Sie im nächsten Artikel sehen werden. Jetzt müssen Sie nur noch herausfinden, welches Gesicht eines der Objekte mit dem anderen Objekt kollidiert, und wir haben unsere Normalität. Manchmal können sich jedoch mehrere Flächen von zwei AABBs schneiden, z. B. wenn sich zwei Ecken schneiden. Das heißt, wir müssen das finden Achse der geringsten Durchdringung.
Zwei Durchdringungsachsen; Die horizontale x-Achse ist die Achse des geringsten Eindringens und diese Kollision sollte entlang der x-Achse aufgelöst werden.Hier ist ein vollständiger Algorithmus für die Erzeugung von AABB-AABB-Krümmer und Kollisionserkennung:
bool AABBvsAABB (Manifold * m) // Ein paar Zeiger auf jedes Objekt setzen Objekt * A = m-> A Objekt * B = m-> B // Vektor von A nach B Vec2 n = B-> pos - A- > pos AABB abox = A-> aabb AABB bbox = B-> aabb // Berechne halbe Ausdehnungen entlang der x-Achse für jedes Objekt float a_extent = (abox.max.x - abox.min.x) / 2 float b_extent = (bbox .max.x - bbox.min.x) / 2 // Berechne Überlappung auf x-Achsen-Float x_overlap = a_extent + b_extent - abs (nx) // SAT-Test auf x-Achse if (x_overlap> 0) // halbe Ausdehnungen berechnen entlang der x-Achse für jedes Objekt float a_extent = (abox.max.y - abox.min.y) / 2 float b_extent = (bbox.max.y - bbox.min.y) / 2 // Berechnen Sie die Überlappung auf dem Float der y-Achse y_overlap = a_extent + b_extent - abs (ny) // SAT-Test auf der y-Achse, wenn (y_overlap> 0) // Ermitteln Sie, welche Achse die Achse der kleinsten Penetration ist, wenn (x_overlap> y_overlap) // Zeigen Sie in Richtung B, dass n ist Punkte von A nach B wenn (nx < 0) m->normal = Vec2 (-1, 0) else m-> normal = Vec2 (0, 0) m-> Durchdringung = x_overlap return true else // Zeigen Sie in Richtung B, dass n von A nach B weist, wenn (n.y < 0) m->normal = Vec2 (0, -1) ansonsten m-> normal = Vec2 (0, 1) m-> Durchdringung = y_overlap geben true zurück
Der letzte Test, den ich behandeln werde, ist der Circle vs. AABB-Test. Die Idee hier ist, den nächsten Punkt auf der AABB dem Kreis zu berechnen; Von dort aus wird der Test in etwas ähnlich dem Circle-vs-Circle-Test umgewandelt. Wenn der nächstgelegene Punkt berechnet und eine Kollision erkannt wurde, ist die Normalität die Richtung, die dem Kreismittelpunkt am nächsten liegt. Die Eindringtiefe ist die Differenz zwischen der Entfernung des nächstgelegenen Punkts und dem Radius des Kreises.
Es gibt einen kniffligen Sonderfall. Wenn sich der Mittelpunkt des Kreises innerhalb der AABB befindet, muss der Mittelpunkt des Kreises auf die nächstgelegene Kante der AABB gekürzt und die Normalität gedreht werden.
bool AABBvsCircle (Manifold * m) // Ein paar Zeiger auf jedes Objekt setzen Objekt * A = m-> A Objekt * B = m-> B // Vektor von A nach B Vec2 n = B-> pos - A- > pos // Nächster Punkt auf A zum Mittelpunkt von B Vec2 am nächsten = n // Berechne halbe Ausdehnungen entlang jeder Achse float x_extent = (A-> aabb.max.x - A-> aabb.min.x) / 2 float y_extent = (A-> aabb.max.y - A-> aabb.min.y) / 2 // Klammerpunkt an den Rändern der AABB am nächsten.x = Klammer (-x_extent, x_extent, am nächsten.x) am nächsten.y = Clamp (-y_extent, y_extent, close.y) bool inside = false // Der Kreis befindet sich in der AABB. Daher müssen wir die Mitte des Kreises // an die nächste Kante klemmen, wenn (n == am nächsten) inside = true // Finden Sie die nächstgelegene Achse, wenn (abs (nx)> abs (ny)) // Auf engste Ausdehnung klemmen, wenn (dichteste.x> 0) nächste.x = x_extent sonst nächste // Auf engste Ausdehnung klammern, wenn (am nächsten.y> 0) am nächsten.y = y_extent am nächsten am nächsten // Ea rly außerhalb des Radius ist kürzer als die Entfernung zum nächstgelegenen Punkt und // Kreis nicht innerhalb der AABB, wenn (d> r * r &&! inside) false zurückgibt // Vermeiden Sie sqrt, bis wir d = sqrt (d) // Collision normal benötigen muss nach außen gewendet werden, wenn der Kreis // innerhalb der AABB war, wenn (innen) m-> normal = -n m-> Durchdringung = r - d else m-> normal = n m-> Durchdringung = r - d return true
Hoffentlich haben Sie inzwischen etwas über Physik-Simulation gelernt. Dieses Tutorial reicht aus, um eine einfache benutzerdefinierte Physik-Engine einzurichten, die komplett neu erstellt wurde. Im nächsten Teil werden wir alle notwendigen Erweiterungen abdecken, die alle Physik-Engines benötigen, einschließlich:
Ich hoffe, Ihnen hat dieser Artikel gefallen, und ich freue mich darauf, Fragen in den Kommentaren zu beantworten.