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

Fix multiple issues with test_body_motion() and test_body_ray_separation(). #35945

Closed
wants to merge 1 commit into from

Conversation

madmiraal
Copy link
Contributor

@madmiraal madmiraal commented Feb 6, 2020

Fixes most of the jitter seen when using either move_and_collide(), move_and_slide() or move_and_slide_with_snap() and colliding with multiple surfaces. This is especially noticeable when two BoxShape CollisionShapes collide, for example a box shaped character on a GridMap.

Fixes #33833.
Fixes #34081.
Fixes #34098.
Fixes #34242.
Fixes #34436.
Fixes #34596.
Fixes #35713.
Fixes #35780.
Fixes #42175.

Update:
Now fixes remaining jitter, sliding in move_and_collide() and inaccurate collision information in Bullet physics:
Fixes #17893
Fixes #18433
Fixes #26550
Fixes #28717
Fixes #32182

Godot Physics v 3.1

Godot-Physics-31

Godot Physics v 3.2

Godot-Physics-32

Godot Physics v3.2 with #35945

Godot-Physics-32-with-35945

Bullet Physics v 3.1

Bullet-Physics-31

Bullet Physics v 3.2

Bullet-Physics-32

Bullet Physics v3.2 with #35945

Bullet-Physics-32-with-35945

@reduz
Copy link
Member

reduz commented Feb 7, 2020

No, I don't think this is a good idea, you may go into corners that apply opposing forces (like this _), so you need the recovery to be iterative to always work.

The jitter is probably due to something else, I would suggest bisecting.

@AndreaCatania
Copy link
Contributor

The jittering is caused by that, but I agree that's not safe remove it.

The best solution would be change how it works entirely.

@AndreaCatania
Copy link
Contributor

In the past I worked on another approach to solve this issue, if you want I can make a proposal @reduz.

@madmiraal
Copy link
Contributor Author

Before I address @reduz's concern, I think I need to clarify a few things.

This PR resolves the issue of the jitter being noticeable when a KinematicBody using move_and_collide(), move_and_slide() or move_and_slide_with_snape() collides with multiple parallel surfaces as reported in #34596, #35713 and #35780. It does not solve the issue of all visible jitter, which still remains and is noticeable in #32182.

The jitter reported in #34596, #35713 and #35780 is noticeable, because the oscillations are greater than 0.01. After this PR the remaining jitter is not noticeable, because the oscillations are less than 0.0001. The jitter (> 0.01) first became noticeable in Godot Physics with 3.1-stable after f2e5405, which "moved EditorDefaultValue to ClassDB" i.e. exposed the issue rather than caused it. The jitter first became noticeable in Bullet Physics with 3.2 alpha 1 with 6dd65c0 a.k.a. #27415, which fixed Bullet Physics to allow for multiple collisions. The cause of the remaining jitter in #32182 (and probably #18433, #20593, #31981, etc.) is subtly different in Godot Physics and Bullet Physics. I have not bisected it to discover its source, because I suspect that it's always been there.

The jitter and the other related issues occur, because the calculation of the safe movement is failing, and the body is then moved into an unsafe position. In Godot Physics, the calculation is failing, because it is not correctly incorporating the collision margin. In Bullet Physics, the calculation is failing, because it's not detecting collisions. Note: when Bullet Physics does detect a collision, it doesn't record the collision details, which causes #26550. A complete solution requires safe movement to be calculated correctly. Unfortunately, I've not yet been able to find a successful solution to this problem.

Moving the body into an unsafe position is not a major problem, because the body is quickly moved into a safe position through the recovery step. In Godot Physics, the recovery step is performed at the start of the next call to move_and_collide(). In Bullet Physics, the recovery step is called at the end of every call to move_and_collide(). However, the calculation of the recovery step is also flawed.

Currently, the recovery step sums the penetration vectors (distance x normal) from each collision shape the body is colliding with. This works when collision shapes are orthogonal e.g. a corner, but not with collision shapes that have normals with components in the same direction e.g. aligned boxes. The extreme case being a BoxShape CollisionShape character on four aligned BoxShape CollisionShapes such as a GridMap. The resultant recovery vector is 4x the length of the correct recovery vector. Even when scaled by 0.4, the recovery vector is still 1.6x the required length. The visible jitter is more extreme in Bullet Physics, because Bullet Physics only scales the vector at the beginning of the call to move_and_collide() and not at the end, which is what is being used to move the body into a safe position.

