第一次提交

This commit is contained in:
不明不惑
2026-03-03 01:23:02 +08:00
commit 3e434877e8
1053 changed files with 102411 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Nodes/GMS_AnimNode_CurvesBlend.h"
#include "Animation/AnimTrace.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimNode_CurvesBlend)
void FGMS_AnimNode_CurvesBlend::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC()
Super::Initialize_AnyThread(Context);
SourcePose.Initialize(Context);
CurvesPose.Initialize(Context);
}
void FGMS_AnimNode_CurvesBlend::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC()
Super::CacheBones_AnyThread(Context);
SourcePose.CacheBones(Context);
CurvesPose.CacheBones(Context);
}
void FGMS_AnimNode_CurvesBlend::Update_AnyThread(const FAnimationUpdateContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC()
Super::Update_AnyThread(Context);
GetEvaluateGraphExposedInputs().Execute(Context);
SourcePose.Update(Context);
const auto CurrentBlendAmount{GetBlendAmount()};
if (FAnimWeight::IsRelevant(CurrentBlendAmount))
{
CurvesPose.Update(Context);
}
TRACE_ANIM_NODE_VALUE(Context, TEXT("Blend Amount"), CurrentBlendAmount);
TRACE_ANIM_NODE_VALUE(Context, TEXT("Blend Mode"), *StaticEnum<EGMS_CurvesBlendMode>()->GetNameStringByValue(static_cast<int64>(GetBlendMode())));
}
void FGMS_AnimNode_CurvesBlend::Evaluate_AnyThread(FPoseContext& Output)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC()
ANIM_MT_SCOPE_CYCLE_COUNTER_VERBOSE(CurvesBlend, !IsInGameThread());
Super::Evaluate_AnyThread(Output);
SourcePose.Evaluate(Output);
const auto CurrentBlendAmount{GetBlendAmount()};
if (!FAnimWeight::IsRelevant(CurrentBlendAmount))
{
return;
}
auto CurvesPoseContext{Output};
CurvesPose.Evaluate(CurvesPoseContext);
switch (GetBlendMode())
{
case EGMS_CurvesBlendMode::BlendByAmount:
Output.Curve.Accumulate(CurvesPoseContext.Curve, CurrentBlendAmount);
break;
case EGMS_CurvesBlendMode::Combine:
Output.Curve.Combine(CurvesPoseContext.Curve);
break;
case EGMS_CurvesBlendMode::CombinePreserved:
Output.Curve.CombinePreserved(CurvesPoseContext.Curve);
break;
case EGMS_CurvesBlendMode::UseMaxValue:
Output.Curve.UseMaxValue(CurvesPoseContext.Curve);
break;
case EGMS_CurvesBlendMode::UseMinValue:
Output.Curve.UseMinValue(CurvesPoseContext.Curve);
break;
case EGMS_CurvesBlendMode::Override:
Output.Curve.Override(CurvesPoseContext.Curve);
break;
}
}
void FGMS_AnimNode_CurvesBlend::GatherDebugData(FNodeDebugData& DebugData)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(GatherDebugData)
TStringBuilder<256> DebugItemBuilder{InPlace, DebugData.GetNodeName(this), TEXTVIEW(": Blend Amount: ")};
DebugItemBuilder.Appendf(TEXT("%.2f"), GetBlendAmount());
DebugData.AddDebugItem(FString{DebugItemBuilder});
SourcePose.GatherDebugData(DebugData.BranchFlow(1.0f));
CurvesPose.GatherDebugData(DebugData.BranchFlow(GetBlendAmount()));
}
float FGMS_AnimNode_CurvesBlend::GetBlendAmount() const
{
return GET_ANIM_NODE_DATA(float, BlendAmount);
}
EGMS_CurvesBlendMode FGMS_AnimNode_CurvesBlend::GetBlendMode() const
{
return GET_ANIM_NODE_DATA(EGMS_CurvesBlendMode, BlendMode);
}

View File

@@ -0,0 +1,51 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Nodes/GMS_AnimNode_GameplayTagsBlend.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimNode_GameplayTagsBlend)
int32 FGMS_AnimNode_GameplayTagsBlend::GetActiveChildIndex()
{
const auto& CurrentActiveTag{GetActiveTag()};
return CurrentActiveTag.IsValid()
? GetTags().Find(CurrentActiveTag) + 1
: 0;
}
const FGameplayTag& FGMS_AnimNode_GameplayTagsBlend::GetActiveTag() const
{
return GET_ANIM_NODE_DATA(FGameplayTag, ActiveTag);
}
const TArray<FGameplayTag>& FGMS_AnimNode_GameplayTagsBlend::GetTags() const
{
return GET_ANIM_NODE_DATA(TArray<FGameplayTag>, Tags);
}
#if WITH_EDITOR
void FGMS_AnimNode_GameplayTagsBlend::RefreshPoses()
{
const auto Difference{BlendPose.Num() - GetTags().Num() - 1};
if (Difference == 0)
{
return;
}
if (Difference > 0)
{
for (auto i{Difference}; i > 0; i--)
{
RemovePose(BlendPose.Num() - 1);
}
}
else
{
for (auto i{Difference}; i < 0; i++)
{
AddPose();
}
}
}
#endif

View File

