// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "BoneControllers/AnimNode_SkeletalControlBase.h" #include "Animation/AnimInstanceProxy.h" #include "Engine/SkeletalMeshSocket.h" //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Socket Reference //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void FSocketReference::InitializeSocketInfo(const FAnimInstanceProxy* InAnimInstanceProxy) { CachedSocketMeshBoneIndex = INDEX_NONE; CachedSocketCompactBoneIndex = FCompactPoseBoneIndex(INDEX_NONE); if (SocketName != NAME_None) { const USkeletalMeshComponent* OwnerMeshComponent = InAnimInstanceProxy->GetSkelMeshComponent(); if (OwnerMeshComponent && OwnerMeshComponent->DoesSocketExist(SocketName)) { USkeletalMeshSocket const* const Socket = OwnerMeshComponent->GetSocketByName(SocketName); if (Socket) { CachedSocketLocalTransform = Socket->GetSocketLocalTransform(); // cache mesh bone index, so that we know this is valid information to follow CachedSocketMeshBoneIndex = OwnerMeshComponent->GetBoneIndex(Socket->BoneName); ensureMsgf(CachedSocketMeshBoneIndex != INDEX_NONE, TEXT("%s : socket has invalid bone."), *SocketName.ToString()); } } else { // @todo : move to graph node warning UE_LOG(LogAnimation, Warning, TEXT("%s: socket doesn't exist"), *SocketName.ToString()); } } } void FSocketReference::InitialzeCompactBoneIndex(const FBoneContainer& RequiredBones) { if (CachedSocketMeshBoneIndex != INDEX_NONE) { const int32 SocketBoneSkeletonIndex = RequiredBones.GetPoseToSkeletonBoneIndexArray()[CachedSocketMeshBoneIndex]; CachedSocketCompactBoneIndex = RequiredBones.GetCompactPoseIndexFromSkeletonIndex(SocketBoneSkeletonIndex); } } ///////////////////////////////////////////////////// // FAnimNode_SkeletalControlBase void FAnimNode_SkeletalControlBase::Initialize_AnyThread(const FAnimationInitializeContext& Context) { FAnimNode_Base::Initialize_AnyThread(Context); ComponentPose.Initialize(Context); AlphaBoolBlend.Reinitialize(); AlphaScaleBiasClamp.Reinitialize(); } void FAnimNode_SkeletalControlBase::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context) { FAnimNode_Base::CacheBones_AnyThread(Context); InitializeBoneReferences(Context.AnimInstanceProxy->GetRequiredBones()); ComponentPose.CacheBones(Context); } void FAnimNode_SkeletalControlBase::UpdateInternal(const FAnimationUpdateContext& Context) { } void FAnimNode_SkeletalControlBase::UpdateComponentPose_AnyThread(const FAnimationUpdateContext& Context) { ComponentPose.Update(Context); } void FAnimNode_SkeletalControlBase::Update_AnyThread(const FAnimationUpdateContext& Context) { UpdateComponentPose_AnyThread(Context); ActualAlpha = 0.f; if (IsLODEnabled(Context.AnimInstanceProxy)) { GetEvaluateGraphExposedInputs().Execute(Context); // Apply the skeletal control if it's valid switch (AlphaInputType) { case EAnimAlphaInputType::Float : ActualAlpha = AlphaScaleBias.ApplyTo(AlphaScaleBiasClamp.ApplyTo(Alpha, Context.GetDeltaTime())); break; case EAnimAlphaInputType::Bool : ActualAlpha = AlphaBoolBlend.ApplyTo(bAlphaBoolEnabled, Context.GetDeltaTime()); break; case EAnimAlphaInputType::Curve : if (UAnimInstance* AnimInstance = Cast(Context.AnimInstanceProxy->GetAnimInstanceObject())) { ActualAlpha = AlphaScaleBiasClamp.ApplyTo(AnimInstance->GetCurveValue(AlphaCurveName), Context.GetDeltaTime()); } break; }; // Make sure Alpha is clamped between 0 and 1. ActualAlpha = FMath::Clamp(ActualAlpha, 0.f, 1.f); if (FAnimWeight::IsRelevant(ActualAlpha) && IsValidToEvaluate(Context.AnimInstanceProxy->GetSkeleton(), Context.AnimInstanceProxy->GetRequiredBones())) { UpdateInternal(Context); } } } bool ContainsNaN(const TArray & BoneTransforms) { for (int32 i = 0; i < BoneTransforms.Num(); ++i) { if (BoneTransforms[i].Transform.ContainsNaN()) { return true; } } return false; } void FAnimNode_SkeletalControlBase::EvaluateComponentPose_AnyThread(FComponentSpacePoseContext& Output) { // Evaluate the input ComponentPose.EvaluateComponentSpace(Output); } void FAnimNode_SkeletalControlBase::EvaluateComponentSpaceInternal(FComponentSpacePoseContext& Context) { } void FAnimNode_SkeletalControlBase::EvaluateComponentSpace_AnyThread(FComponentSpacePoseContext& Output) { EvaluateComponentPose_AnyThread(Output); #if WITH_EDITORONLY_DATA // save current pose before applying skeletal control to compute the exact gizmo location in AnimGraphNode ForwardedPose.CopyPose(Output.Pose); #endif // #if WITH_EDITORONLY_DATA #if DO_CHECK // this is to ensure Source data does not contain NaN ensure(Output.ContainsNaN() == false); #endif // Apply the skeletal control if it's valid if (FAnimWeight::IsRelevant(ActualAlpha) && IsValidToEvaluate(Output.AnimInstanceProxy->GetSkeleton(), Output.AnimInstanceProxy->GetRequiredBones())) { EvaluateComponentSpaceInternal(Output); BoneTransforms.Reset(BoneTransforms.Num()); EvaluateSkeletalControl_AnyThread(Output, BoneTransforms); if (BoneTransforms.Num() > 0) { const float BlendWeight = FMath::Clamp(ActualAlpha, 0.f, 1.f); Output.Pose.LocalBlendCSBoneTransforms(BoneTransforms, BlendWeight); } // we check NaN when you get out of this function in void FComponentSpacePoseLink::EvaluateComponentSpace(FComponentSpacePoseContext& Output) } } void FAnimNode_SkeletalControlBase::AddDebugNodeData(FString& OutDebugData) { OutDebugData += FString::Printf(TEXT("Alpha: %.1f%%"), ActualAlpha*100.f); } void FAnimNode_SkeletalControlBase::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray& OutBoneTransforms) { PRAGMA_DISABLE_DEPRECATION_WARNINGS // Call legacy implementation for backwards compatibility EvaluateBoneTransforms(Output.AnimInstanceProxy->GetSkelMeshComponent(), Output.Pose, OutBoneTransforms); PRAGMA_ENABLE_DEPRECATION_WARNINGS }