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

1711 lines
59 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
GeometryCollection.cpp: FGeometryCollection methods.
=============================================================================*/
#include "GeometryCollection/GeometryCollectionAlgo.h"
#include "GeometryCollection/GeometryCollection.h"
#include "GeometryCollection/RecordedTransformTrack.h"
#include "GeometryCollection/Facades/CollectionHierarchyFacade.h"
#include "GeometryCollection/Facades/CollectionTransformFacade.h"
#include "GeometryCollectionProxyData.h"
#include "Async/ParallelFor.h"
DEFINE_LOG_CATEGORY_STATIC(GeometryCollectionAlgoLog, Log, All);
namespace GeometryCollectionAlgo
{
template<typename TransformType>
void PrintParentHierarchyRecursive(int32 Index
, const TManagedArray<TransformType>& Transform
, const TManagedArray<int32>& Parent
, const TManagedArray<TSet<int32>>& Children
, const TManagedArray<int32>& SimulationType
, const TManagedArray<FString>& BoneName
, int8 Tab = 0
)
{
check(Index >= 0);
check(Index < Transform.Num());
FString Buffer;
Buffer += FString::Printf(TEXT("R(%+6.2f,%+6.2f,%+6.2f,%+6.2f) "), Transform[Index].GetRotation().X, Transform[Index].GetRotation().Y, Transform[Index].GetRotation().Z, Transform[Index].GetRotation().W);
Buffer += FString::Printf(TEXT("S(%+6.2f,%+6.2f,%+6.2f)"), Transform[Index].GetScale3D().X, Transform[Index].GetScale3D().Y, Transform[Index].GetScale3D().Z);
Buffer += FString::Printf(TEXT("T(%+6.2f,%+6.2f,%+6.2f)"), Transform[Index].GetTranslation().X, Transform[Index].GetTranslation().Y, Transform[Index].GetTranslation().Z);
for (int Tdx = 0; Tdx < Tab; Tdx++)
Buffer += " ";
Buffer += FString::Printf(TEXT("[%d] Name : '%s' Parent %d SimulationType %d"), Index, *BoneName[Index], Parent[Index], SimulationType[Index]);
UE_LOG(GeometryCollectionAlgoLog, Log, TEXT("%s"), *Buffer);
for (auto& ChildIndex : Children[Index])
{
PrintParentHierarchyRecursive(ChildIndex, Transform, Parent, Children, SimulationType, BoneName, Tab + 3);
}
}
void PrintParentHierarchy(const FGeometryCollection* Collection)
{
check(Collection);
const TManagedArray<FTransform3f>& Transform = Collection->Transform;
const TManagedArray<FString>& BoneNames = Collection->BoneName;
const TManagedArray<int32>& Parent = Collection->Parent;
const TManagedArray<TSet<int32>>& Children = Collection->Children;
const TManagedArray<int32>& SimulationType = Collection->SimulationType;
int32 NumParticles = Collection->NumElements(FGeometryCollection::TransformGroup);
for (int32 Index = 0; Index < NumParticles; Index++)
{
if (Parent[Index] == FGeometryCollection::Invalid)
{
PrintParentHierarchyRecursive(Index, Transform, Parent, Children, SimulationType, BoneNames);
}
}
}
void ContiguousArray(TArray<int32> & Array, int32 Length)
{
Array.SetNumUninitialized(Length);
for (int i = 0; i < Length; i++)
{
Array[i] = i;
}
}
void BuildIncrementMask(const TArray<int32> & SortedDeletionList, const int32 & Size, TArray<int32> & Mask)
{
Mask.SetNumUninitialized(Size);
for (int Index = 0, DelIndex = 0; Index < Size; Index++)
{
Mask[Index] = DelIndex;
if (DelIndex < SortedDeletionList.Num() && Index == SortedDeletionList[DelIndex])
{
DelIndex++;
}
}
}
void BuildLookupMask(const TArray<int32> & SortedDeletionList, const int32 & Size, TArray<bool> & Mask)
{
Mask.Init(false, Size);
for (int Index = 0; Index < SortedDeletionList.Num(); Index++)
{
if (SortedDeletionList[Index] < Size)
Mask[SortedDeletionList[Index]] = true;
else
break;
}
}
void BuildTransformGroupToGeometryGroupMap(const FGeometryCollection& GeometryCollection, TArray<int32> & TransformToGeometry)
{
int32 NumGeometryGroup = GeometryCollection.NumElements(FGeometryCollection::GeometryGroup);
const TManagedArray<int32>& TransformIndex = GeometryCollection.TransformIndex;
TransformToGeometry.Init(FGeometryCollection::Invalid, GeometryCollection.NumElements(FGeometryCollection::TransformGroup));
for (int32 i = 0; i < NumGeometryGroup; i++)
{
check(TransformIndex[i] != FGeometryCollection::Invalid);
TransformToGeometry[TransformIndex[i]] = i;
}
}
void BuildFaceGroupToGeometryGroupMap(const FGeometryCollection& GeometryCollection, const TArray<int32>& TransformToGeometryMap, TArray<int32> & FaceToGeometry)
{
check(TransformToGeometryMap.Num() == GeometryCollection.NumElements(FGeometryCollection::TransformGroup));
const TManagedArray<FIntVector>& Indices = GeometryCollection.Indices;
const TManagedArray<int32>& BoneMap = GeometryCollection.BoneMap;
int32 NumTransforms = TransformToGeometryMap.Num();
int32 NumFaces = GeometryCollection.NumElements(FGeometryCollection::FacesGroup);
FaceToGeometry.Init(FGeometryCollection::Invalid, NumFaces);
for (int32 i = 0; i < NumFaces; i++)
{
check(0 <= Indices[i][0] && Indices[i][0] < TransformToGeometryMap.Num());
FaceToGeometry[i] = TransformToGeometryMap[Indices[i][0]];
}
}
void ValidateSortedList(const TArray<int32>&SortedDeletionList, const int32 & ListSize)
{
int32 PreviousValue = -1;
int32 DeletionListSize = SortedDeletionList.Num();
if (DeletionListSize)
{
ensureMsgf(DeletionListSize <= ListSize, TEXT("TManagedArray::ValidateSortedList( DeletionList ) DeletionList larger than array"));
for (int32 Index = 0; Index < SortedDeletionList.Num(); Index++)
{
ensureMsgf(PreviousValue < SortedDeletionList[Index], TEXT("TManagedArray::ValidateSortedList( DeletionList ) DeletionList not sorted"));
ensureMsgf(0 <= SortedDeletionList[Index] && SortedDeletionList[Index] < ListSize, TEXT("TManagedArray::ValidateSortedList( DeletionList ) Index out of range"));
PreviousValue = SortedDeletionList[Index];
}
}
}
FVector AveragePosition(FGeometryCollection* Collection, const TArray<int32>& Indices)
{
TManagedArray<FTransform3f>& Transform = Collection->Transform;
int32 NumIndices = Indices.Num();
FVector3f Translation(0);
for (int32 Index = 0; Index < NumIndices; Index++)
{
Translation += Transform[Indices[Index]].GetTranslation();
}
if (NumIndices > 1)
{
Translation /= static_cast<float>(NumIndices);
}
return FVector(Translation);
}
bool HasMultipleRoots(FGeometryCollection * Collection)
{
int32 ParentCount = 0;
TManagedArray<int32>& Parents = Collection->Parent;
for (int32 i = 0; i < Parents.Num(); i++)
{
if (Parents[i] == FGeometryCollection::Invalid) ParentCount++;
if (ParentCount > 1) return true;
}
return false;
}
bool HasCycle(const TManagedArray<int32>& Parents, int32 Node)
{
return GeometryCollection::Facades::FCollectionTransformFacade::HasCycle(Parents, Node);
}
bool HasCycle(const TManagedArray<int32>& Parents, const TArray<int32>& SelectedBones)
{
return GeometryCollection::Facades::FCollectionTransformFacade::HasCycle(Parents, SelectedBones);
}
void ParentTransform(FManagedArrayCollection* ManagedArrayCollection, const int32 TransformIndex, const int32 ChildIndex)
{
if (ManagedArrayCollection)
{
GeometryCollection::Facades::FCollectionTransformFacade Facade(*ManagedArrayCollection);
Facade.ParentTransform(TransformIndex, ChildIndex);
}
}
void ParentTransforms(FManagedArrayCollection* ManagedArrayCollection, const int32 TransformIndex, const TArray<int32>& SelectedBones)
{
if (ManagedArrayCollection)
{
GeometryCollection::Facades::FCollectionTransformFacade Facade(*ManagedArrayCollection);
Facade.ParentTransforms(TransformIndex, SelectedBones);
}
}
void UnparentTransform(FManagedArrayCollection* ManagedArrayCollection, const int32 ChildIndex)
{
if (ManagedArrayCollection)
{
GeometryCollection::Facades::FCollectionTransformFacade Facade(*ManagedArrayCollection);
Facade.UnparentTransform(ChildIndex);
}
}
// Helper type for computing global matrices
using FIndicesNeedMatricesArray = TArray<int32, TInlineAllocator<16>>;
// return false if we failed to get the indices, which can happen if the parent array does not describe a valid tree (e.g., if it contains a loop)
bool GlobalMatricesGetIndicesToProcessHelper(const int32 Index, const FGeometryDynamicCollection& DynamicCollection, int32 ParentNum, TArray<bool>& IsTransformComputed, FIndicesNeedMatricesArray& OutToProcess)
{
checkSlow(Index != FGeometryCollection::Invalid);
checkSlow(OutToProcess.IsEmpty());
OutToProcess.Add(Index);
bool bFoundRootOrComputed = false;
const int32 MaxDepth = ParentNum;
while (OutToProcess.Num() <= MaxDepth)
{
const int32 Parent = DynamicCollection.GetParent(OutToProcess.Last());
if (Parent == INDEX_NONE || IsTransformComputed[Parent])
{
bFoundRootOrComputed = true;
break;
}
OutToProcess.Add(Parent);
}
if (!ensureMsgf(bFoundRootOrComputed, TEXT("Geometry Collection has invalid parent hierarchy, could not find root to create global transforms")))
{
return false;
}
return true;
}
// return false if we failed to get the indices, which can happen if the parent array does not describe a valid tree (e.g., if it contains a loop)
bool GlobalMatricesGetIndicesToProcessHelper(const int32 Index, const TManagedArray<int32>& Parents, TArray<bool>& IsTransformComputed, FIndicesNeedMatricesArray& OutToProcess)
{
checkSlow(Index != FGeometryCollection::Invalid);
checkSlow(OutToProcess.IsEmpty());
OutToProcess.Add(Index);
bool bFoundRootOrComputed = false;
const int32 MaxDepth = Parents.Num();
while (OutToProcess.Num() <= MaxDepth)
{
int32 Parent = Parents[OutToProcess.Last()];
if (Parent == INDEX_NONE || IsTransformComputed[Parent])
{
bFoundRootOrComputed = true;
break;
}
OutToProcess.Add(Parent);
}
if (!ensureMsgf(bFoundRootOrComputed, TEXT("Geometry Collection has invalid parent hierarchy, could not find root to create global transforms")))
{
return false;
}
return true;
}
template<typename TransformType>
void GlobalMatricesHelper(const int32 Index, const FGeometryDynamicCollection& DynamicCollection, TArray<bool>& IsTransformComputed, const TManagedArray<FTransform>* UniformScale, TArray<TransformType>& OutGlobalTransforms)
{
if (IsTransformComputed[Index])
{
return;
}
FIndicesNeedMatricesArray ToProcess;
if (!GlobalMatricesGetIndicesToProcessHelper(Index, DynamicCollection, DynamicCollection.GetNumTransforms(), IsTransformComputed, ToProcess))
{
return;
}
while (!ToProcess.IsEmpty())
{
const int32 ProcessIndex = ToProcess.Pop(EAllowShrinking::No);
const int32 ParentIndex = DynamicCollection.GetParent(ProcessIndex);
TransformType Result = TransformType(DynamicCollection.GetTransform(ProcessIndex));
if (ParentIndex != FGeometryCollection::Invalid)
{
Result *= OutGlobalTransforms[ParentIndex];
}
if (UniformScale)
{
OutGlobalTransforms[ProcessIndex] = TransformType((*UniformScale)[ProcessIndex]) * Result;
}
else
{
OutGlobalTransforms[ProcessIndex] = Result;
}
IsTransformComputed[ProcessIndex] = true;
}
}
// #note: this version outputs FTransforms to support functionality for getting global matrices for an array of indices.
template<typename TransformType, typename TransformTypeOut>
void GlobalMatricesHelper(const int32 Index, const TManagedArray<int32>& Parents, const TManagedArray<TransformType>& Transform, TArray<bool>& IsTransformComputed, const TManagedArray<FTransform>* UniformScale, TArray<TransformTypeOut>& OutGlobalTransforms)
{
if (IsTransformComputed[Index])
{
return;
}
FIndicesNeedMatricesArray ToProcess;
if (!GlobalMatricesGetIndicesToProcessHelper(Index, Parents, IsTransformComputed, ToProcess))
{
return;
}
while (!ToProcess.IsEmpty())
{
const int32 ProcessIndex = ToProcess.Pop(EAllowShrinking::No);
const int32 ParentIndex = Parents[ProcessIndex];
TransformTypeOut Result = TransformTypeOut(Transform[ProcessIndex]);
if (ParentIndex != FGeometryCollection::Invalid)
{
Result *= OutGlobalTransforms[ParentIndex];
}
if (UniformScale)
{
OutGlobalTransforms[ProcessIndex] = TransformTypeOut((*UniformScale)[ProcessIndex]) * Result;
}
else
{
OutGlobalTransforms[ProcessIndex] = TransformTypeOut(Result);
}
IsTransformComputed[ProcessIndex] = true;
}
}
template<typename TransformType>
void GlobalMatricesHelper(const int32 Index, const TManagedArray<int32>& Parents, const TManagedArray<TransformType>& Transform, TArray<bool>& IsTransformComputed, const TManagedArray<FTransform>* UniformScale, TArray<FMatrix>& OutGlobalTransforms)
{
if (IsTransformComputed[Index])
{
return;
}
FIndicesNeedMatricesArray ToProcess;
if (!GlobalMatricesGetIndicesToProcessHelper(Index, Parents, IsTransformComputed, ToProcess))
{
return;
}
while (!ToProcess.IsEmpty())
{
const int32 ProcessIndex = ToProcess.Pop(EAllowShrinking::No);
const int32 ParentIndex = Parents[ProcessIndex];
FMatrix Result = FTransform(Transform[ProcessIndex]).ToMatrixWithScale();
if (ParentIndex != FGeometryCollection::Invalid)
{
Result *= OutGlobalTransforms[ParentIndex];
}
if (UniformScale)
{
OutGlobalTransforms[ProcessIndex] = (*UniformScale)[ProcessIndex].ToMatrixWithScale() * Result;
}
else
{
OutGlobalTransforms[ProcessIndex] = Result;
}
IsTransformComputed[ProcessIndex] = true;
}
}
FTransform GlobalMatricesHelperForIndicesDynCol(const int32 Index, const FGeometryDynamicCollection& DynamicCollection, TArray<bool>& IsTransformComputed, const TManagedArray<FTransform>* UniformScale, TArray<FTransform>& TransformCache)
{
if (IsTransformComputed[Index])
{
return TransformCache[Index];
}
FIndicesNeedMatricesArray ToProcess;
if (!GlobalMatricesGetIndicesToProcessHelper(Index, DynamicCollection, DynamicCollection.GetNumTransforms(), IsTransformComputed, ToProcess))
{
return TransformCache[Index];
}
while (!ToProcess.IsEmpty())
{
const int32 ProcessIndex = ToProcess.Pop(EAllowShrinking::No);
const int32 ParentIndex = DynamicCollection.GetParent(ProcessIndex);
FTransform Result = FTransform(DynamicCollection.GetTransform(ProcessIndex));
if (ParentIndex != FGeometryCollection::Invalid)
{
Result *= TransformCache[ParentIndex];
}
if (UniformScale)
{
TransformCache[ProcessIndex] = (*UniformScale)[ProcessIndex] * Result;
}
else
{
TransformCache[ProcessIndex] = Result;
}
IsTransformComputed[ProcessIndex] = true;
}
return TransformCache[Index];
}
template<typename TransnformType>
TransnformType GlobalMatricesHelperForIndices(const int32 Index, const TManagedArray<int32>& Parents, const TManagedArray<TransnformType>& Transform, TArray<bool>& IsTransformComputed, const TManagedArray<TransnformType>* UniformScale, TArray<FTransform>& TransformCache)
{
if (IsTransformComputed[Index])
{
return TransnformType(TransformCache[Index]);
}
FIndicesNeedMatricesArray ToProcess;
if (!GlobalMatricesGetIndicesToProcessHelper(Index, Parents, IsTransformComputed, ToProcess))
{
return TransnformType(TransformCache[Index]);
}
while (!ToProcess.IsEmpty())
{
const int32 ProcessIndex = ToProcess.Pop(EAllowShrinking::No);
const int32 ParentIndex = Parents[ProcessIndex];
TransnformType Result = Transform[ProcessIndex];
if (ParentIndex != FGeometryCollection::Invalid)
{
Result *= TransnformType(TransformCache[ParentIndex]);
}
if (UniformScale)
{
TransformCache[ProcessIndex] = FTransform((*UniformScale)[ProcessIndex] * Result);
}
else
{
TransformCache[ProcessIndex] = FTransform(Result);
}
IsTransformComputed[ProcessIndex] = true;
}
return TransnformType(TransformCache[Index]);
}
namespace Private
{
FTransform GlobalMatrix(const FGeometryDynamicCollection& DynamicCollection, int32 Index)
{
FTransform Transform = FTransform::Identity;
check(Index < DynamicCollection.GetNumTransforms())
while (Index != FGeometryCollection::Invalid)
{
Transform = Transform * FTransform(DynamicCollection.GetTransform(Index));
Index = DynamicCollection.GetParent(Index);
}
return Transform;
}
}
template<typename TransformType>
TransformType GlobalMatrixTemplate(const TManagedArray<TransformType>& RelativeTransforms, const TManagedArray<int32>& Parents, int32 Index)
{
TransformType Transform = TransformType::Identity;
if (RelativeTransforms.IsValidIndex(Index))
{
do
{
Transform = Transform * RelativeTransforms[Index];
Index = Parents[Index];
} while (Index != FGeometryCollection::Invalid);
}
return Transform;
}
template<typename TransformType>
TransformType GlobalMatrixTemplate(TArrayView<const TransformType> RelativeTransforms, TArrayView<const int32> Parents, int32 Index)
{
TransformType Transform = TransformType::Identity;
if (RelativeTransforms.IsValidIndex(Index))
{
do
{
Transform = Transform * RelativeTransforms[Index];
Index = Parents[Index];
} while (Index != FGeometryCollection::Invalid);
}
return Transform;
}
FTransform GlobalMatrix(const TManagedArray<FTransform>& RelativeTransforms, const TManagedArray<int32>& Parents, int32 Index)
{
return GlobalMatrixTemplate<FTransform>(RelativeTransforms, Parents, Index);
}
FTransform3f GlobalMatrix3f(const TManagedArray<FTransform3f>& RelativeTransforms, const TManagedArray<int32>& Parents, int32 Index)
{
return GlobalMatrixTemplate<FTransform3f>(RelativeTransforms, Parents, Index);
}
FTransform GlobalMatrix(const TManagedArray<FTransform3f>& RelativeTransforms, const TManagedArray<int32>& Parents, int32 Index)
{
return FTransform(GlobalMatrixTemplate<FTransform3f>(RelativeTransforms, Parents, Index));
}
FTransform GlobalMatrix(TArrayView<const FTransform> RelativeTransforms, TArrayView<const int32> Parents, int32 Index)
{
return GlobalMatrixTemplate<FTransform>(RelativeTransforms, Parents, Index);
}
FTransform GlobalMatrix(TArrayView<const FTransform3f> RelativeTransforms, TArrayView<const int32> Parents, int32 Index)
{
return FTransform(GlobalMatrixTemplate<FTransform3f>(RelativeTransforms, Parents, Index));
}
namespace Private
{
void GlobalMatrices(const FGeometryDynamicCollection& DynamicCollection, const TArray<int32>& Indices, TArray<FTransform>& OutGlobalTransforms)
{
TArray<bool> IsTransformComputed;
const int32 NumTransform = DynamicCollection.GetNumTransforms();
IsTransformComputed.AddDefaulted(NumTransform);
TArray<FTransform> TransformCache;
TransformCache.SetNumUninitialized(NumTransform, EAllowShrinking::No);
OutGlobalTransforms.SetNumUninitialized(Indices.Num(), EAllowShrinking::No);
for (int Idx = 0; Idx < Indices.Num(); Idx++)
{
OutGlobalTransforms[Idx] = GlobalMatricesHelperForIndicesDynCol(Indices[Idx], DynamicCollection, IsTransformComputed, nullptr, TransformCache);
}
}
}
template<class TransformType, class TransformTypeOut>
void GlobalMatricesTemplate(const TManagedArray<TransformType>& RelativeTransforms, const TManagedArray<int32>& Parents, const TArray<int32>& Indices, TArray<TransformTypeOut>& OutGlobalTransforms)
{
TArray<bool> IsTransformComputed;
IsTransformComputed.AddDefaulted(RelativeTransforms.Num());
TArray<FTransform> TransformCache;
TransformCache.SetNumUninitialized(RelativeTransforms.Num(), EAllowShrinking::No);
OutGlobalTransforms.SetNumUninitialized(Indices.Num(), EAllowShrinking::No);
for (int Idx = 0; Idx < Indices.Num(); Idx++)
{
OutGlobalTransforms[Idx] = GlobalMatricesHelperForIndices<TransformType>(Indices[Idx], Parents, RelativeTransforms, IsTransformComputed, nullptr, TransformCache);
}
}
void GlobalMatrices(const TManagedArray<FTransform>& RelativeTransforms, const TManagedArray<int32>& Parents, const TArray<int32>& Indices, TArray<FTransform>& Transforms)
{
GlobalMatricesTemplate<FTransform, FTransform>(RelativeTransforms, Parents, Indices, Transforms);
}
void GlobalMatrices(const TManagedArray<FTransform3f>& RelativeTransforms, const TManagedArray<int32>& Parents, const TArray<int32>& Indices, TArray<FTransform3f>& Transforms)
{
GlobalMatricesTemplate<FTransform3f, FTransform3f>(RelativeTransforms, Parents, Indices, Transforms);
}
void GlobalMatricesFromRoot(const int32 ParentTransformIndex, const TManagedArray<FTransform>& RelativeTransforms, const TManagedArray<TSet<int32>>& Children, TArray<FMatrix>& Transforms)
{
if (Children[ParentTransformIndex].Num() > 0)
{
for (int32 ChildIndex : Children[ParentTransformIndex])
{
Transforms[ChildIndex] = RelativeTransforms[ChildIndex].ToMatrixWithScale() * Transforms[ParentTransformIndex];
GlobalMatricesFromRoot(ChildIndex, RelativeTransforms, Children, Transforms);
}
}
}
template<typename MatrixType>
void GlobalMatrices(const TManagedArray<FTransform>& RelativeTransforms, const TManagedArray<int32>& Parents, const TManagedArray<FTransform>& UniformScale, TArray<MatrixType>& OutGlobalTransforms)
{
int32 NumTransforms = RelativeTransforms.Num();
TArray<bool> IsTransformComputed;
IsTransformComputed.AddDefaulted(NumTransforms);
OutGlobalTransforms.SetNumUninitialized(NumTransforms, EAllowShrinking::No);
for (int BoneIdx = 0; BoneIdx < NumTransforms; ++BoneIdx)
{
GlobalMatricesHelper(BoneIdx, Parents, RelativeTransforms, IsTransformComputed, &UniformScale, OutGlobalTransforms);
}
}
namespace Private
{
void GlobalMatrices(const FGeometryDynamicCollection& DynamicCollection, TArray<FTransform>& OutGlobalTransforms)
{
int32 NumTransforms = DynamicCollection.GetNumTransforms();
TArray<bool> IsTransformComputed;
IsTransformComputed.AddDefaulted(NumTransforms);
OutGlobalTransforms.SetNumUninitialized(NumTransforms, EAllowShrinking::No);
for (int BoneIdx = 0; BoneIdx < NumTransforms; ++BoneIdx)
{
GlobalMatricesHelper(BoneIdx, DynamicCollection, IsTransformComputed, nullptr, OutGlobalTransforms);
}
}
void GlobalMatrices(const FGeometryDynamicCollection& DynamicCollection, TArray<FTransform3f>& OutGlobalTransforms)
{
int32 NumTransforms = DynamicCollection.GetNumTransforms();
TArray<bool> IsTransformComputed;
IsTransformComputed.AddDefaulted(NumTransforms);
OutGlobalTransforms.SetNumUninitialized(NumTransforms, EAllowShrinking::No);
for (int BoneIdx = 0; BoneIdx < NumTransforms; ++BoneIdx)
{
GlobalMatricesHelper(BoneIdx, DynamicCollection, IsTransformComputed, nullptr, OutGlobalTransforms);
}
}
}
template<typename MatrixType, typename TransformType>
void GlobalMatrices(const TManagedArray<TransformType>& RelativeTransforms, const TManagedArray<int32>& Parents, TArray<MatrixType>& OutGlobalTransforms)
{
int32 NumTransforms = RelativeTransforms.Num();
TArray<bool> IsTransformComputed;
IsTransformComputed.AddDefaulted(NumTransforms);
OutGlobalTransforms.SetNumUninitialized(NumTransforms, EAllowShrinking::No);
for (int BoneIdx = 0; BoneIdx < NumTransforms; ++BoneIdx)
{
GlobalMatricesHelper(BoneIdx, Parents, RelativeTransforms, IsTransformComputed, nullptr, OutGlobalTransforms);
}
}
template void CHAOS_API GlobalMatrices<FTransform>(const TManagedArray<FTransform>&, const TManagedArray<int32>&, const TManagedArray<FTransform>&, TArray<FTransform>&);
template void CHAOS_API GlobalMatrices<FMatrix>(const TManagedArray<FTransform>&, const TManagedArray<int32>&, const TManagedArray<FTransform>&, TArray<FMatrix>&);
template void CHAOS_API GlobalMatrices<FTransform, FTransform>(const TManagedArray<FTransform>&, const TManagedArray<int32>&, TArray<FTransform>&);
template void CHAOS_API GlobalMatrices<FMatrix, FTransform>(const TManagedArray<FTransform>&, const TManagedArray<int32>&, TArray<FMatrix>&);
template void CHAOS_API GlobalMatrices<FTransform, FTransform3f>(const TManagedArray<FTransform3f>&, const TManagedArray<int32>&, TArray<FTransform>&);
template void CHAOS_API GlobalMatrices<FMatrix, FTransform3f>(const TManagedArray<FTransform3f>&, const TManagedArray<int32>&, TArray<FMatrix>&);
template void CHAOS_API GlobalMatrices<FTransform3f, FTransform3f>(const TManagedArray<FTransform3f>&, const TManagedArray<int32>&, TArray<FTransform3f>&);
template void CHAOS_API GlobalMatrices<FTransform3f, FTransform>(const TManagedArray<FTransform>&, const TManagedArray<int32>&, TArray<FTransform3f>&);
void FloodForOverlappedPairs(int Level, int32 BoneIndex, TMap<int32, int32> &BoneToGroup, const TManagedArray<int32>& Levels, const TMap<int32, FBox>& BoundingBoxes, TSet<TTuple<int32, int32>>& OutOverlappedPairs)
{
if (Levels[BoneIndex] != Level)
{
return;
}
if (BoneToGroup[BoneIndex] > 0)
{
return;
}
BoneToGroup[BoneIndex] = 1;
const FBox& CurrentBoneBounds = BoundingBoxes[BoneIndex];
for (auto &BoneGroup : BoneToGroup)
{
if (BoneGroup.Value < 1 && BoneGroup.Key != BoneIndex) //ungrouped
{
const FBox& BoneBounds = BoundingBoxes[BoneGroup.Key];
if (CurrentBoneBounds.Intersect(BoneBounds))
{
auto TupleA = MakeTuple(BoneIndex, BoneGroup.Key);
auto TupleB = MakeTuple(BoneGroup.Key, BoneIndex);
if (!OutOverlappedPairs.Contains(TupleA) && !OutOverlappedPairs.Contains(TupleB))
{
OutOverlappedPairs.Add(TupleA);
}
FloodForOverlappedPairs(Level, BoneGroup.Key, BoneToGroup, Levels, BoundingBoxes, OutOverlappedPairs);
}
}
}
}
void GetOverlappedPairs(FGeometryCollection* GeometryCollection, int Level, TSet<TTuple<int32, int32>>& OutOverlappedPairs)
{
if (Level > 0)
{
const TManagedArray<int32>& Parents = GeometryCollection->Parent;
if (!ensure(GeometryCollection->HasAttribute("Level", FGeometryCollection::TransformGroup)))
{
return;
}
const TManagedArray<int32>& Levels = GeometryCollection->GetAttribute<int32>("Level", FGeometryCollection::TransformGroup);
TArray<FTransform> Transforms;
GeometryCollectionAlgo::GlobalMatrices(GeometryCollection->Transform, Parents, Transforms);
TArray<int32> TransformToGeometry;
GeometryCollectionAlgo::BuildTransformGroupToGeometryGroupMap(*GeometryCollection, TransformToGeometry);
const TManagedArray<FBox>& BoundingBoxes = GeometryCollection->BoundingBox;
TMap<int32, int32> BoneToGroup;
TMap<int32, FBox> WorldBounds;
for (int32 Element = 0, NumElement = Levels.Num(); Element < NumElement; ++Element)
{
if (Levels[Element] == Level)
{
const FBox& BoneBounds = BoundingBoxes[TransformToGeometry[Element]];
BoneToGroup.Add(Element, 0);
WorldBounds.Add(Element, BoneBounds.TransformBy(Transforms[Element]));
}
}
for (auto &Element : BoneToGroup)
{
if (Element.Value < 1)
{
FloodForOverlappedPairs(Level, Element.Key, BoneToGroup, Levels, WorldBounds, OutOverlappedPairs);
}
}
}
}
void PrepareForSimulation(FGeometryCollection* GeometryCollection, bool CenterAtOrigin/*=true*/)
{
check(GeometryCollection);
}
void ReCenterGeometryAroundCentreOfMass(FGeometryCollection* GeometryCollection, bool CenterAtOrigin/*=true*/)
{
check(GeometryCollection);
TManagedArray<FTransform3f>& Transform = GeometryCollection->Transform;
if (Transform.Num())
{
const TManagedArray<int32>& BoneMap = GeometryCollection->BoneMap;
TManagedArray<FVector3f>& Vertex = GeometryCollection->Vertex;
TArray<int32> SurfaceParticlesCount;
SurfaceParticlesCount.AddZeroed(GeometryCollection->NumElements(FGeometryCollection::TransformGroup));
TArray<FVector3f> CenterOfMass;
CenterOfMass.AddZeroed(GeometryCollection->NumElements(FGeometryCollection::TransformGroup));
for (int i = 0; i < Vertex.Num(); i++)
{
int32 ParticleIndex = BoneMap[i];
SurfaceParticlesCount[ParticleIndex]++;
CenterOfMass[ParticleIndex] += Vertex[i];
}
FVector3f CombinedCenterOfMassWorld(ForceInitToZero);
for (int i = 0; i < Transform.Num(); i++)
{
if (SurfaceParticlesCount[i])
{
CenterOfMass[i] /= static_cast<float>(SurfaceParticlesCount[i]);
FTransform3f Tmp((FVector3f)CenterOfMass[i]);
// Translate back to original object space position (because vertex position will be centered at the origin),
// then apply the original parent transform. This ensures the pivot remains the same
Transform[i] = Tmp * Transform[i];
CombinedCenterOfMassWorld += Transform[i].GetTranslation();
}
}
CombinedCenterOfMassWorld /= static_cast<float>(Transform.Num());
for (int i = 0; i < Vertex.Num(); i++)
{
int32 ParticleIndex = BoneMap[i];
Vertex[i] -= CenterOfMass[ParticleIndex];
}
if (CenterAtOrigin)
{
for (int i = 0; i < Transform.Num(); i++)
{
FTransform3f Tmp(-CombinedCenterOfMassWorld);
// Apply the parent transform, then center at the origin
Transform[i] = Transform[i] * Tmp;
}
}
}
}
void FindOpenBoundaries(const FGeometryCollection* GeometryCollection, const float CoincidentVertexTolerance, TArray<TArray<TArray<int32>>> &BoundaryVertexIndices)
{
check(GeometryCollection);
int32 NumGeometries = GeometryCollection->NumElements(FGeometryCollection::GeometryGroup);
BoundaryVertexIndices.SetNum(NumGeometries);
TMap<int32, int32> CoincidentVerticesMap;
TSet<int32> VertexToDeleteSet_unused; // not needed for this algorithm
ComputeCoincidentVertices(GeometryCollection, CoincidentVertexTolerance, CoincidentVerticesMap, VertexToDeleteSet_unused);
for (int32 GeometryIdx = 0; GeometryIdx < NumGeometries; GeometryIdx++)
{
// Swap VertexIndex in Indices array
const TManagedArray<FIntVector>& Indices = GeometryCollection->Indices;
TMultiMap<int32, int32> OpenEdges;
auto MapCoincident = [&](int32 VtxIndex)
{
if (CoincidentVerticesMap.Contains(VtxIndex))
{
return CoincidentVerticesMap[VtxIndex];
}
else
{
return VtxIndex;
}
};
auto AddEdge = [&](int32 a, int32 b)
{
a = MapCoincident(a);
b = MapCoincident(b);
if (!OpenEdges.RemoveSingle(b, a))
{
OpenEdges.Add(a, b);
}
};
int32 FaceStart = GeometryCollection->FaceStart[GeometryIdx];
int32 FaceEnd = FaceStart + GeometryCollection->FaceCount[GeometryIdx];
int32 NumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup);
for (int32 IdxFace = FaceStart; IdxFace < FaceEnd; ++IdxFace)
{
AddEdge(Indices[IdxFace].X, Indices[IdxFace].Y);
AddEdge(Indices[IdxFace].Y, Indices[IdxFace].Z);
AddEdge(Indices[IdxFace].Z, Indices[IdxFace].X);
}
while (true)
{
const auto &EdgeIter = OpenEdges.CreateConstIterator();
if (!EdgeIter)
{
break;
}
int32 Start = EdgeIter.Key();
int32 Walk = EdgeIter.Value();
TArray<int32> &Boundary = BoundaryVertexIndices[GeometryIdx].Emplace_GetRef();
Boundary.Add(Start);
OpenEdges.RemoveSingle(Start, Walk);
while (Walk != Start)
{
Boundary.Add(Walk);
int32 Next = OpenEdges.FindChecked(Walk);
OpenEdges.RemoveSingle(Walk, Next);
Walk = Next;
}
}
}
}
void TriangulateBoundaries(FGeometryCollection* GeometryCollection, const TArray<TArray<TArray<int32>>> &BoundaryVertexIndices, bool bWoundClockwise, float MinTriangleAreaSq)
{
check(GeometryCollection);
int32 NumGeometries = GeometryCollection->NumElements(FGeometryCollection::GeometryGroup);
check(BoundaryVertexIndices.Num() == NumGeometries);
TArray<TArray<FIntVector>> Faces;
Faces.SetNum(NumGeometries);
TMap<TPair<int32, int32>, int32> FaceCountPerEdge;
auto AddTriToFaceCountPerEdge = [&FaceCountPerEdge](const FIntVector &Face)
{
auto AddEdge = [&FaceCountPerEdge](int32 A, int32 B)
{
FaceCountPerEdge.FindOrAdd(TPair<int32, int32>(FMath::Min(A, B), FMath::Max(A, B)))++;
};
AddEdge(Face.X, Face.Y);
AddEdge(Face.Y, Face.Z);
AddEdge(Face.Z, Face.X);
};
auto CandidateTriWouldMakeNonManifoldEdge = [&FaceCountPerEdge](int32 A, int32 B, int32 C)
{
auto GetCount = [&FaceCountPerEdge](int32 InnerA, int32 InnerB)
{
int32 *Count = FaceCountPerEdge.Find(TPair<int32, int32>(FMath::Min(InnerA, InnerB), FMath::Max(InnerA, InnerB)));
return Count ? *Count : 0;
};
return GetCount(A, B) > 1 || GetCount(B, C) > 1 || GetCount(C, A) > 1;
};
for (const FIntVector &Face : GeometryCollection->Indices)
{
AddTriToFaceCountPerEdge(Face);
}
for (int32 GeometryIdx = 0; GeometryIdx < NumGeometries; GeometryIdx++)
{
const TArray<TArray<int32>> &GeomBoundaries = BoundaryVertexIndices[GeometryIdx];
TArray<FIntVector> &GeomFaces = Faces[GeometryIdx];
// for v0 let's just put a fan here
for (const TArray<int32>& Boundary : GeomBoundaries)
{
if (Boundary.Num() < 3)
{
continue;
}
int32 First = Boundary[0];
for (int32 BoundaryIdx = 1; BoundaryIdx + 1 < Boundary.Num(); BoundaryIdx++)
{
int32 A = First, B = Boundary[BoundaryIdx], C = Boundary[BoundaryIdx + 1];
if (MinTriangleAreaSq > 0)
{
FVector p10(GeometryCollection->Vertex[B] - GeometryCollection->Vertex[A]);
FVector p20(GeometryCollection->Vertex[C] - GeometryCollection->Vertex[A]);
FVector Cross = FVector::CrossProduct(p20, p10);
if (Cross.SizeSquared() < MinTriangleAreaSq)
{
continue;
}
}
if (CandidateTriWouldMakeNonManifoldEdge(A, B, C))
{
continue;
}
if (bWoundClockwise)
{
GeomFaces.Add(FIntVector(A, C, B));
}
else
{
GeomFaces.Add(FIntVector(A, B, C));
}
}
}
}
AddFaces(GeometryCollection, Faces);
}
void AddFaces(FGeometryCollection* GeometryCollection, const TArray<TArray<FIntVector>> &AddFaces)
{
check(GeometryCollection);
int32 NumGeometries = GeometryCollection->NumElements(FGeometryCollection::GeometryGroup);
check(AddFaces.Num() == NumGeometries);
int32 AddCount = 0;
for (const TArray<FIntVector> &GeomFaces : AddFaces)
{
AddCount += GeomFaces.Num();
}
int32 OldNumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup);
GeometryCollection->AddElements(AddCount, FGeometryCollection::FacesGroup);
int32 NewNumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup);
TArray<int32> ShiftedOrder;
ShiftedOrder.SetNum(NewNumFaces);
int32 ShiftedIdx = 0;
int32 NewSpaceIdx = OldNumFaces;
for (int32 GeometryIdx = 0; GeometryIdx < NumGeometries; GeometryIdx++)
{
int32 OldStart = GeometryCollection->FaceStart[GeometryIdx];
int32 NewStart = ShiftedIdx;
int32 OldEnd = OldStart + GeometryCollection->FaceCount[GeometryIdx];
for (int32 OldFaceIdx = OldStart; OldFaceIdx < OldEnd; OldFaceIdx++)
{
ShiftedOrder[ShiftedIdx++] = OldFaceIdx;
}
int32 NumToAdd = AddFaces[GeometryIdx].Num();
for (int32 AddFaceIdx=0; AddFaceIdx < NumToAdd; AddFaceIdx++)
{
GeometryCollection->Indices[NewSpaceIdx] = AddFaces[GeometryIdx][AddFaceIdx];
GeometryCollection->Visible[NewSpaceIdx] = true;
// just copy material from one of the faces in the group
GeometryCollection->MaterialIndex[NewSpaceIdx] = GeometryCollection->MaterialIndex[OldStart];
GeometryCollection->MaterialID[NewSpaceIdx] = GeometryCollection->MaterialID[OldStart];
// also copy internal status from the existing faces
GeometryCollection->Internal[NewSpaceIdx] = GeometryCollection->Internal[OldStart];
ShiftedOrder[ShiftedIdx++] = NewSpaceIdx;
NewSpaceIdx++;
}
GeometryCollection->FaceCount[GeometryIdx] += NumToAdd;
}
GeometryCollection->ReorderElements(FGeometryCollection::FacesGroup, ShiftedOrder);
}
void ResizeGeometries(FGeometryCollection* GeometryCollection, const TArray<int32>& FaceCounts, const TArray<int32>& VertexCounts, bool bDoValidation)
{
check(GeometryCollection);
int32 NumGeometries = GeometryCollection->NumElements(FGeometryCollection::GeometryGroup);
check(FaceCounts.Num() == NumGeometries);
check(VertexCounts.Num() == NumGeometries);
int32 NewNumFaces = 0, NewNumVertices = 0;
TArray<int32> UnusedFaces, UnusedVertices;
auto AddRange = [](TArray<int32>& OutArray, int32 StartIncl, int32 EndExcl)
{
for (int32 Idx = StartIncl; Idx < EndExcl; Idx++)
{
OutArray.Add(Idx);
}
};
auto CountElements = [&AddRange](int32 OldTotal, const TArray<int32>& NewCounts, const TManagedArray<int32>& OldCounts, const TManagedArray<int32>& OldStarts, int32& OutTotal, TArray<int32>& OutUnused)
{
OutTotal = 0;
OutUnused.Reset();
for (int32 Idx = 0; Idx < NewCounts.Num(); Idx++)
{
OutTotal += NewCounts[Idx];
if (OldCounts[Idx] > NewCounts[Idx])
{
AddRange(OutUnused, OldStarts[Idx] + NewCounts[Idx], OldStarts[Idx] + OldCounts[Idx]);
}
}
if (OutTotal > OldTotal)
{
AddRange(OutUnused, OldTotal, OutTotal);
}
};
int32 OldNumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup);
int32 OldNumVertices = GeometryCollection->NumElements(FGeometryCollection::VerticesGroup);
CountElements(OldNumFaces, FaceCounts, GeometryCollection->FaceCount, GeometryCollection->FaceStart, NewNumFaces, UnusedFaces);
CountElements(OldNumVertices, VertexCounts, GeometryCollection->VertexCount, GeometryCollection->VertexStart, NewNumVertices, UnusedVertices);
// add elements at the end if needed
if (NewNumFaces > OldNumFaces)
{
GeometryCollection->AddElements(NewNumFaces - OldNumFaces, FGeometryCollection::FacesGroup);
// fill in end faces with dummy values, rather than leaving them uninitialized (to avoid breaking things on later reordering call)
int LastVertex = OldNumVertices > 0 ? OldNumVertices - 1 : 0;
FIntVector EndFace(LastVertex, LastVertex, LastVertex);
for (int Idx = OldNumFaces; Idx < NewNumFaces; Idx++)
{
GeometryCollection->Indices[Idx] = EndFace;
}
}
if (NewNumVertices > OldNumVertices)
{
GeometryCollection->AddElements(NewNumVertices - OldNumVertices, FGeometryCollection::VerticesGroup);
}
int32 NumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup);
int32 NumVertices = GeometryCollection->NumElements(FGeometryCollection::VerticesGroup);
auto ComputeNewOrder = [&AddRange](int32 Total, const TArray<int32>& NewCounts, const TManagedArray<int32>& OldCounts, const TManagedArray<int32>& OldStarts, TArray<int32>& Unused, TArray<int32>& OutNewOrder)
{
OutNewOrder.Reset();
OutNewOrder.Reserve(Total);
int32 UnusedIdx = 0;
for (int32 GeometryIdx = 0; GeometryIdx < OldStarts.Num(); GeometryIdx++)
{
if (NewCounts[GeometryIdx] < OldCounts[GeometryIdx]) // Geometry shrunk
{
AddRange(OutNewOrder, OldStarts[GeometryIdx], OldStarts[GeometryIdx] + NewCounts[GeometryIdx]);
}
else // Geometry grew
{
AddRange(OutNewOrder, OldStarts[GeometryIdx], OldStarts[GeometryIdx] + OldCounts[GeometryIdx]);
int32 GrowAmt = NewCounts[GeometryIdx] - OldCounts[GeometryIdx];
for (int32 Idx = 0; Idx < GrowAmt; Idx++)
{
OutNewOrder.Add(Unused[UnusedIdx++]);
}
}
}
while (OutNewOrder.Num() < Total)
{
OutNewOrder.Add(Unused[UnusedIdx++]);
}
};
TArray<int32> NewFaceOrder, NewVertexOrder;
ComputeNewOrder(NumFaces, FaceCounts, GeometryCollection->FaceCount, GeometryCollection->FaceStart, UnusedFaces, NewFaceOrder);
ComputeNewOrder(NumVertices, VertexCounts, GeometryCollection->VertexCount, GeometryCollection->VertexStart, UnusedVertices, NewVertexOrder);
GeometryCollection->ReorderElements(FGeometryCollection::VerticesGroup, NewVertexOrder);
GeometryCollection->ReorderElements(FGeometryCollection::FacesGroup, NewFaceOrder);
// fix face/vertex counts and vertex->transform (bone) map
for (int32 GeometryIdx = 0; GeometryIdx < NumGeometries; GeometryIdx++)
{
int32 TransformIdx = GeometryCollection->TransformIndex[GeometryIdx];
GeometryCollection->FaceCount[GeometryIdx] = FaceCounts[GeometryIdx];
GeometryCollection->VertexCount[GeometryIdx] = VertexCounts[GeometryIdx];
int32 VertexStart = GeometryCollection->VertexStart[GeometryIdx];
int32 VertexEnd = VertexStart + GeometryCollection->VertexCount[GeometryIdx];
for (int32 VertexIdx = VertexStart; VertexIdx < VertexEnd; VertexIdx++)
{
GeometryCollection->BoneMap[VertexIdx] = TransformIdx;
}
}
for (int32 GeometryIdx = 0; GeometryIdx < NumGeometries; GeometryIdx++)
{
int32 TransformIdx = GeometryCollection->TransformIndex[GeometryIdx];
// the vertex remapping can leave faces that still refer to 'deleted' vertices
// these faces are then pointing to vertices that aren't in the same geometry
// the intent is that all resized geometry will be re-written by the caller, afterwards
// but leaving these broken faces is dangerous and prevents validation in the meantime
// so we 'fix' them by writing a degenerate (but w/in geo) face in these cases
int32 FaceStart = GeometryCollection->FaceStart[GeometryIdx];
int32 FaceEnd = FaceStart + GeometryCollection->FaceCount[GeometryIdx];
int32 VertexStart = GeometryCollection->VertexStart[GeometryIdx];
FIntVector ReplacementFace(VertexStart, VertexStart, VertexStart);
for (int32 FaceIdx = FaceStart; FaceIdx < FaceEnd; FaceIdx++)
{
FIntVector& Face = GeometryCollection->Indices[FaceIdx];
bool bFaceUsesDeletedVertex = false;
for (int SubIdx = 0; SubIdx < 3; SubIdx++)
{
int32 VertIdx = Face[SubIdx];
bFaceUsesDeletedVertex |=
(GeometryCollection->BoneMap[VertIdx] != TransformIdx) |
(VertIdx >= NewNumVertices);
}
if (bFaceUsesDeletedVertex)
{
Face = ReplacementFace;
}
}
}
FManagedArrayCollection::FProcessingParameters ProcessingParams;
ProcessingParams.bDoValidation = bDoValidation;
// remove trailing elements if needed
if (NewNumVertices < OldNumVertices)
{
TArray<int32> ToDelete;
AddRange(ToDelete, NewNumVertices, OldNumVertices);
GeometryCollection->RemoveElements(FGeometryCollection::VerticesGroup, ToDelete, ProcessingParams);
}
if (NewNumFaces < OldNumFaces)
{
TArray<int32> ToDelete;
AddRange(ToDelete, NewNumFaces, OldNumFaces);
GeometryCollection->RemoveElements(FGeometryCollection::FacesGroup, ToDelete, ProcessingParams);
}
if (bDoValidation)
{
ensure(GeometryCollection->HasContiguousFaces());
ensure(GeometryCollection->HasContiguousVertices());
ensure(GeometryCollectionAlgo::HasValidGeometryReferences(GeometryCollection));
}
}
DEFINE_LOG_CATEGORY_STATIC(LogGeometryCollectionClean, Verbose, All);
void ComputeCoincidentVertices(const FGeometryCollection* GeometryCollection, const float Tolerance, TMap<int32, int32>& CoincidentVerticesMap, TSet<int32>& VertexToDeleteSet)
{
check(GeometryCollection);
const TManagedArray<FVector3f>& VertexArray = GeometryCollection->Vertex;
const TManagedArray<int32>& BoneMapArray = GeometryCollection->BoneMap;
const TManagedArray<int32>& TransformIndexArray = GeometryCollection->TransformIndex;
int32 NumVertices = GeometryCollection->NumElements(FGeometryCollection::VerticesGroup);
int32 NumGeometries = GeometryCollection->NumElements(FGeometryCollection::GeometryGroup);
float ToleranceSquared = Tolerance * Tolerance;
FCriticalSection Mutex;
ParallelFor(NumGeometries, [&](int32 IdxGeometry)
{
TMap<int32, int32> LocalCoincidentVerticesMap;
TSet<int32> LocalVertexToDeleteSet;
int32 TransformIndex = TransformIndexArray[IdxGeometry];
for (int32 IdxVertex = 0; IdxVertex < NumVertices; ++IdxVertex)
{
if (BoneMapArray[IdxVertex] == TransformIndex)
{
if (!LocalVertexToDeleteSet.Contains(IdxVertex))
{
const FVector3f& Vertex = VertexArray[IdxVertex];
for (int32 IdxOtherVertex = 0; IdxOtherVertex < NumVertices; ++IdxOtherVertex)
{
if (BoneMapArray[IdxOtherVertex] == TransformIndex)
{
if ((IdxVertex != IdxOtherVertex) && !LocalVertexToDeleteSet.Contains(IdxOtherVertex))
{
const FVector3f& OtherVertex = VertexArray[IdxOtherVertex];
if ((Vertex - OtherVertex).SizeSquared() < ToleranceSquared)
{
LocalVertexToDeleteSet.Add(IdxOtherVertex);
LocalCoincidentVerticesMap.Add(IdxOtherVertex, IdxVertex);
}
}
}
}
}
}
}
if (LocalVertexToDeleteSet.Num())
{
Mutex.Lock();
CoincidentVerticesMap.Append(LocalCoincidentVerticesMap);
VertexToDeleteSet.Append(LocalVertexToDeleteSet);
Mutex.Unlock();
}
});
}
void DeleteCoincidentVertices(FGeometryCollection* GeometryCollection, float Tolerance)
{
check(GeometryCollection);
TSet<int32> VertexToDeleteSet;
TMap<int32, int32> CoincidentVerticesMap;
ComputeCoincidentVertices(GeometryCollection, Tolerance, CoincidentVerticesMap, VertexToDeleteSet);
// Swap VertexIndex in Indices array
TManagedArray<FIntVector>& IndicesArray = GeometryCollection->Indices;
int32 NumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup);
for (int32 IdxFace = 0; IdxFace < NumFaces; ++IdxFace)
{
if (CoincidentVerticesMap.Contains(IndicesArray[IdxFace].X))
{
IndicesArray[IdxFace].X = CoincidentVerticesMap[IndicesArray[IdxFace].X];
}
if (CoincidentVerticesMap.Contains(IndicesArray[IdxFace].Y))
{
IndicesArray[IdxFace].Y = CoincidentVerticesMap[IndicesArray[IdxFace].Y];
}
if (CoincidentVerticesMap.Contains(IndicesArray[IdxFace].Z))
{
IndicesArray[IdxFace].Z = CoincidentVerticesMap[IndicesArray[IdxFace].Z];
}
}
// Delete vertices
TArray<int32> DelList = VertexToDeleteSet.Array();
DelList.Sort();
GeometryCollection->RemoveElements(FGeometryCollection::VerticesGroup, DelList);
}
void ComputeZeroAreaFaces(const FGeometryCollection* GeometryCollection, const float Tolerance, TSet<int32>& FaceToDeleteSet)
{
check(GeometryCollection);
const TManagedArray<FVector3f>& VertexArray = GeometryCollection->Vertex;
const TManagedArray<FIntVector>& IndicesArray = GeometryCollection->Indices;
const TManagedArray<int32>& BoneMapArray = GeometryCollection->BoneMap;
int32 NumVertices = GeometryCollection->NumElements(FGeometryCollection::VerticesGroup);
int32 NumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup);
for (int32 IdxFace = 0; IdxFace < NumFaces; ++IdxFace)
{
int32 TransformIndex = BoneMapArray[IndicesArray[IdxFace][0]];
if (GeometryCollection->IsGeometry(TransformIndex) && !GeometryCollection->IsClustered(TransformIndex))
{
FVector3f Vertex0 = VertexArray[IndicesArray[IdxFace][0]];
FVector3f Vertex1 = VertexArray[IndicesArray[IdxFace][1]];
FVector3f Vertex2 = VertexArray[IndicesArray[IdxFace][2]];
float Area = 0.5f * ((Vertex0 - Vertex1) ^ (Vertex0 - Vertex2)).Size();
if (Area < Tolerance)
{
FaceToDeleteSet.Add(IdxFace);
}
}
}
}
void DeleteZeroAreaFaces(FGeometryCollection* GeometryCollection, float Tolerance)
{
check(GeometryCollection);
TSet<int32> FaceToDeleteSet;
ComputeZeroAreaFaces(GeometryCollection, Tolerance, FaceToDeleteSet);
TArray<int32> DelList = FaceToDeleteSet.Array();
DelList.Sort();
GeometryCollection->RemoveElements(FGeometryCollection::FacesGroup, DelList);
}
void ComputeHiddenFaces(const FGeometryCollection* GeometryCollection, TSet<int32>& FaceToDeleteSet)
{
check(GeometryCollection);
const TManagedArray<FIntVector>& IndicesArray = GeometryCollection->Indices;
const TManagedArray<bool>& VisibleArray = GeometryCollection->Visible;
const TManagedArray<int32>& BoneMapArray = GeometryCollection->BoneMap;
int32 NumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup);
for (int32 IdxFace = 0; IdxFace < NumFaces; ++IdxFace)
{
int32 TransformIndex = BoneMapArray[IndicesArray[IdxFace][0]];
if (GeometryCollection->IsGeometry(TransformIndex) && !GeometryCollection->IsClustered(TransformIndex))
{
if (!VisibleArray[IdxFace])
{
FaceToDeleteSet.Add(IdxFace);
}
}
}
}
void DeleteHiddenFaces(FGeometryCollection* GeometryCollection)
{
check(GeometryCollection);
TSet<int32> FaceToDeleteSet;
ComputeHiddenFaces(GeometryCollection, FaceToDeleteSet);
TArray<int32> DelList = FaceToDeleteSet.Array();
DelList.Sort();
GeometryCollection->RemoveElements(FGeometryCollection::FacesGroup, DelList);
}
void ComputeStaleVertices(const FGeometryCollection* GeometryCollection, TSet<int32>& VertexToDeleteSet)
{
check(GeometryCollection);
const TManagedArray<FVector3f>& VertexArray = GeometryCollection->Vertex;
const TManagedArray<int32>& BoneMapArray = GeometryCollection->BoneMap;
const TManagedArray<int32>& TransformIndexArray = GeometryCollection->TransformIndex;
const TManagedArray<FIntVector>& IndicesArray = GeometryCollection->Indices;
int32 NumVertices = GeometryCollection->NumElements(FGeometryCollection::VerticesGroup);
int32 NumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup);
TArray<int32> VertexInFaceArray;
VertexInFaceArray.Init(0, NumVertices);
for (int32 IdxFace = 0; IdxFace < NumFaces; ++IdxFace)
{
VertexInFaceArray[IndicesArray[IdxFace].X]++;
VertexInFaceArray[IndicesArray[IdxFace].Y]++;
VertexInFaceArray[IndicesArray[IdxFace].Z]++;
}
if (const TManagedArray<FIntVector4> *TetArrayPtr =
GeometryCollection->FindAttributeTyped<FIntVector4>("Tetrahedron", "Tetrahedral"))
{
int32 NumTetrahedron = GeometryCollection->NumElements("Tetrahedral");
const TManagedArray<FIntVector4>& TetArray = *TetArrayPtr;
for (int32 TetIdx = 0; TetIdx < NumTetrahedron; ++TetIdx)
{
VertexInFaceArray[TetArray[TetIdx].X]++;
VertexInFaceArray[TetArray[TetIdx].Y]++;
VertexInFaceArray[TetArray[TetIdx].Z]++;
VertexInFaceArray[TetArray[TetIdx].W]++;
}
}
for (int32 IdxVertex = 0; IdxVertex < NumVertices; ++IdxVertex)
{
if (VertexInFaceArray[IdxVertex] == 0)
{
VertexToDeleteSet.Add(IdxVertex);
}
}
}
void DeleteStaleVertices(FGeometryCollection* GeometryCollection)
{
check(GeometryCollection);
TSet<int32> VertexToDeleteSet;
ComputeStaleVertices(GeometryCollection, VertexToDeleteSet);
TArray<int32> DelList = VertexToDeleteSet.Array();
DelList.Sort();
GeometryCollection->RemoveElements(FGeometryCollection::VerticesGroup, DelList);
}
void ComputeEdgeInFaces(const FGeometryCollection* GeometryCollection, TMap<FFaceEdge, int32>& FaceEdgeMap)
{
check(GeometryCollection);
const TManagedArray<FIntVector>& IndicesArray = GeometryCollection->Indices;
int32 NumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup);
for (int32 IdxFace = 0; IdxFace < NumFaces; ++IdxFace)
{
for (int32 Idx = 0; Idx < 3; Idx++)
{
int32 VertexIndex1 = IndicesArray[IdxFace][Idx];
int32 VertexIndex2 = IndicesArray[IdxFace][(Idx + 1) % 3];
FFaceEdge Edge{ FMath::Min(VertexIndex1, VertexIndex2),
FMath::Max(VertexIndex1, VertexIndex2) };
if (FaceEdgeMap.Contains(Edge))
{
FaceEdgeMap[Edge]++;
}
else
{
FaceEdgeMap.Add(Edge, 1);
}
}
}
}
void PrintStatistics(const FGeometryCollection* GeometryCollection)
{
check(GeometryCollection);
int32 NumVertices = GeometryCollection->NumElements(FGeometryCollection::VerticesGroup);
int32 NumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup);
int32 NumGeometries = GeometryCollection->NumElements(FGeometryCollection::GeometryGroup);
int32 NumTransforms = GeometryCollection->NumElements(FGeometryCollection::TransformGroup);
int32 NumBreakings = GeometryCollection->NumElements(FGeometryCollection::BreakingGroup);
FString Buffer;
Buffer += FString::Printf(TEXT("\n\n"));
Buffer += FString::Printf(TEXT("------------------------------------------------------------\n"));
Buffer += FString::Printf(TEXT("Number of transforms = %d\n"), NumTransforms);
Buffer += FString::Printf(TEXT("Number of vertices = %d\n"), NumVertices);
Buffer += FString::Printf(TEXT("Number of faces = %d\n"), NumFaces);
Buffer += FString::Printf(TEXT("Number of geometries = %d\n"), NumGeometries);
Buffer += FString::Printf(TEXT("Number of breakings = %d\n"), NumBreakings);
Buffer += FString::Printf(TEXT("------------------------------------------------------------\n\n"));
UE_LOG(LogGeometryCollectionClean, Log, TEXT("%s"), *Buffer);
}
bool HasValidFacesFor(const FGeometryCollection* GeometryCollection, int32 GeometryIndex)
{
ensure(GeometryIndex < GeometryCollection->NumElements(FGeometryCollection::GeometryGroup));
int32 FaceStart = GeometryCollection->FaceStart[GeometryIndex];
int32 FaceCount = GeometryCollection->FaceCount[GeometryIndex];
int32 FaceEnd = FaceStart + FaceCount;
// check faces range within number of elements in faces group
int32 NumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup);
if (FaceStart >= NumFaces)
return false;
if (FaceEnd > NumFaces)
return false;
// check faces index into valid elements of vertices group
int32 VertexStart = GeometryCollection->VertexStart[GeometryIndex];
int32 VertexCount = GeometryCollection->VertexCount[GeometryIndex];
int32 VertexEnd = VertexStart + VertexCount;
const TManagedArray<FIntVector>& Indices = GeometryCollection->Indices;
for (int FaceIdx = FaceStart; FaceIdx < FaceEnd; FaceIdx++)
{
const FIntVector& Face = Indices[FaceIdx];
for (int Idx = 0; Idx < 3; Idx++)
{
if (Face[Idx] < VertexStart || Face[Idx] >= VertexEnd)
{
return false;
}
int32 BoneAndTransformIndex = GeometryCollection->BoneMap[Face[Idx]];
int32 TestGeometryIndex = GeometryCollection->TransformToGeometryIndex[BoneAndTransformIndex];
if (GeometryIndex != TestGeometryIndex)
return false;
}
}
return true;
}
bool HasValidIndicesFor(const FGeometryCollection* GeometryCollection, int32 GeometryIndex)
{
ensure(GeometryIndex < GeometryCollection->NumElements(FGeometryCollection::GeometryGroup));
int32 VertexStart = GeometryCollection->VertexStart[GeometryIndex];
int32 VertexCount = GeometryCollection->VertexCount[GeometryIndex];
int32 VertexEnd = VertexStart + VertexCount;
int32 NumVertices = GeometryCollection->NumElements(FGeometryCollection::VerticesGroup);
if (VertexStart >= NumVertices)
return false;
if (VertexEnd > NumVertices)
return false;
for (int VertIdx = VertexStart; VertIdx < VertexEnd; VertIdx++)
{
int32 BoneAndTransformIndex = GeometryCollection->BoneMap[VertIdx];
int32 TestGeometryIndex = GeometryCollection->TransformToGeometryIndex[BoneAndTransformIndex];
if (GeometryIndex != TestGeometryIndex)
return false;
}
return true;
}
bool HasInvalidIndicesFor(const FGeometryCollection* GeometryCollection, int32 GeometryIndex)
{
ensure(GeometryIndex < GeometryCollection->NumElements(FGeometryCollection::GeometryGroup));
int32 VertexStart = GeometryCollection->VertexStart[GeometryIndex];
int32 VertexCount = GeometryCollection->VertexCount[GeometryIndex];
int32 VertexEnd = VertexStart + VertexCount;
int32 NumVertices = GeometryCollection->NumElements(FGeometryCollection::VerticesGroup);
if (VertexStart >= NumVertices)
return true;
if (VertexEnd > NumVertices)
return true;
for (int32 VertIdx = 0 ; VertIdx < NumVertices ; ++VertIdx)
{
if (VertIdx >= VertexStart && VertIdx < VertexEnd)
continue;
int32 BoneAndTransformIndex = GeometryCollection->BoneMap[VertIdx];
int32 TestGeometryIndex = GeometryCollection->TransformToGeometryIndex[BoneAndTransformIndex];
if (GeometryIndex == TestGeometryIndex)
return true;
}
int32 OurTransformIndex = GeometryCollection->TransformIndex[GeometryIndex];
for (int32 GeomIdx=0, NumGeo = GeometryCollection->TransformIndex.Num(); GeomIdx < NumGeo; ++GeomIdx)
{
if (GeomIdx == GeometryIndex)
continue;
if (GeometryCollection->TransformIndex[GeomIdx] == OurTransformIndex)
return true;
}
return false;
}
bool HasResidualFaces(const FGeometryCollection* GeometryCollection)
{
int32 NumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup);
TArray<bool> IsUsed;
IsUsed.Init(false, NumFaces);
for (int32 GeometryIndex = 0, NumGeometry = GeometryCollection->NumElements(FGeometryCollection::GeometryGroup) ; GeometryIndex < NumGeometry ; ++GeometryIndex)
{
int32 FaceStart = GeometryCollection->FaceStart[GeometryIndex];
int32 FaceCount = GeometryCollection->FaceCount[GeometryIndex];
int32 FaceEnd = FaceStart + FaceCount;
ensureMsgf(FaceStart < NumFaces, TEXT("Geometry %d has invalid face start index %d"), GeometryIndex, FaceStart);
ensureMsgf(FaceEnd <= NumFaces, TEXT("Geometry %d has invalid face end index %d"), GeometryIndex, FaceEnd);
for (int32 Idx = FaceStart; Idx < FaceEnd; Idx++)
{
IsUsed[Idx] = true;
}
}
for (bool Used : IsUsed)
{
if (Used == false)
return true;
}
return false;
}
bool HasResidualIndices(const FGeometryCollection* GeometryCollection)
{
int32 NumVertices = GeometryCollection->NumElements(FGeometryCollection::VerticesGroup);
TArray<bool> IsUsed;
IsUsed.Init(false, NumVertices);
for (int32 GeometryIndex = 0, NumGeometry = GeometryCollection->NumElements(FGeometryCollection::GeometryGroup); GeometryIndex < NumGeometry; ++GeometryIndex)
{
int32 VertexStart = GeometryCollection->VertexStart[GeometryIndex];
int32 VertexCount = GeometryCollection->VertexCount[GeometryIndex];
int32 VertexEnd = VertexStart + VertexCount;
ensureMsgf(VertexStart < NumVertices, TEXT("Geometry %d has invalid vertex start index %d"), GeometryIndex, VertexStart);
ensureMsgf(VertexEnd <= NumVertices, TEXT("Geometry %d has invalid vertex end index %d"), GeometryIndex, VertexEnd);
for (int32 Idx = VertexStart; Idx < VertexEnd; Idx++)
{
IsUsed[Idx] = true;
}
}
for (bool Used : IsUsed)
{
if (Used == false)
return true;
}
return false;
}
bool HasValidGeometryReferences(const FGeometryCollection* GeometryCollection)
{
for (int GeometryIndex = 0; GeometryIndex < GeometryCollection->NumElements(FGeometryCollection::GeometryGroup); GeometryIndex++)
{
if (!HasValidIndicesFor(GeometryCollection, GeometryIndex))
return false;
if (!HasValidFacesFor(GeometryCollection, GeometryIndex))
return false;
if (HasInvalidIndicesFor(GeometryCollection, GeometryIndex))
return false;
}
return true;
}
TArray<int32> ComputeRecursiveOrder(const FManagedArrayCollection& Collection)
{
const int32 NumTransforms = Collection.NumElements(FGeometryCollection::TransformGroup);
Chaos::Facades::FCollectionHierarchyFacade HierarchyFacade(Collection);
if (!ensure(HierarchyFacade.IsValid()))
{
// We cannot compute a recursive ordering without hierarchy attributes
TArray<int32> Transforms;
return Transforms;
}
//traverse cluster hierarchy in depth first and record order
struct FClusterProcessing
{
int32 TransformGroupIndex;
enum
{
None,
VisitingChildren
} State;
FClusterProcessing(int32 InIndex) : TransformGroupIndex(InIndex), State(None) {};
};
TArray<FClusterProcessing> ClustersToProcess;
//enqueue all roots
for (int32 TransformGroupIndex = 0; TransformGroupIndex < NumTransforms; TransformGroupIndex++)
{
if (HierarchyFacade.GetParent(TransformGroupIndex) == FGeometryCollection::Invalid)
{
ClustersToProcess.Emplace(TransformGroupIndex);
}
}
TArray<int32> TransformOrder;
TransformOrder.Reserve(NumTransforms);
while (ClustersToProcess.Num())
{
FClusterProcessing CurCluster = ClustersToProcess.Pop();
const int32 ClusterTransformIdx = CurCluster.TransformGroupIndex;
if (CurCluster.State == FClusterProcessing::VisitingChildren)
{
//children already visited
TransformOrder.Add(ClusterTransformIdx);
}
else
{
const TSet<int32>* ClusterChildren = HierarchyFacade.FindChildren(ClusterTransformIdx);
if (ClusterChildren && ClusterChildren->Num())
{
CurCluster.State = FClusterProcessing::VisitingChildren;
ClustersToProcess.Add(CurCluster);
//order of children doesn't matter as long as all children appear before parent
for (int32 ChildIdx : *ClusterChildren)
{
ClustersToProcess.Emplace(ChildIdx);
}
}
else
{
TransformOrder.Add(ClusterTransformIdx);
}
}
}
return TransformOrder;
}
}