Es gibt einen großen Unterschied zwischen einer Kollisionsmaschine und einer Physikmaschine. Sie tun nicht dasselbe, obwohl die Physik-Engine im Allgemeinen auf einer Kollisions-Engine basiert.
Die Kollisionsmaschine wird dann in zwei Teile aufgeteilt: Kollisionserkennung und Kollisionsreaktion. Letzteres ist in der Regel Teil der Physik-Engine. Aus diesem Grund werden Kollisions- und Physik-Engines normalerweise in derselben Bibliothek zusammengefasst.
Die Kollisionserkennung erfolgt in zwei Formen, diskret und kontinuierlich. Fortgeschrittene Engines unterstützen beide, da sie unterschiedliche Eigenschaften haben. Im Allgemeinen ist die kontinuierliche Kollisionserkennung sehr teuer und wird nur dort eingesetzt, wo sie wirklich benötigt wird. Der Großteil der Kollision und Physik wird mit diskreten Methoden behandelt. Bei diskreten Methoden dringen Objekte ineinander ein und die Physik-Engine arbeitet dann daran, sie auseinander zu drücken. Die Engine verhindert also nicht, dass ein Spieler teilweise durch eine Wand oder den Boden läuft, sondern repariert ihn nur, nachdem festgestellt wurde, dass sich der Spieler teilweise in der Wand / im Boden befindet. Ich werde mich hier auf die diskrete Kollisionserkennung konzentrieren, da ich die meiste Erfahrung darin habe, diese von Grund auf neu zu implementieren.
Kollisionserkennung
Die Kollisionserkennung ist relativ einfach. Jedes Objekt hat eine Transformation und eine Form (möglicherweise mehrere Formen). Bei naiven Ansätzen würde die Kollisionsmaschine eine O (n ^ 2) -Schleife durch alle Objektpaare durchführen und prüfen, ob es eine Überlappung zwischen den Paaren gibt. Bei intelligenteren Ansätzen gibt es mehrere räumliche Datenstrukturen (z. B. für statische und dynamische Objekte), eine Begrenzungsform für jedes Objekt und mehrteilige konvexe Unterformen für jedes Objekt.
Die räumlichen Datenstrukturen umfassen Dinge wie KD-Bäume, dynamische AABB-Bäume, Octrees / Quadtrees, Binary Space Partitioning-Bäume und so weiter. Jede hat ihre Vor- und Nachteile, weshalb manche High-End-Motoren mehr als eine verwenden. Dynamische AABB-Bäume zum Beispiel sind wirklich sehr schnell und eignen sich für die Bearbeitung vieler sich bewegender Objekte, während ein KD-Baum für die statische Ebenengeometrie, mit der Objekte kollidieren, besser geeignet ist. Es gibt auch andere Möglichkeiten.
Die breite Phase verwendet die räumlichen Datenstrukturen und ein abstraktes Begrenzungsvolumen für jedes Objekt. Ein Begrenzungsvolumen ist eine einfache Form, die das gesamte Objekt einschließt, im Allgemeinen mit dem Ziel, es so "dicht" wie möglich einzuschließen, während es billig bleibt, mit Kollisionstests durchzuführen. Die gebräuchlichsten Begrenzungsformen sind achsenausgerichtete Begrenzungsrahmen, objektausgerichtete Begrenzungsrahmen, Kugeln und Kapseln. AABBs werden im Allgemeinen als die schnellsten und einfachsten angesehen (Kugeln sind in einigen Fällen einfacher und schneller, aber viele dieser räumlichen Datenstrukturen erfordern ohnehin die Umwandlung der Kugel in eine AABB), aber sie passen auch in der Regel zu vielen Objekten eher schlecht. Kapseln sind in 3D-Engines zur Behandlung von Kollisionen auf Zeichenebene beliebt. Einige Motoren verwenden zwei Begrenzungsformen:
Die letzte Phase der Kollisionserkennung besteht darin, genau zu erkennen, wo sich die Geometrie schneidet. Dies impliziert normalerweise die Verwendung eines Netzes (oder eines Polygons in 2D), jedoch nicht immer. Der Zweck dieser Phase besteht darin, herauszufinden, ob die Objekte wirklich wirklich kollidieren, ob eine genaue Detailgenauigkeit erforderlich ist (z. B. eine Kugelkollision in einem Schützen, bei der Sie Schüsse ignorieren möchten, die gerade noch fehlen), und um herauszufinden, wo genau die Objekte kollidieren, was sich auf das Verhalten der Objekte auswirkt. Wenn beispielsweise eine Box am Rand eines Tisches sitzt, muss der Motor wissen, an welchen Punkten der Tisch gegen die Box drückt. Je nachdem, wie weit die Box hängt, kann die Box kippen und herunterfallen.
Kontakt Verteilergeneration
Zu den hier verwendeten Algorithmen gehören die bekannten Algorithmen GJK und Minkowski Portal Refinement sowie der Separating Axis-Test. Da die gängigen Algorithmen in der Regel nur für konvexe Formen funktionieren, müssen viele komplexe Objekte in konvexe Unterobjekte aufgeteilt und für jedes Objekt einzeln Kollisionstests durchgeführt werden. Dies ist einer der Gründe, warum vereinfachte Netze häufig für Kollisionen verwendet werden, und auch, weil weniger Dreiecke benötigt werden, um die Verarbeitungszeit zu verkürzen.
Einige dieser Algorithmen sagen Ihnen nicht nur, dass die Objekte sicher kollidiert sind, sondern auch, wo sie kollidiert sind - wie weit sie sich gegenseitig durchdringen und welche "Kontaktpunkte" sie haben. Einige der Algorithmen erfordern zusätzliche Schritte, z. B. das Abschneiden von Polygonen, um diese Informationen abzurufen.
Körperliche Reaktion
Zu diesem Zeitpunkt wurde ein Kontakt entdeckt, und die Physik-Engine verfügt über genügend Informationen, um den Kontakt zu verarbeiten. Die Handhabung der Physik kann sehr komplex werden. Bei einigen Spielen funktionieren einfachere Algorithmen, aber selbst etwas, das so einfach zu sein scheint wie ein stabiler Stapel von Kisten, erweist sich als recht schwierig und erfordert viel Arbeit und nicht offensichtliche Hacks.
Auf der einfachsten Ebene macht die Physik-Engine so etwas: Sie nimmt die kollidierenden Objekte und deren Kontaktverteiler und berechnet die neuen Positionen, die zum Trennen der kollidierten Objekte erforderlich sind. Die Objekte werden an diese neuen Positionen verschoben. Es berechnet auch die Geschwindigkeitsänderung, die aus diesem Stoß resultiert, kombiniert mit Rückgabe- (Bounciness-) und Reibungswerten. Die Physik-Engine wendet auch alle anderen auf die Objekte einwirkenden Kräfte an, z. B. die Schwerkraft, um die neuen Geschwindigkeiten der Objekte und dann (im nächsten Frame) ihre neuen Positionen zu berechnen.
Fortgeschrittenere Physikreaktionen werden schnell kompliziert. Der oben beschriebene Ansatz funktioniert in vielen Situationen nicht, einschließlich eines Objekts, das über zwei anderen Objekten sitzt. Der Umgang mit jedem Paar für sich verursacht "Jitter" und die Objekte werden viel herumspringen. Die grundlegendste Technik besteht darin, eine Reihe von Iterationen zur Geschwindigkeitskorrektur über die Paare von kollidierenden Objekten durchzuführen. Wenn beispielsweise eine Box "A" über zwei anderen Boxen "B" und "C" liegt, wird die Kollision AB zuerst behandelt, wodurch sich die Box A weiter in die Box C hinein neigt. Dann wird die AC-Kollision abends behandelt Ziehen Sie die Kästchen etwas heraus, aber ziehen Sie A nach unten und nach B hinein. Dann wird eine weitere Iteration durchgeführt, sodass der durch die Wechselstromkorrektur verursachte AB-Fehler leicht behoben wird, was zu einem etwas größeren Fehler in der Wechselstromantwort führt. Was behandelt wird, wenn AC erneut verarbeitet wird. Die Anzahl der durchgeführten Iterationen ist nicht festgelegt, und es gibt keinen Punkt, an dem sie "perfekt" wird, sondern nur eine beliebige Anzahl von Iterationen, die keine aussagekräftigen Ergebnisse liefert. 10 Iterationen sind ein typischer erster Versuch, aber es ist eine Feinabstimmung erforderlich, um die beste Zahl für eine bestimmte Engine und die Anforderungen eines bestimmten Spiels zu ermitteln.
Kontakt Zwischenspeichern
Es gibt andere Tricks, die sich als sehr praktisch herausstellen (mehr oder weniger notwendig), wenn es um viele Arten von Spielen geht. Kontakt-Caching ist eine der nützlicheren. Mit einem Kontakt-Cache wird jeder Satz kollidierender Objekte in einer Nachschlagetabelle gespeichert. Wenn in jedem Frame eine Kollision erkannt wird, wird dieser Cache abgefragt, um festzustellen, ob sich die Objekte zuvor in Kontakt befanden. Wenn die Objekte zuvor nicht in Kontakt waren, kann ein Ereignis "Neue Kollision" generiert werden. Wenn die Objekte zuvor in Kontakt waren, können die Informationen verwendet werden, um eine stabilere Reaktion zu erzielen. Alle Einträge im Kontakt-Cache, die nicht in einem Frame aktualisiert wurden, geben zwei Objekte an, die getrennt wurden, und ein Ereignis "Objekt trennen" kann generiert werden. Die Spielelogik hat häufig Verwendungen für diese Ereignisse.
Es ist auch möglich, dass die Spiellogik auf neue Kollisionsereignisse reagiert und sie als ignoriert markiert. Dies ist sehr hilfreich für die Implementierung einiger Funktionen, die auf Plattformen üblich sind, z. B. Plattformen, durch die Sie springen können, auf denen Sie jedoch stehen. Naive Implementierungen können Kollisionen ignorieren, die eine normale Plattform-> Charakterkollision nach unten aufweisen (was anzeigt, dass der Kopf des Spielers den unteren Rand der Plattform berührt). Ohne Kontakt-Caching wird dies jedoch unterbrochen, wenn der Kopf des Spielers über die Plattform stößt und er dann beginnt fallen. Zu diesem Zeitpunkt kann der Kontakt normal nach oben zeigen, was dazu führt, dass der Spieler über die Plattform springt, wenn er dies nicht sollte. Mit Contact Caching kann die Engine die anfängliche Kollisionsnormalität zuverlässig erkennen und alle weiteren Kontaktereignisse ignorieren, bis Plattform und Spieler wieder getrennt werden.
Schlafen
Eine andere sehr nützliche Technik besteht darin, Objekte als "schlafend" zu markieren, wenn sie nicht mit ihnen interagieren. Schlafende Objekte werden nicht aktualisiert, kollidieren nicht mit anderen schlafenden Objekten und sitzen im Grunde genommen nur so lange dort, bis ein anderes nicht schlafendes Objekt mit ihnen kollidiert.
Die Auswirkung ist, dass alle Paare von kollidierenden Objekten, die nur dort sitzen und nichts tun, keine Verarbeitungszeit in Anspruch nehmen. Da es keine konstante Anzahl kleiner physikalischer Korrekturen gibt, sind die Stapel stabil.
Ein Objekt ist ein Kandidat für den Schlaf, wenn es länger als ein Einzelbild eine Geschwindigkeit nahe Null hatte. Beachten Sie, dass das Epsilon, das Sie zum Testen dieser Geschwindigkeit nahe Null verwenden, wahrscheinlich etwas höher sein wird als das übliche Gleitkommavergleich-Epsilon, da Sie mit etwas Jitter bei gestapelten Objekten rechnen müssen und Sie möchten, dass ganze Stapel von Objekten einschlafen, wenn sie Sie bleiben "nah genug", um stabil zu bleiben. Die Schwelle erfordert natürlich Optimierungen und Experimente.
Einschränkungen
Das letzte wichtige Element vieler Physik-Engines ist der Constraint Solver. Der Zweck eines solchen Systems ist es, die Implementierung von Dingen wie Federn, Motoren, Radachsen, simulierten Weichkörpern, Stoffen, Seilen und Ketten und manchmal sogar Flüssigkeiten zu erleichtern (obwohl Flüssigkeiten oft als ein völlig anderes System implementiert werden).
Sogar die Grundlagen des Auflösens von Abhängigkeiten können sehr mathematisch intensiv werden und gehen über mein Fachwissen in diesem Bereich hinaus. Ich empfehle Randy Gallis exzellente Artikelserie über Physik zu lesen, um eine genauere Erklärung des Themas zu erhalten.