pafuhana1213 / KawaiiPhysics

KawaiiPhysics : Simple fake Physics for UnrealEngine4 & 5
MIT License
1.92k stars 287 forks source link

Damping and world damping is framerate dependent #81

Open ElusiveFluffy opened 1 year ago

ElusiveFluffy commented 1 year ago

The world damping values get run every frame, so the higher the framerate the more dampening that happens (most noticeable with long bone chains). At lower FPS it will be more bouncy, but as you increase the max FPS it becomes less bouncy and just goes straight, making the physics not look as good. Heres a video example showing what I mean, can see it the most in the tail

https://user-images.githubusercontent.com/102465188/210066329-c313f65a-ca11-4574-a579-743ee70355ec.mp4

I have found a possible fix but isn't the best implementation to target how it looks at a specific FPS. This is what I did to make it consistent across all framerate unless its lower than target fps, the edit is in "AnimNode_KawaiiPhysics.cpp" (and "AnimNode_KawaiiPhysics.h" to add cumulativeDeltaTime to each bone, had issues if not all bones had their own value for some reason), for some reason though this code randomly stops working for some physics nodes, maybe because I don't know much about C++. (I tried using delta time but couldn't figure out how to get it to work well) image

blueblur22 commented 1 year ago

This problem is also apparent if you use Global Time Dealation. You get more bouncing when using GTD for a slomo effect since this plugin calculates on each frame instead of based on the Game Time delta between frames. I'm not comfortable enough in C++ yet to try to fix this right now. :(

ElusiveFluffy commented 1 year ago

Just found that the damping also has framerate dependency even though the velocity gets multiplied by delta time. I found a better way of removing most of the framerate dependency (still there a bit, and may not work as well above fps higher than 200. There probably is a better way to remove the framerate dependency but I can't think of it). It affects the damping, I just needed to increase the value by 0.05 in all nodes. image I just edited the file in Source/KawaiiPhysics/Private/AnimNode_KawaiiPhysics.cpp in the SimulateModifyBones function (Not the full function below, the function is around line 504) (For anyone with a blueprint only project, using my fix temporarily, to recompile it just delete the binaries and intermediate folders in the plugin folder then open your project and you'll be asked if you want to recompile the plugin (think also need visual studio with C++))

    const USkeletalMeshComponent* SkelComp = Output.AnimInstanceProxy->GetSkelMeshComponent();
    const UWorld* World = SkelComp ? SkelComp->GetWorld() : nullptr;
    FSceneInterface* Scene = World && World->Scene ? World->Scene : nullptr;
    const float Exponent = TargetFramerate * DeltaTime;

    //Scale dampening based on framerate
    FVector2D InputRange = FVector2D(0.1f, 1.3f);
    FVector2D OutputRange = FVector2D(0.5f, 1.15f);

    //transform gravity to component space
    FVector GravityCS = ComponentTransform.InverseTransformVector(Gravity);

    for (int i = 0; i < ModifyBones.Num(); ++i)
    {
        SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_SimulatemodifyBone);

        auto& Bone = ModifyBones[i];
        if (Bone.BoneRef.BoneIndex < 0 && !Bone.bDummy)
        {
            continue;
        }

        if (Bone.ParentIndex < 0)
        {
            Bone.PrevLocation = Bone.Location;
            Bone.Location = Bone.PoseLocation;
            continue;
        }

        auto& ParentBone = ModifyBones[Bone.ParentIndex];
        FVector BonePoseLocation = Bone.PoseLocation;
        FVector ParentBonePoseLocation = ParentBone.PoseLocation;

        float scaleDamping = DeltaTime / (1.0f / TargetFramerate);
        // Move using Velocity( = movement amount in pre frame ) and Damping
        {
            FVector Velocity = (Bone.Location - Bone.PrevLocation) / DeltaTimeOld;
            Bone.PrevLocation = Bone.Location;
                Velocity *= (1.0f - (Bone.PhysicsSettings.Damping * FMath::GetMappedRangeValueClamped(InputRange, OutputRange, scaleDamping)));
            // wind
            if (bEnableWind && Scene)
            {
                SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_Wind);

                Scene->GetWindParameters_GameThread(ComponentTransform.TransformPosition(Bone.PoseLocation), WindDirection, WindSpeed, WindMinGust, WindMaxGust);
                WindDirection = ComponentTransform.Inverse().TransformVector(WindDirection);
                FVector WindVelocity = WindDirection * WindSpeed * WindScale;

                // TODO:Migrate if there are more good method (Currently copying AnimDynamics implementation)
                WindVelocity *= FMath::FRandRange(0.0f, 2.0f);

                Velocity += WindVelocity * TargetFramerate;
            }
            Bone.Location += Velocity * DeltaTime;
        }

        // Follow Translation
        Bone.Location += SkelCompMoveVector * ((1.0f - Bone.PhysicsSettings.WorldDampingLocation) * scaleDamping);

        // Follow Rotation
        Bone.Location += (SkelCompMoveRotation.RotateVector(Bone.PrevLocation) - Bone.PrevLocation)
            * ((1.0f - Bone.PhysicsSettings.WorldDampingRotation) * scaleDamping);

        // Gravity
        // TODO:Migrate if there are more good method (Currently copying AnimDynamics implementation)
        if (CVarEnableOldPhysicsMethodGrayity.GetValueOnAnyThread() == 0)
        {
            Bone.Location += 0.5 * GravityCS * DeltaTime * DeltaTime;
        }
        else
        {
            Bone.Location += GravityCS * DeltaTime;
        }
