Skip to content
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

StaticBody3D detection instability in JoltPhysics #101721

Closed
vaner-org opened this issue Jan 17, 2025 · 9 comments · Fixed by #101815
Closed

StaticBody3D detection instability in JoltPhysics #101721

vaner-org opened this issue Jan 17, 2025 · 9 comments · Fixed by #101815

Comments

@vaner-org
Copy link
Contributor

vaner-org commented Jan 17, 2025

Tested versions

Godot v4.4.beta1, regression from 4.3 stable with Jolt addon.

System information

Windows 10 (build 19044) - Single-window, 1 monitor - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 3080 (NVIDIA; 32.0.15.6636) - AMD Ryzen 9 5950X 16-Core Processor (32 threads)

Issue description

I have two scenes that work as parametrized stair and ladder generators. They both use most of the same code, with the stairs generated as arranged StaticBody3Ds, and ladders generated as arranged CSGCylinders with collision enabled.

In 4.3 stable with the Jolt addon, using an Area3D with "Areas Detect Static Bodies" enabled in settings gave mostly reliable results, as the Area3D came into proximity with the ladders and stairs they triggered the enter and exit signals with reasonable stability. The ladders give phantom exit signals when they shouldn't, but these only occur when the Area3D is moved, so they do not impact performance very much.

However, in 4.4, while the ladder bodies (composed of CSGCylinders) detect the same way, but the steps made of StaticBodies all enter and exit repeatedly every frame, adding a massive performance drop in my project whenever they are encountered by my player.

Steps to reproduce

For the purposes of demonstration, I have arranged my ladder constructor, an ordinary StaticBody, and my stairs constructor side by side.

Image

In both projects, pressing "A" or "D" on the keyboard will move the Area3D, and the game will print entrances and exits to output.

Minimal reproduction project (MRP)

Expected behavior in 4.3 can be seen with jolt-staticbody-mrp-4.3.zip, addon is included.
Problem can be seen in 4.4 with jolt-staticbody-mrp-4.4.zip.

@mihe mihe added this to the 4.4 milestone Jan 17, 2025
@mihe mihe self-assigned this Jan 17, 2025
@mihe mihe moved this from Unassessed to Release Blocker in 4.x Release Blockers Jan 18, 2025
@mihe
Copy link
Contributor

mihe commented Jan 18, 2025

The stairs thing looks to be another regression caused by #100983, but I haven't looked into why just yet.

The ladders give phantom exit signals when they shouldn't, but these only occur when the Area3D is moved, so they do not impact performance very much.

This seems somewhat problematic, and was not something I was aware of. I'll see about figuring out why this is happening as well.

@vaner-org
Copy link
Contributor Author

vaner-org commented Jan 18, 2025

I updated the 4.4 MRP since it had broken dependencies.
Actually, it looks like the ladders' phantom exit problem might be fixed in the beta.

@mihe
Copy link
Contributor

mihe commented Jan 18, 2025

I upgraded your 4.3 MRP to 4.4 instead, and I'm still (on latest master) seeing some weird behavior with regards to the ladder's area events, where it'll spam a bunch of exit and enter events when no CSGCylinder have seemingly exited the Area3D.

Also, while this is irrelevant to the bug at hand, I should probably point out that this approach in general isn't great from a performance standpoint with Jolt, as collision for CSG shapes are done using ConcavePolygonShape3D under the hood, so with enough CSG shapes (or if you crank up their tessellation) you can end up falling into the performance pitfall that's mentioned in the tooltip of the "Areas Detect Static Bodies" project setting:

Image

I should probably add a mention of CSG shapes in there actually.

@vaner-org
Copy link
Contributor Author

vaner-org commented Jan 18, 2025

I appreciate your warning, I do intend to replace the ladder constructor with similar code that uses a CylinderShape3D / MeshInstance system like the stairs do, I never got around to it for some reason. Hopefully the minor difference in approach between the two goes some way in helping diagnose the root cause of this.

Also, while I understand the potential performance cost of this aspect of Jolt, I do worry about this setting defaulting to false confusing people, especially with the parity Jolt aims for with GodotPhysics in every other regard. I know I was puzzled for a while. Would it not be possible to make this setting per-Area3D, to better expose it to the user, and to further narrow its performance cost?

@mihe
Copy link
Contributor

mihe commented Jan 19, 2025

Would it not be possible to make this setting per-Area3D, to better expose it to the user, and to further narrow its performance cost?

Yes, that's the plan. I just need to sit down and finish the server-provided node properties functionality, which has been on my to-do list for a while now, so that we can add more Jolt-specific stuff like this without burdening the shared interfaces.

