3D-Kollisionsdetektion
Dieser Artikel bietet eine Einführung in die verschiedenen Volumentechniken, die zur Implementierung der Kollisionsdetektion in 3D-Umgebungen verwendet werden. Folgeartikel werden Implementierungen in spezifischen 3D-Bibliotheken behandeln.
Achsen-ausgerichtete Begrenzungsrahmen
Wie bei der Kollisionsdetektion in 2D sind achsen-ausgerichtete Begrenzungsrahmen (AABB) der schnellste Algorithmus, um festzustellen, ob zwei Spielelemente überlappen oder nicht. Dies besteht darin, Spielelemente in einem nicht-rotierenden (also achsen-ausgerichteten) Rahmen zu umhüllen und die Position dieser Rahmen im 3D-Koordinatenraum zu überprüfen, um zu sehen, ob sie sich überschneiden.
Die achsen-ausgerichtete Einschränkung besteht aus Leistungsgründen. Der Überlappungsbereich zwischen zwei nicht-rotierenden Boxen kann nur mit logischen Vergleichen überprüft werden, während rotierende Boxen zusätzliche trigonometrische Operationen erfordern, die langsamer zu berechnen sind. Wenn Sie Elemente haben, die sich drehen werden, können Sie entweder die Dimensionen des Begrenzungsrahmens anpassen, sodass er das Objekt immer noch umhüllt, oder Sie entscheiden sich für einen anderen Begrenzungstyp wie Kugeln (die rotationsinvariant sind). Das unten gezeigte animierte GIF zeigt ein grafisches Beispiel eines AABB, der seine Größe anpasst, um das rotierende Element zu umschließen. Der Rahmen ändert ständig seine Dimensionen, um das innenliegende Element eng zu umschließen.
Hinweis: Lesen Sie den Artikel Begrenzungsvolumen mit Three.js, um eine praktische Implementierung dieser Technik zu sehen.
Punkt vs. AABB
Zu überprüfen, ob ein Punkt innerhalb eines AABB liegt, ist ziemlich einfach — wir müssen nur prüfen, ob die Koordinaten des Punktes innerhalb des AABB liegen, indem wir jede Achse separat betrachten. Wenn wir annehmen, dass Px, Py und Pz die Koordinaten des Punktes sind und BminX–BmaxX, BminY–BmaxY und BminZ–BmaxZ die Bereiche jeder Achse des AABB sind, können wir mit der folgenden Formel berechnen, ob eine Kollision zwischen den beiden stattgefunden hat:
Oder in JavaScript:
function isPointInsideAABB(point, box) {
return (
point.x >= box.minX &&
point.x <= box.maxX &&
point.y >= box.minY &&
point.y <= box.maxY &&
point.z >= box.minZ &&
point.z <= box.maxZ
);
}
AABB vs. AABB
Zu überprüfen, ob ein AABB mit einem anderen AABB überschneidet, ist dem Punkt-Test ähnlich. Wir müssen nur einen Test pro Achse durchführen, indem wir die Grenzen der Boxen verwenden. Das unten stehende Diagramm zeigt den Test, den wir über die X-Achse durchführen würden — im Wesentlichen, überschneiden sich die Bereiche AminX–AmaxX und BminX–BmaxX?
Mathematisch würde dies so aussehen:
Und in JavaScript würden wir das so schreiben:
function intersect(a, b) {
return (
a.minX <= b.maxX &&
a.maxX >= b.minX &&
a.minY <= b.maxY &&
a.maxY >= b.minY &&
a.minZ <= b.maxZ &&
a.maxZ >= b.minZ
);
}
Begrenzungskugeln
Die Verwendung von Begrenzungskugeln zur Kollisionsdetektion ist etwas komplexer als AABB, aber immer noch relativ schnell zu testen. Der Hauptvorteil von Kugeln ist, dass sie rotationsinvariant sind, sodass die Begrenzungskugel gleich bleibt, wenn das umhüllte Objekt rotiert. Der Hauptnachteil ist, dass, es sei denn, das umhüllte Objekt ist tatsächlich kugelförmig, die Umhüllung normalerweise nicht gut passt (z.B. wird eine Person mit einer Begrenzungskugel abgewickelt viele falsche Positive hervorrufen, während AABB besser passen würde).
Punkt vs. Kugel
Um zu prüfen, ob eine Kugel einen Punkt enthält, müssen wir den Abstand zwischen dem Punkt und dem Mittelpunkt der Kugel berechnen. Wenn dieser Abstand kleiner oder gleich dem Radius der Kugel ist, liegt der Punkt innerhalb der Kugel.
Unter Berücksichtigung, dass der euklidische Abstand zwischen zwei Punkten A und B ist, würde unsere Formel für die Kollisionsdetektion Punkt vs. Kugel so aussehen:
Oder in JavaScript:
function isPointInsideSphere(point, sphere) {
// we are using multiplications because is faster than calling Math.pow
const distance = Math.sqrt(
(point.x - sphere.x) * (point.x - sphere.x) +
(point.y - sphere.y) * (point.y - sphere.y) +
(point.z - sphere.z) * (point.z - sphere.z),
);
return distance < sphere.radius;
}
Hinweis:
Der obige Code enthält eine Quadratwurzel, die teuer in der Berechnung sein kann. Eine einfache Optimierung besteht darin, sie zu vermeiden, indem die quadratische Distanz mit dem quadratischen Radius verglichen wird, sodass die optimierte Gleichung stattdessen distanceSqr < sphere.radius * sphere.radius
verwenden würde.
Kugel vs. Kugel
Der Kugel-gegen-Kugel-Test ähnelt dem Punkt-gegen-Kugel-Test. Was wir hier testen müssen, ist, dass der Abstand zwischen den Mittelpunkten der Kugeln kleiner oder gleich der Summe ihrer Radien ist.
Mathematisch sieht das so aus:
Oder in JavaScript:
function intersect(sphere, other) {
// we are using multiplications because it's faster than calling Math.pow
const distance = Math.sqrt(
(sphere.x - other.x) * (sphere.x - other.x) +
(sphere.y - other.y) * (sphere.y - other.y) +
(sphere.z - other.z) * (sphere.z - other.z),
);
return distance < sphere.radius + other.radius;
}
Kugel vs. AABB
Zu testen, ob eine Kugel und ein AABB kollidieren, ist etwas komplizierter, bleibt aber einfach und schnell. Ein logischer Ansatz wäre, jeden Scheitelpunkt des AABB zu prüfen und für jeden einen Punkt-gegen-Kugel-Test durchzuführen. Das ist jedoch übertrieben — alle Scheitelpunkte zu testen ist unnötig, da wir den Abstand vom nächsten Punkt des AABB (nicht unbedingt ein Scheitelpunkt) zum Mittelpunkt der Kugel berechnen können, um zu prüfen, ob er kleiner oder gleich dem Radius der Kugel ist. Dieser Wert kann durch Clamping des Mittelpunkts der Kugel an die Grenzen des AABB erhalten werden.
In JavaScript würden wir diesen Test so durchführen:
function intersect(sphere, box) {
// get box closest point to sphere center by clamping
const x = Math.max(box.minX, Math.min(sphere.x, box.maxX));
const y = Math.max(box.minY, Math.min(sphere.y, box.maxY));
const z = Math.max(box.minZ, Math.min(sphere.z, box.maxZ));
// this is the same as isPointInsideSphere
const distance = Math.sqrt(
(x - sphere.x) * (x - sphere.x) +
(y - sphere.y) * (y - sphere.y) +
(z - sphere.z) * (z - sphere.z),
);
return distance < sphere.radius;
}
Die Verwendung einer Physik-Engine
3D-Physik-Engines bieten Kollisionsdetektionsalgorithmen, von denen die meisten ebenfalls auf Begrenzungsvolumen basieren. Eine Physik-Engine funktioniert, indem sie einen physischen Körper erstellt, der üblicherweise an eine visuelle Darstellung davon angehängt wird. Dieser Körper hat Eigenschaften wie Geschwindigkeit, Position, Drehung, Drehmoment, etc., und auch eine physische Form. Diese Form wird in den Kollisionsberechnungen berücksichtigt.
Wir haben eine Live-Kollisionsdemonstration (mit Quellcode) vorbereitet, die Sie sich ansehen können, um solche Techniken in Aktion zu sehen — diese verwendet die Open-Source 3D-Physik-Engine cannon.js.
Siehe auch
Verwandte Artikel auf MDN:
Externe Ressourcen:
- Einfache Schnitttests für Spiele auf Game Developer
- Begrenzungsvolumen auf Wikipedia