Kollisionserkennung mit Begrenzungsvolumen in THREE.js
Dieser Artikel zeigt, wie man Kollisionserkennung zwischen Begrenzungsboxen und Kugeln mit der Three.js-Bibliothek implementiert. Es wird angenommen, dass Sie vor dem Lesen dieses Artikels unseren einführenden Artikel über 3D-Kollisionserkennung gelesen haben und grundlegende Kenntnisse über Three.js besitzen.
Verwendung von Box3
und Sphere
Boxen instanziieren
Um eine Box3
-Instanz zu erstellen, müssen wir die unteren und oberen Grenzen der Box angeben. Normalerweise möchten wir diese AABB mit einem Objekt in unserer 3D-Welt "verknüpfen" (wie einem Charakter). In Three.js verfügen Geometry
-Instanzen über eine boundingBox
-Eigenschaft mit min
- und max
-Grenzen für das Objekt. Beachten Sie, dass, um diese Eigenschaft zu definieren, Sie vorher manuell Geometry.computeBoundingBox
aufrufen müssen.
const knot = new THREE.Mesh(
new THREE.TorusKnotGeometry(0.5, 0.1),
new MeshNormalMaterial({}),
);
knot.geometry.computeBoundingBox();
const knotBBox = new Box3(
knot.geometry.boundingBox.min,
knot.geometry.boundingBox.max,
);
Hinweis:
Die boundingBox
-Eigenschaft nimmt die Geometry
selbst als Bezugspunkt und nicht das Mesh
. Daher werden alle Transformationen wie Skalierung, Position usw., die auf das Mesh
angewendet werden, bei der Berechnung der Box ignoriert.
Eine einfachere Alternative, die das vorherige Problem behebt, ist, diese Grenzen später mit Box3.setFromObject
festzulegen, was die Dimensionen unter Berücksichtigung der Transformationen und aller Kind-Meshes einer 3D-Entität berechnet.
const knot = new THREE.Mesh(
new THREE.TorusKnotGeometry(0.5, 0.1),
new MeshNormalMaterial({}),
);
const knotBBox = new Box3(new THREE.Vector3(), new THREE.Vector3());
knotBBox.setFromObject(knot);
Kugeln instanziieren
Das Instanziieren von Sphere
-Objekten ist ähnlich. Wir müssen den Mittelpunkt und den Radius der Kugel angeben, die der boundingSphere
-Eigenschaft in Geometry
hinzugefügt werden können.
const knot = new THREE.Mesh(
new THREE.TorusKnotGeometry(0.5, 0.1),
new MeshNormalMaterial({}),
);
const knotBSphere = new Sphere(
knot.position,
knot.geometry.boundingSphere.radius,
);
Leider gibt es kein Äquivalent zu Box3.setFromObject
für Sphere-Instanzen. Daher müssen wir die Begrenzungskugel manuell aktualisieren, wenn wir Transformationen anwenden oder die Position des Mesh
ändern. Beispielsweise:
knot.scale.set(2, 2, 2);
knotBSphere.radius = knot.geometry.radius * 2;
Schnittstellentests
Punkt vs. Box3
/ Sphere
Sowohl Box3
als auch Sphere
verfügen über eine containsPoint
-Methode, um diesen Test durchzuführen.
const point = new THREE.Vector3(2, 4, 7);
knotBBox.containsPoint(point);
Box3
vs. Box3
Die Box3.intersectsBox
-Methode steht zur Verfügung, um diesen Test durchzuführen.
knotBbox.intersectsBox(otherBox);
Hinweis:
Dies unterscheidet sich von der Box3.containsBox
-Methode, die überprüft, ob die Box3 eine andere vollständig umschließt.
Sphere
vs. Sphere
In ähnlicher Weise wie zuvor gibt es eine Sphere.intersectsSphere
-Methode, um diesen Test durchzuführen.
knotBSphere.intersectsSphere(otherSphere);
Sphere
vs. Box3
Leider ist dieser Test in Three.js nicht implementiert, aber wir können die Sphere patchen, um einen Algorithmus für die Schnittstelle zwischen Kugel und AABB zu implementieren.
// expand THREE.js Sphere to support collision tests vs. Box3
// we are creating a vector outside the method scope to
// avoid spawning a new instance of Vector3 on every check
THREE.Sphere.__closest = new THREE.Vector3();
THREE.Sphere.prototype.intersectsBox = function (box) {
// get box closest point to sphere center by clamping
THREE.Sphere.__closest.set(this.center.x, this.center.y, this.center.z);
THREE.Sphere.__closest.clamp(box.min, box.max);
const distance = this.center.distanceToSquared(THREE.Sphere.__closest);
return distance < this.radius * this.radius;
};
Demos
Wir haben einige Live-Demos vorbereitet, um diese Techniken zu demonstrieren, mit Quellcode zum Untersuchen.
Verwendung von BoxHelper
Als Alternative zur Verwendung von rohen Box3
- und Sphere
-Objekten bietet Three.js ein nützliches Objekt, um die Handhabung von Begrenzungsboxen zu erleichtern: BoxHelper
(früher BoundingBoxHelper
, das inzwischen veraltet ist). Dieser Helfer nimmt ein Mesh
und berechnet ein Begrenzungsboxvolumen dafür (einschließlich der Kind-Meshes). Dies führt zu einem neuen Box-Mesh
, das die Form der Begrenzungsbox zeigt und zur zuvor gesehenen Methode setFromObject
übergeben werden kann, um eine Begrenzungsbox zu erstellen, die mit dem Mesh
übereinstimmt.
BoxHelper
ist die empfohlene Methode, um 3D-Kollisionen mit Begrenzungsvolumen in Three.js zu handhaben. Kugeltests fehlen zwar, aber die Kompromisse sind den Aufwand wert.
Die Vorteile der Verwendung dieses Helfers sind:
- Er verfügt über eine
update()
-Methode, die sein Begrenzungsbox-Mesh
vergrößert, wenn das verknüpfteMesh
gedreht wird oder sich seine Dimensionen ändern, und seine Position aktualisiert. - Er berücksichtigt die Kind-Meshes bei der Berechnung der Größe der Begrenzungsbox, sodass das ursprüngliche Mesh und alle seine Kinder umschlossen werden.
- Wir können Kollisionen leicht debuggen, indem wir die von
BoxHelper
erstelltenMesh
-Objekte rendern. Standardmäßig werden sie mit einem Material vom TypLineBasicMaterial
erstellt (ein Three.js-Material zum Zeichnen von Drahtgitter-Geometrien).
Der Hauptnachteil ist, dass er nur Box-Begrenzungsvolumina erstellt, sodass Sie für Kugel-gegen-AABB-Tests Ihre eigenen Sphere
-Objekte erstellen müssen.
Um ihn zu verwenden, müssen wir eine neue BoxHelper
-Instanz erstellen und die Geometrie und — optional — eine Farbe angeben, die für das Drahtgittermaterial verwendet wird. Wir müssen das neu erstellte Objekt auch der three.js
-Szene hinzufügen, um es zu rendern. Wir gehen davon aus, dass unsere Szenenvariable den Namen scene
hat.
const knot = new THREE.Mesh(
new THREE.TorusKnotGeometry(0.5, 0.1),
new THREE.MeshNormalMaterial({}),
);
const knotBoxHelper = new THREE.BoxHelper(knot, 0x00ff00);
scene.add(knotBoxHelper);
Um auch unsere tatsächliche Box3
-Begrenzungsbox zu erhalten, erstellen wir ein neues Box3
-Objekt und lassen es die Form und Position des BoxHelper
annehmen.
const box3 = new THREE.Box3();
box3.setFromObject(knotBoxHelper);
Wenn wir die Position, Rotation, Skalierung usw. des Mesh
ändern, müssen wir die update()
-Methode aufrufen, damit die BoxHelper
-Instanz mit ihrem verknüpften Mesh
übereinstimmt. Wir müssen auch setFromObject
erneut aufrufen, um Box3
dem Mesh
anzupassen.
knot.position.set(-3, 2, 1);
knot.rotation.x = -Math.PI / 4;
// update the bounding box so it stills wraps the knot
knotBoxHelper.update();
box3.setFromObject(knotBoxHelper);
Das Durchführen von Kollisionstests erfolgt auf die gleiche Weise wie im obigen Abschnitt erläutert — wir verwenden unser Box3-Objekt wie oben beschrieben.
// box vs. box
box3.intersectsBox(otherBox3);
// box vs. point
box3.containsPoint(point.position);
Demos
Es gibt zwei Demos, die Sie sich auf unserer Live-Demo-Seite ansehen können. Die erste zeigt Punkt-gegen-Box-Kollisionen mit BoxHelper
. Die zweite führt Box-gegen-Box-Tests durch.