-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mesh collision improvements #5818
Conversation
Can |
Yep, I see where it could be useful to clear the cache, excluding the ones that are in use currently. I think we can add it in a subsequent PR, as I am not yet familiar with those reference objects. |
If clearMeshCache will be made public with this PR, then changing its logic in future PRs should be avoided. |
Perhaps I am missing something. I don't see how it would hurt adding a ref object later? The API will stay the same. There would be no change to the signature. The difference would be that it would not crash if called when some components are still using the mesh object. |
|
Hmm. Well, the whole point of using a cache is that it would retain the trimeshes, even when no components are using those. All for the sake of avoiding recalculating the collision mesh when the component is enabled again, which potentially helps avoiding a stutter mid-game.
If I understood Max correctly, then the reason to use ref objects is that the developer would still call |
Yes, it would destroy unused (0 refs) objects, and keep used (0+ refs) objects. One case where clearing trimesh cash with disabled component can lead to issues:
If it will ignore disabled components, then it will not re-use existing trimeshes, as new scene entities are disabled - this is not good. Efficient way is to keep trimeshes as they are referenced. Also automatic clearing when no refs are, with disabling entities - will definitely lead to unintentional re-creations in runtime - this to be avoided at all costs. Please, do not implement auto-clearing of the cache, and do not consider disabled entity as "clearable". |
I understand your concerns, Max. However, I am not sure I can agree. I generally prefer the system to be straightforward, rather than clever. We already have a secret feature, called trimesh cache that only cool kids know about. It does more harm than good, if you don't know about it. Every other shape and rigidbody is destroyed when component is disabled. If I am asking the system to clear the cache, I would like it to do just that, without it trying to interpret my second thoughts of what I really want. It just feels we would add another secret feature. Perhaps it could be customized with options? Nevertheless, I think this can be discussed in the PR that would add ref objects. I consider this one to be ready for review. |
Mesh - is not destroyed, when Render component is disabled. These concepts are not "clever", but consistent within the engine, and following existing logic that used across the engine - is strongly recommended. Disabling/enabling component - should not lead to high CPU load for the benefit of free memory. That is why when you disable any entity, components are not destroyed / re-created, they just get disabled. |
I agree with you. I do prefer the engine to follow defined patterns. After all, this is how the user would start to expect a behavior. I was referring to Ammo shapes and bodies, created in Wasm memory. For every other rigidbody and collision component - when they are disabled, the respected counterparts in Wasm get destroyed and memory is freed. Mesh collision is an exception here, that doesn't follow the pattern. |
Because creation of it is very CPU intensive compared to other objects. 🙏 |
I'd be keen to merge the first two points from the description (indexed array and checkVertexDuplicates) - I wish this was a separate PR. But I'm not super happy with the cache implementation.
|
default checkDuplicates to true Co-authored-by: Martin Valigursky <[email protected]>
@mvaligursky I have reverted the cache back to the original one. |
v2.setValue(positions[i2], positions[i2 + 1], positions[i2 + 2]); | ||
v3.setValue(positions[i3], positions[i3 + 1], positions[i3 + 2]); | ||
triMesh.addTriangle(v1, v2, v3, true); | ||
const numVertices = positions.length / stride; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this could be
const numVertices = vb.getNumVertices();
instead?
|
||
for (let i = 0; i < numVertices; i++) { | ||
v1.setValue(positions[i * stride], positions[i * stride + 1], positions[i * stride + 2]); | ||
triMesh.findOrAddVertex(v1, checkDuplicates); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
based on this (not sure this is the latest version, not an expert here) it seems findOrAddVertex
and addIndex
are internal, and so perhaps we should just keep using addTriangle and pass removeDuplicateVertices as false to it.
https://github.com/bulletphysics/bullet3/blob/master/src/BulletCollision/CollisionShapes/btTriangleMesh.h#L63-L66
One additional reason I suggest this is that I'm not sure the way those functions are currently used is correct. findOrAddVertex
returns a new index in case a duplicate was found, but we're not handling it, which means that this code will access vertices that do not exist (our of range).
https://github.com/bulletphysics/bullet3/blob/39b8de74df93721add193e5b3d9ebee579faebf8/src/BulletCollision/CollisionShapes/btTriangleMesh.cpp#L86
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, so we have 2 methods here:
- using the previous
addTriangle
method with disabled duplicates check - using
findOrAddVertex
andaddIndex
with disabled duplicates check
The second one is about 10% faster than the first one (which can be around 5-15ms for complex meshes). Considering there is a faster method, do we still want to use the addTriangle
? I'm ok with both options. Both of them seem to be order of magnitudes faster than the current method with forced duplicates check.
As for the returned index in case of a duplicate found - I am not sure if I understand. By new index do you mean an old index that was incremented? If that would be the case, then I'd agree - we could potentially point out of bounds. However, I don't see it being the case. This line returns an index of an existing vertex when a duplicate is found, not a new one:
https://github.com/bulletphysics/bullet3/blob/39b8de74df93721add193e5b3d9ebee579faebf8/src/BulletCollision/CollisionShapes/btTriangleMesh.cpp#L115
However, said that, I do think there should be a guard. If we consider worse case scenario - a user provides a mesh with many duplicate vertices and manually disables the duplicate test. This will generate a collision mesh, but with triangles that would not match the shape. If we do use the second method, then to mitigate this case, we could use the first method for the default checkVertexDuplicates
= true
, and go for the second one, when it is explicitly disabled (i.e. the developer knows the mesh is clean).
I'd be ok to just use the first option using The second options I do not believe work at the moment, as we're not using the return value from findOrAddVertex, which is an index of existing vertex that the current vertex is a duplicate of. The example, for our box primitive, I added breakpoint to createBox in procedural.js, and that creates a position array with 72 floats, which means (/3) 24 positions - each face has 4 unique vertices as they need unique UVs. But for collision, this needs to be just 8 .. and so we add 24 positions to ammo and it returns indices of only 8 unique vertices - but we ignore those values. And then later we give it the original indices from the mesh, which point to all 24 verticies. The end result is - ammo has 8 verticies, but indices point to 24 vertices I believe. So we can either remap those to just 8 positions. Or simply use the option 1 which does it. |
On the other hand - disabling dupe test is faster (because their code is just a linear search from what I can see), but for the box we'll end up with 24 positions instead of 8, so raycasting and all that would be more expensive every frame. We could add simple hashing acceleration structure and trivially remove dupes ourselves. Perhaps even have Map<string, number> storing vertices and their index. We'd convert x, y, z to a string this way: I think this is the real solution here, best of both worlds - the speed, removal of dupes, and no additional API. |
Added a local vertex cache. I decided to keep the before this PR: ~2485ms |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi and sorry for not getting to this sooner. This looks great now, happy to merge it, thanks!
Fixes #5765
checkVertexDuplicates
. By default, when we add a new vertex to a trimesh, Ammo will compare the distance against all existing vertices and ignore it, if the distance is smaller than epsilon. This option allows to skip that check. The option istrue
by default, forcing the duplicates check.I confirm I have read the contributing guidelines and signed the Contributor License Agreement.