Added helper functions to improve searching for key frames based on distance.

This commit is contained in:
LongLy 2024-11-25 16:08:03 -07:00
parent 33cfbb9b57
commit f4aa7efde7
2 changed files with 173 additions and 7 deletions

View File

@ -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<FAnimNode_SequenceEvaluator>(
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<UAnimSequence>(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;

View File

@ -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.
*