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!