godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.13k stars 93 forks source link

Implement collision in CPUParticles as a more precise (but slower) alternative to GPUParticles collision #3865

Open Calinou opened 2 years ago

Calinou commented 2 years ago

Describe the project you are working on

The Godot editor :slightly_smiling_face:

Describe the problem or limitation you are having in your project

GPUParticles collision works well, but it requires specially designed nodes to be placed within the level to function. Not only this can take a significant amount of time in complex levels, but the level of precision remains fairly low.

Moreover, since GPUParticles cannot communicate their positions back to the CPU efficiently, it's also impossible to emit a signal on collision with individual particles' positions (or when a particle expires). Emitting a signal on collision allows for many new effects, such as creating meshes, lights or decals at particles' impact points.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Implement precise collision for CPUParticles using the physics server. This is meant to be an alternative to GPUParticles when a high level of precision is needed. Due to the higher CPU usage, this will be limited to low amounts of particles (in the few hundreds at most).

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

CPUParticles collision performance is expected to be better than when using nodes to simulate particles (or even when using low-level servers from scripting), but it will still be significantly slower than GPUParticles.

To reduce CPU usage, collision between particles should be disabled by default. Nonetheless, since the physics server makes this possible, there should be a property to enable particle-to-particle collision (which is another feature that can't be efficiently implemented on GPUParticles).

To further reduce CPU usage, CPUParticles simulation could be made to happen at a lower rate with interpolation. Support for this will require something similar to https://github.com/godotengine/godot-proposals/issues/439.

If this enhancement will not be used often, can it be worked around with a few lines of script?

No, as CPUParticles behavior is not extendable from the scripting API.

Is there a reason why this should be core and not an add-on in the asset library?

This is core rendering functionality.

Calinou commented 2 years ago

I've started work on CPUParticles3D collision: https://github.com/Calinou/godot/tree/cpuparticles-add-collision

However, I get a crash whenever a particle collides with a StaticBody.

Testing project: https://0x0.st/oaO_.zip (will crash as soon as it's opened in the editor, since a particle will collide with the box below it)

Backtrace – Step [11] is the one in the code I added:

================================================================
handle_crash: Program crashed with signal 11
Engine version: Godot Engine v4.0.alpha.custom_build (260d1e5069f022d8cbddd896c90b2a166712a528)
Dumping the backtrace. Please include this when reporting the bug on https://github.com/godotengine/godot/issues
[1] /lib64/libc.so.6(+0x42a70) [0x7f73a173ca70] (??:0)
[2] GodotPhysicsServer3D::_shape_col_cbk(Vector3 const&, int, Vector3 const&, int, void*) (/home/hugo/Documents/Git/godotengine/godot/servers/physics_3d/godot_physics_server_3d.cpp:1738)
[3] _CollectorCallback::call(Vector3 const&, Vector3 const&) (/home/hugo/Documents/Git/godotengine/godot/servers/physics_3d/godot_collision_solver_3d_sat.cpp:84)
[4] bin/godot.linuxbsd.tools.64.llvm() [0x8093512] (/home/hugo/Documents/Git/godotengine/godot/servers/physics_3d/godot_collision_solver_3d_sat.cpp:117)
[5] bin/godot.linuxbsd.tools.64.llvm() [0x8093215] (/home/hugo/Documents/Git/godotengine/godot/servers/physics_3d/godot_collision_solver_3d_sat.cpp:604)
[6] SeparatorAxisTest<GodotSphereShape3D, GodotBoxShape3D, false>::generate_contacts() (/home/hugo/Documents/Git/godotengine/godot/servers/physics_3d/godot_collision_solver_3d_sat.cpp:741)
[7] bin/godot.linuxbsd.tools.64.llvm() [0x807fe03] (/home/hugo/Documents/Git/godotengine/godot/servers/physics_3d/godot_collision_solver_3d_sat.cpp:830)
[8] sat_calculate_penetration(GodotShape3D const*, Transform3D const&, GodotShape3D const*, Transform3D const&, void (*)(Vector3 const&, int, Vector3 const&, int, void*), void*, bool, Vector3*, float, float) (/home/hugo/Documents/Git/godotengine/godot/servers/physics_3d/godot_collision_solver_3d_sat.cpp:2445)
[9] GodotCollisionSolver3D::solve_static(GodotShape3D const*, Transform3D const&, GodotShape3D const*, Transform3D const&, void (*)(Vector3 const&, int, Vector3 const&, int, void*), void*, Vector3*, float, float) (/home/hugo/Documents/Git/godotengine/godot/servers/physics_3d/godot_collision_solver_3d.cpp:422)
[10] GodotPhysicsDirectSpaceState3D::collide_shape(PhysicsDirectSpaceState3D::ShapeParameters const&, Vector3*, int, int&) (/home/hugo/Documents/Git/godotengine/godot/servers/physics_3d/godot_space_3d.cpp:414)
[11] CPUParticles3D::_particles_process(double) (/home/hugo/Documents/Git/godotengine/godot/scene/3d/cpu_particles_3d.cpp:1157)
[12] CPUParticles3D::_update_internal() (/home/hugo/Documents/Git/godotengine/godot/scene/3d/cpu_particles_3d.cpp:630)
[13] CPUParticles3D::_notification(int) (/home/hugo/Documents/Git/godotengine/godot/scene/3d/cpu_particles_3d.cpp:1313)
[14] CPUParticles3D::_notificationv(int, bool) (/home/hugo/Documents/Git/godotengine/godot/scene/3d/cpu_particles_3d.h:38)
[15] Object::notification(int, bool) (/home/hugo/Documents/Git/godotengine/godot/core/object/object.cpp:847)
[16] SceneTree::_notify_group_pause(StringName const&, int) (/home/hugo/Documents/Git/godotengine/godot/scene/main/scene_tree.cpp:862)
[17] SceneTree::process(double) (/home/hugo/Documents/Git/godotengine/godot/scene/main/scene_tree.cpp:453)
[18] Main::iteration() (/home/hugo/Documents/Git/godotengine/godot/main/main.cpp:2739)
[19] OS_LinuxBSD::run() (/home/hugo/Documents/Git/godotengine/godot/platform/linuxbsd/os_linuxbsd.cpp:441)
[20] bin/godot.linuxbsd.tools.64.llvm(main+0x1c0) [0x4246fb0] (/home/hugo/Documents/Git/godotengine/godot/platform/linuxbsd/godot_linuxbsd.cpp:68)
[21] /lib64/libc.so.6(+0x2d590) [0x7f73a1727590] (??:0)
[22] /lib64/libc.so.6(__libc_start_main+0x89) [0x7f73a1727649] (??:0)
[23] bin/godot.linuxbsd.tools.64.llvm(_start+0x25) [0x4246d25] (??:?)
-- END OF BACKTRACE --
================================================================
webredraptors commented 1 year ago

Reading through https://github.com/godotengine/godot/pull/61273 it sounds like the reason this stalled was a lack of support for structs in GDScript. Now it seems that structs may not be too far off: #7329. Hopefully work on CPUParticles signals and collisions can resume soon! Would love to have these features in the projects I'm planning.

Calinou commented 1 year ago

I've continued work on https://github.com/Calinou/godot/tree/cpuparticles-add-collision?rgh-link-date=2022-05-22T00%3A08%3A17Z. It now has feature parity with GPUParticles3D collision, though the Collision Use Scale property isn't implemented yet. Collision masks also haven't been implemented yet, although this should be easy to do.

https://github.com/godotengine/godot-proposals/assets/180032/8c5e4abd-d130-4ff5-aad3-df12e1ed6759

There's also an issue where particles occasionally sink into the ground due to gravity (despite my best efforts to prevent this), and collisions failing if a single particle hits multiple overlapping boxes at once. I've tried increasing the number of contacts reported by the shapecast, but this didn't fix the issue.

Testing project for the above branch: test_cpuparticles3d_collision.zip

webredraptors commented 1 year ago

Wasn't sure if I should post this here or in https://github.com/godotengine/godot/pull/61273. But thanks for continuing to work on this feature!

I substituted Godot Jolt into a build of your engine changes to try to isolate behavior of your code vs built-in physics engine quirks. Particles appeared to clip through surfaces more frequently with Jolt, so I had a hard time determining if the gravity issue or overlapping shape issue persisted. It seems that particle simulation stops much more often because Jolt is causing more frequent clipping.

However, I did notice a problem with collision that occurs consistently, both in Jolt and Godot Physics. Here I've switched back to Godot Physics and slowed down the time scale to make this more apparent:

https://github.com/godotengine/godot-proposals/assets/27940287/3ec98b0f-9f15-4a38-85c0-b259a0d333d2

Notice how the particles mostly pass through the underside of the green shape before collision occurs. It almost seems like they are colliding with the back face of the surface. I also noticed some slight clipping with the orange shape, but zero with the blue shape.

Note that the green shape is an exact duplicate of the other shapes with a different material override. In fact, I can reposition the emitter to drop particles on the "top" side of the green shape, and collision behavior changes:

https://github.com/godotengine/godot-proposals/assets/27940287/2f47f4dd-8918-4f0b-ad0d-b3d5a833ba98

When I reposition the emitter to the underside of the perfectly horizontal blue shape, particles clip entirely through the surface before collision occurs. From the top side, the opposite is true and no clipping occurs whatsoever:

https://github.com/godotengine/godot-proposals/assets/27940287/8a095358-2560-4783-84e5-6b0fa7646ffe

I believe this behavior is connected to the individual face normals of collision shapes. This may also be linked to the issues observed with gravity and overlapping boxes. I will play around more with this when I have time.

Calinou commented 1 year ago

Notice how the particles mostly pass through the underside of the green shape before collision occurs. It almost seems like they are colliding with the back face of the surface. I also noticed some slight clipping with the orange shape, but zero with the blue shape.

This occurs because the raycast destination is moved to account for gravity, so that particles don't sink on the ground. This could be avoided by performing more raycasts, but it would be significantly more expensive to do so (up to 6 raycasts needed per particle, instead of 1).

There's some commented out code you can uncomment here if you want to try this: https://github.com/Calinou/godot/commit/2cda412ec727be4265e110ba4a9ebd40e888874e#diff-778bdd2be9e69834044d9ca57a2341cafd149c69e5d48f1b1a98321e93adb4bcR965-R1008