Files
UnrealEngineUWP/Engine/Source/Runtime/AnimGraphRuntime/Private/AnimNodes/AnimNode_ModifyCurve.cpp
Thomas Sarkanen 502797ca50 Animation Curve Runtime & Editor Improvements
Runtime notes:
- Removes 'smart name' usage across the animation systems.
- Changed curve blending from a uniform array (sized per skeleton) to a sparse array of sorted named values. Blends and other combiners are performed using a dual iteration 'tape merge'.
- Skeleton curves are no longer guaranteed to cover all curve names that can be found at runtime.

Editor notes:
- Curve metadata (flags, bone links etc.) is still present on the skeleton, but can also now exist on a skeletal mesh
- Curve metadata (for morph targets) is still populated on import
- Curves can now be used arbitrarily at runtime

New features:
- New Find/Replace dialog that allows for batch-replacing curves and notifies across all of a project's assets
- New curve debugger tab in various Persona editors that allows for viewing curve values live. This also now allows viewing curves for specific pose watches.
- Pose watches now output curve tracks to the Rewind Debugger

#rb Jurre.deBaare,Nicholas.Frechette,Sara.Schvartzman,Helge.Mathee,Kiaran.Ritchie,Jaime.Cifuentes,Martin.Wilson,Keith.Yerex,Andrean.Franc (and more!)
#jira UE-167776
#jira UE-173716
#jira UE-110407
#preflight 63fc98c81206d91a2bc3ab90
#preflight 63f3ad4f81646f1f24c240c2

[CL 24421496 by Thomas Sarkanen in ue5-main branch]
2023-02-27 07:20:58 -05:00

152 lines
4.3 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimNodes/AnimNode_ModifyCurve.h"
#include "AnimationRuntime.h"
#include "Animation/AnimInstanceProxy.h"
#include "Animation/AnimCurveTypes.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNode_ModifyCurve)
FAnimNode_ModifyCurve::FAnimNode_ModifyCurve()
{
ApplyMode = EModifyCurveApplyMode::Blend;
Alpha = 1.f;
}
void FAnimNode_ModifyCurve::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Initialize_AnyThread)
Super::Initialize_AnyThread(Context);
SourcePose.Initialize(Context);
// Init our last values array to be the right size
if (ApplyMode == EModifyCurveApplyMode::WeightedMovingAverage)
{
LastCurve.Empty();
}
}
void FAnimNode_ModifyCurve::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(CacheBones_AnyThread)
Super::CacheBones_AnyThread(Context);
SourcePose.CacheBones(Context);
}
void FAnimNode_ModifyCurve::Evaluate_AnyThread(FPoseContext& Output)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Evaluate_AnyThread)
FPoseContext SourceData(Output);
SourcePose.Evaluate(SourceData);
Output = SourceData;
// Build internal curves from array & map
FBlendedCurve Curve;
Curve.Reserve(CurveNames.Num());
for(int32 CurveIndex = 0; CurveIndex < CurveNames.Num(); ++CurveIndex)
{
Curve.Add(CurveNames[CurveIndex], CurveValues[CurveIndex]);
}
if(CurveMap.Num() > 0)
{
FBlendedCurve MapCurve;
MapCurve.Reserve(CurveNames.Num());
for(const TPair<FName, float>& NameValuePair : CurveMap)
{
MapCurve.Add(NameValuePair.Key, NameValuePair.Value);
}
// Combine pin & map curves in case they overlap
Curve.Combine(MapCurve);
}
if (ApplyMode == EModifyCurveApplyMode::WeightedMovingAverage)
{
UE::Anim::FNamedValueArrayUtils::Union(Output.Curve, Curve, LastCurve,
[this](UE::Anim::FCurveElement& InOutResult, const UE::Anim::FCurveElement& InCurveElement, const UE::Anim::FCurveElement& InLastCurveElement, UE::Anim::ENamedValueUnionFlags InFlags)
{
// Only apply curves that we are overriding
if(EnumHasAnyFlags(InFlags, UE::Anim::ENamedValueUnionFlags::ValidArg1))
{
InOutResult.Value = ProcessCurveWMAOperation(InCurveElement.Value, InLastCurveElement.Value);
}
});
}
else
{
UE::Anim::FNamedValueArrayUtils::Union(Output.Curve, Curve,
[this](UE::Anim::FCurveElement& InOutResult, const UE::Anim::FCurveElement& InCurveElement, UE::Anim::ENamedValueUnionFlags InFlags)
{
// Only apply curves that we are overriding
if(EnumHasAnyFlags(InFlags, UE::Anim::ENamedValueUnionFlags::ValidArg1))
{
InOutResult.Value = ProcessCurveOperation(InOutResult.Value, InCurveElement.Value);
}
});
}
LastCurve.CopyFrom(Curve);
}
float FAnimNode_ModifyCurve::ProcessCurveOperation(float CurrentValue, float NewValue) const
{
float UseNewValue = CurrentValue;
// Use ApplyMode enum to decide how to apply
if (ApplyMode == EModifyCurveApplyMode::Add)
{
UseNewValue = CurrentValue + NewValue;
}
else if (ApplyMode == EModifyCurveApplyMode::Scale)
{
UseNewValue = CurrentValue * NewValue;
}
else if (ApplyMode == EModifyCurveApplyMode::RemapCurve)
{
const float RemapScale = 1.f / FMath::Max(1.f - NewValue, 0.01f);
UseNewValue = FMath::Min(FMath::Max(CurrentValue - NewValue, 0.f) * RemapScale, 1.f);
}
else if (ApplyMode == EModifyCurveApplyMode::Blend)
{
UseNewValue = NewValue;
}
const float UseAlpha = FMath::Clamp(Alpha, 0.f, 1.f);
return FMath::Lerp(CurrentValue, UseNewValue, UseAlpha);
}
float FAnimNode_ModifyCurve::ProcessCurveWMAOperation(float CurrentValue, float LastValue) const
{
return FMath::WeightedMovingAverage(CurrentValue, LastValue, Alpha);
}
void FAnimNode_ModifyCurve::Update_AnyThread(const FAnimationUpdateContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Update_AnyThread)
// Run update on input pose nodes
SourcePose.Update(Context);
// Evaluate any BP logic plugged into this node
GetEvaluateGraphExposedInputs().Execute(Context);
}
#if WITH_EDITOR
void FAnimNode_ModifyCurve::AddCurve(const FName& InName, float InValue)
{
CurveValues.Add(InValue);
CurveNames.Add(InName);
}
void FAnimNode_ModifyCurve::RemoveCurve(int32 PoseIndex)
{
CurveValues.RemoveAt(PoseIndex);
CurveNames.RemoveAt(PoseIndex);
}
#endif // WITH_EDITOR