jrouwe / JoltPhysics

A multi core friendly rigid body physics and collision detection library. Written in C++. Suitable for games and VR applications. Used by Horizon Forbidden West.
MIT License
6.59k stars 431 forks source link

Restitution 1.0 produces higher bounce every time. #1286

Open Wc4ever opened 2 weeks ago

Wc4ever commented 2 weeks ago

When I set everything to 0.0 but 1 rigid body to restitution 1.0 every next bounce goes higher. Is it intended behaviour? Usually, it is bouncing on the same height in other physics engines. Samples_XUlroQbfnq

git patch with example 0001-restitution-bug.patch

jrouwe commented 2 weeks ago

Hello,

This is a known issue and has been discussed before, see: https://github.com/godot-jolt/godot-jolt/issues/374#issuecomment-1555939027

It is something I should look into though (at least confirm that other physics engines don't have this behavior / don't do the opposite of slowly losing energy).

Wc4ever commented 2 weeks ago

Hi, thanks for answer. I could post info of how it works in other engines if you wish.

Here is test from Bullet. Every damping and friction set to 0. Looks like bullet calculates restitution as multiplication so both objects has to be 1.0 otherwise it dont bounce at all.

https://github.com/user-attachments/assets/a77816e9-bf8e-4a81-b4f2-ea640982fd31

Open Dynamics Engine surface bounce 1.0

https://github.com/user-attachments/assets/7bfe22de-275a-4f6c-a946-d5a2a9b35db9

Unity build-in engine Gosh looks like it grows in Unity also(unexpected). Material settings Bounciness 1.0 Combine type Maximum.

https://github.com/user-attachments/assets/0c643329-8a24-4ede-a158-98fa86779385

Wc4ever commented 2 weeks ago

Flax Engine (Physx 5) Wierdest one so far. Set both material Combine type Maximum and restitution 1.0 and it grows every bounce almost the same as in Jolt. But if I set Combine type Multiply and all materials to 1.0 seems it slows down but eventualy grows up again and then slows down.

https://github.com/user-attachments/assets/e56fa9a3-c09d-48fd-93ef-17a5c9f8ff70

So seems I was wrong about "other engines" but at the same time rebouns at the same height makes more sense to me.

At this point if you belive there is no bug I think it could be just declarated as expected behaviour and closed.

jrouwe commented 2 weeks ago

I'll leave the bug open for now. It's definitively not physically correct. I'll see if there's a way to make this better.

Mainly for future me, I've simplified the simulation loop to:

// Code to simulate a sphere of radius 2 falling from 10 units high
Vec3 pos = Vec3(0, 10, 0);
Vec3 vel = Vec3::sZero();
Vec3 g(0, -9.81f, 0);
float dt = 1.0f / 60.0f;
float maxy = 0;
for (int i = 0; i < 1000; ++i)
{
    // Apply gravity
    // Equivalent to PhysicsSystem::JobApplyGravity
    vel += g * dt;

    // If we're penetrating the ground, we have to reverse the velocity (corresponds to restitution 1).
    // Equivalent to PhysicsSystem::JobSolveVelocityConstraints
    float penetration = 2.0f - pos.GetY();
    if (penetration > 0 && vel.GetY() < 0.0f)
        vel = -vel;

    // Update position
    // Equivalent to PhysicsSystem::JobIntegrateVelocity
    pos += vel * dt;

    // Record new height records (note we only trace when we're going down again to avoid tracing multiple lines for the same bounce)
    if (vel.GetY() <= 0.0f && pos.GetY() > maxy)
    {
        maxy = pos.GetY();
        Trace("Simulated new high: %f", maxy);
    }
}

which produces:

Simulated new high: 9.997275
Simulated new high: 10.209825
Simulated new high: 10.425099
Simulated new high: 10.643100
Simulated new high: 10.863826
Simulated new high: 11.087276
Simulated new high: 11.313452

When simulating the same setup in Jolt we get the exact same results:

New high point: 10.000000
New high point: 10.209825
New high point: 10.425100
New high point: 10.643101
New high point: 10.863826
New high point: 11.087276
New high point: 11.313451

Implementation: SimpleTest.zip