@@ -0,0 +1,296 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Nodes/GMS_AnimNode_LayeredBoneBlend.h"
#include "AnimationRuntime.h"
#include "Animation/AnimInstanceProxy.h"
#include "Animation/AnimTrace.h"
#include "Animation/AnimCurveTypes.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimNode_LayeredBoneBlend)
/////////////////////////////////////////////////////
// FGMS_AnimNode_LayeredBoneBlend
void FGMS_AnimNode_LayeredBoneBlend::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Initialize_AnyThread)
FAnimNode_Base::Initialize_AnyThread(Context);
const int NumPoses = BlendPoses.Num();
checkSlow(BlendWeights.Num() == NumPoses);
// initialize children
BasePose.Initialize(Context);
if (NumPoses > 0)
{
for (int32 ChildIndex = 0; ChildIndex < NumPoses; ++ChildIndex)
{
BlendPoses[ChildIndex].Initialize(Context);
}
}
}
void FGMS_AnimNode_LayeredBoneBlend::RebuildPerBoneBlendWeights(const USkeleton* InSkeleton)
{
if (InSkeleton)
{
if (ExternalLayerSetup.BranchFilters.IsEmpty())
{
FAnimationRuntime::CreateMaskWeights(PerBoneBlendWeights, LayerSetup, InSkeleton);
}
else
{
for (const FBranchFilter& BranchFilter : ExternalLayerSetup.BranchFilters)
{
LayerSetup[0].BranchFilters.Add(BranchFilter);
}
FAnimationRuntime::CreateMaskWeights(PerBoneBlendWeights, LayerSetup, InSkeleton);
}
SkeletonGuid = InSkeleton->GetGuid();
VirtualBoneGuid = InSkeleton->GetVirtualBoneGuid();
}
}
bool FGMS_AnimNode_LayeredBoneBlend::ArePerBoneBlendWeightsValid(const USkeleton* InSkeleton) const
{
return (InSkeleton != nullptr && InSkeleton->GetGuid() == SkeletonGuid && InSkeleton->GetVirtualBoneGuid() == VirtualBoneGuid);
}
void FGMS_AnimNode_LayeredBoneBlend::UpdateCachedBoneData(const FBoneContainer& RequiredBones, const USkeleton* Skeleton)
{
if (LayerSetup.IsValidIndex(0) && LayerSetup[0].BranchFilters.IsEmpty())
{
RebuildPerBoneBlendWeights(Skeleton);
}
// if(RequiredBones.GetSerialNumber() == RequiredBonesSerialNumber)
// {
// return;
// }
if (!ArePerBoneBlendWeightsValid(Skeleton))
{
RebuildPerBoneBlendWeights(Skeleton);
}
// build desired bone weights
const TArray<FBoneIndexType>& RequiredBoneIndices = RequiredBones.GetBoneIndicesArray();
const int32 NumRequiredBones = RequiredBoneIndices.Num();
DesiredBoneBlendWeights.SetNumZeroed(NumRequiredBones);
for (int32 RequiredBoneIndex=0; RequiredBoneIndex<NumRequiredBones; RequiredBoneIndex++)
{
const int32 SkeletonBoneIndex = RequiredBones.GetSkeletonIndex(FCompactPoseBoneIndex(RequiredBoneIndex));
if (ensure(SkeletonBoneIndex != INDEX_NONE))
{
DesiredBoneBlendWeights[RequiredBoneIndex] = PerBoneBlendWeights[SkeletonBoneIndex];
}
}
CurrentBoneBlendWeights.Reset(DesiredBoneBlendWeights.Num());
CurrentBoneBlendWeights.AddZeroed(DesiredBoneBlendWeights.Num());
//Reinitialize bone blend weights now that we have cleared them
FAnimationRuntime::UpdateDesiredBoneWeight(DesiredBoneBlendWeights, CurrentBoneBlendWeights, BlendWeights);
// Build curve source indices
{
// Get the original Reserve value
int32 OriginalReserve = CurvePoseSourceIndices.Max();
CurvePoseSourceIndices.Empty();
Skeleton->ForEachCurveMetaData([this, &RequiredBones](const FName& InCurveName, const FCurveMetaData& InMetaData)
{
for (const FBoneReference& LinkedBone : InMetaData.LinkedBones)
{
FCompactPoseBoneIndex CompactPoseIndex = LinkedBone.GetCompactPoseIndex(RequiredBones);
if (CompactPoseIndex != INDEX_NONE)
{
if (DesiredBoneBlendWeights[CompactPoseIndex.GetInt()].BlendWeight > 0.f)
{
CurvePoseSourceIndices.Add(InCurveName, DesiredBoneBlendWeights[CompactPoseIndex.GetInt()].SourceIndex);
break;
}
}
}
});
// Shrink afterwards to exactly what was used if the Reserve increased, to save memory. Eventually the reserve will
// stabilize at the maximum number of nodes actually used in practice for this specific anim node.
if (CurvePoseSourceIndices.Num() > OriginalReserve)
{
CurvePoseSourceIndices.Shrink();
}
}
RequiredBonesSerialNumber = RequiredBones.GetSerialNumber();
LayerSetup[0].BranchFilters.Reset();
}
void FGMS_AnimNode_LayeredBoneBlend::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(CacheBones_AnyThread)
BasePose.CacheBones(Context);
int32 NumPoses = BlendPoses.Num();
for(int32 ChildIndex=0; ChildIndex<NumPoses; ChildIndex++)
{
BlendPoses[ChildIndex].CacheBones(Context);
}
UpdateCachedBoneData(Context.AnimInstanceProxy->GetRequiredBones(), Context.AnimInstanceProxy->GetSkeleton());
}
void FGMS_AnimNode_LayeredBoneBlend::Update_AnyThread(const FAnimationUpdateContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Update_AnyThread)
bHasRelevantPoses = false;
int32 RootMotionBlendPose = -1;
float RootMotionWeight = 0.f;
const float RootMotionClearWeight = bBlendRootMotionBasedOnRootBone ? 0.f : 1.f;
if (IsLODEnabled(Context.AnimInstanceProxy))
{
GetEvaluateGraphExposedInputs().Execute(Context);
for (int32 ChildIndex = 0; ChildIndex < BlendPoses.Num(); ++ChildIndex)
{
const float ChildWeight = BlendWeights[ChildIndex];
if (FAnimWeight::IsRelevant(ChildWeight))
{
if (bHasRelevantPoses == false)
{
// Update cached data now we know we might be valid
UpdateCachedBoneData(Context.AnimInstanceProxy->GetRequiredBones(), Context.AnimInstanceProxy->GetSkeleton());
// Update weights
FAnimationRuntime::UpdateDesiredBoneWeight(DesiredBoneBlendWeights, CurrentBoneBlendWeights, BlendWeights);
bHasRelevantPoses = true;
if(bBlendRootMotionBasedOnRootBone && !CurrentBoneBlendWeights.IsEmpty())
{
const float NewRootMotionWeight = CurrentBoneBlendWeights[0].BlendWeight;
if(NewRootMotionWeight > ZERO_ANIMWEIGHT_THRESH)
{
RootMotionWeight = NewRootMotionWeight;
RootMotionBlendPose = CurrentBoneBlendWeights[0].SourceIndex;
}
}
}
const float ThisPoseRootMotionWeight = (ChildIndex == RootMotionBlendPose) ? RootMotionWeight : RootMotionClearWeight;
BlendPoses[ChildIndex].Update(Context.FractionalWeightAndRootMotion(ChildWeight, ThisPoseRootMotionWeight));
}
}
}
// initialize children
const float BaseRootMotionWeight = 1.f - RootMotionWeight;
if (BaseRootMotionWeight < ZERO_ANIMWEIGHT_THRESH)
{
BasePose.Update(Context.FractionalWeightAndRootMotion(1.f, BaseRootMotionWeight));
}
else
{
BasePose.Update(Context);
}
TRACE_ANIM_NODE_VALUE(Context, TEXT("Num Poses"), BlendPoses.Num());
}
void FGMS_AnimNode_LayeredBoneBlend::Evaluate_AnyThread(FPoseContext& Output)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Evaluate_AnyThread)
ANIM_MT_SCOPE_CYCLE_COUNTER(BlendPosesInGraph, !IsInGameThread());
const int NumPoses = BlendPoses.Num();
if ((NumPoses == 0) || !bHasRelevantPoses)
{
BasePose.Evaluate(Output);
}
else
{
FPoseContext BasePoseContext(Output);
// evaluate children
BasePose.Evaluate(BasePoseContext);
TArray<FCompactPose> TargetBlendPoses;
TargetBlendPoses.SetNum(NumPoses);
TArray<FBlendedCurve> TargetBlendCurves;
TargetBlendCurves.SetNum(NumPoses);
TArray<UE::Anim::FStackAttributeContainer> TargetBlendAttributes;
TargetBlendAttributes.SetNum(NumPoses);
for (int32 ChildIndex = 0; ChildIndex < NumPoses; ++ChildIndex)
{
if (FAnimWeight::IsRelevant(BlendWeights[ChildIndex]))
{
FPoseContext CurrentPoseContext(Output);
BlendPoses[ChildIndex].Evaluate(CurrentPoseContext);
TargetBlendPoses[ChildIndex].MoveBonesFrom(CurrentPoseContext.Pose);
TargetBlendCurves[ChildIndex].MoveFrom(CurrentPoseContext.Curve);
TargetBlendAttributes[ChildIndex].MoveFrom(CurrentPoseContext.CustomAttributes);
}
else
{
TargetBlendPoses[ChildIndex].ResetToRefPose(BasePoseContext.Pose.GetBoneContainer());
TargetBlendCurves[ChildIndex].InitFrom(Output.Curve);
}
}
// filter to make sure it only includes curves that are linked to the correct bone filter
UE::Anim::FNamedValueArrayUtils::RemoveByPredicate(BasePoseContext.Curve, CurvePoseSourceIndices,
[](const UE::Anim::FCurveElement& InOutBasePoseElement, const UE::Anim::FCurveElementIndexed& InSourceIndexElement)
{
// if source index is set, remove base pose curve value
return (InSourceIndexElement.Index != INDEX_NONE);
});
// Filter child pose curves
for (int32 ChildIndex = 0; ChildIndex < NumPoses; ++ChildIndex)
{
UE::Anim::FNamedValueArrayUtils::RemoveByPredicate(TargetBlendCurves[ChildIndex], CurvePoseSourceIndices,
[ChildIndex](const UE::Anim::FCurveElement& InOutBasePoseElement, const UE::Anim::FCurveElementIndexed& InSourceIndexElement)
{
// if not source, remove it
return (InSourceIndexElement.Index != INDEX_NONE) && (InSourceIndexElement.Index != ChildIndex);
});
}
FAnimationRuntime::EBlendPosesPerBoneFilterFlags BlendFlags = FAnimationRuntime::EBlendPosesPerBoneFilterFlags::None;
if (bMeshSpaceRotationBlend)
{
BlendFlags |= FAnimationRuntime::EBlendPosesPerBoneFilterFlags::MeshSpaceRotation;
}
if (bMeshSpaceScaleBlend)
{
BlendFlags |= FAnimationRuntime::EBlendPosesPerBoneFilterFlags::MeshSpaceScale;
}
FAnimationPoseData AnimationPoseData(Output);
FAnimationRuntime::BlendPosesPerBoneFilter(BasePoseContext.Pose, TargetBlendPoses, BasePoseContext.Curve, TargetBlendCurves, BasePoseContext.CustomAttributes, TargetBlendAttributes, AnimationPoseData, CurrentBoneBlendWeights, BlendFlags, CurveBlendOption);
}
}
void FGMS_AnimNode_LayeredBoneBlend::GatherDebugData(FNodeDebugData& DebugData)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(GatherDebugData)
const int NumPoses = BlendPoses.Num();
FString DebugLine = DebugData.GetNodeName(this);
DebugLine += FString::Printf(TEXT("(Num Poses: %i)"), NumPoses);
DebugData.AddDebugItem(DebugLine);
BasePose.GatherDebugData(DebugData.BranchFlow(1.f));
for (int32 ChildIndex = 0; ChildIndex < NumPoses; ++ChildIndex)
{
BlendPoses[ChildIndex].GatherDebugData(DebugData.BranchFlow(BlendWeights[ChildIndex]));
}
}

