godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.69k stars 21.11k forks source link

[Bullet] Raycasting a BoxShape hints round edges/artifacts #28032

Open Zylann opened 5 years ago

Zylann commented 5 years ago

Godot 3.1

I wanted to use BoxShape to make cubes which I can raycast, but I quickly found that the normals I get from raycasting behave as if the cubes had round edges Oo Not only on the edges, but also a little bit on the faces, there is a spot where normals become oblique.

image

test project: BoxShapeRaycast.zip

Outputting raycast normals on a texture for every pixel of the screen gives the following normal map: image

Same test, in Unity3D, provides a much cleaner result: image

This does not happen with GodotPhysics either. Is that due to how Bullet was configured in Godot?

Edit: while I understand this has practical uses for collision dynamics, it doesnt feel useful for raycasts.

erwincoumans commented 5 years ago

This is due to the collision margin, it makes the shapes slightly rounded. You can avoid it by making the collision margin a bit smaller, but that decreases performance. When generating polyhedral features we could ray cast against those to get precise boundaries/no artifacts. The artifacts could be due to an older version of Bullet or using the btSubsimplexConvexCast instead of btGjkConvexCast.

jitspoe commented 5 years ago

This issue seems to have been introduced (or amplified) in 3.1. I noticed that my character (a box shape kinematic body) would slip off edges after I upgraded from 3.0.6. I'm using move_and_collide() and getting bad normals near edges. I'm using box collision on my character instead of a capsule because I explicitly DON'T want the behavior of rounded edges. I'm making a precision platformer, and I want to have 100% control over how the character behaves on each surface collision. I don't want the character to have any strange ricochet/ramping/slipping behavior on the edges of platforms.

