[SOLVED] setLocalScale - Collision mesh

Hi there,

Apparently setLocalScale only affects visual mesh, what about collision? Any ways to scale/update it without reloading model? Thanks

I’ve not tried this myself but you could try:

  • Disable collision and rigidbody component
  • Set the scale of the entity
  • Enable collision and rigidbody component

That should remove and re-add the physics body to the world

No effect unfortunately


      scale = otherEntity.getLocalScale();
      otherEntity.collision.enabled = false;
      otherEntity.rigidbody.enabled = false;  
      otherEntity.setLocalScale(scale.x + 0.05, scale.y + 0.05, scale.z + 0.05);
      otherEntity.collision.enabled = true;
      otherEntity.rigidbody.enabled = true;  

You probably need to remove the cashed mesh in order for the engine to generate a new one with a new scale. So, after you disabled the components, and before you enable them, something like:

// when using render
for (const mesh of entity.collision.render.meshes) {
    delete this.app.systems.collision._triMeshCache[mesh.id];
}

// when using model
for (const instance of entity.collision.model.meshInstances) {
    delete this.app.systems.collision._triMeshCache[instance.mesh.id];
}
2 Likes

Hi, no effect either

  scale = otherEntity.getLocalScale();
  otherEntity.collision.enabled = false;
  otherEntity.rigidbody.enabled = false;  
  otherEntity.setLocalScale(scale.x + 0.05, scale.y + 0.05, scale.z + 0.05);

  for (const instance of otherEntity.collision.model.meshInstances) {
  delete this.app.systems.collision._triMeshCache[instance.mesh.id];
  }

  otherEntity.collision.enabled = true;
  otherEntity.rigidbody.enabled = true;  

Using debug draw

It should work. You need to debug and see why it doesn’t, if it doesn’t.
First, I’d check if it really doesn’t work by changing the scale to something much larger, e.g. multiply the current by 10 scale.x * 10. Perhaps 0.05 is not a noticeable change. I would also try to reset the cache completely, instead of only the affected mesh.

Couldn’t get it to work, tried deleting this.app.systems.collision._triMeshCache - no luck either
Here’s repro PlayCanvas 3D HTML5 Game Engine
KEY_PAGE_UP to increase scale/clear cache

Has to be something else than triMeshCache

You may need in addition to destroy the shape in Ammo:

for (const mesh of entity.collision.render.meshes) {
    const shape = this.app.systems.collision._triMeshCache[mesh.id];
    Ammo.destroy(shape);
    delete this.app.systems.collision._triMeshCache[mesh.id];
}
1 Like

Have you tried this on render? Model throws errors

Isn’t it that Ammo.destroy(this._triMeshCache[key]); requires meshInstance key? As these are actually stored
Nevertheless, using ammo destroy with valid keys throwing ā€˜Error: Cannot destroy object. (Did you create it yourself?)’

No it’s being actually used in the PlayCanvas engine, I’ve shared the link above.

The logic is it’s not enough to remove the JS reference, Ammo.js occupies a different memory chunk than the one used by regular JS data. So that Ammo.destroy() call is required to clean up that memory.

Now if that solves your problem or not I’m not quite sure. But definitely it shouldn’t give any error if the object is valid. Try using the JS debugger or the console to check that you are using it as expected.

1 Like
for (const instance of otherEntity.collision.model.meshInstances) {

       const shape = this.app.systems.collision._triMeshCache[instance.mesh.id];

        Ammo.destroy(shape);

  delete this.app.systems.collision._triMeshCache[instance.mesh.id];

  }

RuntimeError: indirect call to null


for (const instance of otherEntity.collision.model.meshInstances) {

       const shape = this.app.systems.collision._triMeshCache[instance.key];

        Ammo.destroy(shape);

  delete this.app.systems.collision._triMeshCache[instance.mesh.id];

  }

TypeError: a is undefined

1 Like

It looks like it is not easy to change a scale of a collision mesh at runtime at the moment.

Before clearing the cache, you also want to destroy the reference stored in the collision component:

Ammo.destroy(otherEntity.collision.shape);
otherEntity.collision.shape = null;

Related issue:

2 Likes

I can confirm it works as expected

    scale = otherEntity.getLocalScale();
    
    otherEntity.enabled = false;
    Ammo.destroy(otherEntity.collision.shape);
    otherEntity.collision.shape = null;
    otherEntity.setLocalScale(scale.x + 0.05, scale.y + 0.05, scale.z + 0.05);

  for (const instance of otherEntity.collision.model.meshInstances) {
  delete this.app.systems.collision._triMeshCache[instance.mesh.id];
  }

console.log(this.app.systems.collision._triMeshCache);
otherEntity.enabled = true;


}

Thank you folks

2 Likes

Hi, I’m having issues with this solution. When I manually delete cached collision data in

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?

UPDATE: I’ve started a new thread here, and came up with an alternative solution which does not rely on calling Ammo.destroy (which was causing my bug) - https://forum.playcanvas.com/t/setlocalscale-collision-mesh-ammo-js-errors/40022/2

Update2: My above solution still breaks Ammo nondeterministically (out of memory or null ref, ā€œonly sometimesā€). This solution is working: Multiple Mesh Colliders of Different Scales don't behave properly Ā· Issue #3062 Ā· playcanvas/engine Ā· GitHub

Here’s a snippet to solve the scaling issue for mesh colliders, no warranties/use-at-is:


(function () {
    const app = (pc.AppBase || pc.Application).getApplication();
    app.once('start', () => {

        pc.getScaleUniqueMeshId = (meshId, scale) => {
            const rawMeshId = Math.floor(meshId);
            const precision = 2; //decimal places, 1 means rounding to 0.1, 2 to ~0.01, 3 to ~0.001 etc.
            const roundingPower = Math.pow(10, precision);
            let x = Math.abs(scale.x * roundingPower);
            let y = Math.abs(scale.y * roundingPower);
            let z = Math.abs(scale.z * roundingPower);
            return Number(`${rawMeshId}.${x}${y}${z}`);
        };

        if (typeof Ammo === 'undefined' || !app.systems.collision.implementations.mesh) return console.log('[MeshCollisionSystemImpl] could not detect Ammo');

        const originalCreateAmmoMeshFunction = app.systems.collision.implementations.mesh.createAmmoMesh.toString();
        if (originalCreateAmmoMeshFunction.includes('mesh.id')) {
            const patchedCreateAmmoMeshFunction = originalCreateAmmoMeshFunction.replaceAll('mesh.id', 'pc.getScaleUniqueMeshId(mesh.id, scale)').replaceAll('SEMANTIC_POSITION', 'pc.SEMANTIC_POSITION');
            console.log('[PATCH] MeshCollisionSystemImpl has been patched to support mesh collider scaling');
            app.systems.collision.implementations.mesh.createAmmoMesh = new Function('return ' + patchedCreateAmmoMeshFunction)();
        } else {
            console.warn('[PATCH] Could not patch MeshCollisionSystemImpl: mesh.id is not used in this version of the engine');
        }
    })
})();