aframe-physics-extras: kollision einer dynamischen entität mit einem statischen gltf-modell
Ich versuche, ein Kollisionsereignis zwischen einer dynamischen Kugel und einem statischen gltf-Modell zu erfassen. Ich baue die gltf-Entität folgendermaßen auf:
const template = document.createElement('a-entity');
template.setAttribute('physics-collider', 'ignoreSleep: false');
template.setAttribute('collision-filter', 'collisionForces: false');
template.setAttribute('body', 'type:static; shape:hull');
// add position, scale, url etc
// ...
template.addEventListener('collisions', e => {
// debugger;
})
Dieser Code erstellt eine Entität, im Debug-Modus befindet sich jedoch kein Drahtnetz darum, und das collisionsEreignis wird nicht ausgelöst. Zu Debug-Zwecken habe ich verschiedene Formen ausprobiert. Dadurch wird ein Zylinder um die Entität erstellt, der jedoch zu groß erscheint. Die dynamische Form kreuzt den Zylinder, aber das collisionsEreignis wird nicht immer ausgelöst, eher selten.
template.setAttribute('body', 'type:static; shape:cylinder');
Dann habe ich versucht, die Form manuell zu erstellen:
template.setAttribute('body', 'type:static; shape:none');
template.setAttribute('shape', 'shape: cylinder; height: 5;');
In diesem Fall wird folgende Fehlermeldung angezeigt:
Cannot read property 'bodyOverlapKeeper' of null at NewComponent.<anonymous> (physics-collider.js:34)
Also stecke ich jetzt fest. Könnte jemand raten, was ich falsch mache. Ich möchte die Form des gltf-Modells selbst verwenden. Ich habe es im Mixer geöffnet, aber es scheint in Ordnung zu sein, ich verstehe nicht, warum shape:hulles nicht funktioniert.
PS: Wenn es darauf ankommt, benutze ich Webpack
Antworten
0. using setAttribute
setAttribute behandelt keine Liste wie:
template.setAttribute('body', 'type:static; shape:none');
Geben Sie lieber ein Objekt mit den neuen Attributen an:
element.setAttribute("name", {
"property 1": "value 1",
"property 2": "value 2"
});
1. Dynamische Körper- / Formeinstellung
Davon abgesehen können Sie einen benutzerdefinierten statischen Zylinder wie folgt erstellen:
element.setAttribute("body", {
"type": "static",
"shape": "none"
})
element.setAttribute("shape__cylinder", {
'shape': 'cylinder',
"height": 1.5,
"radiusTop": 0.1,
"radiusBottom": 0.2
})
Schau es dir in dieser Geige an
2. dynamische Form für Modelle
Zum Erstellen einer dynamischen Form für ein GTF-Modell. Persönlich hatte ich kein Glück damit cannon, obwohl es gut mit dem ammoFahrer funktioniert hat . Auf der anderen Seite hatte ich einen großen FPS-Abfall (auf ~ älteren Mobilgeräten). Versuchen Sie daher nach Möglichkeit, aus Gründen der Leistung einfache Kollisionsnetze zu verwenden.
Sie können einen Begrenzungsrahmen eines enthäuteten Modells mit einer einfachen Funktion erhalten, die ich erstellt habe :
let box = new THREE.Box3()
THREE.Box3Utils.fromSkinnedMesh(skinnedMesh, box);
// box should be the bounding box of the skinned mesh
3. animierte Modelle
Ich würde dringend empfehlen, einfache Kollisionsformen zu erstellen und sie an einem bestimmten Knochen im Modell zu befestigen. Ein Ausgangspunkt könnte diese Komponente sein :
<a-gltf-model bone-collider="bone: boneName; halfExtents: 0.15 0.15 0.15">
Der Bounding-Box-Ansatz wird sehr kompliziert sein, da:
- Sie müssten einen Begrenzungsrahmen für ein nicht gedrehtes Netz berechnen
- Wenden Sie die Drehung auf das Ergebnis an (Begrenzungsrahmen sind weltweit ausgerichtet).
- Aktualisieren Sie den Körper bei jedem Tick oder entfernen Sie sogar die Form und fügen Sie sie neu hinzu ( Quelle )
In diesem Beispiel sehen Sie beide Ansätze
In der Zwischenzeit konnte ich ein animiertes Kollisionsnetz für ein GTF-Modell implementieren. Früher habe ich eine Hilfsfunktion implementiert durch @Piotr Adam Milewski hier . Weitere Details zur Funktion finden Sie in dieser Antwort. Alle anderen Vorschläge zur Verbesserung der Leistung sind willkommen.
AFRAME.registerComponent('animated-collision-shape', {
init: function () {
this.nodeMap = {};
this.el.setAttribute('body', 'type: static; shape: none;')
this.el.addEventListener('model-loaded', () => {
const size = new THREE.Vector3();
let box = new THREE.Box3().setFromObject(this.el.object3D);
box.getSize(size);
this.offset = new CANNON.Vec3(0, size.y / 2, 0);
let mesh = this.el.getObject3D("mesh");
mesh.traverse(node => {
if (node.isSkinnedMesh) {
this.skinnedMesh = node;
this.nodeMap[node.uuid] = {
mesh: node,
box: new THREE.Box3()
}
}
});
if (!Object.keys(this.nodeMap).length) {
this.nodeMap[0] = {
mesh: this.el.object3D,
box: new THREE.Box3()
};
}
this.el.components["body"].shouldUpdateBody = true;
})
},
remove: function () {
this.removeBoxes();
},
tick: (function () {
const size = new THREE.Vector3();
let common_box_uuid = null;
return function tick() {
if (
!Object.keys(this.nodeMap).length ||
!this.el.body) {
return;
}
let combine = this.data.combine === true
let i = 0;
for (let uuid in this.nodeMap) {
// Non - skinned case
if (!this.nodeMap[uuid].mesh.isSkinnedMesh) {
this.nodeMap[uuid].box.setFromObject(this.el.object3D);
return;
}
// skinned model. Either separate boxes, or combined
if (common_box_uuid && combine) {
utils.SkinnedMeshBBox.expandAABB(this.nodeMap[uuid].mesh, this.nodeMap[common_box_uuid].box);
} else {
utils.SkinnedMeshBBox.getAABB(this.nodeMap[uuid].mesh, this.nodeMap[uuid].box);
common_box_uuid = uuid
}
if (isFinite(this.nodeMap[common_box_uuid].box.max.x)) {
this.nodeMap[common_box_uuid].box.getSize(size);
if (this.el.body.shapes[i]) {
this.el.body.shapes[i].halfExtents = new CANNON.Vec3(size.x / 2, size.y / 2, size.z / 2);
this.el.body.shapes[i].updateConvexPolyhedronRepresentation();
} else {
let shape = new CANNON.Box(new CANNON.Vec3(size.x / 2, size.y / 2, size.z / 2))
this.el.body.addShape(shape, this.offset, shape.orientation);
}
i++;
}
}
this.el.components["body"].shouldUpdateWireframe = true;
};
})()
})