Added validation and visual error functionality to AnimNode_SkeletalControlBase, which can be used to check referenced bones and show errors on the AnimGraph nodes that use them (so it is clear if the setup does not work due to a bone missing)

#rb thomas.sarkanen
#jira UE-160111
#preflight 63b85453c927e34482895de4

[CL 23599353 by jaime cifuentes in ue5-main branch]
This commit is contained in:
jaime cifuentes
2023-01-06 12:16:12 -05:00
parent f5ea45d372
commit a20a6e0027
9 changed files with 216 additions and 13 deletions
@@ -1773,17 +1773,9 @@ void FAnimNode_RigidBody::InitializeBoneReferences(const FBoneContainer& Require
BaseBoneRef.BoneName = RefSkeleton.GetBoneName(0);
}
if (BaseBoneRef.BoneName != NAME_None)
{
BaseBoneRef.Initialize(RequiredBones);
}
if (!BaseBoneRef.HasValidSetup())
{
// If the user specified a simulation root that is not used by the skelmesh, issue a warning
// (FAnimNode_RigidBody::IsValidToEvaluate will return false and the simulation will not run)
UE_LOG(LogRBAN, Log, TEXT("FAnimNode_RigidBody: RBAN Simulation Base Bone \'%s\' does not exist on SkeletalMesh %s."), *BaseBoneRef.BoneName.ToString(), *GetNameSafe(RequiredBones.GetSkeletalMeshAsset()));
}
// If the user specified a simulation root that is not used by the skelmesh, issue a warning
// (FAnimNode_RigidBody::IsValidToEvaluate will return false and the simulation will not run)
InitializeAndValidateBoneRef(BaseBoneRef, RequiredBones);
bool bHasInvalidBoneReference = false;
for (int32 Index = 0; Index < NumRequiredBoneIndices; ++Index)
@@ -1,11 +1,21 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "BoneControllers/AnimNode_SkeletalControlBase.h"
#include "Engine/SkeletalMesh.h"
#include "Animation/AnimInstanceProxy.h"
#include "Engine/SkeletalMeshSocket.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNode_SkeletalControlBase)
#define LOCTEXT_NAMESPACE "AnimNode_SkeletalControlBase"
#if UE_BUILD_SHIPPING || UE_BUILD_TEST
DECLARE_LOG_CATEGORY_EXTERN(LogSkeletalControlBase, Log, Warning);
#else
DECLARE_LOG_CATEGORY_EXTERN(LogSkeletalControlBase, Log, All);
#endif
DEFINE_LOG_CATEGORY(LogSkeletalControlBase);
/////////////////////////////////////////////////////
// FAnimNode_SkeletalControlBase
@@ -22,6 +32,10 @@ void FAnimNode_SkeletalControlBase::Initialize_AnyThread(const FAnimationInitial
void FAnimNode_SkeletalControlBase::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
{
#if WITH_EDITOR
ClearValidationVisualWarnings();
#endif
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(CacheBones_AnyThread)
FAnimNode_Base::CacheBones_AnyThread(Context);
InitializeBoneReferences(Context.AnimInstanceProxy->GetRequiredBones());
@@ -160,3 +174,65 @@ float FAnimNode_SkeletalControlBase::GetAlpha() const
{
return ActualAlpha;
}
void FAnimNode_SkeletalControlBase::InitializeAndValidateBoneRef(FBoneReference& BoneRef, const FBoneContainer& RequiredBones)
{
if (BoneRef.BoneName != NAME_None)
{
BoneRef.Initialize(RequiredBones);
}
if (!BoneRef.HasValidSetup())
{
const FText ErrorText = FText::Format(LOCTEXT("SkeletalControlBoneError", "Referenced Bone {0} does not exist on SkeletalMesh {1}."),
FText::AsCultureInvariant(BoneRef.BoneName.ToString()),
FText::AsCultureInvariant(GetNameSafe(RequiredBones.GetSkeletalMeshAsset())));
#if WITH_EDITOR
AddValidationVisualWarning(ErrorText);
#endif // WITH_EDITOR
// If the user specified a simulation root that is not used by the skelmesh, issue a warning
UE_LOG(LogSkeletalControlBase, Log, TEXT("%s"), *ErrorText.ToString());
}
}
#if WITH_EDITOR
void FAnimNode_SkeletalControlBase::AddBoneRefMissingVisualWarning(const FString& BoneName, const FString& SkeletalMeshName)
{
const FText ErrorText = FText::Format(LOCTEXT("SkeletalControlBoneError", "Simulation Base Bone {0} does not exist on SkeletalMesh {1}."), FText::FromString(BoneName), FText::FromString(SkeletalMeshName));
AddValidationVisualWarning(ErrorText);
}
void FAnimNode_SkeletalControlBase::AddValidationVisualWarning(FText ValidationVisualWarning)
{
#if WITH_EDITORONLY_DATA
if (ValidationVisualWarningMessage.IsEmpty())
{
ValidationVisualWarningMessage = ValidationVisualWarning;
}
else
{
ValidationVisualWarningMessage = FText::Format(FText::FromString(TEXT("{0}\n{1}")), ValidationVisualWarningMessage, ValidationVisualWarning);
}
#endif
}
void FAnimNode_SkeletalControlBase::ClearValidationVisualWarnings()
{
ValidationVisualWarningMessage = FText::GetEmpty();
}
bool FAnimNode_SkeletalControlBase::HasValidationVisualWarnings() const
{
return ValidationVisualWarningMessage.IsEmpty() == false;
}
FText FAnimNode_SkeletalControlBase::GetValidationVisualWarningMessage() const
{
return ValidationVisualWarningMessage;
}
#endif // WITH_EDITOR
#undef LOCTEXT_NAMESPACE
@@ -90,7 +90,19 @@ public:
// Get the alpha of this node
float GetAlpha() const;
void InitializeAndValidateBoneRef(FBoneReference& BoneRef, const FBoneContainer& RequiredBones);
// Visual warnings are shown on the node but not logged as an error for build system, use with care
// The warnigns are cleared at CacheBones_AnyThread and should be added during InitializeBoneReferences
#if WITH_EDITOR
void AddBoneRefMissingVisualWarning(const FString& BoneName, const FString& SkeletalMeshName);
void AddValidationVisualWarning(FText ValidationVisualWarning);
bool HasValidationVisualWarnings() const;
void ClearValidationVisualWarnings();
FText GetValidationVisualWarningMessage() const;
#endif
protected:
// Interface for derived skeletal controls to implement
// use this function to update for skeletal control base
@@ -113,8 +125,14 @@ protected:
/** Allow base to add info to the node debug output */
void AddDebugNodeData(FString& OutDebugData);
private:
private:
// Resused bone transform array to avoid reallocating in skeletal controls
TArray<FBoneTransform> BoneTransforms;
#if WITH_EDITORONLY_DATA
UPROPERTY(transient)
FText ValidationVisualWarningMessage;
#endif
};