This PR does two things:

  1. Uses the maximum penetration instead of reversing the sum.
  2. Performs a single recovery step instead of 4 x 0.4.

The first change resolves #34596, #35713 and #35780, by using the maximum distance of each component, similar to creating an inverted AABB. This ensures that the recovery step is exactly what is required. This works in a corner (rincón, but not esquina, see #32182), because the orthogonal components are still added together. The second change is implemented, because Bullet Physics already uses a single step.

With regards @reduz's concern. There is a possibility that the recovery step can push the body into another body that it's not currently colliding with. This problem exists whether a single step or iterative partial steps are used. This is the reason, the current implementation needs to limit the number of partial iterations to four. Since the recovery step is exactly what is needed to move the body into a safe place, if it cannot be moved into a safe place, it is, by definition, stuck. In the situation where the other body's collision surface is not parallel e.g. an inverted wedge, a second step could nudge the body away from the tight corner. However, in most circumstances, the test_body_motion() function is called in via move_and_collide() from a _process() loop; so this iterative step will happen anyway. Furthermore, the number of steps required is incalculable and depends on the level of parallelism.

In summary, until a solution is found for correctly calculating the safe movement, this patch ensures that the recovery motion is not excessive, which resolves the serious issues of #34596, #35713 and #35780 that will stop users migrating to 3.2, and it works in corners (rincones) too.

@reduz
Copy link
Member

reduz commented Feb 10, 2020

@madmiraal I will check the code again soon when I have a bit of time, but I have the feeling you are not interpreting what is going on correctly. Even if you have recovery in the same direction, recovery happens only as a fraction of the penetration depth, so it never goes beyond that. This is exactly how all discrete physics engines work. The jitter is most likely just having to adjust the minimum penetration dept in Godot physics, but I am not sure about what is going on in Bullet, I would need to check it again.

@madmiraal
Copy link
Contributor Author

@reduz Just to clarify my understanding of the purpose of the first step in test_body_motion(). It's there to attempt to free a body if it is stuck before attempting to move it. This should be done incrementally, using partial steps, to ensure it doesn't then intersect with another CollisionShape that it's not currently colliding with.

I believe this is based on two assumptions:

  1. Under normal circumstances the body is not stuck; therefore, most of the time, it's not required.
  2. When it is required, and it collides with multiple shapes, these shapes will be orthogonal (an implicit assumption based on the implementation).

The first assumption is not valid, because this function is currently needed every time a previous call to test_body_motion() collides. As explained above, (in Godot Physics) this is due to it miscalculating the safe fraction (possibly due to not including the margin). When a body is being moved against a static object (e.g. when simulating gravity on a floor) this happens on every call to test_body_motion(). Therefore, I believe, gently nudging the body into a safe location is currently an unneeded luxury, and a single step will suffice; especially since gently nudging the body doesn't guarantee that it will be moved into a safe location anyway and Bullet Physics is using a single step already.

The second assumption is not valid when the body is colliding with multiple aligned surfaces e.g. a GridMap. Therefore, this PR proposes using the maximum value in each basis direction to avoid the cumulative error that is causing the visible jitter seen in #34596, #35713 and #35780.

@wareya
Copy link
Contributor

wareya commented Feb 12, 2020

Noting that #18433 (mentioned in a previous comment here) is technically not about jitter but its particular behavior will still probably change as a result of this patch. The problem in #18433 applies even when colliding against a single planar surface and manifests in different ways depending on exactly how move_and_collide() is being used (typically some kind of downhill drag).

@VictorLamoine
Copy link

Any update on this? For now I have compiled Godot using @madmiraal branch
Is there an other pull request solving this issue that has been merged and that I missed?

@madmiraal
Copy link
Contributor Author

Rebased following the renaming of servers. Since #34596, #35713 and #35780 are regression issues from 3.1 I'll create a separate pull request against the 3.2 branch with the original changes.

@madmiraal
Copy link
Contributor Author

Rebased following NULL to nullptr change.

@piratesephiroth
Copy link
Contributor