View File

@@ -0,0 +1,680 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Nodes/GMS_AnimNode_OrientationWarping.h"
#include "Animation/AnimInstanceProxy.h"
#include "Animation/AnimNodeFunctionRef.h"
#include "Animation/AnimRootMotionProvider.h"
#include "BoneControllers/AnimNode_OffsetRootBone.h"
#include "HAL/IConsoleManager.h"
#include "Animation/AnimTrace.h"
#include "Logging/LogVerbosity.h"
#include "VisualLogger/VisualLogger.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimNode_OrientationWarping)
DECLARE_CYCLE_STAT(TEXT("OrientationWarping Eval"), STAT_OrientationWarping_Eval, STATGROUP_Anim);
#if ENABLE_ANIM_DEBUG
static TAutoConsoleVariable<int32> CVarAnimNodeOrientationWarpingDebug(TEXT("a.AnimNode.GenericOrientationWarping.Debug"), 0, TEXT("Turn on visualization debugging for Orientation Warping."));
static TAutoConsoleVariable<int32> CVarAnimNodeOrientationWarpingVerbose(TEXT("a.AnimNode.GenericOrientationWarping.Verbose"), 0, TEXT("Turn on verbose graph debugging for Orientation Warping"));
static TAutoConsoleVariable<int32> CVarAnimNodeOrientationWarpingEnable(TEXT("a.AnimNode.GenericOrientationWarping.Enable"), 1, TEXT("Toggle Orientation Warping"));
#endif
namespace UE::Anim
{
static inline FVector GetAxisVector(const EAxis::Type& InAxis)
{
switch (InAxis)
{
case EAxis::X:
return FVector::ForwardVector;
case EAxis::Y:
return FVector::RightVector;
default:
return FVector::UpVector;
};
}
static inline bool IsInvalidWarpingAngleDegrees(float Angle, float Tolerance)
{
Angle = FRotator::NormalizeAxis(Angle);
return FMath::IsNearlyZero(Angle, Tolerance) || FMath::IsNearlyEqual(FMath::Abs(Angle), 180.f, Tolerance);
}
static float SignedAngleRadBetweenNormals(const FVector& From, const FVector& To, const FVector& Axis)
{
const float FromDotTo = FVector::DotProduct(From, To);
const float Angle = FMath::Acos(FromDotTo);
const FVector Cross = FVector::CrossProduct(From, To);
const float Dot = FVector::DotProduct(Cross, Axis);
return Dot >= 0 ? Angle : -Angle;
}
}
void FGMS_AnimNode_OrientationWarping::GatherDebugData(FNodeDebugData& DebugData)
{
FString DebugLine = DebugData.GetNodeName(this);
#if ENABLE_ANIM_DEBUG
if (CVarAnimNodeOrientationWarpingVerbose.GetValueOnAnyThread() == 1)
{
if (Mode == EWarpingEvaluationMode::Manual)
{
DebugLine += TEXT("\n - Evaluation Mode: (Manual)");
DebugLine += FString::Printf(TEXT("\n - Orientation Angle: (%.3fd)"), FMath::RadiansToDegrees(ActualOrientationAngleRad));
}
else
{
DebugLine += TEXT("\n - Evaluation Mode: (Graph)");
DebugLine += FString::Printf(TEXT("\n - Orientation Angle: (%.3fd)"), FMath::RadiansToDegrees(ActualOrientationAngleRad));
// Locomotion angle is already in degrees.
DebugLine += FString::Printf(TEXT("\n - Locomotion Angle: (%.3fd)"), LocomotionAngle);
DebugLine += FString::Printf(TEXT("\n - Locomotion Delta Angle Threshold: (%.3fd)"), LocomotionAngleDeltaThreshold);
#if WITH_EDITORONLY_DATA
DebugLine += FString::Printf(TEXT("\n - Root Motion Delta Attribute Found: %s)"), (bFoundRootMotionAttribute) ? TEXT("true") : TEXT("false"));
#endif
}
if (const UEnum* TypeEnum = FindObject<UEnum>(nullptr, TEXT("/Script/CoreUObject.EAxis")))
{
DebugLine += FString::Printf(TEXT("\n - Rotation Axis: (%s)"), *(TypeEnum->GetNameStringByIndex(static_cast<int32>(RotationAxis))));
}
DebugLine += FString::Printf(TEXT("\n - Rotation Interpolation Speed: (%.3fd)"), RotationInterpSpeed);
}
else
#endif
{
const float ActualOrientationAngleDegrees = FMath::RadiansToDegrees(ActualOrientationAngleRad);
DebugLine += FString::Printf(TEXT("(Orientation Angle: %.3fd)"), ActualOrientationAngleDegrees);
}
DebugData.AddDebugItem(DebugLine);
ComponentPose.GatherDebugData(DebugData);
}
void FGMS_AnimNode_OrientationWarping::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
FAnimNode_SkeletalControlBase::Initialize_AnyThread(Context);
Reset(Context);
//早一点拿到ExternalBoneReference.
if (IsLODEnabled(Context.AnimInstanceProxy))
{
GetEvaluateGraphExposedInputs().Execute(Context);
}
}
void FGMS_AnimNode_OrientationWarping::UpdateInternal(const FAnimationUpdateContext& Context)
{
FAnimNode_SkeletalControlBase::UpdateInternal(Context);
// If we just became relevant and haven't been initialized yet, then reset.
if (!bIsFirstUpdate && UpdateCounter.HasEverBeenUpdated() && !UpdateCounter.WasSynchronizedCounter(Context.AnimInstanceProxy->GetUpdateCounter()))
{
Reset(Context);
}
UpdateCounter.SynchronizeWith(Context.AnimInstanceProxy->GetUpdateCounter());
BlendWeight = Context.GetFinalBlendWeight();
// if (WarpingSpace == EOrientationWarpingSpace::RootBoneTransform)
// {
// if (UE::AnimationWarping::FRootOffsetProvider* RootOffsetProvider = Context.GetMessage<UE::AnimationWarping::FRootOffsetProvider>())
// {
// WarpingSpaceTransform = RootOffsetProvider->GetRootTransform();
// }
// else
// {
// WarpingSpaceTransform = Context.AnimInstanceProxy->GetComponentTransform();
// }
// }
if (WarpingSpace == EOrientationWarpingSpace::ComponentTransform)
{
WarpingSpaceTransform = Context.AnimInstanceProxy->GetComponentTransform();
}
}
void FGMS_AnimNode_OrientationWarping::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms)
{
SCOPE_CYCLE_COUNTER(STAT_OrientationWarping_Eval);
check(OutBoneTransforms.Num() == 0);
float TargetOrientationAngleRad;
const float DeltaSeconds = Output.AnimInstanceProxy->GetDeltaSeconds();
const float MaxAngleCorrectionRad = FMath::DegreesToRadians(MaxCorrectionDegrees);
const FVector RotationAxisVector = UE::Anim::GetAxisVector(RotationAxis);
FVector LocomotionForward = FVector::ZeroVector;
bool bGraphDrivenWarping = false;
const UE::Anim::IAnimRootMotionProvider* RootMotionProvider = UE::Anim::IAnimRootMotionProvider::Get();
if (Mode == EWarpingEvaluationMode::Graph)
{
bGraphDrivenWarping = !!RootMotionProvider;
ensureMsgf(bGraphDrivenWarping, TEXT("Graph driven Orientation Warping expected a valid root motion delta provider interface."));
}
#if WITH_EDITORONLY_DATA
bFoundRootMotionAttribute = false;
#endif
#if ENABLE_ANIM_DEBUG
FTransform RootMotionTransformDelta = FTransform::Identity;
float RootMotionDeltaAngleRad = 0.0;
const float PreviousOrientationAngleRad = ActualOrientationAngleRad;
#endif
// We will likely need to revisit LocomotionAngle participating as an input to orientation warping.
// Without velocity information from the motion model (such as the capsule), LocomotionAngle isn't enough
// information in isolation for all cases when deciding to warp.
//
// For example imagine that the motion model has stopped moving with zero velocity due to a
// transition into a strafing stop. During that transition we may play an animation with non-zero
// velocity for an arbitrary number of frames. In this scenario the concept of direction is meaningless
// since we cannot orient the animation to match a zero velocity and consequently a zero direction,
// since that would break the pose. For those frames, we would incorrectly over-orient the strafe.
//
// The solution may be instead to pass velocity with the actor base rotation, allowing us to retain
// speed information about the motion. It may also allow us to do more complex orienting behavior
// when multiple degrees of freedom can be considered.
if (WarpingSpace == EOrientationWarpingSpace::ComponentTransform)
{
WarpingSpaceTransform = Output.AnimInstanceProxy->GetComponentTransform();
}
if (bGraphDrivenWarping)
{
#if !ENABLE_ANIM_DEBUG
FTransform RootMotionTransformDelta = FTransform::Identity;
#endif
bGraphDrivenWarping = RootMotionProvider->ExtractRootMotion(Output.CustomAttributes, RootMotionTransformDelta);
// Graph driven orientation warping will modify the incoming root motion to orient towards the intended locomotion angle
if (bGraphDrivenWarping)
{
#if WITH_EDITORONLY_DATA
// Graph driven Orientation Warping expects a root motion delta to be present in the attribute stream.
bFoundRootMotionAttribute = true;
#endif
// In UE, forward is defined as +x; consequently this is also true when sampling an actor's velocity. Historically the skeletal
// mesh component forward will not match the actor, requiring us to correct the rotation before sampling the LocomotionForward.
// In order to make orientation warping 'pure' in the future we will need to provide more context about the intent of
// the actor vs the intent of the animation in their respective spaces. Specifically, we will need some form the following information:
//
// 1. Actor Forward
// 2. Actor Velocity
// 3. Skeletal Mesh Relative Rotation
if (LocomotionDirection.SquaredLength() > UE_SMALL_NUMBER)
{
// if we have a LocomotionDirection vector, transform into root bone local space
LocomotionForward = WarpingSpaceTransform.InverseTransformVector(LocomotionDirection);
LocomotionForward.Normalize();
}
else
{
LocomotionAngle = FRotator::NormalizeAxis(LocomotionAngle);
// UE-184297 Avoid storing LocomotionAngle in radians in case haven't updated the pinned input, to avoid a DegToRad(RadianValue)
const float LocomotionAngleRadians = FMath::DegreesToRadians(LocomotionAngle);
const FQuat LocomotionRotation = FQuat(RotationAxisVector, LocomotionAngleRadians);
const FTransform SkeletalMeshRelativeTransform = Output.AnimInstanceProxy->GetComponentRelativeTransform();
const FQuat SkeletalMeshRelativeRotation = SkeletalMeshRelativeTransform.GetRotation();
LocomotionForward = SkeletalMeshRelativeRotation.UnrotateVector(LocomotionRotation.GetForwardVector()).GetSafeNormal();
}
// Flatten locomotion direction, along the rotation axis.
LocomotionForward = (LocomotionForward - RotationAxisVector.Dot(LocomotionForward) * RotationAxisVector).GetSafeNormal();
// @todo: Graph mode using a "manual value" makes no sense. Restructure logic to address this in the future.
if (bUseManualRootMotionVelocity)
{
RootMotionTransformDelta.SetTranslation(ManualRootMotionVelocity * DeltaSeconds);
}
FVector RootMotionDeltaTranslation = RootMotionTransformDelta.GetTranslation();
// Flatten root motion translation, along the rotation axis.
RootMotionDeltaTranslation = RootMotionDeltaTranslation - RotationAxisVector.Dot(RootMotionDeltaTranslation) * RotationAxisVector;
const float RootMotionDeltaSpeed = RootMotionDeltaTranslation.Size() / DeltaSeconds;
if (RootMotionDeltaSpeed < MinRootMotionSpeedThreshold)
{
// If we're under the threshold, snap orientation angle to 0, and let interpolation handle the delta
TargetOrientationAngleRad = 0.0f;
}
else
{
const FVector PreviousRootMotionDeltaDirection = RootMotionDeltaDirection;
// Hold previous direction if we can't calculate it from current move delta, because the root is no longer moving
RootMotionDeltaDirection = RootMotionDeltaTranslation.GetSafeNormal(UE_SMALL_NUMBER, PreviousRootMotionDeltaDirection);
TargetOrientationAngleRad = UE::Anim::SignedAngleRadBetweenNormals(RootMotionDeltaDirection, LocomotionForward, RotationAxisVector);
// Motion Matching may return an animation that deviates a lot from the movement direction (e.g movement direction going bwd and motion matching could return the fwd animation for a few frames)
// When that happens, since we use the delta between root motion and movement direction, we would be over-rotating the lower body and breaking the pose during those frames
// So, when that happens we use the inverse of the root motion direction to calculate our target rotation.
// This feels a bit 'hacky' but its the only option I've found so far to mitigate the problem
if (LocomotionAngleDeltaThreshold > 0.f)
{
if (FMath::Abs(FMath::RadiansToDegrees(TargetOrientationAngleRad)) > LocomotionAngleDeltaThreshold)
{
TargetOrientationAngleRad = FMath::UnwindRadians(TargetOrientationAngleRad + FMath::DegreesToRadians(180.0f));
RootMotionDeltaDirection = -RootMotionDeltaDirection;
}
}
// Don't compensate interpolation by the root motion angle delta if the previous angle isn't valid.
if (bCounterCompenstateInterpolationByRootMotion && !PreviousRootMotionDeltaDirection.IsNearlyZero(UE_SMALL_NUMBER))
{
#if !ENABLE_ANIM_DEBUG
float RootMotionDeltaAngleRad;
#endif
// Counter the interpolated orientation angle by the root motion direction angle delta.
// This prevents our interpolation from fighting the natural root motion that's flowing through the graph.
RootMotionDeltaAngleRad = UE::Anim::SignedAngleRadBetweenNormals(RootMotionDeltaDirection, PreviousRootMotionDeltaDirection, RotationAxisVector);
// Root motion may have large deltas i.e. bad blends or sudden direction changes like pivots.
// If there's an instantaneous pop in root motion direction, this is likely a pivot.
const float MaxRootMotionDeltaToCompensateRad = FMath::DegreesToRadians(MaxRootMotionDeltaToCompensateDegrees);
if (FMath::Abs(RootMotionDeltaAngleRad) < MaxRootMotionDeltaToCompensateRad)
{
ActualOrientationAngleRad = FMath::UnwindRadians(ActualOrientationAngleRad + RootMotionDeltaAngleRad);
}
}
// Rotate the root motion delta fully by the warped angle
const FVector WarpedRootMotionTranslationDelta = FQuat(RotationAxisVector, TargetOrientationAngleRad).RotateVector(RootMotionDeltaTranslation);
RootMotionTransformDelta.SetTranslation(WarpedRootMotionTranslationDelta);
}
// Forward the side effects of orientation warping on the root motion contribution for this sub-graph
const bool bRootMotionOverridden = RootMotionProvider->OverrideRootMotion(RootMotionTransformDelta, Output.CustomAttributes);
ensureMsgf(bRootMotionOverridden, TEXT("Graph driven Orientation Warping expected a root motion delta to be present in the attribute stream prior to warping/overriding it."));
}
else
{
// Early exit on missing root motion delta attribute
return;
}
}
else
{
// Manual orientation warping will take the angle directly
TargetOrientationAngleRad = FRotator::NormalizeAxis(OrientationAngle);
TargetOrientationAngleRad = FMath::DegreesToRadians(TargetOrientationAngleRad);
}
// Optionally interpolate the effective orientation towards the target orientation angle
// When the orientation warping node becomes relevant, the input pose orientation may not be aligned with the desired orientation.
// Instead of interpolating this difference, snap to the desired orientation if it's our first update to minimize corrections over-time.
if ((RotationInterpSpeed > 0.f) && !bIsFirstUpdate)
{
const float SmoothOrientationAngleRad = FMath::FInterpTo(ActualOrientationAngleRad, TargetOrientationAngleRad, DeltaSeconds, RotationInterpSpeed);
// Limit our interpolation rate to prevent pops.
// @TODO: Use better, more physically accurate interpolation here.
ActualOrientationAngleRad = FMath::Clamp(SmoothOrientationAngleRad, ActualOrientationAngleRad - MaxAngleCorrectionRad, ActualOrientationAngleRad + MaxAngleCorrectionRad);
}
else
{
ActualOrientationAngleRad = TargetOrientationAngleRad;
}
ActualOrientationAngleRad = FMath::Clamp(ActualOrientationAngleRad, -MaxAngleCorrectionRad, MaxAngleCorrectionRad);
// Allow the alpha value of the node to affect the final rotation
ActualOrientationAngleRad *= ActualAlpha;
if (bScaleByGlobalBlendWeight)
{
ActualOrientationAngleRad *= BlendWeight;
}
#if ENABLE_ANIM_DEBUG
bool bDebugging = false;
#if WITH_EDITORONLY_DATA
bDebugging = bDebugging || bEnableDebugDraw;
#else
constexpr float DebugDrawScale = 1.f;
#endif
const int32 DebugIndex = CVarAnimNodeOrientationWarpingDebug.GetValueOnAnyThread();
bDebugging = bDebugging || (DebugIndex > 0);
if (bDebugging)
{
const FTransform ComponentTransform = Output.AnimInstanceProxy->GetComponentTransform();
const FVector ActorForwardDirection = Output.AnimInstanceProxy->GetActorTransform().GetRotation().GetForwardVector();
FVector DebugArrowOffset = FVector::ZAxisVector * DebugDrawScale;
// Draw debug shapes
{
const FVector ForwardDirection = bGraphDrivenWarping
? ComponentTransform.GetRotation().RotateVector(LocomotionForward)
: ActorForwardDirection;
Output.AnimInstanceProxy->AnimDrawDebugDirectionalArrow(
ComponentTransform.GetLocation() + DebugArrowOffset,
ComponentTransform.GetLocation() + DebugArrowOffset + ForwardDirection * 100.f * DebugDrawScale,
40.f * DebugDrawScale, FColor::Red, false, 0.f, 2.f * DebugDrawScale);
const FVector RotationDirection = bGraphDrivenWarping
? ComponentTransform.GetRotation().RotateVector(RootMotionDeltaDirection)
: ActorForwardDirection.RotateAngleAxis(OrientationAngle, RotationAxisVector);
DebugArrowOffset += FVector::ZAxisVector * DebugDrawScale;
Output.AnimInstanceProxy->AnimDrawDebugDirectionalArrow(
ComponentTransform.GetLocation() + DebugArrowOffset,
ComponentTransform.GetLocation() + DebugArrowOffset + RotationDirection * 100.f * DebugDrawScale,
40.f * DebugDrawScale, FColor::Blue, false, 0.f, 2.f * DebugDrawScale);
const float ActualOrientationAngleDegrees = FMath::RadiansToDegrees(ActualOrientationAngleRad);
const FVector WarpedRotationDirection = bGraphDrivenWarping
? RotationDirection.RotateAngleAxis(ActualOrientationAngleDegrees, RotationAxisVector)
: ActorForwardDirection.RotateAngleAxis(ActualOrientationAngleDegrees, RotationAxisVector);
DebugArrowOffset += FVector::ZAxisVector * DebugDrawScale;
Output.AnimInstanceProxy->AnimDrawDebugDirectionalArrow(
ComponentTransform.GetLocation() + DebugArrowOffset,
ComponentTransform.GetLocation() + DebugArrowOffset + WarpedRotationDirection * 100.f * DebugDrawScale,
40.f * DebugDrawScale, FColor::Green, false, 0.f, 2.f * DebugDrawScale);
}
// Draw text on mesh in world space
{
TStringBuilder<1024> DebugLine;
const float PreviousOrientationAngleDegrees = FMath::RadiansToDegrees(PreviousOrientationAngleRad);
const float ActualOrientationAngleDegrees = FMath::RadiansToDegrees(ActualOrientationAngleRad);
const float TargetOrientationAngleDegrees = FMath::RadiansToDegrees(TargetOrientationAngleRad);
if (Mode == EWarpingEvaluationMode::Manual)
{
DebugLine.Appendf(TEXT("\n - Previous Orientation Angle: (%.3fd)"), PreviousOrientationAngleDegrees);
DebugLine.Appendf(TEXT("\n - Orientation Angle: (%.3fd)"), ActualOrientationAngleDegrees);
DebugLine.Appendf(TEXT("\n - Target Orientation Angle: (%.3fd)"), TargetOrientationAngleRad);
}
else
{
if (RotationInterpSpeed > 0.0f)
{
DebugLine.Appendf(TEXT("\n - Previous Orientation Angle: (%.3fd)"), FMath::RadiansToDegrees(PreviousOrientationAngleRad));
DebugLine.Appendf(TEXT("\n - Root Motion Frame Delta Angle: (%.3fd)"), FMath::RadiansToDegrees(RootMotionDeltaAngleRad));
}
DebugLine.Appendf(TEXT("\n - Actual Orientation Angle: (%.3fd)"), FMath::RadiansToDegrees(ActualOrientationAngleRad));
DebugLine.Appendf(TEXT("\n - Target Orientation Angle: (%.3fd)"), FMath::RadiansToDegrees(TargetOrientationAngleRad));
// Locomotion angle is already in degrees.
DebugLine.Appendf(TEXT("\n - Locomotion Angle: (%.3fd)"), LocomotionAngle);
DebugLine.Appendf(TEXT("\n - Root Motion Delta: %s)"), *RootMotionTransformDelta.GetTranslation().ToString());
DebugLine.Appendf(TEXT("\n - Root Motion Speed: %.3fd)"), RootMotionTransformDelta.GetTranslation().Size() / DeltaSeconds);
}
Output.AnimInstanceProxy->AnimDrawDebugInWorldMessage(DebugLine.ToString(), FVector::UpVector * 50.0f, FColor::Yellow, 1.f /*TextScale*/);
}
}
#endif
#if ANIM_TRACE_ENABLED
{
const float PreviousOrientationAngleDegrees = FMath::RadiansToDegrees(PreviousOrientationAngleRad);
const float ActualOrientationAngleDegrees = FMath::RadiansToDegrees(ActualOrientationAngleRad);
const float TargetOrientationAngleDegrees = FMath::RadiansToDegrees(TargetOrientationAngleRad);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Previous OrientationAngle Degrees"), PreviousOrientationAngleDegrees);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Actual Orientation Angle Degrees"), ActualOrientationAngleDegrees);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Target Orientation Angle Degrees"), TargetOrientationAngleDegrees);
if (Mode == EWarpingEvaluationMode::Graph)
{
const float RootMotionDeltaAngleDegrees = FMath::RadiansToDegrees(RootMotionDeltaAngleRad);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Root Motion Delta Angle Degrees"), RootMotionDeltaAngleDegrees);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Locomotion Angle"), LocomotionAngle);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Root Motion Translation Delta"), RootMotionTransformDelta.GetTranslation());
const float RootMotionSpeed = RootMotionTransformDelta.GetTranslation().Size() / DeltaSeconds;
TRACE_ANIM_NODE_VALUE(Output, TEXT("Root Motion Speed"), RootMotionSpeed);
}
}
#endif
#if ENABLE_VISUAL_LOG
if (FVisualLogger::IsRecording())
{
const FTransform ComponentTransform = Output.AnimInstanceProxy->GetComponentTransform();
const FVector ActorForwardDirection = Output.AnimInstanceProxy->GetActorTransform().GetRotation().GetForwardVector();
FVector DebugArrowOffset = FVector::ZAxisVector * DebugDrawScale;
// Draw debug shapes
{
const FVector ForwardDirection = bGraphDrivenWarping
? ComponentTransform.GetRotation().RotateVector(LocomotionForward)
: ActorForwardDirection;
UE_VLOG_ARROW(Output.AnimInstanceProxy->GetAnimInstanceObject(), "OrientationWarping", Display,
ComponentTransform.GetLocation() + DebugArrowOffset,
ComponentTransform.GetLocation() + DebugArrowOffset + ForwardDirection * 100.f * DebugDrawScale,
FColor::Red, TEXT(""));
const FVector RotationDirection = bGraphDrivenWarping
? ComponentTransform.GetRotation().RotateVector(RootMotionDeltaDirection)
: ActorForwardDirection.RotateAngleAxis(OrientationAngle, RotationAxisVector);
DebugArrowOffset += FVector::ZAxisVector * DebugDrawScale;
UE_VLOG_ARROW(Output.AnimInstanceProxy->GetAnimInstanceObject(), "OrientationWarping", Display,
ComponentTransform.GetLocation() + DebugArrowOffset,
ComponentTransform.GetLocation() + DebugArrowOffset + RotationDirection * 100.f * DebugDrawScale,
FColor::Blue, TEXT(""));
const float ActualOrientationAngleDegrees = FMath::RadiansToDegrees(ActualOrientationAngleRad);
const FVector WarpedRotationDirection = bGraphDrivenWarping
? RotationDirection.RotateAngleAxis(ActualOrientationAngleDegrees, RotationAxisVector)
: ActorForwardDirection.RotateAngleAxis(ActualOrientationAngleDegrees, RotationAxisVector);
DebugArrowOffset += FVector::ZAxisVector * DebugDrawScale;
UE_VLOG_ARROW(Output.AnimInstanceProxy->GetAnimInstanceObject(), "OrientationWarping", Display,
ComponentTransform.GetLocation() + DebugArrowOffset,
ComponentTransform.GetLocation() + DebugArrowOffset + WarpedRotationDirection * 100.f * DebugDrawScale,
FColor::Green, TEXT(""));
}
}
#endif
const float RootOffset = FMath::UnwindRadians(ActualOrientationAngleRad * DistributedBoneOrientationAlpha);
// Rotate Root Bone first, as that cheaply rotates the whole pose with one transformation.
if (!FMath::IsNearlyZero(RootOffset, KINDA_SMALL_NUMBER))
{
const FQuat RootRotation = FQuat(RotationAxisVector, RootOffset);
const FCompactPoseBoneIndex RootBoneIndex(0);
FTransform RootBoneTransform(Output.Pose.GetComponentSpaceTransform(RootBoneIndex));
RootBoneTransform.SetRotation(RootRotation * RootBoneTransform.GetRotation());
RootBoneTransform.NormalizeRotation();
Output.Pose.SetComponentSpaceTransform(RootBoneIndex, RootBoneTransform);
}
const int32 NumSpineBones = SpineBoneDataArray.Num();
const bool bSpineOrientationAlpha = !FMath::IsNearlyZero(DistributedBoneOrientationAlpha, KINDA_SMALL_NUMBER);
const bool bUpdateSpineBones = (NumSpineBones > 0) && bSpineOrientationAlpha;
if (bUpdateSpineBones)
{
// Spine bones counter rotate body orientation evenly across all bones.
for (int32 ArrayIndex = 0; ArrayIndex < NumSpineBones; ArrayIndex++)
{
const FOrientationWarpingSpineBoneData& BoneData = SpineBoneDataArray[ArrayIndex];
const FQuat SpineBoneCounterRotation = FQuat(RotationAxisVector, -ActualOrientationAngleRad * DistributedBoneOrientationAlpha * BoneData.Weight);
check(BoneData.Weight > 0.f);
FTransform SpineBoneTransform(Output.Pose.GetComponentSpaceTransform(BoneData.BoneIndex));
SpineBoneTransform.SetRotation((SpineBoneCounterRotation * SpineBoneTransform.GetRotation()));
SpineBoneTransform.NormalizeRotation();
Output.Pose.SetComponentSpaceTransform(BoneData.BoneIndex, SpineBoneTransform);
}
}
const float IKFootRootOrientationAlpha = 1.f - DistributedBoneOrientationAlpha;
const bool bUpdateIKFootRoot = (IKFootData.IKFootRootBoneIndex != FCompactPoseBoneIndex(INDEX_NONE)) && !FMath::IsNearlyZero(IKFootRootOrientationAlpha, KINDA_SMALL_NUMBER);
// Rotate IK Foot Root
if (bUpdateIKFootRoot)
{
const FQuat BoneRotation = FQuat(RotationAxisVector, ActualOrientationAngleRad * IKFootRootOrientationAlpha);
FTransform IKFootRootTransform(Output.Pose.GetComponentSpaceTransform(IKFootData.IKFootRootBoneIndex));
IKFootRootTransform.SetRotation(BoneRotation * IKFootRootTransform.GetRotation());
IKFootRootTransform.NormalizeRotation();
Output.Pose.SetComponentSpaceTransform(IKFootData.IKFootRootBoneIndex, IKFootRootTransform);
// IK Feet
// These match the root orientation, so don't rotate them. Just preserve root rotation.
// We need to update their translation though, since we rotated their parent (the IK Foot Root bone).
const int32 NumIKFootBones = IKFootData.IKFootBoneIndexArray.Num();
const bool bUpdateIKFootBones = bUpdateIKFootRoot && (NumIKFootBones > 0);
if (bUpdateIKFootBones)
{
const FQuat IKFootRotation = FQuat(RotationAxisVector, -ActualOrientationAngleRad * IKFootRootOrientationAlpha);
for (int32 ArrayIndex = 0; ArrayIndex < NumIKFootBones; ArrayIndex++)
{
const FCompactPoseBoneIndex& IKFootBoneIndex = IKFootData.IKFootBoneIndexArray[ArrayIndex];
FTransform IKFootBoneTransform(Output.Pose.GetComponentSpaceTransform(IKFootBoneIndex));
IKFootBoneTransform.SetRotation(IKFootRotation * IKFootBoneTransform.GetRotation());
IKFootBoneTransform.NormalizeRotation();
Output.Pose.SetComponentSpaceTransform(IKFootBoneIndex, IKFootBoneTransform);
}
}
}
OutBoneTransforms.Sort(FCompareBoneTransformIndex());
bIsFirstUpdate = false;
}
bool FGMS_AnimNode_OrientationWarping::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones)
{
#if ENABLE_ANIM_DEBUG
if (CVarAnimNodeOrientationWarpingEnable.GetValueOnAnyThread() == 0)
{
return false;
}
#endif
if (RotationAxis == EAxis::None)
{
return false;
}
if (Mode == EWarpingEvaluationMode::Manual && UE::Anim::IsInvalidWarpingAngleDegrees(OrientationAngle, KINDA_SMALL_NUMBER))
{
return false;
}
if (SpineBoneDataArray.IsEmpty())
{
return false;
}
else
{
for (const auto& Spine : SpineBoneDataArray)
{
if (Spine.BoneIndex == INDEX_NONE)
{
return false;
}
}
}
if (IKFootData.IKFootRootBoneIndex == INDEX_NONE)
{
return false;
}
if (IKFootData.IKFootBoneIndexArray.IsEmpty())
{
return false;
}
else
{
for (const auto& IKFootBoneIndex : IKFootData.IKFootBoneIndexArray)
{
if (IKFootBoneIndex == INDEX_NONE)
{
return false;
}
}
}
return true;
}
void FGMS_AnimNode_OrientationWarping::InitializeBoneReferences(const FBoneContainer& RequiredBones)
{
ExternalBoneReference.IKFootRootBone.Initialize(RequiredBones);
IKFootData.IKFootRootBoneIndex = ExternalBoneReference.IKFootRootBone.GetCompactPoseIndex(RequiredBones);
IKFootData.IKFootBoneIndexArray.Reset();
for (auto& Bone : ExternalBoneReference.IKFootBones)
{
Bone.Initialize(RequiredBones);
IKFootData.IKFootBoneIndexArray.Add(Bone.GetCompactPoseIndex(RequiredBones));
}
SpineBoneDataArray.Reset();
for (auto& Bone : ExternalBoneReference.SpineBones)
{
Bone.Initialize(RequiredBones);
SpineBoneDataArray.Add(FOrientationWarpingSpineBoneData(Bone.GetCompactPoseIndex(RequiredBones)));
}
if (SpineBoneDataArray.Num() > 0)
{
// Sort bones indices so we can transform parent before child
SpineBoneDataArray.Sort(FOrientationWarpingSpineBoneData::FCompareBoneIndex());
// Assign Weights.
TArray<int32, TInlineAllocator<20>> IndicesToUpdate;
for (int32 Index = SpineBoneDataArray.Num() - 1; Index >= 0; Index--)
{
// If this bone's weight hasn't been updated, scan its parents.
// If parents have weight, we add it to 'ExistingWeight'.
// split (1.f - 'ExistingWeight') between all members of the chain that have no weight yet.
if (SpineBoneDataArray[Index].Weight == 0.f)
{
IndicesToUpdate.Reset(SpineBoneDataArray.Num());
float ExistingWeight = 0.f;
IndicesToUpdate.Add(Index);
const FCompactPoseBoneIndex CompactBoneIndex = SpineBoneDataArray[Index].BoneIndex;
for (int32 ParentIndex = Index - 1; ParentIndex >= 0; ParentIndex--)
{
if (RequiredBones.BoneIsChildOf(CompactBoneIndex, SpineBoneDataArray[ParentIndex].BoneIndex))
{
if (SpineBoneDataArray[ParentIndex].Weight > 0.f)
{
ExistingWeight += SpineBoneDataArray[ParentIndex].Weight;
}
else
{
IndicesToUpdate.Add(ParentIndex);
}
}
}
check(IndicesToUpdate.Num() > 0);
const float WeightToShare = 1.f - ExistingWeight;
const float IndividualWeight = WeightToShare / float(IndicesToUpdate.Num());
for (int32 UpdateListIndex = 0; UpdateListIndex < IndicesToUpdate.Num(); UpdateListIndex++)
{
SpineBoneDataArray[IndicesToUpdate[UpdateListIndex]].Weight = IndividualWeight;
}
}
}
}
}
void FGMS_AnimNode_OrientationWarping::Reset(const FAnimationBaseContext& Context)
{
bIsFirstUpdate = true;
RootMotionDeltaDirection = FVector::ZeroVector;
ManualRootMotionVelocity = FVector::ZeroVector;
ActualOrientationAngleRad = 0.f;
}