Procedural Mesh / Collision Generation and Updates

Hi everyone,

we are currently trying to implement procedural mesh generation including collisions for our current project.

We have managed to implement a working solution, however we had to resort to some workarounds to get everything to work as expected. Since these workarounds seem quite fragile, we wanted to consult with you, whether this is the actual best way to implement this.

Desired Outcome

For our project we want to be able to generate a custom mesh from code, which will be used both for rendering as well as for physics collisions. We also want to be able to dynamically update this mesh at runtime, incuding its collision shape.

Current Implementation and encountered Problems

In order to achieve this we manually calculate the vertex positions, indices, uvs and normals and create a new pc.Mesh object via pc.createMesh. In order to render the mesh, we are using pc.RenderComponent and in order to have physics collisions we also add a pc.CollisionComponent + pc.RigidbodyComponent.
When we update our mesh, we first recalculate positions, indices, uvs and normals accordingly and reset them with Mesh.setPositions, setNormals, etc. and finally call Mesh.update.

1. CollisionComponent in combination with RenderComponent

Now comes the tricky part: we can directly assign a MeshInstance to RenderComponent.meshInstances, however CollisionComponent will only either accept a ModelComponent (which is legacy according to the editor) or a renderAsset. Since our mesh is procedurally generated, we don’t have an existing asset, but only a mesh.
We wanted to avoid using the legacy ModelComponent, thus we decided to create a new pc.Asset from code when the mesh is generated for the first time and assign it to RenderComponent.asset as well as CollisionComponent.renderAsset.
I.e. something like this:

let asset = new pc.Asset('Custom Name', 'render');
// Some mesh initialization code here (see next code sample)
pc.Application.getApplication().assets.add(asset);
this.renderComponent.asset = asset;
this.collisionComponent.renderAsset = asset;

Do we really have to create a Render Asset by hand in order to be able to use the CollisionComponent with a custom mesh without having to use the legacy ModelComponent?

2. Creating a Render Asset from Code: can’t create Render object

In order to be able to set our custom mesh to the created RenderAsset, we have to set asset.resource to a new Render object and set its meshes field accordingly. However, for some reason we are not able to access the Render class in order to create a new instance of it ourselves. Writing new pc.Render() will yield an error. We had to dig into the engine code quite a while in order to find a way to create a new pc.Render instance and this was the best we could come up with (and this is where it gets really hacky):

let render = pc.app.loader.getHandler('render').open(undefined, undefined);
render.meshes = [this.mesh];
asset.resource = render;

Is there any better way for us to create a new Render instance, or to set the mesh of the RenderAsset?

3. Asset Loading of Procedural Asset: have to manually set loaded

We noticed some other strange behaviour when setting the RenderComponent.asset to our asset we just created. This led to render.meshes being reset to null for some reason. In order to avoid this, we had to manually set asset.loaded to true first. I.e.:

// Asset creation as shown earlier
asset.loaded = true;

this.renderComponent.asset = asset;
this.collisionComponent.renderAsset = asset;

Is this behaviour expected?

4. Updating Collision Mesh causes Ammo Memory Leak

When changing the mesh, we update its data as described earlier and then also call recreatePhysicsShapes on pc.CollisionComponentSystem in order to have the CollisionComponent update its collision shape accordingly. Unfortunately, this will not update the collision mesh and the CollisionComponent will still have its initial collision shape. When we tried to implement the same approach with ModelComponent instead and resetting the mesh each time we update it, the collision shape actually updated, but an OOM Exception would be thrown by Ammo after a couple of hundred updates.
After digging into the engine code, we noticed that PlayCanvas’ CollisionComponentSystem creates an internal cache of all Ammo.btTriangleMesh (called _triMeshCache), which is actually never cleared (except onDestroy of the entire system). Thus, when creating a lot of collision meshes, we will eventually run OOM, even when destroying the ‘old’ meshes using pc.Mesh.destroy() everytime. This _triMeshCache also prevents us from updating the collision mesh, since PlayCanvas will perform a lookup using Mesh.id, which didn’t change when updating the mesh using pc.Mesh.update() and thus will continue using the old collision mesh from the cache.
In order to workaround this problem, we had to manually destroy the old Ammo collision mesh from the _triMeshCache and delete the entry from the cache dictionary:

let collisionSystem = this.collisionComponent.system;
Ammo.destroy(collisionSystem._triMeshCache[this.mesh.id]);
delete collisionSystem._triMeshCache[this.mesh.id];
collisionSystem.recreatePhysicalShapes(this.collisionComponent);

Did we miss anything here, or is this currently the only way to procedurally update a collision mesh without leaving memory leaks?

Final Remarks

We would really appreciate your input on these questions. We would like to avoid having our code break in the future due to our workarounds relying on private APIs / hidden engine features.

Thank you for your time and efforts!

6 Likes

Hi Sam,

Thanks for the long write up, that’s very useful.

tiny point regarding the use of pc.createMesh - you should probably just create the mesh yourself, as the code is very similar to the code you use to update the mesh. See here: engine/procedural.js at bbed86cb66b0f37def0104e1c3eed16ca55c2121 · playcanvas/engine · GitHub

Regarding the rest - I don’t see any better way to do what you need to do, and agree it’s a limitation. I’ve created a ticket to track this: Implement simple API to allow Meshes / Renders to be used as a Collider · Issue #4181 · playcanvas/engine · GitHub - subscribe to it to track any updates to it.

The code you’re using should not break in the near future, as that’s not directly the areas we are touching much at the moment, but it’d be great to solve.

4 Likes

Hi Martin,

Thanks for your insights and creating the GitHub Issue! That’s very helpful.