From f4aa7efde7265ab43d2d1484a6deaeb83ef6e8f7 Mon Sep 17 00:00:00 2001 From: LongLy Date: Mon, 25 Nov 2024 16:08:03 -0700 Subject: [PATCH] Added helper functions to improve searching for key frames based on distance. --- .../Libraries/OLSLocomotionBPLibrary.cpp | 167 +++++++++++++++++- .../Public/Libraries/OLSLocomotionBPLibrary.h | 13 +- 2 files changed, 173 insertions(+), 7 deletions(-) diff --git a/Source/OLSAnimation/Private/Libraries/OLSLocomotionBPLibrary.cpp b/Source/OLSAnimation/Private/Libraries/OLSLocomotionBPLibrary.cpp index 39ef185..020015c 100644 --- a/Source/OLSAnimation/Private/Libraries/OLSLocomotionBPLibrary.cpp +++ b/Source/OLSAnimation/Private/Libraries/OLSLocomotionBPLibrary.cpp @@ -72,9 +72,163 @@ float UOLSLocomotionBPLibrary::GetTimeAtDistance(const UAnimSequenceBase* animSe return 0.f; } +float UOLSLocomotionBPLibrary::GetDistanceRange(const UAnimSequenceBase* animSequence, const FName& curveName) +{ + FAnimCurveBufferAccess bufferCurveAccess(animSequence, curveName); + if (bufferCurveAccess.IsValid()) + { + const int32 numSamples = bufferCurveAccess.GetNumSamples(); + if (numSamples >= 2) + { + return (bufferCurveAccess.GetValue(numSamples - 1) - bufferCurveAccess.GetValue(0)); + } + } + return 0.f; +} + +float UOLSLocomotionBPLibrary::GetTimeAfterDistanceTraveled(const UAnimSequenceBase* animSequence, + float currentTime, + float distanceTraveled, FName curveName, + const bool shouldAllowLooping) +{ + float newTime = currentTime; + if (!animSequence) + { + // Avoid infinite loops if the animation doesn't cover any distance. + if (!FMath::IsNearlyZero(GetDistanceRange(animSequence, curveName))) + { + float accumulatedDistance = 0.f; + + const float sequenceLength = animSequence->GetPlayLength(); + constexpr float stepTime = 1.f / 30.f; + + // Distance Matching expects the distance curve on the animation to increase monotonically. If the curve fails to increase in value + // after a certain number of iterations, we abandon the algorithm to avoid an infinite loop. + + // Traverse the distance curve, accumulating animated distance until the desired distance is reached. + while ((accumulatedDistance < distanceTraveled) && (shouldAllowLooping || (newTime + stepTime < sequenceLength))) + { + const float currentDistance = GetDistanceCurveValueAtTime(animSequence, newTime, curveName); + const float distanceAfterStep = GetDistanceCurveValueAtTime(animSequence, newTime + stepTime, curveName); + const float animationDistanceThisStep = distanceAfterStep - currentDistance; + + if (!FMath::IsNearlyZero(animationDistanceThisStep)) + { + // Keep advancing if the desired distance hasn't been reached. + if (accumulatedDistance + animationDistanceThisStep < distanceTraveled) + { + FAnimationRuntime::AdvanceTime(shouldAllowLooping, stepTime, newTime, sequenceLength); + accumulatedDistance += animationDistanceThisStep; + } + // Once the desired distance is passed, find the approximate time between samples where the distance will be reached. + else + { + const float DistanceAlpha = (distanceTraveled - accumulatedDistance) / animationDistanceThisStep; + FAnimationRuntime::AdvanceTime(shouldAllowLooping, DistanceAlpha * stepTime, newTime, sequenceLength); + break; + } + + } + else + { + FAnimationRuntime::AdvanceTime(shouldAllowLooping, stepTime, newTime, sequenceLength); + break; + + } + } + } + else + { + UE_LOG(LogOLSLocomotionLibrary, Warning, + TEXT( + "Anim sequence (%s) is missing a distance curve or doesn't cover enough distance for GetTimeAfterDistanceTraveled." + ), *GetNameSafe(animSequence)); + } + } + else + { + UE_LOG(LogOLSLocomotionLibrary, Warning, TEXT("Invalid AnimSequence passed to GetTimeAfterDistanceTraveled")); + } + + return newTime; +} + +FSequenceEvaluatorReference UOLSLocomotionBPLibrary::AdvanceTimeByDistanceMatching(float& outDesiredPlayRate, + const FAnimUpdateContext& updateContext, + const FSequenceEvaluatorReference& sequenceEvaluator, + const float distanceTraveled, + const FName curveName, + const FVector2D playRateClamp /* = FVector2D(0.75f, 1.25f)*/) +{ + sequenceEvaluator.CallAnimNodeFunction( + TEXT("AdvanceTimeByDistanceMatching"), + [&outDesiredPlayRate, updateContext, distanceTraveled, curveName, playRateClamp]( + FAnimNode_SequenceEvaluator& inSequenceEvaluator) + { + if (const FAnimationUpdateContext* animationUpdateContext = updateContext.GetContext()) + { + const float deltaTime = animationUpdateContext->GetDeltaTime(); + + if (deltaTime > 0 && distanceTraveled > 0) + { + if (const UAnimSequenceBase* animSequence = Cast(inSequenceEvaluator.GetSequence())) + { + const float currentTime = inSequenceEvaluator.GetExplicitTime(); + const float currentAssetLength = inSequenceEvaluator.GetCurrentAssetLength(); + const bool shouldAllowLooping = inSequenceEvaluator.IsLooping(); + float timeAfterDistanceTraveled = GetTimeAfterDistanceTraveled( + animSequence, + currentTime, + distanceTraveled, + curveName, + shouldAllowLooping); + + // Calculate the effective playrate that would result from advancing the animation by the distance traveled. + // // Account for the animation looping. + if (timeAfterDistanceTraveled < currentTime) + { + timeAfterDistanceTraveled += currentAssetLength; + } + float effectivePlayRate = (timeAfterDistanceTraveled - currentTime) / deltaTime; + + outDesiredPlayRate = effectivePlayRate; + // Clamp the effective play rate. + if (playRateClamp.X >= 0.f && playRateClamp.X < playRateClamp.Y) + { + effectivePlayRate = FMath::Clamp(effectivePlayRate, playRateClamp.X, playRateClamp.Y); + } + + // Advance animation time by the effective play rate. + float newTime = currentTime; + FAnimationRuntime::AdvanceTime(false, effectivePlayRate * deltaTime, newTime, + currentAssetLength); + + if (!inSequenceEvaluator.SetExplicitTime(newTime)) + { + UE_LOG(LogOLSLocomotionLibrary, Warning, + TEXT( + "Could not set explicit time on sequence evaluator, value is not dynamic. Set it as Always Dynamic." + )); + } + } + else + { + UE_LOG(LogOLSLocomotionLibrary, Warning, TEXT("Sequence evaluator does not have an anim sequence to play.")); + } + } + } + else + { + UE_LOG(LogOLSLocomotionLibrary, Warning, + TEXT("AdvanceTimeByDistanceMatching called with invalid context")); + } + }); + + return sequenceEvaluator; +} + FSequenceEvaluatorReference UOLSLocomotionBPLibrary::DistanceMatchToTarget(const FAnimUpdateContext& updateContext, - const FSequenceEvaluatorReference& - sequenceEvaluator, + const FSequenceEvaluatorReference& sequenceEvaluator, float distanceToTarget, bool shouldDistanceMatchStop, float stopDistanceThreshHold, @@ -85,16 +239,16 @@ FSequenceEvaluatorReference UOLSLocomotionBPLibrary::DistanceMatchToTarget(const TEXT("DistanceMatchToTarget"), [updateContext,sequenceEvaluator,distanceToTarget, shouldDistanceMatchStop,stopDistanceThreshHold,animEndTime, curveName]( - FAnimNode_SequenceEvaluator& InSequenceEvaluator) + FAnimNode_SequenceEvaluator& inSequenceEvaluator) { - if (const UAnimSequenceBase* animSequence = InSequenceEvaluator.GetSequence()) + if (const UAnimSequenceBase* animSequence = inSequenceEvaluator.GetSequence()) { if (GetDistanceCurveValueAtTime(animSequence, USequenceEvaluatorLibrary::GetAccumulatedTime(sequenceEvaluator), curveName) > stopDistanceThreshHold && !shouldDistanceMatchStop) { const float newTime = GetTimeAtDistance(animSequence, -distanceToTarget, curveName); - if (!InSequenceEvaluator.SetExplicitTime(newTime)) + if (!inSequenceEvaluator.SetExplicitTime(newTime)) { UE_LOG(LogOLSLocomotionLibrary, Warning, TEXT( @@ -219,7 +373,8 @@ FVector UOLSLocomotionBPLibrary::PredictGroundMovementStopLocation(const FVector } FVector UOLSLocomotionBPLibrary::PredictGroundMovementPivotLocation(const FVector& acceleration, - const FVector& velocity, float groundFriction) + const FVector& velocity, + float groundFriction) { FVector predictedPivotLocation = FVector::ZeroVector; diff --git a/Source/OLSAnimation/Public/Libraries/OLSLocomotionBPLibrary.h b/Source/OLSAnimation/Public/Libraries/OLSLocomotionBPLibrary.h index 27ba564..75b45f8 100644 --- a/Source/OLSAnimation/Public/Libraries/OLSLocomotionBPLibrary.h +++ b/Source/OLSAnimation/Public/Libraries/OLSLocomotionBPLibrary.h @@ -20,9 +20,20 @@ private: static float GetDistanceCurveValueAtTime(const UAnimSequenceBase* animSequence, const float time, const FName& curveName); static float GetTimeAtDistance(const UAnimSequenceBase* animSequence, const float& distance, FName curveName); - + static float GetDistanceRange(const UAnimSequenceBase* animSequence, const FName& curveName); + static float GetTimeAfterDistanceTraveled(const UAnimSequenceBase* animSequence, float currentTime, float distanceTraveled, FName curveName, const bool shouldAllowLooping); + public: + UFUNCTION(BlueprintCallable, Category = "OLS|Function Library", meta=(BlueprintThreadSafe)) + static FSequenceEvaluatorReference AdvanceTimeByDistanceMatching(float& outDesiredPlayRate, + const FAnimUpdateContext& updateContext, + const FSequenceEvaluatorReference& sequenceEvaluator, + const float distanceTraveled, + const FName curveName, + const FVector2D playRateClamp = FVector2D(0.75f, 1.25f)); + + /** * Adjusts the playback time of an animation sequence to match a specified distance to a target. *