blueblur22 commented 1 year ago

@ElusiveFluffy Tested this out. It doesn't really fix the problem, but side by side I think your variant works a little better for my purposes. It seems at lower speed time dealation you get less jiggle, but this is preferable to having excessive movement in slow-mo.

Both variants still get a big jiggle if you get a sudden frame drop/stutter.

Anyway thanks for sharing!

Also you have a typo at the end: if (CVarEnableOldPhysicsMethodGra**y**ity.GetValueOnAnyThread() == 0)if (CVarEnableOldPhysicsMethodGra**v**ity.GetValueOnAnyThread() == 0)

ElusiveFluffy commented 1 year ago

Yeah, it doesn't fully fix the issues with it, but overall it works a bit better at different framerates, I'm still want to try some other things.

I don't think I've noticed the big jiggle from that, but I also haven't tested that much.

Oh, I didn't notice that or edit that line, that was how it originally was in the plugin

zjw1996 commented 5 months ago

Have you solved the problem of performance being affected by frame rate now?

ElusiveFluffy commented 5 months ago

@zjw1996

Have you solved the problem of performance being affected by frame rate now?

I never really figured out how to 100% solve it, may need a complete rewrite or something. I did find a weird way that mostly fixes it but at really low and high fps (like below 20fps, and above around 240 I think) it doesn't work as well. I've tried multiple different things using delta time and this is the best I got

This function is in the file Source/KawaiiPhysics/Private/AnimNode_KawaiiPhysics.cpp, the SimulateModifyBones function its around line 504, just replace the whole function with this and recompile

(This is different from the code above that I sent before)