Now my box almost behaves like a capsule. :( I've set the margin as small as possible -- even tried setting it to 0 in code -- but that's not enough.

I tried to hack around it using raycasts to determine the actual surface normal, but, to my surprise, those ALSO exhibit this behavior.

I suspect this changelist may have introduced this: https://github.com/godotengine/godot/commit/6dd65c0d67960b0f0b26a24bd2f10fe8d54aa37a#diff-a61d01bcaef4874b6713c0b00e6d4571

Seems the fundamental problem is that the normal returned is this: p_recover_result.normal (the normal needed for penetration recovery) instead of the surface normal or collision normal. When I've dealt with game physics in the past, there are usually 2 normals available after a collision: One for the collision. One for the surface. If it's necessary to have the depenetration normal, it would actually be nice to have 3 normals available in the KinematicCollision -- depenetration normal, surface normal, and collision normal.

@Zylann Have you tried running this test on 3.0.6?

@aqnuep How feasible would it be to return the collision normal instead of the depenetration normal or add additional normals that can be accessed?

TwistedTwigleg commented 5 years ago

While working on a prototype project, I found a potential workaround:

With those changes, I get the following output:

Godot_Normal_Output

Which is much closer to what I would expect a Raycast to return.

Interestingly, if using a Convex collision shape generated from the Mesh drop down the issue is still present, the normal vector returned by the Raycast behaves as if the cube is rounded. Does the BoxShape extend/use Convex shapes? Maybe something in the code for Convex shapes is causing this issue.

Regardless, it appears that Trimesh/Concave shapes do not exhibit this behavior.

Zylann commented 5 years ago

@jitspoe

BoxShape in Godot 3: image --> Positions seemed to not use margins but normals were, so the cube ended up correctly contoured but with off normals at the edges.

BoxShape in Godot 3.1: image --> both position and normal account for margin which makes for a slightly puffed cube. Colors of the normal map are also a tiny bit brighter, which means normals were slightly too short before, or slightly bigger now?

ConvexShape in Godot 3: image --> The cube looks similar to the 3.1 BoxShape, however its normal map is a tiny bit less bright

ConvexShape in Godot 3.1: image --> The cube still looks similar but margin is a bit bigger.

Note 1: In 3.0.6, I didnt find any way to set the margin, while I've set it to 0.01 for 3.1 examples.

Note 2: The margin I've set is actually smaller than what the screenshots show. If I make it 10 times smaller it still doesn't make any difference.

Note 3: Noise patterns are always the same given a configuration, even after restarting the game.

It's clear to me this is due to margins and the way we've setup the collision world, and that there were a few subtle differences between 3.0.6 and 3.1. Still not sure what the noise is, and it looks like margins are capped internally, that's a bit messy.

When I've dealt with game physics in the past, there are usually 2 normals available after a collision: One for the collision. One for the surface. If it's necessary to have the depenetration normal, it would actually be nice to have 3 normals available in the KinematicCollision -- depenetration normal, surface normal, and collision normal.

If you think it's a needed feature, please make a feature request.

jknightdoeswork commented 4 years ago

Just chiming in here. This is a very important bug to me.

My experience is primarily with mesh edges and move_and_collide, but they are deeply related.

Using a BoxShape for the ground with a SphereShape as the character and move_and_collide causes normals around seams to be unpredictable and inexact. Even on top of the middle of the BoxShape, you get normals like this: (-0.000001, 1, -0.000002) from move_and_collide.

Using ConcavePolygonShape produces precise normals using move_and_collide.

Just to iterate on my experience here:

I am making a minigolf game. I started with a SphereShape on a RigidBody, and KennyNL's minigolf tiles. This produced unexpected and unacceptable results when colliding with the seams between tiles, both on the ground and on walls.

I have spent several weeks understanding and exploring these issue. I tried many things, from mesh merging, to constraining angles, to overriding normals, to using a custom solver with a KinematicBody.

When I was using a RigidBody, I confirmed that a Rigidbody will bounce over seams between any shape type. I believe it is due to internal faces between meshes. You have to not only merge the meshes, enable Smooth TriMesh Collision but allso remove internal faces. Removing interior faces is a difficult problem. I wrote a GDScript to remove interior faces from tiles, but it only worked in a simple subset of cases.

This person helped me find a solution that will work for me and my game: https://godotengine.org/qa/42375/unexpected-collision-normals-touching-adjacent-collision?show=68257#c68257

Which is to use ConcavePolygonShape instead of BoxShape for the tiles and move_and_collide instead of a RigidBody.

Overall, I think this suite of "inexact normals" is an extremely important set of bugs. I know it has cost me a ton of time and produced a large assortment of physics bugs in my golf game.

I also know that RigidBody's will bounce over seams between tiles in UnrealEngine and Unity.

I think we can and should solve the RigidBody seam bouncing problem by improving the CSG system. This would allow users to have properly baked collision meshes - with interior faces removed.

We can and should make BoxShape + move_and_collide produce more exact normals.

Using a concave shape (which displays inexact normals) will cause move_and_collide to not do any sweep testing, and it will cause recover_from_penetration to use RFP_convex_convex_test. The bug is thus in one of those 2 code paths.

I will investigate further into both RFP_convex_convex_test and convexSweepTest to determine which of these code paths is producing inexact normals in move_and_collide.

I'm mentioning it here because I think this suite of problems are all interrelated.

davidhatten commented 4 years ago

I've run into this issue in my own way, though not on a RigidBody and neither the Trimesh nor Convex mesh generation workarounds work for me. The object being raycasted to is a StaticBody, and I'm trying to pivot around it based on the normal of the intersecting ray, and this fuzziness has been a bear to workaround. I'm glad to find this thread though, I feel better about not fiddling with this and moving on to other systems instead.

Edit: Changing the target to a RigidBody didn't work either, as of now the workaround stated doesn't seem to work? But i'll mess around with it more, maybe I'm doing something wrong here.

jitspoe commented 4 years ago

This might be significantly improved by compiling bullet with BT_USE_DOUBLE_PRECISION defined and adding btResult.m_flags |= btTriangleRaycastCallback::kF_UseGjkConvexCastRaytest; to space_bullet.cpp's intersect_ray() before calling rayTest().

jitspoe commented 2 months ago

Finally made a PR for this here: https://github.com/godotengine/godot/pull/95675

It doesn't address the rounded edges, but the raycast results are significantly better on the flat areas and don't seem to be registering outside of the box anymore. @Zylann Do you still have the project that generates images? Would be curious to see if there are any stray pixels with this PR.

Zylann commented 2 months ago

I dont know about the project, but I made an addon that visualizes colliders through raycasting here: https://github.com/Zylann/godot_collision_scanner/tree/godot4 I updated it for Godot 4 but havent used in a while so maybe there are things to adjust