I\'m trying to catch collision event between a dynamic sphere and a static gltf model. I\'m building the gltf entity the following way:
const template = docum
Meanwhile I was able to implement animated collision mesh for a gltf model. I used a helper function implemented by @Piotr Adam Milewski here. See more details about the function in this answer. Any other suggestions to improve performance are appreciated.
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;
};
})()
})
0. using setAttribute
setAttribute
won't handle a list like:
template.setAttribute('body', 'type:static; shape:none');
Rather provide an object of the new attributes:
element.setAttribute("name", {
"property 1": "value 1",
"property 2": "value 2"
});
1. dynamic body/shape set-up
That being said, you can create a custom static cylinder like this:
element.setAttribute("body", {
"type": "static",
"shape": "none"
})
element.setAttribute("shape__cylinder", {
'shape': 'cylinder',
"height": 1.5,
"radiusTop": 0.1,
"radiusBottom": 0.2
})
check it out in this fiddle
2. dynamic shape for models
As for creating a dynamic shape for a gltf model. Personally i had no luck using cannon
, although it worked well with the ammo
driver. On the other hand I've had a huge FPS drop (on ~older mobile devices), so If possible, try using simple collision meshes for performance sake.
You can get a bounding box of a skinned model with a simple function I made:
let box = new THREE.Box3()
THREE.Box3Utils.fromSkinnedMesh(skinnedMesh, box);
// box should be the bounding box of the skinned mesh
3. animated models
I'd highly recommend creating simple collision shapes and attaching them to a certain bone in the model. A starting point could be this component:
<a-gltf-model bone-collider="bone: boneName; halfExtents: 0.15 0.15 0.15">
You can see both approaches in this example