I tested this and it has no effect at all on the weird collisions of kinematicbody2d with slopes (#20593)

@madmiraal
Copy link
Contributor Author

Updated to also fix #18433, #26550 and #32182 and improve #31981. This change has been added to this PR, because the problems are all interlinked.

@madmiraal madmiraal changed the title Perform a single recovery step and reverse the maximum penetration instead of the sum before testing motion. Fix multiple issues with test_body_motion() and test_body_ray_separaton(). Apr 12, 2020
@wareya
Copy link
Contributor

wareya commented Apr 12, 2020

The underlying problem in #18433 is not linked to jitter and can't be truly fixed without entirely removing overlap rectification from the code paths used by move_and_collide (i.e. breaking backwards compatibility or adding more optional arguments).

@madmiraal
Copy link
Contributor Author

@wareya The remaining jitter and the sliding seen in #18433 had the same cause. This has now been fixed and included in this PR.

@madmiraal
Copy link
Contributor Author

Added a fix for the sticking identified by @MBZSoftProds.

@reduz
Copy link
Member

reduz commented Apr 27, 2020

@madmiraal

The first assumption is not valid, because this function is currently needed every time a previous call to test_body_motion() collides. As explained above, (in Godot Physics) this is due to it miscalculating the safe fraction (possibly due to not including the margin).

Actually, all this is done on purpose, and the reason it works this way is due to numerical precision. Assuming stuck by checking margin then move safely without margin. If it did not work this way, your body would approach the collision point infinitely and start causing floating point precision problems. Again, all physics engines work like this to avoid running into this problem.

I will give an overhaul to the physics engine when I am done with rendering and will eventually check issues there, so from my side I will not do any work or changes on physics until I can spend time on it in some months.

When it is required, and it collides with multiple shapes, these shapes will be orthogonal (an implicit assumption based on the implementation).

Shapes can be any amount in any direction, and you have to always properly free from them as a first step using incremental biases. This is unavoidable. This algorithm is expensive, and considerably more expensive than just using a rigid body because its proper CCD.

@madmiraal
Copy link
Contributor Author

Setting aside the enticing discussion on how a physics engine should work and why collision margins are necessary, I think it’s important to focus on what is causing the issues and how this PR addresses them. Also, even if the approach in this PR is flawed, it should at least be considered as a workaround until the the physics engine is overhauled, because the issues it addresses are cited as one of the main reasons people are not moving to 3.2. The 3.2 version of this PR is #37498.

To be specific, the issues addressed are caused by the current implementation of test_body_motion() and test_body_ray_separation() which is used by move_and_collide(), which is used by move_and_slide(), which is used by move_and_slide_with_snap(), which are all only used by KinematicBodys in 2D and 3D. Godot’s Bullet implementation copies the Godot Physics approach, with some slight differences.

In the current implementation test_body_motion() is divided into three steps. In Godot physics these are:

  • Move to a safe position with margin using 4 incremental steps.
  • Test the motion without margin. If there is a collision, find the maximum safe proportion and minimum unsafe proportion of the desired motion.
  • Use the unsafe proportion to extract collision information, and use the safe proportion to move the body.

In Bullet physics these are:

  • Move to a safe position using 4 incremental steps.
  • Test the motion with a tolerance equivalent to the margin. If there is a collision, use the minimum unsafe proportion of the desired motion.
  • Use the unsafe proportion to move and extract the collision information, and then move to a safe position using a single step.

In the current implementation of both Godot and Bullet physics, the move to a safe position with margin is calculated by summing the vectors of the shortest penetration distance between each collision shape. When using 4 incremental steps this distance is multiplied by 0.4 and repeated 4 times.

The jitter described in the issues is caused by the move to a safe position being greater than the attempted motion. To simplify things, I’m only going to cover test_body_motion() with box CollisionShapes continually hitting multiple aligned box Collisionhapes in 3D i.e. a box character with a downward velocity (simulated gravity) on a GridMap of boxes. However the same applies to other CollisonShapes including RayShapes and 2D and shapes that are not aligned.

The current Godot implementation works for a body (pink) colliding with a single surface (blue) as follows:

Godot Current 1 Surface

  • Step 1: The penetration distance with margin (the dashed lines) between the body and the surface is calculated (a). This value is multiplied by 0.4 (the vector arrow between (a) and (b)). The body is moved out by this amount. This process is repeated up to four times (b), (c), (d) and (e). Although I’ve shown them as four separate steps, the movements are actually added together and only a single recovery movement is performed, but the effect is the same. Note that even after four iterations there still remains a penetration with margin.
  • Step 2: The movement without margin is attempted (f). If there is a collision, the safe and unsafe distances are calculated.
  • Step 3: The unsafe distance is used to gather the collision information, and the safe distance is used to move the body (g).

Since the distance between the body and the surface in (a) and (g) is roughly the same, no jitter is seen. However, when the body collides with four parallel surfaces, for example on a GridMap the current implementation results in jitter, because it works in the following way:

Godot Current 4 Surfaces

  • Step 1: The penetration distance with margin between the body and each of the four surfaces is calculated (a). The values for each of the four surfaces are added together and multiplied by 0.4 (the four vector arrow between (a) and (b)) for a total of 1.6x the penetration distance The body is moved out by this amount. Since the recovery motion is greater than the penetration distance, there is no longer any penetration with margin; so only one iteration is performed.
  • Step 2: The movement is attempted (c). With reasonable motion values, there is no collision.
  • Step 3: The body is moved (d).
    In the next frame, steps 1-3 are repeated as follows:
  • Step 1: The penetration distance with margin between the body and each of the four surfaces is calculated (e). There may or may not be a recovery motion. However, if there is any penetration with margin only one iteration will be performed, because the recovery motion will be the penetration distance multiplied by 1.6.
  • Step 2: The movement is attempted (f). There may or may not be a collision.
  • Step 3: The body is moved (g).
    Jitter is seen because the distances between the body and the surfaces at end of each frame (a), (d) and (g) are all different.

This PR proposes modifying the calculation of the recovery motion by using the maximum penetration distance of each surface instead of summing them together, and only performing a single iteration instead of four. This results in the following when the body collides with a single surface:

Godot Proposed 1 Surface

  • Step 1: The penetration distance with margin between the body and the surface is calculated (a). The body is moved out by this amount (b). Note that there is now no penetration with margin.
  • Step 2: The movement without margin is attempted (c). If there is a collision, the safe and unsafe distances are calculated.
  • Step 3: The unsafe distance is used to gather the collision information, and the safe distance is used to move the body (d).

Since the distance between the body and the surface in (a) and (d) is roughly the same, no jitter is seen. When the body collides with four parallel surfaces, for example on a GridMap the proposed approach works in exactly the same way:

Godot Proposed 4 Surfaces

  • Step 1: The penetration distance with margin between each of the four surfaces is calculated (a). The body is moved out by the difference between the maximum and minimum (negative) amount in each axial direction (b). If possible, i.e. the body is not stuck, the body will be moved to a position with no penetration distance with margin an no more! Note, if the body is stuck in an axial direction it will be moved the median position.
  • Step 2: The movement without margin is attempted (c). If there is a collision, the safe and unsafe distances are calculated.
  • Step 3: The unsafe distance is used to gather the collision information, and the safe distance is used to move the body (d).

The result is exactly the same as for a single surface, hence no jitter is seen.

For Bullet physics the situation is slightly different, when the body is colliding with a single surface it works as follows:

Bullet Current 1 Surface

  • Step 1: The body is checked to ensure it is not currently colliding with the surface (a). Generally it’s not, but numerical errors in the depenetration algorithm may result in it being moved slightly. Note: if it is moved it is only moved 0.4 times the residual penetration and this is repeated four times, which, as explained in the current Godot physics with one surface, will not actually result in complete depenetration.
  • Step 2: The movement is attempted with a tolerance equivalent to the collision margin (b). With reasonable motion values, there is no collision.
  • Step 3: The depenetration algorithm is used to calculate the penetration distance. The body is moved out by this distance (d). Unlike step 1, this is only done once and the full penetration distance is used.

Again, since the distance between the body and the surface in (a) and (d) is roughly the same, no jitter is seen. However, when the body collides with four parallel surfaces, for example on a GridMap a large amount of jitter is seen, because it works in the following way:

Bullet Current 4 Surfaces

  • Step 1: The body is checked to ensure it is not currently colliding with the surface (a).
  • Step 2: The movement is attempted with a tolerance equivalent to the collision margin (b). With reasonable motion values, there is no collision.
  • Step 3: The depenetration algorithm is used to calculate the penetration distance with each surface. The body is moved out by the sum of theses distances (d). Note, with 4 parallel surfaces, the distance is 4 times the required distance.
    In the following frames, steps 1-3 are repeated as follows:
  • Step 1: The body is checked to ensure it is not currently colliding with the surface.
  • Step 2: The movement is attempted with a tolerance equivalent to the collision margin (e), (f), (g). There is no collision.

There is a lot of jitter, because the distance between the body and the surfaces at the end of each from: (a), (d), (e), (f) and (g) vary a lot.

This PR proposes modifying the calculation of the recovery motion and the motion in Bullet physics. The calculation for the recovery motion is the same as the proposed calculation for Godot physics. The calculation of the motion is different, because the tolerance is not used and both the safe and unsafe motion distances are calculated. Note, in Bullet physics the collision margin is included in the CollsionShapes for all calcualtions. This results in the following when the body collides with a single surface:

Bullet Proposed 1 Surface

  • Step 1: The body is checked to ensure it is not currently colliding with the surface (a).
  • Step 2: The movement is attempted without a tolerance. If there is a collision the minimum unsafe distance is returned (b).
  • Step 3: The unsafe distance is used to extract the collision information. A binary search (similar to the Godot method) is used to find the maximum safe distance that the body can be moved. The body is only moved the safe distance (c).

Since the distance between the body and the surface in (a) and (c) is roughly the same, no jitter is seen. When the body collides with four parallel surfaces, for example on a GridMap the proposed approach works in exactly the same way:

Bullet Proposed 4 Surfaces

  • Step 1: The body is checked to ensure it is not currently colliding with the surface (a).
  • Step 2: The movement is attempted without a tolerance. If there is a collision the minimum unsafe distance is returned (b).
  • Step 3: The unsafe distance is used to extract the collision information. A binary search (similar to the Godot method) is used to find the maximum safe distance that the body can be moved. The body is only moved the safe distance (c).

The result is exactly the same as for a single surface, hence no jitter is seen.

If required I can do a similar expalantion for multiple surfaces in differing directions, but hopefully this is sufficient.

@madmiraal
Copy link
Contributor Author

Rebased following merge of #37314.

@madmiraal
Copy link
Contributor Author

I have tested and analysed the circle/sphere shape in a wedge and I have not found a problem with the approach used in this PR. In fact, I have found that the approach used in this PR actually improves the situation.

First, for the benefit of others reading this, I want to clarify the situation presented. Since, we're assuming that "things either fit or not" the circle shape would not overlap the shapes creating the wedge. Therefore, for a 30° wedge, the worst case scenario would look like this:
PR 35945 Wedge
The black dashed line represents the KinematicBody2D Safe Margin, which is used by Godot physics' method test_body_motion() method first step (free the body is stuck). The red arrows represent the vectors used to attempt to "free" the KinematicBody2D. The smaller red arrows represent their their x- and y-components. The green arrow represents the resultant movement.

Despite using different methods to calculate the green arrow, the direction of the resultant movement (the green arrow) is the same for both the current approach and the approach used in this PR. The difference is the length of the green arrow and the number of times the green arrow is calculated and the KinematicBody2D is moved (the iterations).

With the current default Safe Margin of 0.08 and a wedge angle of 30°, the length of the green arrow is calculated to be:

Iteration 3.2.3 3.2.4.rc1 3.2.4.rc1+PR35945
1 0.0299 0.0373 0.0746
2 0.0187 0.0199
3 0.0117 0.0106
4 0.0074 0.0057
Total 0.0677 0.0735 0.0746

This shows that this PR does a better job i.e. moves it further – in one step – of "freeing" the KinematicBody2D than the method used in 3.2.3 or the #42574 modified approach included in 3.2.4.rc1.

pouleyKetchoupp added a commit to nekomatata/godot that referenced this pull request Feb 1, 2021
Extracted from godotengine#35945

Using min/max from different axes instead of adding recovery vectors
together in order to avoid cases where the recovery is too high when
colliding with several shapes at once along the same direction.

Co-authored-by: Marcel Admiraal <madmiraal@users.noreply.github.com>
pouleyKetchoupp added a commit to nekomatata/godot that referenced this pull request Feb 1, 2021
Extracted from godotengine#35945

Using min/max from different axes instead of adding recovery vectors
together in order to avoid cases where the recovery is too high when
colliding with several shapes at once along the same direction.

Co-authored-by: Marcel Admiraal <madmiraal@users.noreply.github.com>
@reduz
Copy link
Member

reduz commented Feb 2, 2021

@madmiraal al Yeah I guess in this case yours will give a similar result because it's vertical, and because the original approach is not actually moving the object, but the recovery vector, so in this very specific case it's not fundamentally that different. That said, I still think it's not a great idea.

Again, as I mentioned in my previous post, the way I see things are:

  1. Doing the iterative process correctly is expensive, prefer to not to do it.
  2. The current iterative solver likely takes too many shortcuts. It may be possible to improve and make it closer to the correct way it should work, but honestly other physics engines don't care about correctness on this either. I have some ideas I would like to try, maybe in some days, but honestly I still think it's not worth it.
  3. Your approach is wrong, I am sorry coming with examples of why it's difficult, but after you try these things for a while you realize that cutting corners this way sooner or later has side effects, so I am never going to approve it no matter how much you insist. Please don't be offended about it. I apologize that I should have been clearer on this earlier. It's not going to happen, so I hope you eventually accept this and move on.

Again, the way I see it, I really believe all this is time mis-spent because to me the problem is solved already. I am open to looking for ways to improving the approximation to the "correct" solver, but not to cut corners like this.

@mindinsomnia
Copy link

I do not know if any such tests exist or not, but if not, perhaps it would be worth exploring creating some tests to verify results of depenetration methods?

The results could be used to confirm whether or not @madmiraal 's approach could work, and also be useful for identifying regressions for any later changes to physics, which could be important in the near future given the significant amount of work related to physics in Godot that is planned.

@reduz
Copy link
Member

reduz commented Feb 2, 2021

@mindinsomnia The problem is that his approach cuts too many corners, as well as being anisotropic (diagonals wont work as well), when you do these kind of things, in my experience, you are not really certain what will fail (because all examples you can think of will work) until it fails (in a situation you did not think about). It's a sort of experience about not trusting certain types of solutions to problems that you get you grow up doing these things over and over again.

In any case, I think the problem @madmiraal is trying to solve with this PR can be solved more correctly (in a non-anisotropic way and closer to how an actual full solver would work) in a different way with a minimal modification to the current code. Will hope to try this out in the coming days.

@reduz
Copy link
Member

reduz commented Feb 2, 2021

image

Also, I had to think a bit more, but here is a better example that will not work with this PR. Because the recovery is anisotropic, this will produce no recovery at all (which should be towards the upper right diagonal), and most likely cause the sphere to fall through the bottom.

I am pretty sure that there are a lot more cases using diagonals that will break this PR because of its assumed orthogonality.

@Riteo
Copy link
Contributor

Riteo commented Feb 2, 2021

Sooo... Will this be PR closed, or will it stay open until all the good bits are taken out?

@wareya
Copy link
Contributor

wareya commented Feb 2, 2021

This is tangential, but I will note that there are a lot of situations in which it is preferable to depenetrate in a way that prefers some directions over others. For example, if you have a shooter character walking up and down shallow slopes, it makes a lot more sense to try to depenetrate them vertically before trying to depenetrate them along the collision normal.

Additionally, getting accurate collision normal data between two overlapping objects is hard, especially if one of them is a mesh or an AABB. So sometimes, a depenetration algorithm that looks like it prefers some directions over others will actually give more accurate results than one that tries to prefer directions derived from collision normals. (I happen to know that bullet in particular can be fairly poor at giving direction data for contact pairs; no idea how godot physics works when it comes to this.)

Considering both of these two things together, it makes sense to think that, at the very least, the presence of anisotropic depenetration code is not necessarily a mark against a depenetration system, as long as there is also isotropic depenetration code.

@reduz
Copy link
Member

reduz commented Feb 2, 2021

@wareya That is not really the problem that this PR is attempting to solve. (TBH the real problem here is that this should work for users in Godot Physics right now and nobody ported the same fix to Bullet).

@Riteo I want to experiment a bit more with this in the coming days, but I need to finish my upcoming PR.

@reduz
Copy link
Member

reduz commented Feb 3, 2021

Here are some extra thoughts on this. You guys got me involved, so now bear with me :)

