Files
UnrealEngine/Engine/Plugins/Animation/PoseSearch/Source/Runtime/Private/PoseSearchSchema.cpp
2025-05-18 13:04:45 +08:00

422 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PoseSearch/PoseSearchSchema.h"
#include "Animation/MirrorDataTable.h"
#include "AnimationRuntime.h"
#include "PoseSearch/PoseSearchContext.h"
#include "PoseSearch/PoseSearchDefines.h"
#include "PoseSearch/PoseSearchResult.h"
#include "PoseSearch/PoseSearchFeatureChannel_Padding.h"
#include "PoseSearch/PoseSearchFeatureChannel_PermutationTime.h"
#include "PoseSearch/PoseSearchFeatureChannel_Pose.h"
#include "PoseSearch/PoseSearchFeatureChannel_Trajectory.h"
#include "UObject/ObjectSaveContext.h"
PRAGMA_DISABLE_DEPRECATION_WARNINGS
FPoseSearchRoledSkeleton::FPoseSearchRoledSkeleton(const FPoseSearchRoledSkeleton&) = default;
FPoseSearchRoledSkeleton& FPoseSearchRoledSkeleton::operator=(const FPoseSearchRoledSkeleton&) = default;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
void UPoseSearchSchema::AddChannel(UPoseSearchFeatureChannel* Channel)
{
Channels.Add(Channel);
}
void UPoseSearchSchema::AddTemporaryChannel(UPoseSearchFeatureChannel* TemporaryChannel)
{
TemporaryChannel->Finalize(this);
FinalizedChannels.Add(TemporaryChannel);
}
TConstArrayView<float> UPoseSearchSchema::BuildQuery(UE::PoseSearch::FSearchContext& SearchContext) const
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_PoseSearch_BuildQuery);
SearchContext.AddNewFeatureVectorBuilder(this);
for (const TObjectPtr<UPoseSearchFeatureChannel>& ChannelPtr : GetChannels())
{
ChannelPtr->BuildQuery(SearchContext);
}
return SearchContext.EditFeatureVector();
}
void UPoseSearchSchema::AddSkeleton(USkeleton* Skeleton, UMirrorDataTable* MirrorDataTable, const UE::PoseSearch::FRole& Role)
{
FPoseSearchRoledSkeleton& RoledSkeleton = Skeletons.AddDefaulted_GetRef();
RoledSkeleton.Skeleton = Skeleton;
RoledSkeleton.MirrorDataTable = MirrorDataTable;
RoledSkeleton.Role = Role;
}
bool UPoseSearchSchema::AreSkeletonsCompatible(const UPoseSearchSchema* Other) const
{
if (Skeletons.Num() != Other->Skeletons.Num())
{
return false;
}
for (int32 SkeletonIndex = 0; SkeletonIndex < Skeletons.Num(); ++SkeletonIndex)
{
if (Skeletons[SkeletonIndex].Skeleton != Other->Skeletons[SkeletonIndex].Skeleton)
{
return false;
}
if (Skeletons[SkeletonIndex].Role != Other->Skeletons[SkeletonIndex].Role)
{
return false;
}
}
return true;
}
void UPoseSearchSchema::AddDefaultChannels()
{
// defaulting UPoseSearchSchema for a meaningful locomotion setup
AddChannel(NewObject<UPoseSearchFeatureChannel_Trajectory>(this, NAME_None, RF_Transactional));
AddChannel(NewObject<UPoseSearchFeatureChannel_Pose>(this, NAME_None, RF_Transactional));
Finalize();
}
void UPoseSearchSchema::InitBoneContainersFromRoledSkeleton(TMap<FName, FBoneContainer>& RoledBoneContainers) const
{
RoledBoneContainers.Reset();
RoledBoneContainers.Reserve(Skeletons.Num());
for (const FPoseSearchRoledSkeleton& RoledSkeleton : Skeletons)
{
FBoneContainer& RoledBoneContainer = RoledBoneContainers.Add(RoledSkeleton.Role);
// Add a curve filter to our bone container to only eval curves actually used by the schema.
const UE::Anim::FCurveFilterSettings CurveFilterSettings(UE::Anim::ECurveFilterMode::AllowOnlyFiltered, &RoledSkeleton.RequiredCurves);
PRAGMA_DISABLE_DEPRECATION_WARNINGS
RoledBoneContainer.InitializeTo(RoledSkeleton.BoneIndicesWithParents_DEPRECATED, CurveFilterSettings, *RoledSkeleton.Skeleton);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
}
bool UPoseSearchSchema::AllRoledSkeletonHaveMirrorDataTable() const
{
for (const FPoseSearchRoledSkeleton& RoledSkeleton : Skeletons)
{
if (!RoledSkeleton.MirrorDataTable)
{
return false;
}
}
return true;
}
const FPoseSearchRoledSkeleton* UPoseSearchSchema::GetRoledSkeleton(const UE::PoseSearch::FRole& Role) const
{
for (const FPoseSearchRoledSkeleton& RoledSkeleton : Skeletons)
{
if (RoledSkeleton.Role == Role)
{
return &RoledSkeleton;
}
}
return nullptr;
}
FPoseSearchRoledSkeleton* UPoseSearchSchema::GetRoledSkeleton(const UE::PoseSearch::FRole& Role)
{
for (FPoseSearchRoledSkeleton& RoledSkeleton : Skeletons)
{
if (RoledSkeleton.Role == Role)
{
return &RoledSkeleton;
}
}
return nullptr;
}
const UE::PoseSearch::FRole UPoseSearchSchema::GetDefaultRole() const
{
if (!Skeletons.IsEmpty())
{
return Skeletons[0].Role;
}
return UE::PoseSearch::DefaultRole;
}
USkeleton* UPoseSearchSchema::GetSkeleton(const UE::PoseSearch::FRole& Role) const
{
if (const FPoseSearchRoledSkeleton* RoledSkeleton = GetRoledSkeleton(Role))
{
return RoledSkeleton->Skeleton.Get();
}
return nullptr;
}
UMirrorDataTable* UPoseSearchSchema::GetMirrorDataTable(const UE::PoseSearch::FRole& Role) const
{
if (const FPoseSearchRoledSkeleton* RoledSkeleton = GetRoledSkeleton(Role))
{
return RoledSkeleton->MirrorDataTable.Get();
}
return nullptr;
}
TConstArrayView<FBoneReference> UPoseSearchSchema::GetBoneReferences(const UE::PoseSearch::FRole& Role) const
{
const FPoseSearchRoledSkeleton* RoledSkeleton = GetRoledSkeleton(Role);
check(RoledSkeleton);
return RoledSkeleton->BoneReferences;
}
int8 UPoseSearchSchema::AddBoneReference(const FBoneReference& BoneReference, const UE::PoseSearch::FRole& Role)
{
return AddBoneReference(BoneReference, Role, true);
}
int8 UPoseSearchSchema::AddBoneReference(const FBoneReference& BoneReference, const UE::PoseSearch::FRole& Role, bool bDefaultWithRootBone)
{
using namespace UE::PoseSearch;
FPoseSearchRoledSkeleton* RoledSkeleton = GetRoledSkeleton(Role);
if (!RoledSkeleton)
{
UE_LOG(LogPoseSearch, Error, TEXT("UPoseSearchSchema::AddBoneReference: couldn't find data for the requested Role '%s' in UPoseSearchSchema '%s'"), *Role.ToString(), *GetNameSafe(this));
return InvalidSchemaBoneIdx;
}
int32 SchemaBoneIdx = 0;
const USkeleton* Skeleton = RoledSkeleton->Skeleton;
if (!Skeleton)
{
UE_LOG(LogPoseSearch, Error, TEXT("UPoseSearchSchema::AddBoneReference: couldn't find Skeleton with Role '%s' in UPoseSearchSchema '%s'"), *Role.ToString(), *GetNameSafe(this));
return InvalidSchemaBoneIdx;
}
bool bDefaultToRootBone = true;
FBoneReference TempBoneReference = BoneReference;
if (TempBoneReference.BoneName != NAME_None)
{
TempBoneReference.Initialize(Skeleton);
if (!TempBoneReference.HasValidSetup())
{
UE_LOG(LogPoseSearch, Error, TEXT("UPoseSearchSchema::AddBoneReference: couldn't initialize FBoneReference '%s' with Skeleton '%s' with Role '%s' in UPoseSearchSchema '%s'"),
*TempBoneReference.BoneName.ToString(), *GetNameSafe(Skeleton), *Role.ToString(), *GetNameSafe(this));
return InvalidSchemaBoneIdx;
}
}
else if (bDefaultWithRootBone)
{
TempBoneReference.BoneName = Skeleton->GetReferenceSkeleton().GetBoneName(int32(RootBoneIndexType));
TempBoneReference.Initialize(Skeleton);
check(TempBoneReference.HasValidSetup());
}
else
{
return TrajectorySchemaBoneIdx;
}
SchemaBoneIdx = RoledSkeleton->BoneReferences.AddUnique(TempBoneReference);
check(SchemaBoneIdx >= 0 && SchemaBoneIdx < 128);
return int8(SchemaBoneIdx);
}
int8 UPoseSearchSchema::AddCurveReference(const FName& CurveReference, const UE::PoseSearch::FRole& Role)
{
using namespace UE::PoseSearch;
FPoseSearchRoledSkeleton* RoledSkeleton = GetRoledSkeleton(Role);
if (!RoledSkeleton)
{
UE_LOG(LogPoseSearch, Error, TEXT("UPoseSearchSchema::AddCurveReference: couldn't find data for the requested Role '%s' in UPoseSearchSchema '%s'"), *Role.ToString(), *GetNameSafe(this));
return InvalidSchemaCurveIdx;
}
const USkeleton* Skeleton = RoledSkeleton->Skeleton;
if (!Skeleton)
{
UE_LOG(LogPoseSearch, Error, TEXT("UPoseSearchSchema::AddCurveReference: couldn't find Skeleton with Role '%s' in UPoseSearchSchema '%s'"), *Role.ToString(), *GetNameSafe(this));
return InvalidSchemaCurveIdx;
}
// Curves are loosely bound, so there's no guarantee this curve will ever exist in any of the assets indexed by the database.
const int32 CurveIdx = RoledSkeleton->RequiredCurves.AddUnique(CurveReference);
check(CurveIdx >= 0 && CurveIdx < 128);
return int8(CurveIdx);
}
void UPoseSearchSchema::ResetFinalize()
{
for (FPoseSearchRoledSkeleton& RoledSkeleton : Skeletons)
{
RoledSkeleton.BoneReferences.Reset();
PRAGMA_DISABLE_DEPRECATION_WARNINGS
RoledSkeleton.BoneIndicesWithParents_DEPRECATED.Reset();
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
FinalizedChannels.Reset();
SchemaCardinality = 0;
}
void UPoseSearchSchema::Finalize()
{
using namespace UE::PoseSearch;
ResetFinalize();
// adding as first bone reference the root bone
for (int32 RoledSkeletonIndex = 0; RoledSkeletonIndex < Skeletons.Num(); ++RoledSkeletonIndex)
{
const FPoseSearchRoledSkeleton& RoledSkeleton = Skeletons[RoledSkeletonIndex];
for (int32 ComparisonIndex = RoledSkeletonIndex + 1; ComparisonIndex < Skeletons.Num(); ++ComparisonIndex)
{
if (Skeletons[ComparisonIndex].Role == RoledSkeleton.Role)
{
UE_LOG(LogPoseSearch, Error, TEXT("UPoseSearchSchema::Finalize: couldn't Finalize '%s' because of duplicate Role '%s' in Skeletons"), *GetNameSafe(this), *RoledSkeleton.Role.ToString());
ResetFinalize();
return;
}
}
const int8 SchemaBoneIdx = AddBoneReference(FBoneReference(), RoledSkeleton.Role);
if (SchemaBoneIdx != RootSchemaBoneIdx)
{
UE_LOG(LogPoseSearch, Error, TEXT("UPoseSearchSchema::Finalize: couldn't Finalize '%s' because couldn't initialize root bone properly"), *GetNameSafe(this));
ResetFinalize();
return;
}
}
for (const TObjectPtr<UPoseSearchFeatureChannel>& ChannelPtr : Channels)
{
if (ChannelPtr)
{
FinalizedChannels.Add(ChannelPtr);
if (!ChannelPtr->Finalize(this))
{
#if WITH_EDITOR
TLabelBuilder LabelBuilder;
FString Label = ChannelPtr->GetLabel(LabelBuilder).ToString();
UE_LOG(LogPoseSearch, Error, TEXT("UPoseSearchSchema::Finalize: couldn't Finalize '%s' because of Channel '%s'"), *GetNameSafe(this), *Label);
#else // WITH_EDITOR
UE_LOG(LogPoseSearch, Error, TEXT("UPoseSearchSchema::Finalize: couldn't Finalize '%s' because of Channel '%s'"), *GetNameSafe(this), *GetNameSafe(ChannelPtr));
#endif // WITH_EDITOR
ResetFinalize();
return;
}
}
}
// AddDependentChannels can add channels to FinalizedChannels, so we need a while loop
int32 ChannelIndex = 0;
while (ChannelIndex < FinalizedChannels.Num())
{
FinalizedChannels[ChannelIndex]->AddDependentChannels(this);
++ChannelIndex;
}
for (const TObjectPtr<UPoseSearchFeatureChannel>& ChannelPtr : FinalizedChannels)
{
check(ChannelPtr);
if (ChannelPtr->GetPermutationTimeType() != EPermutationTimeType::UseSampleTime)
{
// there's at least one channel that uses UsePermutationTime or UseSampleToPermutationTime: we automatically add a UPoseSearchFeatureChannel_PermutationTime if not already in the schema
UPoseSearchFeatureChannel_PermutationTime::FindOrAddToSchema(this);
break;
}
}
// adding padding if required
if (bAddDataPadding)
{
// calculating how many floats of padding are required to make the data 16 bytes padded
const int32 PaddingSize = SchemaCardinality % (16 / sizeof(float));
if (PaddingSize > 0)
{
UPoseSearchFeatureChannel_Padding::AddToSchema(this, PaddingSize);
}
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
// Initialize references to obtain bone indices and fill out bone index array
for (FPoseSearchRoledSkeleton& RoledSkeleton : Skeletons)
{
for (FBoneReference& BoneRef : RoledSkeleton.BoneReferences)
{
check(BoneRef.HasValidSetup());
RoledSkeleton.BoneIndicesWithParents_DEPRECATED.AddUnique(BoneRef.BoneIndex);
if (RoledSkeleton.MirrorDataTable)
{
if (RoledSkeleton.MirrorDataTable->BoneToMirrorBoneIndex.IsValidIndex(BoneRef.BoneIndex))
{
const FSkeletonPoseBoneIndex MirroredBoneIndex = RoledSkeleton.MirrorDataTable->BoneToMirrorBoneIndex[BoneRef.BoneIndex];
if (MirroredBoneIndex.IsValid())
{
RoledSkeleton.BoneIndicesWithParents_DEPRECATED.AddUnique(MirroredBoneIndex.GetInt());
}
}
else
{
UE_LOG(LogPoseSearch, Warning, TEXT("UPoseSearchSchema::Finalize: couldn't Finalize '%s' because bone index doest not exist in mirror table or mirror table is empty."), *GetNameSafe(this));
}
}
}
// Build separate index array with parent indices guaranteed to be present. Sort for EnsureParentsPresent.
check(!RoledSkeleton.BoneIndicesWithParents_DEPRECATED.IsEmpty());
RoledSkeleton.BoneIndicesWithParents_DEPRECATED.Sort();
FAnimationRuntime::EnsureParentsPresent(RoledSkeleton.BoneIndicesWithParents_DEPRECATED, RoledSkeleton.Skeleton->GetReferenceSkeleton());
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
void UPoseSearchSchema::PostLoad()
{
Super::PostLoad();
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (Skeleton_DEPRECATED)
{
Skeletons.AddDefaulted_GetRef().Skeleton = Skeleton_DEPRECATED;
Skeleton_DEPRECATED = nullptr;
}
if (MirrorDataTable_DEPRECATED)
{
if (Skeletons.IsEmpty())
{
Skeletons.AddDefaulted_GetRef().MirrorDataTable = MirrorDataTable_DEPRECATED;
}
else
{
Skeletons[0].MirrorDataTable = MirrorDataTable_DEPRECATED;
}
MirrorDataTable_DEPRECATED = nullptr;
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
for (FPoseSearchRoledSkeleton& Skeleton : Skeletons)
{
if (Skeleton.MirrorDataTable)
{
// adding a ConditionalPostLoad dependency to UMirrorDataTable, that via UMirrorDataTable::FillMirrorArrays
// populates UMirrorDataTable::BoneToMirrorBoneIndex used in UPoseSearchSchema::Finalize
Skeleton.MirrorDataTable->ConditionalPostLoad();
}
}
Finalize();
}
#if WITH_EDITOR
void UPoseSearchSchema::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Finalize();
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif