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 collisions with long dtime, in particular with bouncing #15029

Merged
merged 3 commits into from
Oct 26, 2024

Conversation

kno10
Copy link
Contributor

@kno10 kno10 commented Aug 21, 2024

Closes #15027

When there is lag in the server (generating map chunks, spawning structures, mobs...) and it misses some steps and we have a large dtime, entities can bounce badly on beds. Seen for example in Voxelibre and Mineclonia.

The main issue is that speed_f is the speed at the end of the movement, not at collision time. For a large timestep, this speed can be much too large (consider a mob just over a bed, accelerated by gravity for a long timestep).

This patch (A) introduces aspeed_f, which is the average movement speed not the maximum (B) on collision, the speed is updated according to the estimated dtime until collision.

For the villager example above, in particular (B) means a much smaller velocity when bouncing.

For further detail, see #15027

@Zughy Zughy changed the title Fix collision for bounces, closes #15027 Fix collision when bouncing Aug 21, 2024
@Zughy Zughy added this to the 5.10.0 milestone Aug 21, 2024
@kno10 kno10 changed the title Fix collision when bouncing Fix collision with long dtime, in particular with bouncing Aug 21, 2024
@kno10 kno10 changed the title Fix collision with long dtime, in particular with bouncing Fix collisions with long dtime, in particular with bouncing Aug 21, 2024
@sfan5
Copy link
Collaborator

sfan5 commented Aug 26, 2024

It's hard to tell which are cosmetic changes and which are functional changes that fix the bug. Is it aspeed_f?

@kno10
Copy link
Contributor Author

kno10 commented Aug 27, 2024

This patch (A) introduces aspeed_f, which is the average movement speed not the maximum (B) on collision, the speed is updated according to the estimated dtime until collision.

So yes, aspeed_f is the most important part of it. The old code used the speed at the end of the movement, not the average speed during the time step. But further down, aspeed is updated not with the full dtime, but with nearest_dtime

aspeed_f = *speed_f + accel_f * 0.5f * nearest_dtime;
*pos_f += truncate(aspeed_f * nearest_dtime, 100.0f);

This could probably be one line, but the important thing is that the average speed during the partial time nearest_dtime is correct, instead of the acceleration over the full dtime as before (which caused a very high impact speed on bouncy materials with lag), if nearest_dtime is much smaller than dtime.

@sfan5
Copy link
Collaborator

sfan5 commented Sep 6, 2024

Can you separate the fix and the style changes into two commits?

@Zughy Zughy added the Action / change needed Code still needs changes (PR) / more information requested (Issues) label Sep 6, 2024
@sfan5 sfan5 removed the Action / change needed Code still needs changes (PR) / more information requested (Issues) label Sep 10, 2024
src/collision.cpp Outdated Show resolved Hide resolved
src/collision.cpp Show resolved Hide resolved
@kno10 kno10 requested a review from SmallJoker September 15, 2024 11:05
@kno10
Copy link
Contributor Author

kno10 commented Sep 16, 2024

This would be really nice to have in 5.9.1 or 5.9.2 IMHO.

