// © 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 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 lsTargetBlendPoses; lsTargetBlendPoses.SetNum(1); TArray msTargetBlendPoses; msTargetBlendPoses.SetNum(1); TArray lsTargetBlendCurves; lsTargetBlendCurves.SetNum(1); TArray msTargetBlendCurves; msTargetBlendCurves.SetNum(1); TArray lsTargetBlendAttributes; lsTargetBlendAttributes.SetNum(1); TArray 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 lsDesiredBlendWeight; lsDesiredBlendWeight.Add(MaskBlendWeights[maskIndex].LocalSpaceBlendAlpha); TArray 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 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& 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 lsDesiredBlendWeight; lsDesiredBlendWeight.Add(MaskBlendWeights[maskIndex].LocalSpaceBlendAlpha); TArray 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); }