So, here are my two proposed solutions, one for a more robust one-step solver (non-iterative) and another for a more robust iterative one.

For a one-step solver, this is the best I can come up with. I tried this approach in the new particle collider and it seems to work very well. Its kind of similar to what this PR attempts to do, but does not have the anisotropy limitation.

Vector2 recover_motion;
for (int i = 0; i < cbk.amount; i++) {
    Vector2 a = sr[i * 2 + 0]; //own point
    Vector2 b = sr[i * 2 + 1]; //foreign point

    Vector2 n = b-n; 
    real_t l = n.length(); //collision length
    n.normalize(); //collision normal
    
    l -=MAX(0, n.dot(recover_motion)); //maximum amount you can move in this direction, without accumulating
    if (l > 0.0) {
        recover_motion += n * l;    
    }
}

What this does Is just push away the contact, but ensure it never pushes again more than the maximum depth in a given direction. In my view, this is what, in my mind, the correct version of this PR would be (in practice does more or less the same, likely not as good with collision on all sides, could be worked around, but does not fail on the test case I posted). I am still not sure if I prefer this over an iterative solution, given the iterative solution is a lot more robust.

So I tried to come up with a more correct iterative solution and this is what I have so far. I think it would be worth testing as it requires minimal change to what there is now:

Vector2 recover_motion;
for (int i = 0; i < cbk.amount; i++) {
    Vector2 a = sr[i * 2 + 0]; //own point
    Vector2 b = sr[i * 2 + 1]; //foreign point

    //compute plane on b towards a
    Vector2 n = (a-b).normalized();  //normal
    float d = n.dot(b); //distance

    //compute depth on recovered motion
    real_t depth = n.dot(a + recover_motion) - d;
    if (depth > 0.0) { //only recover if there is penetration
        recover_motion -= n * depth * 0.4;
    }
}

