4
\$\begingroup\$

I am having trouble with a frame independent buoyancy force using substepping in Unreal Engine 4.

The original problem (which I am attempting to solve using substepping) is that when you apply a buoyancy force (which is proportional to the depth of the object), the force needs to change as the object rises out of the water. Consider the following, where an object is under water and we either have 2FPS or 42FPS (the curve representing the true force of the object as is rises)

Difference in framerate on velocity

Clearly the lower the framerate, the larger force is applied for too long resulting in the object accelerating too much.

Substepping alone does not fix this as the force being applied isn't recalculated every substep.

If I hook the force calculation into the substep then compare running at 60FPS and 10FPS I can see that at 10FPS I get 6 substeps every frame so I believe it should be the same.

However, I still see a discrepancy. That is, at 10 FPS the object leaves the water at almost double the velocity.

Some data from the 60 vs. 10 FPS runs

                       |  60FPS      |   10FPS
___________________________________________________
Velocity Leaving Water |  815 cm/s   |   1576cm/s
Time in water          |  0.89s      |   0.43s
Total displacement     |  122        |   41.84
Max Displacement       |  209cm      |   150cm

Here the time in water is measured by summing the dt's of each substep and the total displacement is the displacement at each substep multiplied by the dt.

I believe the problem is that the elapsed physics time is nearly double for the higher frame rate, though why this would mean the lower frame rate one comes out faster, I don't understand.

The code to apply the force is:

void UBuoyantPointComponent::CalculateBuoyancyPhysics(float DeltaTime, FBodyInstance* BI)
{
    // Data passed in from the main thread, now just reduced to the components offset which doesn't change
    PhysicsLock.lock();
    FPhysSubstepData LocalSubstepData = FPhysSubstepData(SubstepData);
    PhysicsLock.unlock();

    FTransform BodyTransform = BI->GetUnrealWorldTransform();
    const FVector& CalcCompLocation = (LocalSubstepData.ComponentOffset * BodyTransform).GetLocation();

    const float Altitude = CalcCompLocation.Z;
    const float Difference = 0.0f - Altitude;

    if (Difference > 0) // we are under the sea since the wave height is greater than the height of this point
    {
        float SubmergedVolume = FMath::Min(Difference, 100.0f) * 100.0f * 100.0f;

        FVector ComputedForce = ((FVector::UpVector * SubmergedVolume * BuoyancyStrength));
        BI->AddForceAtPosition(ComputedForce, CalcCompLocation);
    }
}

I can't even work out what I can try to gain more insight into this so any help would be greatly appreciated.

Update

Printing out data every substep reveals some interesting things that I didn't spot before. (Full log, the --- indicates a game tick, each line is a substep tick). In summary though:

The first set of substeps don't actually move the object at all

This means after the first frame the 10FPS object is moving considerably faster than the 60FPS one since it has had a longer period of not moving and just accumulating force.

dv calculation seems to be out by a half

My dv which I think should be how much the velocity changes from to frame seems to be out by a factor of a half. It is being computed in the following way:

`DeltaTime * ((SubmergedVolume * BuoyancyStrength) / BI->GetBodyMass())`

Where (SubmergedVolume * BuoyancyStrength) is the only force applied to the object.

This discrepancy is magnified by the lower framerate and the objects speed increases twice a fast at lower depths (i.e. larger force) for longer.

\$\endgroup\$
2
\$\begingroup\$

I don't think there is much you can do to prevent this, because essentially your graph shows you the problem. The physics system has to apply the force according to the tick (or substep) delta, meaning that at some point it is just not possible to update the position of an object independent of the framerate.

That is also the reason why you can specify the max delta time for the physics tick and substep tick in your project settings (Settings -> Physics -> Framerate). Maybe try to lower these values to 0,01666 and 0,00833 (or even lower).

Another possibility would be to not use forces and just update the position directly depending on the elapsed time. Just whip up a sine-like function that uses the absolute elapsed time as input and gives you the object position in water as output. Then use this function to update your object position each tick. I know - it is not as nice, but it would be completely independent of the framerate.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.