// 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 UPoseSearchSchema::BuildQuery(UE::PoseSearch::FSearchContext& SearchContext) const { QUICK_SCOPE_CYCLE_COUNTER(STAT_PoseSearch_BuildQuery); SearchContext.AddNewFeatureVectorBuilder(this); for (const TObjectPtr& 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(this, NAME_None, RF_Transactional)); AddChannel(NewObject(this, NAME_None, RF_Transactional)); Finalize(); } void UPoseSearchSchema::InitBoneContainersFromRoledSkeleton(TMap& 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 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& 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& 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