@@ -454,8 +443,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
// Find nearest collision of the two boxes (raytracing-like)
f32 dtime_tmp = nearest_dtime;
CollisionAxis collided = axisAlignedCollision(box_info.box,
Copy link
Contributor

@appgurueu appgurueu Oct 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought: This is still some kind of linear approximation of a quadratic function, just now with the average speed, isn't it?

We could make axisAlignedCollision take acceleration into account properly: If acceleration on an axis is zero, the current code is fine. Otherwise, exactly solve the quadratic equation for the first point in time >= 0 where the faces touch. (We might want doubles here?)

So for example if we're checking for an X axis collision, we look at the current velocity. Say the X component is > 0. So we want to check whether our +X face smashes into another the -X face of another box. Now we have the distance between those faces and we want to find the point in time at which they touch; we then check whether the YZ projections of the AABBs overlap. That's what the code is essentially currently doing for all three axes, except it doesn't take acceleration into account: It's just dtime = distance / std::abs(speed.Y); when it should be $vt + \frac{1}{2}at = d$ solved for $t$ where $d$ is the distance between the faces solved for $t$.

If we do this, some extra optimizations might be needed to not hurt performance, as axisAlignedCollision is pretty hot.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not previously check axisAlignedCollision, and this fix here is quite urgent IMHO - there are plenty of complaints in minetest games such as Voxelibre and Mineclonia about mobs bouncing (on beds) through roofs.
Hence I opened #15236 to discuss this part. But there also is #15227 which further complicates things when considering two moving objects (although it should be possible to compute this similarly by using their relative positions and relative speeds, which should reduce this to the base case again).

Copy link
Member

@SmallJoker SmallJoker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did the villager drop test + 6 seconds server delay.

ACTION[Server]: singleplayer executed local t0 = os.time() while os.time() < t0+6 do end
WARNING[Server]: collisionMoveSimple: maximum step interval exceeded, lost movement details!

Works. As far as I can tell, there's no noticeable difference for the client-side physics because its step size is small enough.

@appgurueu
Copy link
Contributor

I will review this properly in a couple days when the feature freeze starts (till then I will prioritize reviewing feature PRs; since this is a bug fix it can be merged during feature freeze).

@Zughy
Copy link
Contributor

Zughy commented Oct 22, 2024

@appgurueu reminder

Copy link
Contributor

@appgurueu appgurueu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes the issue it claims to fix. The math should at least be less bogus than before, on average, now. This seems fine as a quick fix for 5.10.0.


I was hoping there would be a simpler fix lurking here (by simply using the current speed before applying acceleration in the linear approximation), but I tried to implement it and it's not as simple (specifically the "no collisions" case poses a major problem; we do want to apply acceleration there, but we don't know whether by doing so we overshoot because there will be an early collision). I think ultimately I'll implement https://github.com/minetest/minetest/pull/15029/files?diff=split&w=1#r1786903169.

src/collision.cpp Outdated Show resolved Hide resolved
}
} else if (nearest_dtime > 0) {
aspeed_f = *speed_f + accel_f * 0.5f * nearest_dtime;
Copy link
Contributor

@appgurueu appgurueu Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is somewhat nonsensical, but I see no better way of doing it yet short of implementing the proper solution. (I do get that it tries to compensate for the fact that we already added half of the dtime worth of acceleration, but overall the math doesn't check out, even for a linear approximation.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 0.5 comes from adding the average acceleration over the interval nearest_dtime.
So it's fine as long as nearest_dtime is accurate (which it is not entirely, as discussed elsewhere).
As we only simulate up to nearest_dtime in this iteration, we have to update the average speed to match this segment only. We could iterate multiple times here to approximate this numerically (recheck the collision with the new aspeed until the change is tiny), but I believe this will be fine and is better fixed by improving the axisAlignedCollision function.
But when nearest_dtime is much smaller than dtime, then this version will be more accurate than not updating aspeed_f (because the initial aspeed_f then was not very accurate).

src/collision.cpp Show resolved Hide resolved
kno10 added 2 commits October 24, 2024 02:26
1. use average speed not final speed (half as much acceleration)
2. when detecting a collision, compute the speed at the moment of
   collision, instead of using the final speed
3. after collision, set the acceleration on this axis to 0
src/collision.cpp Outdated Show resolved Hide resolved
@kno10
Copy link
Contributor Author

kno10 commented Oct 24, 2024

@grorp ok, reverted back to const v3f vec

@grorp grorp merged commit cb6c8eb into luanti-org:master Oct 26, 2024
15 checks passed
SmallJoker added a commit that referenced this pull request Nov 8, 2024
kno10 added a commit to kno10/minetest that referenced this pull request Nov 10, 2024
@kno10
Copy link
Contributor Author

kno10 commented Nov 10, 2024

Improved version #15408 - please consider that for 5.11

kno10 added a commit to kno10/minetest that referenced this pull request Nov 12, 2024
kno10 added a commit to kno10/minetest that referenced this pull request Nov 13, 2024
kno10 added a commit to kno10/minetest that referenced this pull request Nov 15, 2024
kno10 added a commit to kno10/minetest that referenced this pull request Nov 17, 2024
kno10 added a commit to kno10/minetest that referenced this pull request Nov 24, 2024
kno10 added a commit to kno10/minetest that referenced this pull request Dec 5, 2024
kno10 added a commit to kno10/minetest that referenced this pull request Dec 27, 2024
kno10 added a commit to kno10/minetest that referenced this pull request Jan 13, 2025
kno10 added a commit to kno10/minetest that referenced this pull request Jan 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bounciness issues with large dtime steps
6 participants