The above is more close to how a correct iterative solver would work, as it considers motion (something the the current one does not) and I think it should also solve the jittering problem. Of course I may be wrong so all this need to be tested, so If you guys want to give a check to this or discuss these ideas, let me know.

@madmiraal
Copy link
Contributor Author

madmiraal commented Feb 7, 2021

Rebased following merge of #45564.

…ion().

The test_body_motion() and test_body_ray_separation() functions perform an
initial recovery step to attempt to move the body to a safe place before
testing whether the body can safely move. Currently this is done in four
partial steps, and during each step the detected penetrations are reversed
by summing them together.

This patch changes this approach to a single step and uses the maximum
penetration instead of the sum. The same approach is applied to Godot
Physics 2D, 3D and Bullet Physics, and to both CollisionShapes and
RayShapes.

Also, fixes Bullet physics not detecting collisions and relying on the
recovery step. Furthermore, ensures that Bullet physics only moves a
safe amount by using a binary search as done in Godot physics.
@madmiraal
Copy link
Contributor Author

Rebased following merge of #43952.

@akien-mga akien-mga changed the title Fix multiple issues with test_body_motion() and test_body_ray_separaton(). Fix multiple issues with test_body_motion() and test_body_ray_separation(). Feb 18, 2021
@YuriSizov YuriSizov requested a review from a team August 24, 2021 22:41
@reduz
Copy link
Member

reduz commented Aug 31, 2021

I am closing this pull request for the following reasons:

  • The issue it attempts to fix is resolved in master using a more conservative technique.
  • Lack of interest by the author in further discussion.

I think this is a very interesting and clever idea, and it is entirely possible that in practice it solves all possible cases that may arise, besides being far more efficient.

The reason it was not adopted is that, given how unconventional it is, It is not possible for us to establish with 100% confidence that it will not have future side effects or fail in very specific situations. Given this is such a critical and often used piece of the engine, a more conservative approach using an iterative solver was preferred.

I am still thankful and appreciative of all the effort that went into this, which contributed to making Godot a much better piece of free and open source technology for everyone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment