Files
UnrealEngine/Engine/Source/Runtime/Experimental/Chaos/Private/GeometryCollection/GeometryCollectionClusteringUtility.cpp
2025-05-18 13:04:45 +08:00

1014 lines
33 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "GeometryCollection/GeometryCollectionClusteringUtility.h"
#include "GeometryCollection/GeometryCollection.h"
#include "GeometryCollection/GeometryCollectionAlgo.h"
#include "Containers/Set.h"
#include "Async/ParallelFor.h"
#include "GeometryCollection/Facades/CollectionHierarchyFacade.h"
static int32 ChaosValidateResultsOfEditOperations = 0;
static FAutoConsoleVariableRef CVarChaosStillCheckDistanceThreshold(TEXT("p.fracture.ValidateResultsOfEditOperations"), ChaosValidateResultsOfEditOperations, TEXT("When on this will enable result validation for fracture tool edit operations (can be slow for large geometry collection) [def:0]"));
int32 FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNode(FGeometryCollection* GeometryCollection, const int32 InsertAtIndex, const TArray<int32>& SelectedBones, bool CalcNewLocalTransform, bool Validate)
{
check(GeometryCollection);
TManagedArray<int32>& Parents = GeometryCollection->Parent;
int32 ParentIdx = InsertAtIndex < Parents.Num() ? Parents[InsertAtIndex] : INDEX_NONE;
return ClusterBonesUnderNewNodeWithParent(GeometryCollection, ParentIdx, SelectedBones, CalcNewLocalTransform, Validate);
}
int32 FGeometryCollectionClusteringUtility::ClusterBonesUnderNewNodeWithParent(FGeometryCollection* GeometryCollection, const int32 ParentOfNewNode, const TArray<int32>& SelectedBones, bool CalcNewLocalTransform, bool Validate)
{
check(GeometryCollection);
TManagedArray<FTransform3f>& Transforms = GeometryCollection->Transform;
TManagedArray<FString>& BoneNames = GeometryCollection->BoneName;
TManagedArray<int32>& Parents = GeometryCollection->Parent;
TManagedArray<TSet<int32>>& Children = GeometryCollection->Children;
TManagedArray<int32>& SimType = GeometryCollection->SimulationType;
// insert a new node between the selected bones and their shared parent
int NewBoneIndex = GeometryCollection->AddElements(1, FGeometryCollection::TransformGroup);
int32 OriginalParentIndex = ParentOfNewNode;
Parents[NewBoneIndex] = OriginalParentIndex;
Children[NewBoneIndex] = TSet<int32>(SelectedBones);
SimType[NewBoneIndex] = FGeometryCollection::ESimulationTypes::FST_Clustered;
Transforms[NewBoneIndex] = FTransform3f::Identity;
// re-parent all the geometry nodes under the new shared bone
GeometryCollectionAlgo::ParentTransforms(GeometryCollection, NewBoneIndex, SelectedBones);
UpdateHierarchyLevelOfChildren(GeometryCollection, NewBoneIndex);
// Parent Bone Fixup of Children - add the new node under the first bone selected
// #todo: might want to add it to the one closest to the root in the hierarchy
if (OriginalParentIndex != FGeometryCollection::Invalid)
{
Children[OriginalParentIndex].Add(NewBoneIndex);
}
// Update new cluster's bone name
BoneNames[NewBoneIndex] = FString::Printf(TEXT("ClusterBone_%d"), NewBoneIndex);
if (Validate)
{
ValidateResults(GeometryCollection);
}
return NewBoneIndex;
}
void FGeometryCollectionClusteringUtility::ClusterAllBonesUnderNewRoot(FGeometryCollection* GeometryCollection, FName RootName, bool bUpdateChildBoneNames)
{
check(GeometryCollection);
bool CalcNewLocalTransform = true;
TManagedArray<FTransform3f>& Transforms = GeometryCollection->Transform;
TManagedArray<FString>& BoneNames = GeometryCollection->BoneName;
TManagedArray<int32>& Parents = GeometryCollection->Parent;
TManagedArray<TSet<int32>>& Children = GeometryCollection->Children;
TManagedArray<int32>& SimulationType = GeometryCollection->SimulationType;
TManagedArray<FLinearColor>& BoneColors = GeometryCollection->BoneColor;
TArray<int32> ChildBones;
int32 NumElements = GeometryCollection->NumElements(FGeometryCollection::TransformGroup);
for (int ChildIndex = 0; ChildIndex < NumElements; ChildIndex++)
{
if (Parents[ChildIndex] == FGeometryCollection::Invalid)
ChildBones.Push(ChildIndex);
}
// insert a new Root node
int RootNoneIndex = GeometryCollection->AddElements(1, FGeometryCollection::TransformGroup);
if (GeometryCollection->HasAttribute("Level", FGeometryCollection::TransformGroup))
{
TManagedArray<int32>& Levels = GeometryCollection->ModifyAttribute<int32>("Level", FGeometryCollection::TransformGroup);
// all bones shifted down one in hierarchy
for (int ChildIndex = 0; ChildIndex < NumElements; ChildIndex++)
{
Levels[ChildIndex] += 1;
}
Levels[RootNoneIndex] = 0;
}
// New Bone Setup takes level/parent from the first of the Selected Bones
if (RootName.IsNone())
{
BoneNames[RootNoneIndex] = "ClusterBone";
}
else
{
BoneNames[RootNoneIndex] = RootName.ToString();
}
Parents[RootNoneIndex] = FGeometryCollection::Invalid;
Children[RootNoneIndex] = TSet<int32>(ChildBones);
SimulationType[RootNoneIndex] = FGeometryCollection::ESimulationTypes::FST_Clustered;
check(GeometryCollection->IsTransform(RootNoneIndex));
if (GeometryCollection->HasAttribute("ExplodedVector", FGeometryCollection::TransformGroup) &&
GeometryCollection->HasAttribute("ExplodedTransform", FGeometryCollection::TransformGroup) )
{
TManagedArray<FVector3f>& ExplodedVectors = GeometryCollection->ModifyAttribute<FVector3f>("ExplodedVector", FGeometryCollection::TransformGroup);
TManagedArray<FTransform>& ExplodedTransforms = GeometryCollection->ModifyAttribute<FTransform>("ExplodedTransform", FGeometryCollection::TransformGroup);
FVector3f SumOfOffsets(0, 0, 0);
for (int32 ChildBoneIndex : ChildBones)
{
ExplodedVectors[ChildBoneIndex] = FVector3f(Transforms[ChildBoneIndex].GetLocation());
ExplodedTransforms[ChildBoneIndex] = FTransform(Transforms[ChildBoneIndex]);
SumOfOffsets += ExplodedVectors[ChildBoneIndex];
}
ExplodedTransforms[RootNoneIndex] = FTransform(Transforms[RootNoneIndex]);
// This bones offset is the average of all the selected bones
ExplodedVectors[RootNoneIndex] = SumOfOffsets / static_cast<float>(ChildBones.Num());
}
// Selected Bone Setup
for (int32 ChildBoneIndex : ChildBones)
{
Parents[ChildBoneIndex] = RootNoneIndex;
}
Transforms[RootNoneIndex] = FTransform3f::Identity;
if (bUpdateChildBoneNames)
{
RecursivelyUpdateChildBoneNames(RootNoneIndex, Children, BoneNames);
}
const FColor RandBoneColor(FMath::Rand() % 100 + 5, FMath::Rand() % 100 + 5, FMath::Rand() % 100 + 5, 255);
BoneColors[RootNoneIndex] = FLinearColor(RandBoneColor);
ValidateResults(GeometryCollection);
}
void FGeometryCollectionClusteringUtility::ClusterBonesUnderExistingRoot(FGeometryCollection* GeometryCollection, const TArray<int32>& SourceElements)
{
check(GeometryCollection);
bool CalcNewLocalTransform = true;
TManagedArray<int32>& Levels = GeometryCollection->ModifyAttribute<int32>("Level", FGeometryCollection::TransformGroup);
TManagedArray<FString>& BoneNames = GeometryCollection->BoneName;
TManagedArray<int32>& Parents = GeometryCollection->Parent;
TManagedArray<TSet<int32>>& Children = GeometryCollection->Children;
TManagedArray<int32>& SimulationType = GeometryCollection->SimulationType;
TArray<int32> RootBonesOut;
GetRootBones(GeometryCollection, RootBonesOut);
check(RootBonesOut.Num() == 1); // only expecting a single root node
int32 RootBoneElement = RootBonesOut[0];
check(Levels[RootBoneElement] == 0);
check(Parents[RootBoneElement] == FGeometryCollection::Invalid);
// re-parent all the geometry nodes under the root node
GeometryCollectionAlgo::ParentTransforms(GeometryCollection, RootBoneElement, SourceElements);
// update source levels and transforms in our custom attributes
for (int32 Element : SourceElements)
{
if (Element != RootBoneElement)
{
Levels[Element] = 1;
}
}
// delete all the redundant transform nodes that we no longer use
TArray<int32> NodesToDelete;
for (int Element = 0; Element < GeometryCollection->NumElements(FGeometryCollection::TransformGroup); Element++)
{
if (Element != RootBoneElement && GeometryCollection->IsTransform(Element))
{
NodesToDelete.Add(Element);
}
}
if (NodesToDelete.Num() > 0)
{
NodesToDelete.Sort();
FManagedArrayCollection::FProcessingParameters Params;
Params.bDoValidation = false;
GeometryCollection->RemoveElements(FGeometryCollection::TransformGroup, NodesToDelete, Params);
}
// the root bone index could have changed after the above RemoveElements
RootBonesOut.Empty();
GetRootBones(GeometryCollection, RootBonesOut);
RootBoneElement = RootBonesOut[0];
RecursivelyUpdateChildBoneNames(RootBoneElement, Children, BoneNames);
ValidateResults(GeometryCollection);
}
void FGeometryCollectionClusteringUtility::ClusterBonesUnderExistingNode(FGeometryCollection* GeometryCollection, const TArray<int32>& SourceElements)
{
int32 MergeNode = PickBestNodeToMergeTo(GeometryCollection, SourceElements);
ClusterBonesUnderExistingNode(GeometryCollection, MergeNode, SourceElements);
}
void FGeometryCollectionClusteringUtility::ClusterBonesUnderExistingNode(FGeometryCollection* GeometryCollection, int32 MergeNode, const TArray<int32>& SourceElementsIn)
{
check(GeometryCollection);
bool CalcNewLocalTransform = true;
TManagedArray<int32>& Parents = GeometryCollection->Parent;
TManagedArray<TSet<int32>>& Children = GeometryCollection->Children;
TManagedArray<FString>& BoneNames = GeometryCollection->BoneName;
// These attributes are apparently deprecated?
//TManagedArray<FTransform>& ExplodedTransforms = GeometryCollection->GetAttribute<FTransform>("ExplodedTransform", FGeometryCollection::TransformGroup);
//TManagedArray<FVector3f>& ExplodedVectors = GeometryCollection->GetAttribute<FVector3f>("ExplodedVector", FGeometryCollection::TransformGroup);
// remove Merge Node if it's in the list - happens due to the way selection works
TArray<int32> SourceElements;
for (int32 Element : SourceElementsIn)
{
if (Element != MergeNode)
{
SourceElements.Push(Element);
}
}
if (MergeNode != FGeometryCollection::Invalid)
{
bool IllegalOperation = false;
for (int32 SourceElement : SourceElements)
{
if (NodeExistsOnThisBranch(GeometryCollection, MergeNode, SourceElement))
{
IllegalOperation = true;
break;
}
}
if (!IllegalOperation)
{
TArray<int32> ParentsToUpdateNames;
// determine original parents of moved nodes so we can update their children's names
for (int32 SourceElement : SourceElementsIn)
{
int32 Parent = Parents[SourceElement];
if (Parent != FGeometryCollection::Invalid)
{
ParentsToUpdateNames.AddUnique(Parent);
}
}
//ResetSliderTransforms(ExplodedTransforms, Transforms);
// re-parent all the geometry nodes under existing merge node
GeometryCollectionAlgo::ParentTransforms(GeometryCollection, MergeNode, SourceElements);
// update source levels and transforms in our custom attributes
//for (int32 Element : SourceElements)
//{
// ExplodedTransforms[Element] = Transforms[Element];
// ExplodedVectors[Element] = Transforms[Element].GetLocation();
//}
UpdateHierarchyLevelOfChildren(GeometryCollection, MergeNode);
RecursivelyUpdateChildBoneNames(MergeNode, Children, BoneNames);
for (int32 NodeIndex : ParentsToUpdateNames)
{
if (NodeIndex != INDEX_NONE)
{
RecursivelyUpdateChildBoneNames(NodeIndex, Children, BoneNames);
}
}
}
}
// add common root node if multiple roots found
if (FGeometryCollectionClusteringUtility::ContainsMultipleRootBones(GeometryCollection))
{
FGeometryCollectionClusteringUtility::ClusterAllBonesUnderNewRoot(GeometryCollection);
}
ValidateResults(GeometryCollection);
}
void FGeometryCollectionClusteringUtility::ClusterBonesByContext(FGeometryCollection* GeometryCollection, int32 MergeNode, const TArray<int32>& SourceElementsIn)
{
if (GeometryCollection->IsTransform(MergeNode))
{
ClusterBonesUnderExistingNode(GeometryCollection, MergeNode, SourceElementsIn);
}
else
{
TArray<int32> SourceElements = SourceElementsIn;
SourceElements.Push(MergeNode);
ClusterBonesUnderNewNode(GeometryCollection, MergeNode, SourceElements, true);
}
}
void FGeometryCollectionClusteringUtility::CollapseHierarchyOneLevel(FGeometryCollection* GeometryCollection, TArray<int32>& SourceElements)
{
check(GeometryCollection);
bool CalcNewLocalTransform = true;
TManagedArray<int32>& Parents = GeometryCollection->Parent;
TManagedArray<TSet<int32>>& Children = GeometryCollection->Children;
TManagedArray<FString>& BoneNames = GeometryCollection->BoneName;
TManagedArray<int32>& Levels = GeometryCollection->ModifyAttribute<int32>("Level", FGeometryCollection::TransformGroup);
for (int32 SourceElement : SourceElements)
{
int32 DeletedNode = SourceElement;
if (DeletedNode != FGeometryCollection::Invalid)
{
int32 NewParentElement = Parents[DeletedNode];
if (NewParentElement != FGeometryCollection::Invalid)
{
for (int32 ChildElement : Children[DeletedNode])
{
Children[NewParentElement].Add(ChildElement);
Levels[ChildElement] -= 1;
Parents[ChildElement] = NewParentElement;
}
Children[DeletedNode].Empty();
}
}
}
SourceElements.Sort();
GeometryCollection->RemoveElements(FGeometryCollection::TransformGroup, SourceElements);
TArray<int32> Roots;
GetRootBones(GeometryCollection, Roots);
if (!Roots.IsEmpty())
{
RecursivelyUpdateChildBoneNames(Roots[0], Children, BoneNames);
}
ValidateResults(GeometryCollection);
}
bool FGeometryCollectionClusteringUtility::NodeExistsOnThisBranch(const FGeometryCollection* GeometryCollection, int32 TestNode, int32 TreeElement)
{
const TManagedArray<TSet<int32>>& Children = GeometryCollection->Children;
if (TestNode == TreeElement)
return true;
if (Children[TreeElement].Num() > 0)
{
for (int32 ChildIndex : Children[TreeElement])
{
if (NodeExistsOnThisBranch(GeometryCollection, TestNode, ChildIndex))
return true;
}
}
return false;
}
void FGeometryCollectionClusteringUtility::RenameBone(FGeometryCollection* GeometryCollection, int32 BoneIndex, const FString& NewName, bool UpdateChildren /* = true */)
{
TManagedArray<FString>& BoneNames = GeometryCollection->BoneName;
const TManagedArray<TSet<int32>>& Children = GeometryCollection->Children;
BoneNames[BoneIndex] = NewName;
if (UpdateChildren)
{
FGeometryCollectionClusteringUtility::RecursivelyUpdateChildBoneNames(BoneIndex, Children, BoneNames, true);
}
}
int32 FGeometryCollectionClusteringUtility::PickBestNodeToMergeTo(const FManagedArrayCollection* Collection, const TArray<int32>& SourceElements)
{
const Chaos::Facades::FCollectionHierarchyFacade HierarchyFacade(*Collection);
if (!HierarchyFacade.IsValid() || !HierarchyFacade.HasLevelAttribute())
{
return -1;
}
// which of the source elements is the most significant, closest to the root that has children (is a cluster)
int32 ElementClosestToRoot = -1;
int32 LevelClosestToRoot = -1;
for (int32 Element : SourceElements)
{
const TSet<int32>* Children = HierarchyFacade.FindChildren(Element);
int32 Level = HierarchyFacade.GetInitialLevel(Element);
if (Children && !Children->IsEmpty() && (Level < LevelClosestToRoot || LevelClosestToRoot == -1))
{
LevelClosestToRoot = Level;
ElementClosestToRoot = Element;
}
}
return ElementClosestToRoot;
}
void FGeometryCollectionClusteringUtility::ResetSliderTransforms(TManagedArray<FTransform>& ExplodedTransforms, TManagedArray<FTransform>& Transforms)
{
for (int Element = 0; Element < Transforms.Num(); Element++)
{
Transforms[Element] = ExplodedTransforms[Element];
}
}
bool FGeometryCollectionClusteringUtility::ContainsMultipleRootBones(FGeometryCollection* GeometryCollection)
{
check(GeometryCollection);
const TManagedArray<int32>& Parents = GeometryCollection->Parent;
// never assume the root bone is always index 0 in the particle group
int NumRootBones = 0;
for (int i = 0; i < Parents.Num(); i++)
{
if (Parents[i] == FGeometryCollection::Invalid)
{
NumRootBones++;
if (NumRootBones > 1)
{
return true;
}
}
}
return false;
}
void FGeometryCollectionClusteringUtility::GetRootBones(const FGeometryCollection* GeometryCollection, TArray<int32>& RootBonesOut)
{
check(GeometryCollection);
checkSlow(RootBonesOut.Num() == 0);
const TManagedArray<int32>& Parents = GeometryCollection->Parent;
// never assume the root bone is always index 0 in the particle group
for (int i = 0; i < Parents.Num(); i++)
{
if (Parents[i] == FGeometryCollection::Invalid)
{
RootBonesOut.Add(i);
}
}
}
bool FGeometryCollectionClusteringUtility::IsARootBone(const FGeometryCollection* GeometryCollection, int32 InBone)
{
check(GeometryCollection);
const TManagedArray<int32>& Parents = GeometryCollection->Parent;
return (Parents[InBone] == FGeometryCollection::Invalid);
}
void FGeometryCollectionClusteringUtility::GetClusteredBonesWithCommonParent(const FGeometryCollection* GeometryCollection, int32 SourceBone, TArray<int32>& BonesOut)
{
check(GeometryCollection);
const TManagedArray<int32>& Parents = GeometryCollection->Parent;
const TManagedArray<int32>& SimulationType = GeometryCollection->SimulationType;
// then see if this bone as any other bones clustered to it
if (SimulationType[SourceBone] == FGeometryCollection::ESimulationTypes::FST_Clustered)
{
int32 SourceParent = Parents[SourceBone];
for (int i = 0; i < Parents.Num(); i++)
{
if (SourceParent == Parents[i] && (SimulationType[i] == FGeometryCollection::ESimulationTypes::FST_Clustered))
BonesOut.AddUnique(i);
}
}
}
void FGeometryCollectionClusteringUtility::GetChildBonesFromLevel(const FGeometryCollection* GeometryCollection, int32 SourceBone, int32 Level, TArray<int32>& BonesOut)
{
check(GeometryCollection);
if (!ensure(GeometryCollection->HasAttribute("Level", FGeometryCollection::TransformGroup)))
{
return;
}
const TManagedArray<int32>& Levels = GeometryCollection->GetAttribute<int32>("Level", FGeometryCollection::TransformGroup);
const TManagedArray<int32>& Parents = GeometryCollection->Parent;
const TManagedArray<TSet<int32>>& Children = GeometryCollection->Children;
if (SourceBone >= 0)
{
int32 SourceParent = SourceBone;
while (Levels[SourceParent] > Level)
{
if (Parents[SourceParent] == -1)
break;
SourceParent = Parents[SourceParent];
}
RecursiveAddAllChildren(Children, SourceParent, BonesOut);
}
}
void FGeometryCollectionClusteringUtility::GetBonesToLevel(const FGeometryCollection* GeometryCollection, int32 Level, TArray<int32>& BonesOut, bool bOnlyClusteredOrRigid, bool bSkipFiltered)
{
check(GeometryCollection);
if (!ensure(GeometryCollection->HasAttribute("Level", FGeometryCollection::TransformGroup)))
{
return;
}
const TManagedArray<int32>& Levels = GeometryCollection->GetAttribute<int32>("Level", FGeometryCollection::TransformGroup);
const TManagedArray<int32>& SimType = GeometryCollection->SimulationType;
bool bAllLevels = Level == -1;
int32 NumBones = GeometryCollection->NumElements(FGeometryCollection::TransformGroup);
for (int32 BoneIdx = 0; BoneIdx < NumBones; BoneIdx++)
{
bool bIsRigid = SimType[BoneIdx] == FGeometryCollection::ESimulationTypes::FST_Rigid;
bool bIsClustered = SimType[BoneIdx] == FGeometryCollection::ESimulationTypes::FST_Clustered;
if (
// (if skipping embedded) sim type is clustered or rigid
(!bOnlyClusteredOrRigid || bIsClustered || bIsRigid)
&&
// (if skipping nodes the outliner has filtered) sim type is clustered or level is an exact match or level has an exact-match child
(bAllLevels || !bSkipFiltered || bIsClustered || Levels[BoneIdx] == Level || (GeometryCollection->Children[BoneIdx].Num() > 0 && Levels[BoneIdx] + 1 == Level))
&&
// level is at or before the target
(bAllLevels || Levels[BoneIdx] <= Level)
)
{
BonesOut.Add(BoneIdx);
}
}
}
void FGeometryCollectionClusteringUtility::GetChildBonesAtLevel(const FGeometryCollection* GeometryCollection, int32 SourceBone, int32 Level, TArray<int32>& BonesOut)
{
check(GeometryCollection);
if (Level == -1)
{
GetLeafBones(GeometryCollection, SourceBone, false, BonesOut);
}
else
{
if (!ensure(GeometryCollection->HasAttribute("Level", FGeometryCollection::TransformGroup)))
{
return;
}
const TManagedArray<int32>& Levels = GeometryCollection->GetAttribute<int32>("Level", FGeometryCollection::TransformGroup);
const TManagedArray<TSet<int32>>& Children = GeometryCollection->Children;
if (Levels[SourceBone] == Level)
{
BonesOut.Push(SourceBone);
}
else
{
for (int32 Child : Children[SourceBone])
{
GetChildBonesAtLevel(GeometryCollection, Child, Level, BonesOut);
}
}
}
}
void FGeometryCollectionClusteringUtility::RecursiveAddAllChildren(const TManagedArray<TSet<int32>>& Children, int32 SourceBone, TArray<int32>& BonesOut)
{
BonesOut.AddUnique(SourceBone);
for (int32 Child : Children[SourceBone])
{
RecursiveAddAllChildren(Children, Child, BonesOut);
}
}
int32 FGeometryCollectionClusteringUtility::GetParentOfBoneAtSpecifiedLevel(const FGeometryCollection* GeometryCollection, int32 SourceBone, int32 Level, bool bSkipFiltered)
{
check(GeometryCollection);
const TManagedArray<int32>& Parents = GeometryCollection->Parent;
if (!ensure(GeometryCollection->HasAttribute("Level", FGeometryCollection::TransformGroup)))
{
return -1;
}
const TManagedArray<int32>& Levels = GeometryCollection->GetAttribute<int32>("Level", FGeometryCollection::TransformGroup);
const TManagedArray<int32>& SimTypes = GeometryCollection->SimulationType;
if (SourceBone >= 0 && SourceBone < Parents.Num())
{
int32 SourceParent = SourceBone;
while (Levels[SourceParent] > Level ||
// go to parents of bones that will be filtered by the outliner (i.e., rigid/embedded at the wrong level)
(bSkipFiltered &&
Levels[SourceParent] != Level &&
GeometryCollection->SimulationType[SourceParent] != FGeometryCollection::ESimulationTypes::FST_Clustered &&
(GeometryCollection->Children[SourceParent].Num() == 0 || Levels[SourceParent] + 1 != Level)
))
{
if (Parents[SourceParent] == -1)
{
break;
}
SourceParent = Parents[SourceParent];
}
return SourceParent;
}
return FGeometryCollection::Invalid;
}
void FGeometryCollectionClusteringUtility::RecursivelyUpdateChildBoneNames(int32 BoneIndex, const TManagedArray<TSet<int32>>& Children, TManagedArray<FString>& BoneNames, bool OverrideBoneNames /*= false*/)
{
if (!ensure(BoneIndex > -1 && BoneIndex < Children.Num()))
{
return;
}
if (Children[BoneIndex].Num() > 0)
{
const FString& ParentName = BoneNames[BoneIndex];
int DisplayIndex = 1;
for (int32 ChildIndex : Children[BoneIndex])
{
FString NewName;
int32 FoundIndex = 0;
FString ChunkNumberStr( FString::FromInt(DisplayIndex++) );
// enable this if we don't want to override the child names with parent names
bool HasExistingName = BoneNames[ChildIndex].FindChar('_', FoundIndex);
if (!OverrideBoneNames && HasExistingName && FoundIndex > 0)
{
FString CurrentName = BoneNames[ChildIndex].Left(FoundIndex);
int32 FoundNumberIndex = 0;
bool ParentHasNumbers = ParentName.FindChar('_', FoundNumberIndex);
if (ParentHasNumbers && FoundNumberIndex > 0)
{
FString ParentNumbers = ParentName.Right(ParentName.Len() - FoundNumberIndex);
NewName = CurrentName + ParentNumbers + "_" + ChunkNumberStr;
}
else
{
NewName = CurrentName + "_" + ChunkNumberStr;
}
}
else
{
NewName = ParentName + "_" + ChunkNumberStr;
}
BoneNames[ChildIndex] = NewName;
RecursivelyUpdateChildBoneNames(ChildIndex, Children, BoneNames, OverrideBoneNames);
}
}
}
void FGeometryCollectionClusteringUtility::UpdateHierarchyLevelOfChildren(FGeometryCollection* GeometryCollection, int32 ParentElement)
{
if (!GeometryCollection->HasAttribute("Level", FGeometryCollection::TransformGroup))
{
GeometryCollection->AddAttribute<int32>("Level", FGeometryCollection::TransformGroup);
}
TManagedArray<int32>& Levels = GeometryCollection->ModifyAttribute<int32>("Level", FGeometryCollection::TransformGroup);
const TManagedArray<TSet<int32>>& Children = GeometryCollection->Children;
check(ParentElement < Levels.Num());
check(ParentElement < Children.Num());
if (ParentElement != INDEX_NONE)
{
RecursivelyUpdateHierarchyLevelOfChildren(Levels, Children, ParentElement);
}
else
{
TArray<int32> RootBonesOut;
GetRootBones(GeometryCollection, RootBonesOut);
for (int32 RootBone : RootBonesOut)
{
RecursivelyUpdateHierarchyLevelOfChildren(Levels, Children, RootBone);
}
}
}
void FGeometryCollectionClusteringUtility::UpdateHierarchyLevelOfChildren(FManagedArrayCollection& InCollection, int32 ParentElement)
{
Chaos::Facades::FCollectionHierarchyFacade HierarchyFacade(InCollection);
HierarchyFacade.GenerateLevelAttribute();
}
void FGeometryCollectionClusteringUtility::RecursivelyUpdateHierarchyLevelOfChildren(TManagedArray<int32>& Levels, const TManagedArray<TSet<int32>>& Children, int32 ParentElement)
{
check(ParentElement < Levels.Num());
check(ParentElement < Children.Num());
for (int32 Element : Children[ParentElement])
{
Levels[Element] = Levels[ParentElement] + 1;
RecursivelyUpdateHierarchyLevelOfChildren(Levels, Children, Element);
}
}
void FGeometryCollectionClusteringUtility::CollapseLevelHierarchy(int8 Level, FGeometryCollection* GeometryCollection)
{
check(GeometryCollection);
if (!GeometryCollection->HasAttribute("Level", FGeometryCollection::TransformGroup))
{
UpdateHierarchyLevelOfChildren(GeometryCollection, -1);
}
const TManagedArray<int32>& Levels = GeometryCollection->GetAttribute<int32>("Level", FGeometryCollection::TransformGroup);
TArray<int32> Elements;
if (Level == -1) // AllLevels
{
for (int Element = 0; Element < GeometryCollection->NumElements(FGeometryCollection::TransformAttribute); Element++)
{
if (GeometryCollection->IsGeometry(Element))
{
Elements.Add(Element);
}
}
if (Elements.Num() > 0)
{
ClusterBonesUnderExistingRoot(GeometryCollection, Elements);
}
}
else
{
for (int Element = 0; Element < GeometryCollection->NumElements(FGeometryCollection::TransformAttribute); Element++)
{
// if matches selected level then re-parent this node to the root
if (Levels[Element] == Level)
{
Elements.Add(Element);
}
}
if (Elements.Num() > 0)
{
CollapseHierarchyOneLevel(GeometryCollection, Elements);
}
}
}
void FGeometryCollectionClusteringUtility::CollapseSelectedHierarchy(int8 Level, const TArray<int32>& SelectedBones, FGeometryCollection* GeometryCollection)
{
check(GeometryCollection);
if (!GeometryCollection->HasAttribute("Level", FGeometryCollection::TransformGroup))
{
UpdateHierarchyLevelOfChildren(GeometryCollection, -1);
}
const TManagedArray<int32>& Levels = GeometryCollection->GetAttribute<int32>("Level", FGeometryCollection::TransformGroup);
const TManagedArray<TSet<int32>>& Children = GeometryCollection->Children;
// can't collapse root node away and doesn't make sense to operate when AllLevels selected
if (Level > 0)
{
TArray<int32> Elements;
for (int32 Element = 0; Element < SelectedBones.Num(); Element++)
{
int32 Index = SelectedBones[Element];
// if matches selected level then re-parent this node to the root if it's not a leaf node
if (Levels[Index] == Level && Children[Index].Num() > 0)
{
Elements.Add(SelectedBones[Element]);
}
}
if (Elements.Num() > 0)
{
CollapseHierarchyOneLevel(GeometryCollection, Elements);
}
}
}
void FGeometryCollectionClusteringUtility::ValidateResults(FGeometryCollection* GeometryCollection)
{
if (ChaosValidateResultsOfEditOperations)
{
const TManagedArray<int32>& Parents = GeometryCollection->Parent;
const TManagedArray<TSet<int32>>& Children = GeometryCollection->Children;
const TManagedArray<FString>& BoneNames = GeometryCollection->BoneName;
// there should only ever be one root node
int NumRootNodes = 0;
for (int i = 0; i < Parents.Num(); i++)
{
if (Parents[i] == FGeometryCollection::Invalid)
{
NumRootNodes++;
}
}
check(NumRootNodes == 1);
ensure(GeometryCollection->HasContiguousFaces());
ensure(GeometryCollection->HasContiguousVertices());
}
}
void FGeometryCollectionClusteringUtility::GetLeafBones(const FManagedArrayCollection* Collection, int BoneIndex, bool bOnlyRigids, TArray<int32>& LeafBonesOut)
{
if (!ensure(BoneIndex >= 0 && Collection != nullptr))
{
return;
}
const TManagedArrayAccessor<TSet<int32>> ChildrenAttribute(*Collection, FGeometryCollection::ChildrenAttribute, FGeometryCollection::TransformGroup);
const TManagedArrayAccessor<int32> SimulationTypeAttribute(*Collection , FGeometryCollection::SimulationTypeAttribute, FGeometryCollection::TransformGroup);
if (ChildrenAttribute.IsValid() && SimulationTypeAttribute.IsValid())
{
const TManagedArray<TSet<int32>>& Children = ChildrenAttribute.Get();
const TManagedArray<int32>& SimulationType = SimulationTypeAttribute.Get();
if (!bOnlyRigids && Children[BoneIndex].Num() == 0)
{
LeafBonesOut.Push(BoneIndex);
}
else if (bOnlyRigids && SimulationType[BoneIndex] == FGeometryCollection::ESimulationTypes::FST_Rigid)
{
LeafBonesOut.Push(BoneIndex);
}
else if (Children[BoneIndex].Num() > 0)
{
for (int32 ChildElement : Children[BoneIndex])
{
GetLeafBones(Collection, ChildElement, bOnlyRigids, LeafBonesOut);
}
}
}
}
void FGeometryCollectionClusteringUtility::MoveUpOneHierarchyLevel(FGeometryCollection* GeometryCollection, const TArray<int32>& SelectedBones)
{
check(GeometryCollection);
TManagedArray<int32>& Parents = GeometryCollection->Parent;
TManagedArray<TSet<int32>>& Children = GeometryCollection->Children;
TManagedArray<FString>& BoneNames = GeometryCollection->BoneName;
for (int32 BoneIndex : SelectedBones)
{
int32 Parent = Parents[BoneIndex];
if (Parents[BoneIndex] != FGeometryCollection::Invalid)
{
int32 ParentsParent = Parents[Parent];
if (ParentsParent != FGeometryCollection::Invalid)
{
TArray<int32> InBones;
InBones.Push(BoneIndex);
GeometryCollectionAlgo::ParentTransforms(GeometryCollection, ParentsParent, InBones);
UpdateHierarchyLevelOfChildren(GeometryCollection, ParentsParent);
RecursivelyUpdateChildBoneNames(ParentsParent, Children, BoneNames);
}
}
}
ValidateResults(GeometryCollection);
}
int32 FGeometryCollectionClusteringUtility::FindLowestCommonAncestor(const FManagedArrayCollection* Collection, const TArray<int32>& SelectedBones)
{
const int32 SelectionCount = SelectedBones.Num();
if (SelectionCount == 0)
{
return INDEX_NONE;
}
int32 LCA = SelectedBones[0];
for (int32 Index = 1; Index < SelectionCount; ++Index)
{
if (LCA == INDEX_NONE)
{
return INDEX_NONE;
}
LCA = FindLowestCommonAncestor(Collection, LCA, SelectedBones[Index]);
}
return LCA;
}
int32 FGeometryCollectionClusteringUtility::FindLowestCommonAncestor(const FManagedArrayCollection* Collection, int32 N0, int32 N1)
{
const TManagedArray<int32>* Parent = Collection->FindAttribute<int32>("Parent", FGeometryCollection::TransformGroup);
if (!Parent)
{
return INDEX_NONE;
}
// Record the path to root from the first
TArray<int32> PathToRoot0;
PathToRoot0.Add(N0);
while (PathToRoot0.Last() != INDEX_NONE)
{
PathToRoot0.Add((*Parent)[PathToRoot0.Last()]);
}
// Traverse from the second node to root and return the first node found that is in the first path.
int32 LCA = N1;
while (LCA != INDEX_NONE)
{
if (PathToRoot0.Contains(LCA))
{
return LCA;
}
LCA = (*Parent)[LCA];
}
// No common ancestor
return INDEX_NONE;
}
bool FGeometryCollectionClusteringUtility::RemoveClustersOfOnlyOneChild(FGeometryCollection* GeometryCollection)
{
check(GeometryCollection);
bool bRemovedAny = false;
TArray<int32> DeletionList;
do
{
DeletionList.Reset();
for (int32 Idx = 0, Num = GeometryCollection->Transform.Num(); Idx < Num; ++Idx)
{
int32 ParentIdx = GeometryCollection->Parent[Idx];
if (ParentIdx != INDEX_NONE && GeometryCollection->IsClustered(Idx))
{
if (GeometryCollection->Children[Idx].Num() == 1)
{
DeletionList.Add(Idx);
GeometryCollectionAlgo::ParentTransforms(GeometryCollection, ParentIdx, GeometryCollection->Children[Idx].Array());
UpdateHierarchyLevelOfChildren(GeometryCollection, ParentIdx);
RecursivelyUpdateChildBoneNames(ParentIdx, GeometryCollection->Children, GeometryCollection->BoneName);
}
}
}
if (DeletionList.Num())
{
// Note: List is ordered by construction, so do not need to Sort()
FManagedArrayCollection::FProcessingParameters Params;
Params.bDoValidation = false; // for perf reasons
GeometryCollection->RemoveElements(FGeometryCollection::TransformGroup, DeletionList, Params);
bRemovedAny = true;
}
// Need to repeat until an iteration doesn't remove any nodes, to fully collapse any chains of single-child clusters
} while (DeletionList.Num());
return bRemovedAny;
}
bool FGeometryCollectionClusteringUtility::RemoveDanglingClusters(FGeometryCollection* GeometryCollection)
{
check(GeometryCollection);
const TManagedArray<int32>& SimulationType = GeometryCollection->SimulationType;
const int32 TransformCount = GeometryCollection->Transform.Num();
TArray<int32> DeletionList;
for (int32 Idx = 0; Idx < TransformCount; ++Idx)
{
if(GeometryCollection->IsClustered(Idx))
{
TArray<int32> LeafBones;
GetLeafBones(GeometryCollection, Idx, true, LeafBones);
if (LeafBones.Num() == 0)
{
DeletionList.Add(Idx);
}
}
}
if (DeletionList.Num())
{
// Note: List is ordered by construction, so do not need to Sort()
FManagedArrayCollection::FProcessingParameters Params;
Params.bDoValidation = false; // for perf reasons
GeometryCollection->RemoveElements(FGeometryCollection::TransformGroup, DeletionList, Params);
return true;
}
return false;
}