// 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 void PrintParentHierarchyRecursive(int32 Index , const TManagedArray& Transform , const TManagedArray& Parent , const TManagedArray>& Children , const TManagedArray& SimulationType , const TManagedArray& 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& Transform = Collection->Transform; const TManagedArray& BoneNames = Collection->BoneName; const TManagedArray& Parent = Collection->Parent; const TManagedArray>& Children = Collection->Children; const TManagedArray& 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 & Array, int32 Length) { Array.SetNumUninitialized(Length); for (int i = 0; i < Length; i++) { Array[i] = i; } } void BuildIncrementMask(const TArray & SortedDeletionList, const int32 & Size, TArray & 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 & SortedDeletionList, const int32 & Size, TArray & 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 & TransformToGeometry) { int32 NumGeometryGroup = GeometryCollection.NumElements(FGeometryCollection::GeometryGroup); const TManagedArray& 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& TransformToGeometryMap, TArray & FaceToGeometry) { check(TransformToGeometryMap.Num() == GeometryCollection.NumElements(FGeometryCollection::TransformGroup)); const TManagedArray& Indices = GeometryCollection.Indices; const TManagedArray& 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&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& Indices) { TManagedArray& 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(NumIndices); } return FVector(Translation); } bool HasMultipleRoots(FGeometryCollection * Collection) { int32 ParentCount = 0; TManagedArray& 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& Parents, int32 Node) { return GeometryCollection::Facades::FCollectionTransformFacade::HasCycle(Parents, Node); } bool HasCycle(const TManagedArray& Parents, const TArray& 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& 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>; // 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& 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& Parents, TArray& 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 void GlobalMatricesHelper(const int32 Index, const FGeometryDynamicCollection& DynamicCollection, TArray& IsTransformComputed, const TManagedArray* UniformScale, TArray& 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 void GlobalMatricesHelper(const int32 Index, const TManagedArray& Parents, const TManagedArray& Transform, TArray& IsTransformComputed, const TManagedArray* UniformScale, TArray& 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 void GlobalMatricesHelper(const int32 Index, const TManagedArray& Parents, const TManagedArray& Transform, TArray& IsTransformComputed, const TManagedArray* UniformScale, TArray& 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& IsTransformComputed, const TManagedArray* UniformScale, TArray& 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 TransnformType GlobalMatricesHelperForIndices(const int32 Index, const TManagedArray& Parents, const TManagedArray& Transform, TArray& IsTransformComputed, const TManagedArray* UniformScale, TArray& 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 TransformType GlobalMatrixTemplate(const TManagedArray& RelativeTransforms, const TManagedArray& 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 TransformType GlobalMatrixTemplate(TArrayView RelativeTransforms, TArrayView 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& RelativeTransforms, const TManagedArray& Parents, int32 Index) { return GlobalMatrixTemplate(RelativeTransforms, Parents, Index); } FTransform3f GlobalMatrix3f(const TManagedArray& RelativeTransforms, const TManagedArray& Parents, int32 Index) { return GlobalMatrixTemplate(RelativeTransforms, Parents, Index); } FTransform GlobalMatrix(const TManagedArray& RelativeTransforms, const TManagedArray& Parents, int32 Index) { return FTransform(GlobalMatrixTemplate(RelativeTransforms, Parents, Index)); } FTransform GlobalMatrix(TArrayView RelativeTransforms, TArrayView Parents, int32 Index) { return GlobalMatrixTemplate(RelativeTransforms, Parents, Index); } FTransform GlobalMatrix(TArrayView RelativeTransforms, TArrayView Parents, int32 Index) { return FTransform(GlobalMatrixTemplate(RelativeTransforms, Parents, Index)); } namespace Private { void GlobalMatrices(const FGeometryDynamicCollection& DynamicCollection, const TArray& Indices, TArray& OutGlobalTransforms) { TArray IsTransformComputed; const int32 NumTransform = DynamicCollection.GetNumTransforms(); IsTransformComputed.AddDefaulted(NumTransform); TArray 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 void GlobalMatricesTemplate(const TManagedArray& RelativeTransforms, const TManagedArray& Parents, const TArray& Indices, TArray& OutGlobalTransforms) { TArray IsTransformComputed; IsTransformComputed.AddDefaulted(RelativeTransforms.Num()); TArray TransformCache; TransformCache.SetNumUninitialized(RelativeTransforms.Num(), EAllowShrinking::No); OutGlobalTransforms.SetNumUninitialized(Indices.Num(), EAllowShrinking::No); for (int Idx = 0; Idx < Indices.Num(); Idx++) { OutGlobalTransforms[Idx] = GlobalMatricesHelperForIndices(Indices[Idx], Parents, RelativeTransforms, IsTransformComputed, nullptr, TransformCache); } } void GlobalMatrices(const TManagedArray& RelativeTransforms, const TManagedArray& Parents, const TArray& Indices, TArray& Transforms) { GlobalMatricesTemplate(RelativeTransforms, Parents, Indices, Transforms); } void GlobalMatrices(const TManagedArray& RelativeTransforms, const TManagedArray& Parents, const TArray& Indices, TArray& Transforms) { GlobalMatricesTemplate(RelativeTransforms, Parents, Indices, Transforms); } void GlobalMatricesFromRoot(const int32 ParentTransformIndex, const TManagedArray& RelativeTransforms, const TManagedArray>& Children, TArray& Transforms) { if (Children[ParentTransformIndex].Num() > 0) { for (int32 ChildIndex : Children[ParentTransformIndex]) { Transforms[ChildIndex] = RelativeTransforms[ChildIndex].ToMatrixWithScale() * Transforms[ParentTransformIndex]; GlobalMatricesFromRoot(ChildIndex, RelativeTransforms, Children, Transforms); } } } template void GlobalMatrices(const TManagedArray& RelativeTransforms, const TManagedArray& Parents, const TManagedArray& UniformScale, TArray& OutGlobalTransforms) { int32 NumTransforms = RelativeTransforms.Num(); TArray 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& OutGlobalTransforms) { int32 NumTransforms = DynamicCollection.GetNumTransforms(); TArray 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& OutGlobalTransforms) { int32 NumTransforms = DynamicCollection.GetNumTransforms(); TArray IsTransformComputed; IsTransformComputed.AddDefaulted(NumTransforms); OutGlobalTransforms.SetNumUninitialized(NumTransforms, EAllowShrinking::No); for (int BoneIdx = 0; BoneIdx < NumTransforms; ++BoneIdx) { GlobalMatricesHelper(BoneIdx, DynamicCollection, IsTransformComputed, nullptr, OutGlobalTransforms); } } } template void GlobalMatrices(const TManagedArray& RelativeTransforms, const TManagedArray& Parents, TArray& OutGlobalTransforms) { int32 NumTransforms = RelativeTransforms.Num(); TArray 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(const TManagedArray&, const TManagedArray&, const TManagedArray&, TArray&); template void CHAOS_API GlobalMatrices(const TManagedArray&, const TManagedArray&, const TManagedArray&, TArray&); template void CHAOS_API GlobalMatrices(const TManagedArray&, const TManagedArray&, TArray&); template void CHAOS_API GlobalMatrices(const TManagedArray&, const TManagedArray&, TArray&); template void CHAOS_API GlobalMatrices(const TManagedArray&, const TManagedArray&, TArray&); template void CHAOS_API GlobalMatrices(const TManagedArray&, const TManagedArray&, TArray&); template void CHAOS_API GlobalMatrices(const TManagedArray&, const TManagedArray&, TArray&); template void CHAOS_API GlobalMatrices(const TManagedArray&, const TManagedArray&, TArray&); void FloodForOverlappedPairs(int Level, int32 BoneIndex, TMap &BoneToGroup, const TManagedArray& Levels, const TMap& BoundingBoxes, TSet>& 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>& OutOverlappedPairs) { if (Level > 0) { const TManagedArray& Parents = GeometryCollection->Parent; if (!ensure(GeometryCollection->HasAttribute("Level", FGeometryCollection::TransformGroup))) { return; } const TManagedArray& Levels = GeometryCollection->GetAttribute("Level", FGeometryCollection::TransformGroup); TArray Transforms; GeometryCollectionAlgo::GlobalMatrices(GeometryCollection->Transform, Parents, Transforms); TArray TransformToGeometry; GeometryCollectionAlgo::BuildTransformGroupToGeometryGroupMap(*GeometryCollection, TransformToGeometry); const TManagedArray& BoundingBoxes = GeometryCollection->BoundingBox; TMap BoneToGroup; TMap 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& Transform = GeometryCollection->Transform; if (Transform.Num()) { const TManagedArray& BoneMap = GeometryCollection->BoneMap; TManagedArray& Vertex = GeometryCollection->Vertex; TArray SurfaceParticlesCount; SurfaceParticlesCount.AddZeroed(GeometryCollection->NumElements(FGeometryCollection::TransformGroup)); TArray 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(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(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>> &BoundaryVertexIndices) { check(GeometryCollection); int32 NumGeometries = GeometryCollection->NumElements(FGeometryCollection::GeometryGroup); BoundaryVertexIndices.SetNum(NumGeometries); TMap CoincidentVerticesMap; TSet 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& Indices = GeometryCollection->Indices; TMultiMap 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 &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>> &BoundaryVertexIndices, bool bWoundClockwise, float MinTriangleAreaSq) { check(GeometryCollection); int32 NumGeometries = GeometryCollection->NumElements(FGeometryCollection::GeometryGroup); check(BoundaryVertexIndices.Num() == NumGeometries); TArray> Faces; Faces.SetNum(NumGeometries); TMap, int32> FaceCountPerEdge; auto AddTriToFaceCountPerEdge = [&FaceCountPerEdge](const FIntVector &Face) { auto AddEdge = [&FaceCountPerEdge](int32 A, int32 B) { FaceCountPerEdge.FindOrAdd(TPair(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(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> &GeomBoundaries = BoundaryVertexIndices[GeometryIdx]; TArray &GeomFaces = Faces[GeometryIdx]; // for v0 let's just put a fan here for (const TArray& 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> &AddFaces) { check(GeometryCollection); int32 NumGeometries = GeometryCollection->NumElements(FGeometryCollection::GeometryGroup); check(AddFaces.Num() == NumGeometries); int32 AddCount = 0; for (const TArray &GeomFaces : AddFaces) { AddCount += GeomFaces.Num(); } int32 OldNumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup); GeometryCollection->AddElements(AddCount, FGeometryCollection::FacesGroup); int32 NewNumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup); TArray 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& FaceCounts, const TArray& 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 UnusedFaces, UnusedVertices; auto AddRange = [](TArray& OutArray, int32 StartIncl, int32 EndExcl) { for (int32 Idx = StartIncl; Idx < EndExcl; Idx++) { OutArray.Add(Idx); } }; auto CountElements = [&AddRange](int32 OldTotal, const TArray& NewCounts, const TManagedArray& OldCounts, const TManagedArray& OldStarts, int32& OutTotal, TArray& 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& NewCounts, const TManagedArray& OldCounts, const TManagedArray& OldStarts, TArray& Unused, TArray& 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 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 ToDelete; AddRange(ToDelete, NewNumVertices, OldNumVertices); GeometryCollection->RemoveElements(FGeometryCollection::VerticesGroup, ToDelete, ProcessingParams); } if (NewNumFaces < OldNumFaces) { TArray 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& CoincidentVerticesMap, TSet& VertexToDeleteSet) { check(GeometryCollection); const TManagedArray& VertexArray = GeometryCollection->Vertex; const TManagedArray& BoneMapArray = GeometryCollection->BoneMap; const TManagedArray& 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 LocalCoincidentVerticesMap; TSet 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 VertexToDeleteSet; TMap CoincidentVerticesMap; ComputeCoincidentVertices(GeometryCollection, Tolerance, CoincidentVerticesMap, VertexToDeleteSet); // Swap VertexIndex in Indices array TManagedArray& 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 DelList = VertexToDeleteSet.Array(); DelList.Sort(); GeometryCollection->RemoveElements(FGeometryCollection::VerticesGroup, DelList); } void ComputeZeroAreaFaces(const FGeometryCollection* GeometryCollection, const float Tolerance, TSet& FaceToDeleteSet) { check(GeometryCollection); const TManagedArray& VertexArray = GeometryCollection->Vertex; const TManagedArray& IndicesArray = GeometryCollection->Indices; const TManagedArray& 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 FaceToDeleteSet; ComputeZeroAreaFaces(GeometryCollection, Tolerance, FaceToDeleteSet); TArray DelList = FaceToDeleteSet.Array(); DelList.Sort(); GeometryCollection->RemoveElements(FGeometryCollection::FacesGroup, DelList); } void ComputeHiddenFaces(const FGeometryCollection* GeometryCollection, TSet& FaceToDeleteSet) { check(GeometryCollection); const TManagedArray& IndicesArray = GeometryCollection->Indices; const TManagedArray& VisibleArray = GeometryCollection->Visible; const TManagedArray& 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 FaceToDeleteSet; ComputeHiddenFaces(GeometryCollection, FaceToDeleteSet); TArray DelList = FaceToDeleteSet.Array(); DelList.Sort(); GeometryCollection->RemoveElements(FGeometryCollection::FacesGroup, DelList); } void ComputeStaleVertices(const FGeometryCollection* GeometryCollection, TSet& VertexToDeleteSet) { check(GeometryCollection); const TManagedArray& VertexArray = GeometryCollection->Vertex; const TManagedArray& BoneMapArray = GeometryCollection->BoneMap; const TManagedArray& TransformIndexArray = GeometryCollection->TransformIndex; const TManagedArray& IndicesArray = GeometryCollection->Indices; int32 NumVertices = GeometryCollection->NumElements(FGeometryCollection::VerticesGroup); int32 NumFaces = GeometryCollection->NumElements(FGeometryCollection::FacesGroup); TArray 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 *TetArrayPtr = GeometryCollection->FindAttributeTyped("Tetrahedron", "Tetrahedral")) { int32 NumTetrahedron = GeometryCollection->NumElements("Tetrahedral"); const TManagedArray& 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 VertexToDeleteSet; ComputeStaleVertices(GeometryCollection, VertexToDeleteSet); TArray DelList = VertexToDeleteSet.Array(); DelList.Sort(); GeometryCollection->RemoveElements(FGeometryCollection::VerticesGroup, DelList); } void ComputeEdgeInFaces(const FGeometryCollection* GeometryCollection, TMap& FaceEdgeMap) { check(GeometryCollection); const TManagedArray& 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& 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 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 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 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 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 ClustersToProcess; //enqueue all roots for (int32 TransformGroupIndex = 0; TransformGroupIndex < NumTransforms; TransformGroupIndex++) { if (HierarchyFacade.GetParent(TransformGroupIndex) == FGeometryCollection::Invalid) { ClustersToProcess.Emplace(TransformGroupIndex); } } TArray 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* 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; } }