void FAnimNode_KawaiiPhysics::SimulateModifyBones(FComponentSpacePoseContext& Output, const FBoneContainer& BoneContainer, FTransform& ComponentTransform)
{
    SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_SimulatemodifyBones);

    if (DeltaTime <= 0.0f)
    {
        return;
    }

    // for wind
    FVector WindDirection;
    float WindSpeed;
    float WindMinGust;
    float WindMaxGust;

    const USkeletalMeshComponent* SkelComp = Output.AnimInstanceProxy->GetSkelMeshComponent();
    const UWorld* World = SkelComp ? SkelComp->GetWorld() : nullptr;
    FSceneInterface* Scene = World && World->Scene ? World->Scene : nullptr;
    const float Exponent = TargetFramerate * DeltaTime;

    //Clamp Min Max
    float DampingMin = 0.15f;
    float DampingMax = 1.0f;

    //transform gravity to component space
    FVector GravityCS = ComponentTransform.InverseTransformVector(Gravity);

    for (int32 i = 0; i < ModifyBones.Num(); ++i)
    {
        SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_SimulatemodifyBone);

        auto& Bone = ModifyBones[i];
        if (Bone.BoneRef.BoneIndex < 0 && !Bone.bDummy)
        {
            continue;
        }

        if (Bone.ParentIndex < 0)
        {
            Bone.PrevLocation = Bone.Location;
            Bone.Location = Bone.PoseLocation;
            continue;
        }

        auto& ParentBone = ModifyBones[Bone.ParentIndex];
        FVector BonePoseLocation = Bone.PoseLocation;
        FVector ParentBonePoseLocation = ParentBone.PoseLocation;

        float scaleDamping = DeltaTime / (1.0f / TargetFramerate);

        float dampingMultiplyer = (0.1581f * FMath::Pow(scaleDamping, 3)) - (0.9996 * FMath::Pow(scaleDamping, 2)) + (1.6943f * scaleDamping) + 0.0958f;
        dampingMultiplyer = FMath::Clamp(dampingMultiplyer, DampingMin, DampingMax);
        // Move using Velocity( = movement amount in pre frame ) and Damping
        {
            FVector Velocity = (Bone.Location - Bone.PrevLocation) / DeltaTimeOld;
            Bone.PrevLocation = Bone.Location;
                Velocity *= (1.0f - (Bone.PhysicsSettings.Damping * dampingMultiplyer));

            // wind
            if (bEnableWind && Scene)
            {
                SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_Wind);

                Scene->GetWindParameters_GameThread(ComponentTransform.TransformPosition(Bone.PoseLocation), WindDirection, WindSpeed, WindMinGust, WindMaxGust);
                WindDirection = ComponentTransform.Inverse().TransformVector(WindDirection);
                FVector WindVelocity = WindDirection * WindSpeed * WindScale;

                // TODO:Migrate if there are more good method (Currently copying AnimDynamics implementation)
                WindVelocity *= FMath::FRandRange(0.0f, 2.0f);

                Velocity += WindVelocity * TargetFramerate;
            }
            Bone.Location += Velocity * DeltaTime;
        }

        // Follow Translation
        Bone.Location += SkelCompMoveVector * ((1.0f - Bone.PhysicsSettings.WorldDampingLocation) * scaleDamping);

        // Follow Rotation
        Bone.Location += (SkelCompMoveRotation.RotateVector(Bone.PrevLocation) - Bone.PrevLocation)
            * ((1.0f - Bone.PhysicsSettings.WorldDampingRotation) * scaleDamping);

        // Gravity
        // TODO:Migrate if there are more good method (Currently copying AnimDynamics implementation)
        if (CVarEnableOldPhysicsMethodGravity.GetValueOnAnyThread() == 0)
        {
            Bone.Location += 0.5 * GravityCS * DeltaTime * DeltaTime;
        }
        else
        {
            Bone.Location += GravityCS * DeltaTime;
        }

        // Pull to Pose Location
        FVector BaseLocation = ParentBone.Location + (BonePoseLocation - ParentBonePoseLocation);
        Bone.Location += (BaseLocation - Bone.Location) *
            (1.0f - FMath::Pow(1.0f - Bone.PhysicsSettings.Stiffness, Exponent));

        {
            SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_AdjustBone);

            // Adjust by each collisions
            AdjustBySphereCollision(Bone, SphericalLimits);
            AdjustBySphereCollision(Bone, SphericalLimitsData);
            AdjustByCapsuleCollision(Bone, CapsuleLimits);
            AdjustByCapsuleCollision(Bone, CapsuleLimitsData);
            AdjustByPlanerCollision(Bone, PlanarLimits);
            AdjustByPlanerCollision(Bone, PlanarLimitsData);
            if (bAllowWorldCollision)
            {
                SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_WorldCollision);
                AdjustByWorldCollision(Bone, SkelComp, BoneContainer);
            }
            // Adjust by angle limit
            AdjustByAngleLimit(Output, BoneContainer, ComponentTransform, Bone, ParentBone);

            // Adjust by Planar Constraint
            AdjustByPlanarConstraint(Bone, ParentBone);
        }

        // Restore Bone Length
        float BoneLength = (BonePoseLocation - ParentBonePoseLocation).Size();
        Bone.Location = (Bone.Location - ParentBone.Location).GetSafeNormal() * BoneLength + ParentBone.Location;
    }
    DeltaTimeOld = DeltaTime;
}