2021-09-17 09:44:41 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "AnimDistanceMatchingLibrary.h"
2021-11-18 14:37:34 -05:00
# include "Animation/AnimNode_SequencePlayer.h"
2021-09-17 09:44:41 -04:00
# include "Animation/AnimSequence.h"
# include "AnimNodes/AnimNode_SequenceEvaluator.h"
2021-09-17 19:15:44 -04:00
# include "Curves/CurveFloat.h"
2022-01-25 12:07:55 -05:00
# include "Animation/AnimCurveCompressionCodec_UniformIndexable.h"
2021-09-17 09:44:41 -04:00
DEFINE_LOG_CATEGORY_STATIC ( LogAnimDistanceMatchingLibrary , Verbose , All ) ;
2022-01-25 12:07:55 -05:00
namespace UE : : Anim : : DistanceMatchingUtility
2021-09-17 09:44:41 -04:00
{
2022-01-25 12:07:55 -05:00
static float GetDistanceRange ( const UAnimSequenceBase * InAnimSequence , USkeleton : : AnimCurveUID CurveUID )
2021-09-17 09:44:41 -04:00
{
2022-01-25 12:07:55 -05:00
FAnimCurveBufferAccess BufferCurveAccess ( InAnimSequence , CurveUID ) ;
if ( BufferCurveAccess . IsValid ( ) )
2021-09-17 09:44:41 -04:00
{
2022-01-25 12:07:55 -05:00
const int32 NumSamples = BufferCurveAccess . GetNumSamples ( ) ;
if ( NumSamples > = 2 )
2021-09-17 09:44:41 -04:00
{
2022-01-25 12:07:55 -05:00
return ( BufferCurveAccess . GetValue ( NumSamples - 1 ) - BufferCurveAccess . GetValue ( 0 ) ) ;
}
}
return 0.f ;
}
2021-09-17 09:44:41 -04:00
2022-01-25 12:07:55 -05:00
static USkeleton : : AnimCurveUID GetCurveUID ( const UAnimSequenceBase * InAnimSequence , FName CurveName )
{
if ( const USkeleton * Skeleton = InAnimSequence - > GetSkeleton ( ) )
{
const FSmartNameMapping * CurveNameMapping = Skeleton - > GetSmartNameContainer ( USkeleton : : AnimCurveMappingName ) ;
if ( CurveNameMapping )
{
return CurveNameMapping - > FindUID ( CurveName ) ;
}
}
return SmartName : : MaxUID ;
}
static float GetAnimPositionFromDistance ( const UAnimSequenceBase * InAnimSequence , const float & InDistance , USkeleton : : AnimCurveUID CurveUID )
{
FAnimCurveBufferAccess BufferCurveAccess ( InAnimSequence , CurveUID ) ;
if ( BufferCurveAccess . IsValid ( ) )
{
const int32 NumKeys = BufferCurveAccess . GetNumSamples ( ) ;
if ( NumKeys < 2 )
{
return 0.f ;
}
// Some assumptions:
// - keys have unique values, so for a given value, it maps to a single position on the timeline of the animation.
// - key values are sorted in increasing order.
int32 First = 1 ;
int32 Last = NumKeys - 1 ;
int32 Count = Last - First ;
while ( Count > 0 )
{
int32 Step = Count / 2 ;
int32 Middle = First + Step ;
if ( InDistance > BufferCurveAccess . GetValue ( Middle ) )
2021-09-17 09:44:41 -04:00
{
2022-01-25 12:07:55 -05:00
First = Middle + 1 ;
Count - = Step + 1 ;
2021-11-18 14:37:34 -05:00
}
else
{
2022-01-25 12:07:55 -05:00
Count = Step ;
}
}
const float KeyAValue = BufferCurveAccess . GetValue ( First - 1 ) ;
const float KeyBValue = BufferCurveAccess . GetValue ( First ) ;
const float Diff = KeyBValue - KeyAValue ;
const float Alpha = ! FMath : : IsNearlyZero ( Diff ) ? ( ( InDistance - KeyAValue ) / Diff ) : 0.f ;
const float KeyATime = BufferCurveAccess . GetTime ( First - 1 ) ;
const float KeyBTime = BufferCurveAccess . GetTime ( First ) ;
return FMath : : Lerp ( KeyATime , KeyBTime , Alpha ) ;
}
return 0.f ;
}
/**
* Advance from the current time to a new time in the animation that will result in the desired distance traveled by the authored root motion .
*/
static float GetTimeAfterDistanceTraveled ( const UAnimSequenceBase * AnimSequence , float CurrentTime , float DistanceTraveled , USkeleton : : AnimCurveUID CurveUID , const bool bAllowLooping )
{
float NewTime = CurrentTime ;
if ( AnimSequence ! = nullptr )
{
// Avoid infinite loops if the animation doesn't cover any distance.
if ( ! FMath : : IsNearlyZero ( UE : : Anim : : DistanceMatchingUtility : : GetDistanceRange ( AnimSequence , CurveUID ) ) )
{
float AccumulatedDistance = 0.f ;
const float SequenceLength = AnimSequence - > GetPlayLength ( ) ;
const 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.
const int32 StuckLoopThreshold = 5 ;
int32 StuckLoopCounter = 0 ;
// Traverse the distance curve, accumulating animated distance until the desired distance is reached.
while ( ( AccumulatedDistance < DistanceTraveled ) & & ( bAllowLooping | | ( NewTime + StepTime < SequenceLength ) ) )
{
const float CurrentDistance = AnimSequence - > EvaluateCurveData ( CurveUID , NewTime ) ;
const float DistanceAfterStep = AnimSequence - > EvaluateCurveData ( CurveUID , NewTime + StepTime ) ;
const float AnimationDistanceThisStep = DistanceAfterStep - CurrentDistance ;
if ( ! FMath : : IsNearlyZero ( AnimationDistanceThisStep ) )
2021-11-18 14:37:34 -05:00
{
2022-01-25 12:07:55 -05:00
// Keep advancing if the desired distance hasn't been reached.
if ( AccumulatedDistance + AnimationDistanceThisStep < DistanceTraveled )
{
FAnimationRuntime : : AdvanceTime ( bAllowLooping , 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 ( bAllowLooping , DistanceAlpha * StepTime , NewTime , SequenceLength ) ;
AccumulatedDistance = DistanceTraveled ;
break ;
}
StuckLoopCounter = 0 ;
}
else
{
+ + StuckLoopCounter ;
if ( StuckLoopCounter > = StuckLoopThreshold )
{
UE_LOG ( LogAnimDistanceMatchingLibrary , Warning , TEXT ( " Failed to advance any distance after %d loops on anim sequence (%s). Aborting. " ) , StuckLoopThreshold , * GetNameSafe ( AnimSequence ) ) ;
break ;
}
2021-11-18 14:37:34 -05:00
}
2021-09-17 09:44:41 -04:00
}
}
2022-01-25 12:07:55 -05:00
else
{
UE_LOG ( LogAnimDistanceMatchingLibrary , Warning , TEXT ( " Anim sequence (%s) is missing a distance curve or doesn't cover enough distance for GetTimeAfterDistanceTraveled. " ) , * GetNameSafe ( AnimSequence ) ) ;
}
2021-09-17 09:44:41 -04:00
}
else
{
2022-01-25 12:07:55 -05:00
UE_LOG ( LogAnimDistanceMatchingLibrary , Warning , TEXT ( " Invalid AnimSequence passed to GetTimeAfterDistanceTraveled " ) ) ;
2021-09-17 09:44:41 -04:00
}
2022-01-25 12:07:55 -05:00
return NewTime ;
}
2021-09-17 09:44:41 -04:00
}
FSequenceEvaluatorReference UAnimDistanceMatchingLibrary : : AdvanceTimeByDistanceMatching ( const FAnimUpdateContext & UpdateContext , const FSequenceEvaluatorReference & SequenceEvaluator ,
2022-01-25 12:07:55 -05:00
float DistanceTraveled , FName DistanceCurveName , FVector2D PlayRateClamp )
2021-09-17 09:44:41 -04:00
{
SequenceEvaluator . CallAnimNodeFunction < FAnimNode_SequenceEvaluator > (
TEXT ( " AdvanceTimeByDistanceMatching " ) ,
2022-01-25 12:07:55 -05:00
[ & UpdateContext , DistanceTraveled , DistanceCurveName , PlayRateClamp ] ( FAnimNode_SequenceEvaluator & InSequenceEvaluator )
2021-09-17 09:44:41 -04:00
{
if ( const FAnimationUpdateContext * AnimationUpdateContext = UpdateContext . GetContext ( ) )
{
const float DeltaTime = AnimationUpdateContext - > GetDeltaTime ( ) ;
2021-11-18 14:37:34 -05:00
if ( DeltaTime > 0 & & DistanceTraveled > 0 )
2021-09-17 09:44:41 -04:00
{
2022-01-25 12:07:55 -05:00
if ( const UAnimSequenceBase * AnimSequence = Cast < UAnimSequence > ( InSequenceEvaluator . GetSequence ( ) ) )
2021-09-17 09:44:41 -04:00
{
2021-11-18 14:37:34 -05:00
const float CurrentTime = InSequenceEvaluator . GetExplicitTime ( ) ;
const float CurrentAssetLength = InSequenceEvaluator . GetCurrentAssetLength ( ) ;
const bool bAllowLooping = InSequenceEvaluator . GetShouldLoop ( ) ;
2021-09-17 09:44:41 -04:00
2022-01-25 12:07:55 -05:00
const USkeleton : : AnimCurveUID CurveUID = UE : : Anim : : DistanceMatchingUtility : : GetCurveUID ( AnimSequence , DistanceCurveName ) ;
float TimeAfterDistanceTraveled = UE : : Anim : : DistanceMatchingUtility : : GetTimeAfterDistanceTraveled ( AnimSequence , CurrentTime , DistanceTraveled , CurveUID , bAllowLooping ) ;
2021-09-17 09:44:41 -04:00
2021-11-18 14:37:34 -05:00
// 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 ;
2021-09-17 09:44:41 -04:00
2021-11-18 14:37:34 -05:00
// Clamp the effective play rate.
if ( PlayRateClamp . X > = 0.0f & & 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 ( bAllowLooping , EffectivePlayRate * DeltaTime , NewTime , CurrentAssetLength ) ;
if ( ! InSequenceEvaluator . SetExplicitTime ( NewTime ) )
{
UE_LOG ( LogAnimDistanceMatchingLibrary , Warning , TEXT ( " Could not set explicit time on sequence evaluator, value is not dynamic. Set it as Always Dynamic. " ) ) ;
}
}
else
{
UE_LOG ( LogAnimDistanceMatchingLibrary , Warning , TEXT ( " Sequence evaluator does not have an anim sequence to play. " ) ) ;
2021-09-17 09:44:41 -04:00
}
}
}
else
{
UE_LOG ( LogAnimDistanceMatchingLibrary , Warning , TEXT ( " AdvanceTimeByDistanceMatching called with invalid context " ) ) ;
}
} ) ;
return SequenceEvaluator ;
}
FSequenceEvaluatorReference UAnimDistanceMatchingLibrary : : DistanceMatchToTarget ( const FSequenceEvaluatorReference & SequenceEvaluator ,
2022-01-25 12:07:55 -05:00
float DistanceToTarget , FName DistanceCurveName )
2021-09-17 09:44:41 -04:00
{
SequenceEvaluator . CallAnimNodeFunction < FAnimNode_SequenceEvaluator > (
TEXT ( " DistanceMatchToTarget " ) ,
2022-01-25 12:07:55 -05:00
[ DistanceToTarget , DistanceCurveName ] ( FAnimNode_SequenceEvaluator & InSequenceEvaluator )
2021-09-17 09:44:41 -04:00
{
2022-01-25 12:07:55 -05:00
if ( const UAnimSequenceBase * AnimSequence = Cast < UAnimSequence > ( InSequenceEvaluator . GetSequence ( ) ) )
2021-09-17 09:44:41 -04:00
{
2022-01-25 12:07:55 -05:00
const USkeleton : : AnimCurveUID CurveUID = UE : : Anim : : DistanceMatchingUtility : : GetCurveUID ( AnimSequence , DistanceCurveName ) ;
if ( AnimSequence - > HasCurveData ( CurveUID ) )
2021-09-17 09:44:41 -04:00
{
// By convention, distance curves store the distance to a target as a negative value.
2022-01-25 12:07:55 -05:00
const float NewTime = UE : : Anim : : DistanceMatchingUtility : : GetAnimPositionFromDistance ( AnimSequence , - DistanceToTarget , CurveUID ) ;
2021-09-17 09:44:41 -04:00
if ( ! InSequenceEvaluator . SetExplicitTime ( NewTime ) )
{
UE_LOG ( LogAnimDistanceMatchingLibrary , Warning , TEXT ( " Could not set explicit time on sequence evaluator, value is not dynamic. Set it as Always Dynamic. " ) ) ;
}
}
else
{
2022-01-25 12:07:55 -05:00
UE_LOG ( LogAnimDistanceMatchingLibrary , Warning , TEXT ( " DistanceMatchToTarget called with invalid DistanceCurveName or animation (%s) is missing a distance curve. " ) , * GetNameSafe ( AnimSequence ) ) ;
2021-09-17 09:44:41 -04:00
}
}
else
{
UE_LOG ( LogAnimDistanceMatchingLibrary , Warning , TEXT ( " Sequence evaluator does not have an anim sequence to play. " ) ) ;
}
} ) ;
return SequenceEvaluator ;
2021-11-18 14:37:34 -05:00
}
FSequencePlayerReference UAnimDistanceMatchingLibrary : : SetPlayrateToMatchSpeed ( const FSequencePlayerReference & SequencePlayer , float SpeedToMatch , FVector2D PlayRateClamp )
{
SequencePlayer . CallAnimNodeFunction < FAnimNode_SequencePlayer > (
TEXT ( " SetPlayrateToMatchSpeed " ) ,
[ SpeedToMatch , PlayRateClamp ] ( FAnimNode_SequencePlayer & InSequencePlayer )
{
if ( const UAnimSequence * AnimSequence = Cast < UAnimSequence > ( InSequencePlayer . GetSequence ( ) ) )
{
const float AnimLength = AnimSequence - > GetPlayLength ( ) ;
if ( ! FMath : : IsNearlyZero ( AnimLength ) )
{
// Calculate the speed as: (distance traveled by the animation) / (length of the animation)
const FVector RootMotionTranslation = AnimSequence - > ExtractRootMotionFromRange ( 0.0f , AnimLength ) . GetTranslation ( ) ;
const float RootMotionDistance = RootMotionTranslation . Size2D ( ) ;
if ( ! FMath : : IsNearlyZero ( RootMotionDistance ) )
{
const float AnimationSpeed = RootMotionDistance / AnimLength ;
float DesiredPlayRate = SpeedToMatch / AnimationSpeed ;
if ( PlayRateClamp . X > = 0.0f & & PlayRateClamp . X < PlayRateClamp . Y )
{
DesiredPlayRate = FMath : : Clamp ( DesiredPlayRate , PlayRateClamp . X , PlayRateClamp . Y ) ;
}
if ( ! InSequencePlayer . SetPlayRate ( DesiredPlayRate ) )
{
UE_LOG ( LogAnimDistanceMatchingLibrary , Warning , TEXT ( " Could not set play rate on sequence player, value is not dynamic. Set it as Always Dynamic. " ) ) ;
}
}
else
{
UE_LOG ( LogAnimDistanceMatchingLibrary , Warning , TEXT ( " Unable to adjust playrate for animation with no root motion delta (%s). " ) , * GetNameSafe ( AnimSequence ) ) ;
}
}
else
{
UE_LOG ( LogAnimDistanceMatchingLibrary , Warning , TEXT ( " Unable to adjust playrate for zero length animation (%s). " ) , * GetNameSafe ( AnimSequence ) ) ;
}
}
else
{
UE_LOG ( LogAnimDistanceMatchingLibrary , Warning , TEXT ( " Sequence player does not have an anim sequence to play. " ) ) ;
}
} ) ;
return SequencePlayer ;
2021-09-17 09:44:41 -04:00
}