Hi, I’m having issues with this solution: [SOLVED] setLocalScale - Collision mesh - #14 by Newbie_Coder
When I manually delete cached collision data using
for (const instance of otherEntity.collision.model.meshInstances) {
delete this.app.systems.collision._triMeshCache[instance.mesh.id];
}
Ammo.destroy(otherEntity.collision.shape);
Ammo will randomly (!) throw errors, like this:
Uncaught TypeError: ha[L[((L[(e >> 2)] + 8) >> 2)]] is not a function
at lh (ammo.js:32:24329)
at Array.HA (ammo.js:36:38107)
at Array.EA (ammo.js:32:119491)
at Uj (ammo.js:34:38987)
at x.addRigidBody (ammo.js:611:426)
at RigidBodyComponentSystem.addBody (playcanvas.js:56507:27)
at RigidBodyComponent.enableSimulation (playcanvas.js:56148:22)
at set mask (playcanvas.js:56020:15)
at template.js:117:24
at Array.forEach (<anonymous>)
I believe what’s happening is other pieces of my game rely on the entity in question, and when its mesh cache is destroyed, sometimes those other pieces ask Ammo for information that I destroyed using this manual call. However, this should not happen if the Ammo deletion and re-creation of the mesh happens instantly — yet it does happen sometimes which leads me to believe that Playcanvas<>Ammo is calculating physics asynchronously (and non-deterministically) to save on performance.
The result is that this manual solution is a non-starter for my use case. I would need to disconnect each reference to that collider and pause the expectation for it to exist while this operation completes during an unknown time.
Is there any other way to scale a mesh collider without directly accessing Ammo mesh caches?
Playcanvas<>Ammo is calculating physics asynchronously
Nothing of the sorts. Everything is synchronous and executes in order. PlayCanvas stops the execution of its systems until Ammo Wasm finishes its stuff.
Do you set the collision.shape
to null
after you destroy it in Ammo? This is needed for the engine logic to not think the shape is available and force it to be recreated.
@LeXXik thanks for your reply. It makes sense that everything should be synchronous.
Yes, I am setting it to null. Here is my complete code block that is the culprit:
// colEnt is the pc.Entity
colEnt.enabled=false;
Ammo.destroy(colEnt.collision.shape); // culprit
console.log("ammo destroyed");
colEnt.collision.shape = null;
for (const mesh of colEnt.collision.render.meshes) {
delete pc.app.systems.collision._triMeshCache[mesh.id];
}
colEnt.enabled=true;
I’ve been able to successfully reproduce my issue by running the above code more than N times per frame (for example 20-50 times). Please see video attached where I’ve narrowed my issue to that line: https://youtu.be/Gj4K5WjoUQ8
I’ve solved it (possible?) and here is my method:
- Scale entity (render will appear scaled correctly)
- “clone” the entity by entity.clone(), and create a brand-new mesh by copying existing mesh data to the clone (render only)
- create another new mesh based on the new entity’s new render mesh
- destroy and recreate collision with using model using the newly created mesh
- destroy the old entity
So far it seems to be working. I did encounter a similar Ammo.js error on destroying and re-creating the collider but it has stopped and I can’t reproduce it. ¯_(ツ)_/¯
Here’s the working code to scale a mesh collider to its render size:
updateCollider4({oldEntity}){
const entity = oldEntity.cloneWithMesh();
const pos = oldEntity.getPosition();
const rot = oldEntity.getEulerAngles();
const parent = oldEntity.parent;
parent.addChild(entity);
entity.moveTo(pos,rot);
const renderComp = entity.render;
const mesh = renderComp.meshInstances[0].mesh;
const node = new pc.GraphNode();
const meshInstance = new pc.MeshInstance(mesh, new pc.StandardMaterial(), node);
const model = new pc.Model();
model.graph = node;
model.meshInstances = [meshInstance];
entity.collision.model = model;
entity.removeComponent('collision');
// entity.addComponent('collision');
entity.addComponent('collision', { type: 'mesh', model });
oldEntity.destroy();
return entity;
}
and here is my cloneWithMesh():
pc.Entity.prototype.cloneWithMesh = function(){
const r = this.getComponentsInChildren('render')[0];
const m = r.meshInstances[0].mesh;
const positions =[];
const uvs = [];
const indexArray = [];
const normals = [];
m.getPositions(positions);
m.getUvs(0,uvs);
m.getIndices(indexArray);
m.getNormals(normals);
function updateMesh(mesh, initAll) {
mesh.setPositions(positions);
mesh.setIndices(indexArray);
mesh.setNormals(normals);
mesh.setUvs(0,uvs);
//if (initAll) { mesh.setUvs(0, uvs); mesh.setIndices(indexArray); }
mesh.update(pc.PRIMITIVE_TRIANGLES);
}
const mesh = new pc.Mesh(pc.app.graphicsDevice);
mesh.clear(true,false);
updateMesh(mesh,true);
const material = r.meshInstances[0].material.clone();
const meshInstance = new pc.MeshInstance(mesh, material);
const cloneWithNewMesh = this.clone();
pc.app.root.addChild(cloneWithNewMesh);
cloneWithNewMesh.getComponentsInChildren('render')[0].meshInstances = [meshInstance]; // awkward
return cloneWithNewMesh;
}