But yes, I know this setting is confusing and has become something that most people just enable without thinking much about, if they can find it in the first place. Not having in-editor documentation for it in the extension probably didn't help either.

I've sort of been hoping to see issue reports about the performance impact of enabling this setting, just so I can get a sense for how it's affecting real projects out there, but I suspect it's difficult to corelate the two once you do find yourself in trouble.

@mihe
Copy link
Contributor

mihe commented Jan 19, 2025

I managed to track down the stairs thing and pushed a fix for it: #101815

I still need to look at the ladder thing though, but I'll make a new issue for that instead.

@vaner-org
Copy link
Contributor Author

vaner-org commented Jan 20, 2025

Thank you for resolving it so quickly, I appreciate it.

If I may add further to the conversation:

I've sort of been hoping to see issue reports about the performance impact of enabling this setting, just so I can get a sense for how it's affecting real projects out there, but I suspect it's difficult to corelate the two once you do find yourself in trouble.

I would imagine that the tooltip itself dissuades complaints, however. Had I encountered this issue not as a regression but the first time I tried to enable the project setting, there's a good chance I would have taken it at face value, at least until I found that it was my only path forward.

More importantly, I assume that most people are going to be importing their final environmental collisions through models with -colonly flags from Blender, which I understand are always ConcavePolygonShape3D, which again would likely be replacing placeholder CSGShapes with collisions in engine, making this the most common use case. I would argue it's a lot more difficult for the developer just starting out to find the setting, than for a seasoned developer to make the correlation when (if) there is trouble. That being said, I do understand why it's off by default.

Is there something unique to Jolt that makes the process of ConcavePolygonShape3D overlap detection against Area3D less optimal than collision detection against a RigidBody3D or CharacterBody3D?

Yes, that's the plan. I just need to sit down and finish the server-provided node properties functionality, which has been on my to-do list for a while now, so that we can add more Jolt-specific stuff like this without burdening the shared interfaces.

Maybe something like shape-specific masking could help to really narrow down an Area3D's domain, when this is added. Looking forward to it!

@mihe
Copy link
Contributor

mihe commented Jan 20, 2025

More importantly, I assume that most people are going to be importing their final environmental collisions through models with -colonly flags from Blender, which I understand are always ConcavePolygonShape3D, which again would likely be replacing placeholder CSGShapes with collisions in engine, making this the most common use case.

I understand that ConcavePolygonShape3D is widely used, and I'm certainly not saying that its use should be avoided, although doing so is often beneficial for performance as well. Instead, I would perhaps question the need to enable this setting in the first place, as there are often other approaches you can take, like just doing a shape query instead.

You can also use a more primitive kinematic body (i.e. AnimatableBody3D, CharacterBody3D or RigidBody3D frozen as kinematic) as a sort of proxy for the static body, which can then be detected by Area3D relatively cheaply without enabling this setting. I understand that this might not be a very intuitive approach though, given the discrepancy with Godot Physics, and the fact that Godot sort of pretends that kinematic bodies aren't their own distinct type of body, e.g. by having AnimatableBody3D inherit from StaticBody3D.

Is there something unique to Jolt that makes the process of ConcavePolygonShape3D overlap detection against Area3D less optimal than collision detection against a RigidBody3D or CharacterBody3D?

The important difference (as I understand it) between Jolt and Godot Physics when it comes to Area3D is the fact that Jolt keeps track of the individual contacts between the Area3D and any overlapping body, whereas Godot Physics doesn't. This becomes problematic with complex shapes, like ConcavePolygonShape3D, as you'll generate at least one contact for every triangle you're overlapping with, which puts a lot of pressure (both in terms of memory usage and CPU time) on the contact cache that Jolt maintains internally.

@vaner-org
Copy link
Contributor Author

vaner-org commented Jan 20, 2025

In my game specifically, I'm using an Area3D as a child of the player to detect static and animated environmental collision meshes in their proximity, to parse which edges are climbable.

Image

Since I use the body_entered and body_exited signals to conduct my mesh processing (welding, normal testing etc.) at runtime, I wasn't aware of more optimized ways of doing this, but now that you mention it, using a spherical shape query every other frame is something I will test, though I've found it less than reliable in the past.

The important difference (as I understand it) between Jolt and Godot Physics when it comes to Area3D is the fact that Jolt keeps track of the individual contacts between the Area3D and any overlapping body, whereas Godot Physics doesn't.

Ah, now I see. I imagine there isn't a way to internally limit this, which must be the elephant in the room here. I certainly can't see a use case for that level of tracking, and hope it can be culled somehow in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Release Blocker
Development

Successfully merging a pull request may close this issue.

2 participants