OLS/Source/OLSAnimation/Private/Nodes/AnimNode_OLSMask.cpp
2024-09-22 17:11:19 -04:00

496 lines
20 KiB
C++

// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
#include "Nodes/AnimNode_OLSMask.h"
#include "Animation/AnimInstanceProxy.h"
void FAnimNode_OLSMask::Initialize_AnyThread(const FAnimationInitializeContext& context)
{
// Don't call SUPER since it's empty.
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Initialize_AnyThread)
FAnimNode_Base::Initialize_AnyThread(context);
AnimationPose.Initialize(context);
MaskPose.Initialize(context);
BaseAdditivePose.Initialize(context);
for (int32 index = 0; index < BlendMasks.Num(); ++index)
{
if (BlendMasks[index].bShouldAddSlot)
{
BlendMasks[index].Slot.SlotName = BlendMasks[index].SlotName;
BlendMasks[index].Slot.Source = MaskPose;
BlendMasks[index].Slot.Initialize_AnyThread(context);
}
}
if (!context.AnimInstanceProxy->GetSkeleton())
{
return;
}
// Invalidate the cached per-bone blend weights from the skeleton
InvalidatePerBoneBlendWeights();
}
void FAnimNode_OLSMask::CacheBones_AnyThread(const FAnimationCacheBonesContext& context)
{
// Don't call SUPER since it's empty.
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(CacheBones_AnyThread)
AnimationPose.CacheBones(context);
BaseAdditivePose.CacheBones(context);
MaskPose.CacheBones(context);
const int32 numBlendMasks = BlendMasks.Num();
for (int32 maskIndex = 0; maskIndex < numBlendMasks; ++maskIndex)
{
const FOLSMaskSettings currentMask = BlendMasks[maskIndex];
const FName blendProfileName = BlendMasks[maskIndex].BlendProfileName;
if (const TObjectPtr<UBlendProfile> blendProfile = context.AnimInstanceProxy->GetSkeleton()->GetBlendProfile(
blendProfileName))
{
BlendMasks[maskIndex].BlendProfile = blendProfile;
}
if (BlendMasks[maskIndex].bShouldAddSlot)
{
BlendMasks[maskIndex].Slot.CacheBones_AnyThread(context);
}
}
UpdateCachedBoneData(context.AnimInstanceProxy->GetRequiredBones(), context.AnimInstanceProxy->GetSkeleton());
}
void FAnimNode_OLSMask::Update_AnyThread(const FAnimationUpdateContext& Context)
{
// Don't call SUPER since it's empty.
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Update_AnyThread)
bHasRelevantPoses = false;
if (BlendMasks.Num() > 0)
{
if (IsLODEnabled(Context.AnimInstanceProxy))
{
GetEvaluateGraphExposedInputs().Execute(Context);
if (!bHasRelevantPoses)
{
MaskPose.Update(Context);
BaseAdditivePose.Update(Context);
AnimationPose.Update(Context);
for (int32 Index = 0; Index < BlendMasks.Num(); ++Index)
{
if (BlendMasks[Index].bShouldAddSlot)
{
BlendMasks[Index].Slot.Update_AnyThread(Context);
}
}
UpdateCachedBoneData(Context.AnimInstanceProxy->GetRequiredBones(),
Context.AnimInstanceProxy->GetSkeleton());
bHasRelevantPoses = true;
}
return;
}
// Clear BlendWeights if disabled by LODThreshold.
for (int32 ChildIndex = 0; ChildIndex < MaskBlendWeights.Num(); ++ChildIndex)
{
MaskBlendWeights[ChildIndex].LocalSpaceBlendAlpha = 0.0f;
MaskBlendWeights[ChildIndex].MeshSpaceBlendAlpha = 0.0f;
}
return;
}
AnimationPose.Update(Context);
}
void FAnimNode_OLSMask::Evaluate_AnyThread(FPoseContext& output)
{
// Don't call SUPER since it's empty.
if (BlendMasks.Num() > 0)
{
AnimationPose.Evaluate(output);
FPoseContext animationPoseContext(output);
animationPoseContext = output;
FPoseContext updatedAnimationPoseContext(output);
updatedAnimationPoseContext = animationPoseContext;
FPoseContext maskPoseContext(output);
MaskPose.Evaluate(maskPoseContext);
FPoseContext baseAddPoseContext(output);
BaseAdditivePose.Evaluate(baseAddPoseContext);
FPoseContext baseMSAddPoseContext(output);
baseMSAddPoseContext = baseAddPoseContext;
FPoseContext animationLocalSpaceAdditive(output);
animationLocalSpaceAdditive = animationPoseContext;
FPoseContext animationMeshSpaceAdditive(output);
animationMeshSpaceAdditive = animationPoseContext;
MakeAdditivePose(animationLocalSpaceAdditive, baseAddPoseContext, false);
MakeAdditivePose(animationMeshSpaceAdditive, baseMSAddPoseContext, true);
for (int32 maskIndex = 0; maskIndex < BlendMasks.Num(); ++maskIndex)
{
//Apply Blending For Each Mask
TArray<FCompactPose> lsTargetBlendPoses;
lsTargetBlendPoses.SetNum(1);
TArray<FCompactPose> msTargetBlendPoses;
msTargetBlendPoses.SetNum(1);
TArray<FBlendedCurve> lsTargetBlendCurves;
lsTargetBlendCurves.SetNum(1);
TArray<FBlendedCurve> msTargetBlendCurves;
msTargetBlendCurves.SetNum(1);
TArray<UE::Anim::FStackAttributeContainer> lsTargetBlendAttributes;
lsTargetBlendAttributes.SetNum(1);
TArray<UE::Anim::FStackAttributeContainer> msTargetBlendAttributes;
msTargetBlendAttributes.SetNum(1);
FPoseContext outMaskPoseContext(output);
(BlendMasks[maskIndex].bShouldAddSlot)
? BlendMasks[maskIndex].Slot.Evaluate_AnyThread(outMaskPoseContext)
: BlendMasks[maskIndex].Slot.Evaluate_AnyThread(outMaskPoseContext);
UpdateWeightsFromCurves(output.AnimInstanceProxy->GetSkeleton(), outMaskPoseContext, maskIndex);
FAnimationPoseData inOutAnimationPoseData(outMaskPoseContext);
const FAnimationPoseData additiveAnimationPoseData(
BlendMasks[maskIndex].bUseMeshSpaceAdditive ? animationMeshSpaceAdditive : animationLocalSpaceAdditive);
if (FAnimWeight::IsRelevant(MaskBlendWeights[maskIndex].AdditiveAlpha))
{
FAnimationRuntime::AccumulateAdditivePose(inOutAnimationPoseData, additiveAnimationPoseData,
MaskBlendWeights[maskIndex].AdditiveAlpha,
BlendMasks[maskIndex].bUseMeshSpaceAdditive
? AAT_RotationOffsetMeshSpace
: AAT_LocalSpaceBase);
outMaskPoseContext.Pose.NormalizeRotations();
}
FPoseContext currentPoseContext(animationPoseContext);
FAnimationPoseData blendedAnimationPoseData(currentPoseContext);
const FAnimationPoseData animationPoseOneData(animationPoseContext);
const FAnimationPoseData animationPoseTwoData(outMaskPoseContext);
TArray<float> lsDesiredBlendWeight;
lsDesiredBlendWeight.Add(MaskBlendWeights[maskIndex].LocalSpaceBlendAlpha);
TArray<float> msDesiredBlendWeight;
msDesiredBlendWeight.Add(MaskBlendWeights[maskIndex].MeshSpaceBlendAlpha);
FAnimationRuntime::UpdateDesiredBoneWeight(AllBoneBlendWeights[maskIndex].LocalSpaceDesiredBoneBlendWeights,
AllBoneBlendWeights[maskIndex].LocalSpaceCurrentBoneBlendWeights,
lsDesiredBlendWeight);
FAnimationRuntime::UpdateDesiredBoneWeight(AllBoneBlendWeights[maskIndex].MeshSpaceDesiredBoneBlendWeights,
AllBoneBlendWeights[maskIndex].MeshSpaceCurrentBoneBlendWeights,
msDesiredBlendWeight);
FAnimationRuntime::BlendTwoPosesTogether(animationPoseOneData, animationPoseTwoData,
(1.0f - MaskBlendWeights[maskIndex].OverrideAlpha),
blendedAnimationPoseData);
FPoseContext msCurrentPoseContext(animationPoseContext);
msCurrentPoseContext = currentPoseContext;
if (FAnimWeight::IsRelevant(MaskBlendWeights[maskIndex].LocalSpaceBlendAlpha))
{
lsTargetBlendPoses[0].MoveBonesFrom(currentPoseContext.Pose);
lsTargetBlendCurves[0].MoveFrom(currentPoseContext.Curve);
lsTargetBlendAttributes[0].MoveFrom(currentPoseContext.CustomAttributes);
}
else
{
lsTargetBlendPoses[0].ResetToRefPose(animationPoseContext.Pose.GetBoneContainer());
lsTargetBlendCurves[0].InitFrom(output.Curve);
}
if (FAnimWeight::IsRelevant(MaskBlendWeights[maskIndex].MeshSpaceBlendAlpha))
{
msTargetBlendPoses[0].MoveBonesFrom(msCurrentPoseContext.Pose);
msTargetBlendCurves[0].MoveFrom(msCurrentPoseContext.Curve);
msTargetBlendAttributes[0].MoveFrom(msCurrentPoseContext.CustomAttributes);
}
else
{
msTargetBlendPoses[0].ResetToRefPose(animationPoseContext.Pose.GetBoneContainer());
msTargetBlendCurves[0].InitFrom(output.Curve);
}
// Filter to make sure it only includes curves that are linked to the correct bone filter
UE::Anim::FNamedValueArrayUtils::RemoveByPredicate(animationPoseContext.Curve,
AllBoneBlendWeights[maskIndex].
LocalCurvePoseSourceIndices,
[](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 to make sure it only includes curves that are linked to the correct bone filter
UE::Anim::FNamedValueArrayUtils::RemoveByPredicate(animationPoseContext.Curve,
AllBoneBlendWeights[maskIndex].
MeshCurvePoseSourceIndices,
[](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);
});
FAnimationRuntime::EBlendPosesPerBoneFilterFlags msBlendFlags =
FAnimationRuntime::EBlendPosesPerBoneFilterFlags::None;
msBlendFlags |= FAnimationRuntime::EBlendPosesPerBoneFilterFlags::MeshSpaceRotation;
FAnimationRuntime::EBlendPosesPerBoneFilterFlags lsBlendFlags =
FAnimationRuntime::EBlendPosesPerBoneFilterFlags::None;
if (FAnimWeight::IsRelevant(MaskBlendWeights[maskIndex].LocalSpaceBlendAlpha))
{
FAnimationPoseData animationPoseData(output);
FAnimationRuntime::BlendPosesPerBoneFilter(updatedAnimationPoseContext.Pose, lsTargetBlendPoses,
updatedAnimationPoseContext.Curve, lsTargetBlendCurves,
updatedAnimationPoseContext.CustomAttributes,
lsTargetBlendAttributes, animationPoseData,
AllBoneBlendWeights[maskIndex].
LocalSpaceCurrentBoneBlendWeights, lsBlendFlags,
BlendMasks[maskIndex].CurveBlendOption);
updatedAnimationPoseContext = output;
updatedAnimationPoseContext.Pose.NormalizeRotations();
}
if (FAnimWeight::IsRelevant(MaskBlendWeights[maskIndex].MeshSpaceBlendAlpha))
{
FAnimationPoseData MSAnimationPoseData(output);
FAnimationRuntime::BlendPosesPerBoneFilter(updatedAnimationPoseContext.Pose, msTargetBlendPoses,
updatedAnimationPoseContext.Curve, msTargetBlendCurves,
updatedAnimationPoseContext.CustomAttributes,
msTargetBlendAttributes, MSAnimationPoseData,
AllBoneBlendWeights[maskIndex].
MeshSpaceCurrentBoneBlendWeights, msBlendFlags,
BlendMasks[maskIndex].CurveBlendOption);
updatedAnimationPoseContext = output;
updatedAnimationPoseContext.Pose.NormalizeRotations();
}
}
return;
}
AnimationPose.Evaluate(output);
}
void FAnimNode_OLSMask::InvalidatePerBoneBlendWeights()
{
RequiredBonesSerialNumber = 0;
SkeletonGuid = FGuid();
VirtualBoneGuid = FGuid();
}
void FAnimNode_OLSMask::InvalidateCachedBoneData()
{
RequiredBonesSerialNumber = 0;
}
void FAnimNode_OLSMask::RebuildPerBoneBlendWeights(const USkeleton* skeleton)
{
if (skeleton)
{
if (BlendMasks.Num() > 0)
{
PerBoneBlendWeights.SetNum(BlendMasks.Num());
TArray<UBlendProfile*> currentBlendProfile;
for (int32 Index = 0; Index < BlendMasks.Num(); ++Index)
{
if (BlendMasks[Index].BlendProfile && BlendMasks[Index].BlendProfile->IsBlendMask())
{
currentBlendProfile.Reset();
currentBlendProfile.Add(BlendMasks[Index].BlendProfile);
FAnimationRuntime::CreateMaskWeights(PerBoneBlendWeights[Index].PerBoneBlendWeights,
currentBlendProfile, skeleton);
}
else
{
const FReferenceSkeleton& refSkeleton = skeleton->GetReferenceSkeleton();
const int32 numBones = refSkeleton.GetNum();
PerBoneBlendWeights[Index].PerBoneBlendWeights.Reset(numBones);
// We only store non-zero weights in blend masks. Initialize all to zero.
PerBoneBlendWeights[Index].PerBoneBlendWeights.AddZeroed(numBones);
}
}
}
SkeletonGuid = skeleton->GetGuid();
VirtualBoneGuid = skeleton->GetVirtualBoneGuid();
}
}
bool FAnimNode_OLSMask::ArePerBoneBlendWeightsValid(const USkeleton* skeleton) const
{
return (skeleton && skeleton->GetGuid() == SkeletonGuid && skeleton->GetVirtualBoneGuid() == VirtualBoneGuid);
}
void FAnimNode_OLSMask::UpdateCachedBoneData(const FBoneContainer& requiredBones, const USkeleton* skeleton)
{
if (requiredBones.GetSerialNumber() == RequiredBonesSerialNumber)
{
return;
}
if (requiredBones.GetBoneIndicesArray().Num() > 0)
{
if (!ArePerBoneBlendWeightsValid(skeleton))
{
RebuildPerBoneBlendWeights(skeleton);
}
for (int32 Index = 0; Index < BlendMasks.Num(); ++Index)
{
UpdateBodyPartCachedBoneData(requiredBones, skeleton, Index);
}
RequiredBonesSerialNumber = requiredBones.GetSerialNumber();
}
}
void FAnimNode_OLSMask::UpdateBodyPartCachedBoneData(const FBoneContainer& requiredBones, const USkeleton* skeleton,
const int32 maskIndex)
{
const TArray<FBoneIndexType>& requiredBoneIndices = requiredBones.GetBoneIndicesArray();
const int32 numRequiredBones = requiredBoneIndices.Num();
AllBoneBlendWeights[maskIndex].LocalSpaceDesiredBoneBlendWeights.SetNumZeroed(numRequiredBones);
AllBoneBlendWeights[maskIndex].MeshSpaceDesiredBoneBlendWeights.SetNumZeroed(numRequiredBones);
for (int32 requiredBoneIndex = 0; requiredBoneIndex < numRequiredBones; requiredBoneIndex++)
{
const int32 skeletonBoneIndex = requiredBones.GetSkeletonIndex(FCompactPoseBoneIndex(requiredBoneIndex));
if (ensure(skeletonBoneIndex != INDEX_NONE))
{
AllBoneBlendWeights[maskIndex].LocalSpaceDesiredBoneBlendWeights[requiredBoneIndex] = PerBoneBlendWeights[
maskIndex].PerBoneBlendWeights[skeletonBoneIndex];
AllBoneBlendWeights[maskIndex].MeshSpaceDesiredBoneBlendWeights[requiredBoneIndex] = PerBoneBlendWeights[
maskIndex].PerBoneBlendWeights[skeletonBoneIndex];
}
}
AllBoneBlendWeights[maskIndex].LocalSpaceCurrentBoneBlendWeights.Reset(
AllBoneBlendWeights[maskIndex].LocalSpaceDesiredBoneBlendWeights.Num());
AllBoneBlendWeights[maskIndex].MeshSpaceCurrentBoneBlendWeights.Reset(
AllBoneBlendWeights[maskIndex].MeshSpaceDesiredBoneBlendWeights.Num());
AllBoneBlendWeights[maskIndex].LocalSpaceCurrentBoneBlendWeights.AddZeroed(
AllBoneBlendWeights[maskIndex].LocalSpaceDesiredBoneBlendWeights.Num());
AllBoneBlendWeights[maskIndex].MeshSpaceCurrentBoneBlendWeights.AddZeroed(
AllBoneBlendWeights[maskIndex].MeshSpaceDesiredBoneBlendWeights.Num());
TArray<float> lsDesiredBlendWeight;
lsDesiredBlendWeight.Add(MaskBlendWeights[maskIndex].LocalSpaceBlendAlpha);
TArray<float> msDesiredBlendWeight;
msDesiredBlendWeight.Add(MaskBlendWeights[maskIndex].MeshSpaceBlendAlpha);
FAnimationRuntime::UpdateDesiredBoneWeight(AllBoneBlendWeights[maskIndex].LocalSpaceDesiredBoneBlendWeights,
AllBoneBlendWeights[maskIndex].LocalSpaceCurrentBoneBlendWeights,
lsDesiredBlendWeight);
FAnimationRuntime::UpdateDesiredBoneWeight(AllBoneBlendWeights[maskIndex].MeshSpaceDesiredBoneBlendWeights,
AllBoneBlendWeights[maskIndex].MeshSpaceCurrentBoneBlendWeights,
msDesiredBlendWeight);
// Build curve source indices
AllBoneBlendWeights[maskIndex].LocalCurvePoseSourceIndices.Empty();
AllBoneBlendWeights[maskIndex].LocalCurvePoseSourceIndices.Reserve(skeleton->GetNumCurveMetaData());
skeleton->ForEachCurveMetaData(
[this, &requiredBones, maskIndex](const FName& curveName, const FCurveMetaData& metaData)
{
for (const FBoneReference& linkedBone : metaData.LinkedBones)
{
FCompactPoseBoneIndex compactPoseIndex = linkedBone.GetCompactPoseIndex(requiredBones);
if (compactPoseIndex != INDEX_NONE)
{
if (AllBoneBlendWeights[maskIndex].LocalSpaceDesiredBoneBlendWeights[compactPoseIndex.GetInt()].BlendWeight > 0.f)
{
AllBoneBlendWeights[maskIndex].LocalCurvePoseSourceIndices.Add(
curveName,
AllBoneBlendWeights[maskIndex].LocalSpaceDesiredBoneBlendWeights[compactPoseIndex.GetInt()].
SourceIndex);
break;
}
}
}
});
// Build curve source indices
AllBoneBlendWeights[maskIndex].MeshCurvePoseSourceIndices.Empty();
AllBoneBlendWeights[maskIndex].MeshCurvePoseSourceIndices.Reserve(skeleton->GetNumCurveMetaData());
skeleton->ForEachCurveMetaData(
[this, &requiredBones, maskIndex](const FName& curveName, const FCurveMetaData& metaData)
{
for (const FBoneReference& linkedBone : metaData.LinkedBones)
{
FCompactPoseBoneIndex compactPoseIndex = linkedBone.GetCompactPoseIndex(requiredBones);
if (compactPoseIndex != INDEX_NONE)
{
if (AllBoneBlendWeights[maskIndex].MeshSpaceDesiredBoneBlendWeights[compactPoseIndex.GetInt()].
BlendWeight > 0.f)
{
AllBoneBlendWeights[maskIndex].MeshCurvePoseSourceIndices.Add(
curveName,
AllBoneBlendWeights[maskIndex].MeshSpaceDesiredBoneBlendWeights[compactPoseIndex.GetInt()].
SourceIndex);
break;
}
}
}
});
}
void FAnimNode_OLSMask::UpdateWeightsFromCurves(const USkeleton* skeleton, const FPoseContext& desiredCurvesPose,
const int32 index)
{
if (BlendMasks[index].WeightCurves.OverrideCurve != NAME_None)
{
MaskBlendWeights[index].OverrideAlpha = desiredCurvesPose.Curve.Get(
BlendMasks[index].WeightCurves.OverrideCurve);
}
if (BlendMasks[index].WeightCurves.AdditiveCurve != NAME_None)
{
MaskBlendWeights[index].AdditiveAlpha = desiredCurvesPose.Curve.Get(
BlendMasks[index].WeightCurves.AdditiveCurve);
}
if (BlendMasks[index].WeightCurves.LocalSpaceBlendCurve != NAME_None)
{
MaskBlendWeights[index].LocalSpaceBlendAlpha = desiredCurvesPose.Curve.Get(
BlendMasks[index].WeightCurves.LocalSpaceBlendCurve);
}
if (BlendMasks[index].WeightCurves.MeshSpaceBlendCurve != NAME_None)
{
MaskBlendWeights[index].MeshSpaceBlendAlpha = desiredCurvesPose.Curve.Get(
BlendMasks[index].WeightCurves.MeshSpaceBlendCurve);
}
}
void FAnimNode_OLSMask::MakeAdditivePose(FPoseContext& outAdditivePose, FPoseContext& outBasePose,
const bool shouldUseMeshSpaceAdditive)
{
if (shouldUseMeshSpaceAdditive)
{
FAnimationRuntime::ConvertPoseToMeshRotation(outAdditivePose.Pose);
FAnimationRuntime::ConvertPoseToMeshRotation(outBasePose.Pose);
}
FAnimationRuntime::ConvertPoseToAdditive(outAdditivePose.Pose, outBasePose.Pose);
outAdditivePose.Curve.ConvertToAdditive(outBasePose.Curve);
UE::Anim::Attributes::ConvertToAdditive(outBasePose.CustomAttributes, outAdditivePose.CustomAttributes);
}