As said in the other ticket: When the collision has been detected, the gravity has been applied already so the bounce velocity is overestimated.

wiltosoft commented 4 days ago

https://github.com/user-attachments/assets/7b9c515f-9490-4a32-9d19-ebed71b9a34b

Could this be related to experiencing infinite bounce? This is with friction/restitution 0.8/0.8 on the ball and 1.0/0.9 on the plane.

jrouwe commented 3 days ago

If I adjust the code above with your parameters:

// Code to simulate a sphere of radius 2 falling from 10 units high
Vec3 pos = Vec3(0, 10, 0);
Vec3 vel = Vec3::sZero();
Vec3 g(0, -9.81f, 0);
float dt = 1.0f / 60.0f;
float restitution_floor = 0.9f;
float restitution_sphere = 0.8f;
float restitution_combined = max(restitution_floor, restitution_sphere);
for (int i = 0; i < 1000; ++i)
{
    bool positive_before = vel.GetY() >= 0.0f;

    // Apply gravity
    // Equivalent to PhysicsSystem::JobApplyGravity
    vel += g * dt;

    // If we're penetrating the ground, we apply restitution
    // Equivalent to PhysicsSystem::JobSolveVelocityConstraints
    float penetration = 2.0f - pos.GetY();
    if (penetration > 0 && vel.GetY() < 0.0f)
        vel = -restitution_combined * vel;

    // If velocity changes from positive to negative, we trace the height
    bool positive_after = vel.GetY() >= 0.0f;
    if (positive_before && !positive_after)
        Trace("Bounce height: %f", pos.GetY());

    // Update position
    // Equivalent to PhysicsSystem::JobIntegrateVelocity
    pos += vel * dt;
}

Then no:

Bounce height: 10.000000
Bounce height: 8.627144
Bounce height: 7.512838
Bounce height: 6.609490
Bounce height: 5.857011
Bounce height: 5.247907
Bounce height: 4.739192
Bounce height: 4.324055
Bounce height: 3.972433
Bounce height: 3.679149

But as you can see from the code above, it depends on more than just the restitution (gravity and delta time).

wiltosoft commented 3 days ago

Thank you for your reply @jrouwe :) Appreciated.

I took your code and changed the iterations to 4000 and I see the same effect, I think? I can provide the physics world code I am using for the above video too. I may well be doing something daft tho'!

image

Edit: FWIW, I think this pretty much sorts it for both situations. I'm not yet familiar enough with your code to suggest a patch tho'!

    // Code to simulate a sphere of radius 2 falling from 10 units high
    Vec3 pos = Vec3(0, 10, 0);
    Vec3 vel = Vec3::sZero();
    Vec3 g(0, -9.81f, 0);
    float dt = 1.0f / 60.0f;
    float restitution_floor = 0.9f;
    float restitution_sphere = 0.8f;
    float restitution_combined = max(restitution_floor, restitution_sphere);
    for (int i = 0; i < 10000; ++i)
    {
        bool positive_before = vel.GetY() >= 0.0f;

        // Apply gravity
        // Equivalent to PhysicsSystem::JobApplyGravity
        vel += g * dt;

        // If we're penetrating the ground, we have to reverse the velocity (corresponds to restitution 1).
        // Equivalent to PhysicsSystem::JobSolveVelocityConstraints
        float penetration = 2.0f - pos.GetY();
        if (penetration > 0 && vel.GetY() < 0.0f)
            vel = -restitution_combined * (vel - g * dt);

        // If velocity changes from positive to negative, we trace the height
        bool positive_after = vel.GetY() >= 0.0f;
        if (positive_before && !positive_after)
            Trace("Iteration %5d - Bounce height: %f", i, pos.GetY());

        // Update position
        // Equivalent to PhysicsSystem::JobIntegrateVelocity
        pos += vel * dt;
    }
jrouwe commented 2 days ago

You're right, I didn't simulate it for long enough. It is indeed the same thing. At some point the velocity that we gain in one time step due to gravity equals the velocity we lose during the bounce and we end up in a steady state. Your solution is similar to what I had in mind. And yes, the Jolt code is a bit more involved than this simple example, so it needs a bit more thought.