// Copyright Epic Games, Inc. All Rights Reserved. #include "SkeletonTreeBuilder.h" #include "IPersonaPreviewScene.h" #include "SkeletonTreeBoneItem.h" #include "SkeletonTreeSocketItem.h" #include "SkeletonTreeAttachedAssetItem.h" #include "SkeletonTreeVirtualBoneItem.h" #include "Animation/DebugSkelMeshComponent.h" #define LOCTEXT_NAMESPACE "SkeletonTreeBuilder" void FSkeletonTreeBuilderOutput::Add(const TSharedPtr& InItem, const FName& InParentName, const FName& InParentType, bool bAddToHead) { Add(InItem, InParentName, TArray>({ InParentType }), bAddToHead); } void FSkeletonTreeBuilderOutput::Add(const TSharedPtr& InItem, const FName& InParentName, TArrayView InParentTypes, bool bAddToHead) { TSharedPtr ParentItem = Find(InParentName, InParentTypes); if (ParentItem.IsValid()) { InItem->SetParent(ParentItem); if (bAddToHead) { ParentItem->GetChildren().Insert(InItem, 0); } else { ParentItem->GetChildren().Add(InItem); } } else { if (bAddToHead) { Items.Insert(InItem, 0); } else { Items.Add(InItem); } } LinearItems.Add(InItem); } TSharedPtr FSkeletonTreeBuilderOutput::Find(const FName& InName, const FName& InType) const { return Find(InName, TArray>({ InType })); } TSharedPtr FSkeletonTreeBuilderOutput::Find(const FName& InName, TArrayView InTypes) const { for (const TSharedPtr& Item : LinearItems) { bool bPassesType = (InTypes.Num() == 0); for (const FName& TypeName : InTypes) { if (Item->IsOfTypeByName(TypeName)) { bPassesType = true; break; } } if (bPassesType && Item->GetAttachName() == InName) { return Item; } } return nullptr; } FSkeletonTreeBuilder::FSkeletonTreeBuilder(const FSkeletonTreeBuilderArgs& InBuilderArgs) : BuilderArgs(InBuilderArgs) { } void FSkeletonTreeBuilder::Initialize(const TSharedRef& InSkeletonTree, const TSharedPtr& InPreviewScene, FOnFilterSkeletonTreeItem InOnFilterSkeletonTreeItem) { SkeletonTreePtr = InSkeletonTree; EditableSkeletonPtr = InSkeletonTree->GetEditableSkeleton(); PreviewScenePtr = InPreviewScene; OnFilterSkeletonTreeItem = InOnFilterSkeletonTreeItem; } void FSkeletonTreeBuilder::Build(FSkeletonTreeBuilderOutput& Output) { if(BuilderArgs.bShowBones) { AddBones(Output); } if (BuilderArgs.bShowVirtualBones) { AddVirtualBones(Output); } if (BuilderArgs.bShowSockets) { AddSockets(Output); } if (BuilderArgs.bShowAttachedAssets) { AddAttachedAssets(Output); } } void FSkeletonTreeBuilder::Filter(const FSkeletonTreeFilterArgs& InArgs, const TArray>& InItems, TArray>& OutFilteredItems) { OutFilteredItems.Empty(); for (const TSharedPtr& Item : InItems) { if (InArgs.TextFilter.IsValid() && InArgs.bFlattenHierarchyOnFilter) { FilterRecursive(InArgs, Item, OutFilteredItems); } else { ESkeletonTreeFilterResult FilterResult = FilterRecursive(InArgs, Item, OutFilteredItems); if (FilterResult != ESkeletonTreeFilterResult::Hidden) { OutFilteredItems.Add(Item); } Item->SetFilterResult(FilterResult); } } } ESkeletonTreeFilterResult FSkeletonTreeBuilder::FilterRecursive(const FSkeletonTreeFilterArgs& InArgs, const TSharedPtr& InItem, TArray>& OutFilteredItems) { ESkeletonTreeFilterResult FilterResult = ESkeletonTreeFilterResult::Shown; InItem->GetFilteredChildren().Empty(); if (InArgs.TextFilter.IsValid() && InArgs.bFlattenHierarchyOnFilter) { FilterResult = FilterItem(InArgs, InItem); InItem->SetFilterResult(FilterResult); if (FilterResult != ESkeletonTreeFilterResult::Hidden) { OutFilteredItems.Add(InItem); } for (const TSharedPtr& Item : InItem->GetChildren()) { FilterRecursive(InArgs, Item, OutFilteredItems); } } else { // check to see if we have any children that pass the filter ESkeletonTreeFilterResult DescendantsFilterResult = ESkeletonTreeFilterResult::Hidden; for (const TSharedPtr& Item : InItem->GetChildren()) { ESkeletonTreeFilterResult ChildResult = FilterRecursive(InArgs, Item, OutFilteredItems); if (ChildResult != ESkeletonTreeFilterResult::Hidden) { InItem->GetFilteredChildren().Add(Item); } if (ChildResult > DescendantsFilterResult) { DescendantsFilterResult = ChildResult; } } FilterResult = FilterItem(InArgs, InItem); if (DescendantsFilterResult > FilterResult) { FilterResult = ESkeletonTreeFilterResult::ShownDescendant; } InItem->SetFilterResult(FilterResult); } return FilterResult; } ESkeletonTreeFilterResult FSkeletonTreeBuilder::FilterItem(const FSkeletonTreeFilterArgs& InArgs, const TSharedPtr& InItem) { return OnFilterSkeletonTreeItem.Execute(InArgs, InItem); } bool FSkeletonTreeBuilder::IsShowingBones() const { return BuilderArgs.bShowBones; } bool FSkeletonTreeBuilder::IsShowingSockets() const { return BuilderArgs.bShowSockets; } bool FSkeletonTreeBuilder::IsShowingAttachedAssets() const { return BuilderArgs.bShowAttachedAssets; } void FSkeletonTreeBuilder::AddBones(FSkeletonTreeBuilderOutput& Output) { const USkeleton& Skeleton = EditableSkeletonPtr.Pin()->GetSkeleton(); const FReferenceSkeleton& RefSkeleton = Skeleton.GetReferenceSkeleton(); struct FBoneInfo { FBoneInfo(const FName& InBoneName, const FName& InParentName, int32 InDepth) : BoneName(InBoneName) , ParentName(InParentName) , Depth(InDepth) { SortString = BoneName.ToString(); SortNumber = 0; SortLength = SortString.Len(); // Split the bone name into string prefix and numeric suffix for sorting (different from FName to support leading zeros in the numeric suffix) int32 Index = SortLength - 1; for (int32 PlaceValue = 1; Index >= 0 && FChar::IsDigit(SortString[Index]); --Index, PlaceValue *= 10) { SortNumber += static_cast(SortString[Index] - '0') * PlaceValue; } SortString.LeftInline(Index + 1, EAllowShrinking::No); } bool operator<(const FBoneInfo& RHS) { // Sort parents before children if (Depth != RHS.Depth) { return Depth < RHS.Depth; } // Sort alphabetically by string prefix if (int32 SplitNameComparison = SortString.Compare(RHS.SortString)) { return SplitNameComparison < 0; } // Sort by number if the string prefixes match if (SortNumber != RHS.SortNumber) { return SortNumber < RHS.SortNumber; } // Sort by length to give us the equivalent to alphabetical sorting if the numbers match (which gives us the following sort order: bone_, bone_0, bone_00, bone_000, bone_001, bone_01, bone_1, etc) return (SortNumber == 0) ? SortLength < RHS.SortLength : SortLength > RHS.SortLength; } FName BoneName = NAME_None; FName ParentName = NAME_None; int32 Depth = 0; FString SortString; int32 SortNumber = 0; int32 SortLength = 0; }; TArray Bones; Bones.Reserve(RefSkeleton.GetRawBoneNum()); // Gather the bones from the skeleton for (int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetRawBoneNum(); ++BoneIndex) { const FName& BoneName = RefSkeleton.GetBoneName(BoneIndex); FName ParentName = NAME_None; int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex); int32 Depth = 0; if (ParentIndex != INDEX_NONE) { ParentName = RefSkeleton.GetBoneName(ParentIndex); Depth = Bones[ParentIndex].Depth + 1; } Bones.Emplace(BoneName, ParentName, Depth); } // Sort the bones lexically (and also by depth in order to maintain the invariant of parents before children) Algo::Sort(Bones); // Add the sorted bones to the skeleton tree for (const FBoneInfo& Bone : Bones) { TSharedRef DisplayBone = CreateBoneTreeItem(Bone.BoneName); Output.Add(DisplayBone, Bone.ParentName, FSkeletonTreeBoneItem::GetTypeId()); } } void FSkeletonTreeBuilder::AddSockets(FSkeletonTreeBuilderOutput& Output) { const USkeleton& Skeleton = EditableSkeletonPtr.Pin()->GetSkeleton(); // Add the sockets for the skeleton AddSocketsFromData(Skeleton.Sockets, ESocketParentType::Skeleton, Output); // Add the sockets for the mesh if (PreviewScenePtr.IsValid()) { UDebugSkelMeshComponent* PreviewMeshComponent = PreviewScenePtr.Pin()->GetPreviewMeshComponent(); if (USkeletalMesh* const SkeletalMesh = PreviewMeshComponent->GetSkeletalMeshAsset()) { AddSocketsFromData(SkeletalMesh->GetMeshOnlySocketList(), ESocketParentType::Mesh, Output); } } } void FSkeletonTreeBuilder::AddAttachedAssets(FSkeletonTreeBuilderOutput& Output) { const USkeleton& Skeleton = EditableSkeletonPtr.Pin()->GetSkeleton(); // Mesh attached items... if (PreviewScenePtr.IsValid()) { UDebugSkelMeshComponent* PreviewMeshComponent = PreviewScenePtr.Pin()->GetPreviewMeshComponent(); if (USkeletalMesh* const SkeletalMesh = PreviewMeshComponent->GetSkeletalMeshAsset()) { AddAttachedAssetContainer(SkeletalMesh->GetPreviewAttachedAssetContainer(), Output); } } // ...skeleton attached items AddAttachedAssetContainer(Skeleton.PreviewAttachedAssetContainer, Output); } void FSkeletonTreeBuilder::AddSocketsFromData(const TArray< USkeletalMeshSocket* >& SocketArray, ESocketParentType ParentType, FSkeletonTreeBuilderOutput& Output) { const USkeleton& Skeleton = EditableSkeletonPtr.Pin()->GetSkeleton(); for (auto SocketIt = SocketArray.CreateConstIterator(); SocketIt; ++SocketIt) { USkeletalMeshSocket* Socket = *(SocketIt); bool bIsCustomized = false; if (ParentType == ESocketParentType::Mesh) { bIsCustomized = EditableSkeletonPtr.Pin()->DoesSocketAlreadyExist(nullptr, FText::FromName(Socket->SocketName), ESocketParentType::Skeleton, nullptr); } else { if (PreviewScenePtr.IsValid()) { UDebugSkelMeshComponent* PreviewMeshComponent = PreviewScenePtr.Pin()->GetPreviewMeshComponent(); if (USkeletalMesh* const SkeletalMesh = PreviewMeshComponent->GetSkeletalMeshAsset()) { bIsCustomized = EditableSkeletonPtr.Pin()->DoesSocketAlreadyExist(nullptr, FText::FromName(Socket->SocketName), ESocketParentType::Mesh, SkeletalMesh); } } } Output.Add(CreateSocketTreeItem(Socket, ParentType, bIsCustomized), Socket->BoneName, { FSkeletonTreeBoneItem::GetTypeId(), FSkeletonTreeVirtualBoneItem::GetTypeId() }, /*bAddToHead*/ true); } } void FSkeletonTreeBuilder::AddAttachedAssetContainer(const FPreviewAssetAttachContainer& AttachedObjects, FSkeletonTreeBuilderOutput& Output) { for (auto Iter = AttachedObjects.CreateConstIterator(); Iter; ++Iter) { const FPreviewAttachedObjectPair& Pair = (*Iter); Output.Add(CreateAttachedAssetTreeItem(Pair.GetAttachedObject(), Pair.AttachedTo), Pair.AttachedTo, { FSkeletonTreeBoneItem::GetTypeId(), FSkeletonTreeSocketItem::GetTypeId() }, /*bAddToHead*/ true); } } void FSkeletonTreeBuilder::AddVirtualBones(FSkeletonTreeBuilderOutput& Output) { const TArray& VirtualBones = EditableSkeletonPtr.Pin()->GetSkeleton().GetVirtualBones(); for (const FVirtualBone& VirtualBone : VirtualBones) { Output.Add(CreateVirtualBoneTreeItem(VirtualBone.VirtualBoneName), VirtualBone.SourceBoneName, { FSkeletonTreeBoneItem::GetTypeId(), FSkeletonTreeVirtualBoneItem::GetTypeId() }, /*bAddToHead*/ true); } } TSharedRef FSkeletonTreeBuilder::CreateBoneTreeItem(const FName& InBoneName) { return MakeShareable(new FSkeletonTreeBoneItem(InBoneName, SkeletonTreePtr.Pin().ToSharedRef())); } TSharedRef FSkeletonTreeBuilder::CreateSocketTreeItem(USkeletalMeshSocket* InSocket, ESocketParentType InParentType, bool bInIsCustomized) { return MakeShareable(new FSkeletonTreeSocketItem(InSocket, InParentType, bInIsCustomized, SkeletonTreePtr.Pin().ToSharedRef())); } TSharedRef FSkeletonTreeBuilder::CreateAttachedAssetTreeItem(UObject* InAsset, const FName& InAttachedTo) { return MakeShareable(new FSkeletonTreeAttachedAssetItem(InAsset, InAttachedTo, SkeletonTreePtr.Pin().ToSharedRef())); } TSharedRef FSkeletonTreeBuilder::CreateVirtualBoneTreeItem(const FName& InBoneName) { return MakeShareable(new FSkeletonTreeVirtualBoneItem(InBoneName, SkeletonTreePtr.Pin().ToSharedRef())); } #undef LOCTEXT_NAMESPACE