aframe-physics-extras: kollision einer dynamischen entität mit einem statischen gltf-modell

Oct 05 2020

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

1 PiotrAdamMilewski Oct 06 2020 at 07:23

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

NikitaFedorov Nov 30 2020 at 13:14

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;
        };
    })()
})