// Copyright Epic Games, Inc. All Rights Reserved. #include "SkeletalSimplifierMeshManager.h" /** * Slightly modified version of the quadric simplifier found in MeshSimplifier\Private\MeshSimplify.h * that code caries the copyright-- */ // Copyright (C) 2009 Nine Realms, Inc #define INVALID_EDGE_ID UINT32_MAX SkeletalSimplifier::FSimplifierMeshManager::FSimplifierMeshManager(const MeshVertType* InSrcVerts, const uint32 InNumSrcVerts, const uint32* InSrcIndexes, const uint32 InNumSrcIndexes, const bool bMergeBonesIfSamePos) : bWeldBonesIfSamePos(bMergeBonesIfSamePos), NumSrcVerts(InNumSrcVerts), NumSrcTris(InNumSrcIndexes / 3), ReducedNumVerts(InNumSrcVerts), ReducedNumTris(InNumSrcIndexes / 3), EdgeVertIdHashMap(1 << FMath::Min(16u, FMath::FloorLog2(InNumSrcVerts))) { // Allocate verts and tris VertArray.Empty(); VertArray.AddDefaulted(NumSrcVerts); TriArray.Empty(); TriArray.AddDefaulted(NumSrcTris); // Deep copy the verts for (uint32 i = 0; i < InNumSrcVerts; ++i) { VertArray[ i ].vert = InSrcVerts[ i ]; } // Register the verts with the tris for (uint32 i = 0; i < (uint32)NumSrcTris; ++i) { uint32 Offset = 3 * i; for (uint32 j = 0; j < 3; ++j) { uint32 IndexIdx = Offset + j; uint32 VertIdx = InSrcIndexes[ IndexIdx ]; checkSlow(IndexIdx < InNumSrcIndexes); checkSlow(VertIdx < InNumSrcVerts); TriArray[ i ].verts[ j ] = &VertArray[ VertIdx ]; } } // Register each tri with the vert. for (int32 i = 0; i < NumSrcTris; ++i) { for (int32 j = 0; j < 3; ++j) { SimpTriType* TriPtr = &TriArray[ i ]; SimpVertType* VertPtr = TriPtr->verts[j]; VertPtr->adjTris.Add(TriPtr); } } // Group the verts that share the same location. GroupVerts(VertArray); // Set the Attribute ElementIDs on the verts - used to track split and non-split attributes. // Note: this requires that GroupVerts() has already been called. SetAttributeIDS(VertArray); // Populate EdgeArray MakeEdges(VertArray, NumSrcTris, EdgeArray); // Links all the edges together GroupEdges(EdgeArray); { EdgeVertIdHashMap.Resize(EdgeArray.Num()); { TArray HashValues; ResizeArray(HashValues, EdgeArray.Num()); const auto* Edges = EdgeArray.GetData(); for (int32 i = 0, I = EdgeArray.Num(); i < I; ++i) { HashValues[i] = HashEdge(Edges[i].v0, Edges[i].v1); } for (int32 i = 0, I = EdgeArray.Num(); i < I; i++) { EdgeVertIdHashMap.Add(HashValues[i], i); } } } } void SkeletalSimplifier::FSimplifierMeshManager::GroupVerts(TArray& Verts) { const int32 NumVerts = Verts.Num(); // group verts that share a point FHashTable HashTable(1 << FMath::Min(16u, FMath::FloorLog2(NumVerts / 2)), NumVerts); TArray HashValues; ResizeArray(HashValues, NumVerts); { // Compute the hash values for (int32 i = 0; i < NumVerts; ++i) { HashValues[i] = HashPoint((FVector)Verts[i].GetPos()); } // insert the hash values. for (int i = 0; i < NumVerts; i++) { HashTable.Add(HashValues[i], i); } } const bool bTestCoincidentBones = !bWeldBonesIfSamePos; for (int i = 0; i < NumVerts; i++) { SimpVertType* v1 = &Verts[i]; // already grouped if (v1->next != v1) { continue; } // find any matching verts const uint32 hash = HashValues[i]; for (int j = HashTable.First(hash); HashTable.IsValid(j); j = HashTable.Next(j)) { SimpVertType* v2 = &Verts[j]; if (v1 == v2) continue; // link if the verts have the same pos (and optionally same bones) if (IsCoincident(v1, v2, bTestCoincidentBones)) { checkSlow(v2->next == v2); checkSlow(v2->prev == v2); // insert v2 after v1 v2->next = v1->next; v2->next->prev = v2; v2->prev = v1; v1->next = v2; } } } } void SkeletalSimplifier::FSimplifierMeshManager::SetAttributeIDS(TArray& Verts) { const int32 NumVerts = Verts.Num(); // set element ids on the verts. VertPtrArray CoincidentSimpVerts; int NormalID = 0; int TangentID = 0; int BiTangetID = 0; int ColorID = 0; int UVIDs[MAX_TEXCOORDS]; for (int i = 0; i < MAX_TEXCOORDS; ++i) UVIDs[i] = 0; for (int i = 0; i < NumVerts; ++i) { CoincidentSimpVerts.Reset(); SimpVertType& HeadSimpVert = Verts[i]; auto& HeadVertAttrs = HeadSimpVert.vert.BasicAttributes; // already processed this vert ( and all the coincident verts with it). if (HeadVertAttrs.ElementIDs.ColorID != -1) continue; // give this vert the next available element Id HeadVertAttrs.ElementIDs.NormalID = NormalID++; HeadVertAttrs.ElementIDs.TangentID = TangentID++; HeadVertAttrs.ElementIDs.BiTangentID = BiTangetID++; HeadVertAttrs.ElementIDs.ColorID = ColorID++; for (int t = 0; t < MAX_TEXCOORDS; ++t) { HeadVertAttrs.ElementIDs.TexCoordsID[t] = UVIDs[t]++; } // collect the verts in the linklist - they share the same location. GetVertsInGroup(HeadSimpVert, CoincidentSimpVerts); // The HeadSimpVert is the j=0 one.. for (int j = 1; j < CoincidentSimpVerts.Num(); ++j) { SimpVertType* ActiveSimpVert = CoincidentSimpVerts[j]; auto& ActiveVertAttrs = ActiveSimpVert->vert.BasicAttributes; checkSlow(ActiveVertAttrs.ElementIDs.NormalID == -1); // If normals match another vert at this location, they should share ID for (int k = 0; k < j; ++k) { SimpVertType* ProcessedSimpVert = CoincidentSimpVerts[k]; auto& ProcessedVertAttrs = ProcessedSimpVert->vert.BasicAttributes; if (ProcessedVertAttrs.Normal == ActiveVertAttrs.Normal) { ActiveVertAttrs.ElementIDs.NormalID = ProcessedVertAttrs.ElementIDs.NormalID; break; } } // If normal didn't mach with any earlier vert at this location. Give new ID if (ActiveVertAttrs.ElementIDs.NormalID == -1) { ActiveVertAttrs.ElementIDs.NormalID = NormalID++; } // If tangents match another vert at this location, they should share ID for (int k = 0; k < j; ++k) { SimpVertType* ProcessedSimpVert = CoincidentSimpVerts[k]; auto& ProcessedVertAttrs = ProcessedSimpVert->vert.BasicAttributes; if (ProcessedVertAttrs.Tangent == ActiveVertAttrs.Tangent) { ActiveVertAttrs.ElementIDs.TangentID = ProcessedVertAttrs.ElementIDs.TangentID; break; } } // If tangent didn't mach with any earlier vert at this location. Give new ID if (ActiveVertAttrs.ElementIDs.TangentID == -1) { ActiveVertAttrs.ElementIDs.TangentID = TangentID++; } // If Bitangents match another vert at this location, they should share ID for (int k = 0; k < j; ++k) { SimpVertType* ProcessedSimpVert = CoincidentSimpVerts[k]; auto& ProcessedVertAttrs = ProcessedSimpVert->vert.BasicAttributes; if (ProcessedVertAttrs.BiTangent == ActiveVertAttrs.BiTangent) { ActiveVertAttrs.ElementIDs.BiTangentID = ProcessedVertAttrs.ElementIDs.BiTangentID; break; } } // If BiTangent didn't mach with any earlier vert at this location. Give new ID if (ActiveVertAttrs.ElementIDs.BiTangentID == -1) { ActiveVertAttrs.ElementIDs.BiTangentID = BiTangetID++; } // If Color match another vert at this location, they should share ID for (int k = 0; k < j; ++k) { SimpVertType* ProcessedSimpVert = CoincidentSimpVerts[k]; auto& ProcessedVertAttrs = ProcessedSimpVert->vert.BasicAttributes; if (ProcessedVertAttrs.Color == ActiveVertAttrs.Color) { ActiveVertAttrs.ElementIDs.ColorID = ProcessedVertAttrs.ElementIDs.ColorID; break; } } //If Color didn't mach with any earlier vert at this location. Give new ID if (ActiveVertAttrs.ElementIDs.ColorID == -1) { ActiveVertAttrs.ElementIDs.ColorID = ColorID++; } for (int t = 0; t < MAX_TEXCOORDS; ++t) { // look for texture match for (int k = 0; k < j; ++k) { SimpVertType* ProcessedSimpVert = CoincidentSimpVerts[k]; auto& ProcessedVertAttrs = ProcessedSimpVert->vert.BasicAttributes; if (ProcessedVertAttrs.TexCoords[t] == ActiveVertAttrs.TexCoords[t]) { ActiveVertAttrs.ElementIDs.TexCoordsID[t] = ProcessedVertAttrs.ElementIDs.TexCoordsID[t]; break; } } // If TexCoord didn't mach with any earlier vert at this location. Give new ID if (ActiveVertAttrs.ElementIDs.TexCoordsID[t] == -1) { ActiveVertAttrs.ElementIDs.TexCoordsID[t] = UVIDs[t]++; } } } } // @todo Maybe add one pass at to split element bow-ties. // These could have formed along a UV seam if a both sides of the seam shared the same value at a isolated vertex - but in that case a split vertex might not have been generated.. } void SkeletalSimplifier::FSimplifierMeshManager::MakeEdges(const TArray& Verts, const int32 NumTris, TArray& Edges) { // Populate the TArray of edges. const int32 NumVerts = Verts.Num(); int32 maxEdgeSize = FMath::Min(3 * NumTris, 3 * FMath::Max(NumVerts,2) - 6); Edges.Empty(maxEdgeSize); for (int i = 0; i < NumVerts; i++) { AppendConnectedEdges(&Verts[i], Edges); } // Guessed wrong on num edges. Array was resized so fix up pointers. if (Edges.Num() > maxEdgeSize) { for (int32 i = 0; i < Edges.Num(); ++i) { SimpEdgeType& edge = Edges[i]; edge.next = &edge; edge.prev = &edge; } } } void SkeletalSimplifier::FSimplifierMeshManager::AppendConnectedEdges(const SimpVertType* Vert, TArray< SimpEdgeType >& Edges) { // Need to cast the vert - but the method we are calling on it really should be const.. SimpVertType* V = const_cast(Vert); checkSlow(V->adjTris.Num() > 0); TArray< SimpVertType*, TInlineAllocator<64> > adjVerts; V->FindAdjacentVerts(adjVerts); SimpVertType* v0 = V; for (SimpVertType* v1 : adjVerts) { if (v0 < v1) { // add edge Edges.AddDefaulted(); SimpEdgeType& edge = Edges.Last(); edge.v0 = v0; edge.v1 = v1; } } } void SkeletalSimplifier::FSimplifierMeshManager::GroupEdges(TArray< SimpEdgeType >& Edges) { FHashTable HashTable(1 << FMath::Min(16u, FMath::FloorLog2(Edges.Num() / 2)), Edges.Num()); TArray HashValues; ResizeArray(HashValues, Edges.Num()); { for (int32 i = 0, I = Edges.Num(); i < I; ++i) { uint32 Hash0 = HashPoint((FVector)Edges[i].v0->GetPos()); uint32 Hash1 = HashPoint((FVector)Edges[i].v1->GetPos()); HashValues[i] = Murmur32({ FMath::Min(Hash0, Hash1), FMath::Max(Hash0, Hash1) }); } } for (int32 i = 0, IMax = Edges.Num(); i < IMax; ++i) { HashTable.Add(HashValues[i], i); } const bool bTestCoincidentBones = !bWeldBonesIfSamePos; for (int32 i = 0, IMax = Edges.Num(); i < IMax; ++i) { // already grouped if (Edges[i].next != &Edges[i]) { continue; } // find any matching edges uint32 Hash = HashValues[i]; for (uint32 j = HashTable.First(Hash); HashTable.IsValid(j); j = HashTable.Next(j)) { SimpEdgeType* e1 = &Edges[i]; SimpEdgeType* e2 = &Edges[j]; if (e1 == e2) continue; bool m1 = (e1->v0 == e2->v0 || IsCoincident(e1->v0, e2->v0, bTestCoincidentBones) ) && (e1->v1 == e2->v1 || IsCoincident(e1->v1, e2->v1, bTestCoincidentBones) ); // same edge but with reversed orientation bool m2 = (e1->v0 == e2->v1 || IsCoincident(e1->v0, e2->v1, bTestCoincidentBones)) && (e1->v1 == e2->v0 || IsCoincident(e1->v1, e2->v0, bTestCoincidentBones)); // backwards if (m2) { Swap(e2->v0, e2->v1); } // link if (m1 || m2) { checkSlow(e2->next == e2); checkSlow(e2->prev == e2); e2->next = e1->next; e2->prev = e1; e2->next->prev = e2; e2->prev->next = e2; } } } } void SkeletalSimplifier::FSimplifierMeshManager::GetCoincidentVertGroups(VertPtrArray& CoincidentVertGroups) { for (int32 vId = 0; vId < NumSrcVerts; ++vId) { SimpVertType* Vert = &VertArray[vId]; // Removed vert if (Vert->TestFlags(SIMP_REMOVED)) { continue; } // Single vert if (Vert->next == Vert && Vert->prev == Vert) { continue; } // Find and add the max vert in this group. { SimpVertType* tmp = Vert; SimpVertType* maxVert = Vert; while (tmp->next != Vert) { tmp = tmp->next; if (tmp > maxVert && !tmp->TestFlags(SIMP_REMOVED)) { maxVert = tmp; } } CoincidentVertGroups.AddUnique(maxVert); } } } void SkeletalSimplifier::FSimplifierMeshManager::WeldNonSplitBasicAttributes(EVtxElementWeld WeldType) { // Gather the split-vertex groups. VertPtrArray CoincidentVertGroups; GetCoincidentVertGroups(CoincidentVertGroups); // For each split group, weld the attributes that have the same element ID int32 NumCoincidentVertGroups = CoincidentVertGroups.Num(); for (int32 i = 0; i < NumCoincidentVertGroups; ++i) { SimpVertType* HeadVert = CoincidentVertGroups[i]; if (!HeadVert) continue; // Get the verts that are in this group. VertPtrArray VertGroup; GetVertsInGroup(*HeadVert, VertGroup); int32 NumVertsInGroup = VertGroup.Num(); // reject any groups that weren't really split. if (NumVertsInGroup < 2) continue; // functor that partitions attributes by attribute ID and welds attributes in a partition to the average value. auto Weld = [&VertGroup, NumVertsInGroup](auto& IDAccessor, auto& ValueAccessor, auto ZeroValue) { // container used to sort coincident vert by ID. // used to find groups with same ID to weld together. TArray> VertAndIDArray; for (SimpVertType* v : VertGroup) { VertAndIDArray.Emplace(v, IDAccessor(v)); } // sort by ID VertAndIDArray.Sort([](const FVertAndID& A, const FVertAndID& B)->bool {return A.ID < B.ID; }); // find and process the partitions. int32 PartitionStart = 0; while (PartitionStart < NumVertsInGroup) { auto AveValue = ZeroValue; int32 PartitionElID = VertAndIDArray[PartitionStart].ID; int32 PartitionEnd = NumVertsInGroup; for (int32 n = PartitionStart; n < NumVertsInGroup; ++n) { if (VertAndIDArray[n].ID == PartitionElID) { AveValue += ValueAccessor(VertAndIDArray[n].SrcVert); } else { PartitionEnd = n; break; } } AveValue /= (PartitionEnd - PartitionStart); for (int32 n = PartitionStart; n < PartitionEnd; ++n) { ValueAccessor(VertAndIDArray[n].SrcVert) = AveValue; } PartitionStart = PartitionEnd; } }; // Weld Normals with the same NormalID. if (WeldType == EVtxElementWeld::Normal) { auto NormalIDAccessor = [](SimpVertType* SimpVert)->int32 { return SimpVert->vert.BasicAttributes.ElementIDs.NormalID; }; auto NormalValueAccessor = [](SimpVertType* SimpVert)->FVector3f& { return SimpVert->vert.BasicAttributes.Normal; }; FVector3f ZeroValue(0, 0, 0); Weld(NormalIDAccessor, NormalValueAccessor, ZeroValue); } // Weld Tangents with same TangentID if (WeldType == EVtxElementWeld::Tangent) { auto TangentIDAccessor = [](SimpVertType* SimpVert)->int32 { return SimpVert->vert.BasicAttributes.ElementIDs.TangentID; }; auto TangentValueAccessor = [](SimpVertType* SimpVert)->FVector3f& { return SimpVert->vert.BasicAttributes.Tangent; }; FVector3f ZeroValue(0, 0, 0); Weld(TangentIDAccessor, TangentValueAccessor, ZeroValue); } // Weld BiTangent with same BiTangentID if (WeldType == EVtxElementWeld::BiTangent) { auto BiTangentIDAccessor = [](SimpVertType* SimpVert)->int32 { return SimpVert->vert.BasicAttributes.ElementIDs.BiTangentID; }; auto BiTangentValueAccessor = [](SimpVertType* SimpVert)->FVector3f& { return SimpVert->vert.BasicAttributes.BiTangent; }; FVector3f ZeroValue(0, 0, 0); Weld(BiTangentIDAccessor, BiTangentValueAccessor, ZeroValue); } // Weld Color with same ColorID if (WeldType == EVtxElementWeld::Color) { auto ColorIDAccessor = [](SimpVertType* SimpVert)->int32 { return SimpVert->vert.BasicAttributes.ElementIDs.ColorID; }; auto ColorValueAccessor = [](SimpVertType* SimpVert)->FLinearColor& { return SimpVert->vert.BasicAttributes.Color; }; FLinearColor ZeroValue = FLinearColor::Transparent; Weld(ColorIDAccessor, ColorValueAccessor, ZeroValue); } // Weld UVs with same TexCoordsID if (WeldType == EVtxElementWeld::UV) { int32 NumTexCoords = SkeletalSimplifier::BasicAttrArray::NumUVs; for (int32 t = 0; t < NumTexCoords; ++t) { auto TexCoordIDAccessor = [t](SimpVertType* SimpVert)->int32 { return SimpVert->vert.BasicAttributes.ElementIDs.TexCoordsID[t]; }; auto TexCoordValueAccessor = [t](SimpVertType* SimpVert)->FVector2f& { return SimpVert->vert.BasicAttributes.TexCoords[t]; }; FVector2f ZeroValue(0, 0); Weld(TexCoordIDAccessor, TexCoordValueAccessor, ZeroValue); } } // After welding, need to "correct" the vert to make sure the vector attributes are normalized etc. for (SimpVertType* v : VertGroup) { v->vert.Correct(); } } } // @todo, this shares a lot of code with GroupEdges - they should be unified.. void SkeletalSimplifier::FSimplifierMeshManager::RebuildEdgeLinkLists(EdgePtrArray& CandidateEdgePtrArray) { const uint32 NumEdges = CandidateEdgePtrArray.Num(); // Fix edge groups - when one edge of a triangle collapses the opposing edges end up merging.. this accounts for that i think. { FHashTable HashTable(128, NumEdges); // ungroup edges for (uint32 i = 0; i < NumEdges; ++i) { SimpEdgeType* edge = CandidateEdgePtrArray[i]; if (edge->TestFlags(SIMP_REMOVED)) continue; edge->next = edge; edge->prev = edge; } // Hash Edges. for (uint32 i = 0; i < NumEdges; ++i) { SimpEdgeType& edge = *CandidateEdgePtrArray[i]; if (edge.TestFlags(SIMP_REMOVED)) continue; HashTable.Add(HashEdgePosition(edge), i); } const bool bTestCoincidentBones = !bWeldBonesIfSamePos; // regroup edges for (uint32 i = 0; i < NumEdges; ++i) { SimpEdgeType* edge = CandidateEdgePtrArray[i]; if (edge->TestFlags(SIMP_REMOVED)) continue; // already grouped if (edge->next != edge) continue; // find any matching edges uint32 hash = HashEdgePosition(*edge); SimpEdgeType* e1 = edge; for (uint32 j = HashTable.First(hash); HashTable.IsValid(j); j = HashTable.Next(j)) { SimpEdgeType* e2 = CandidateEdgePtrArray[j]; if (e1 == e2) continue; bool m1 = (e1->v0 == e2->v0 && e1->v1 == e2->v1) || ( IsCoincident(e1->v0, e2->v0, bTestCoincidentBones) && IsCoincident(e1->v1, e2->v1, bTestCoincidentBones) ); // same edge but with reversed orientation bool m2 = (e1->v0 == e2->v1 && e1->v1 == e2->v0) || ( IsCoincident(e1->v0, e2->v1, bTestCoincidentBones) && IsCoincident(e1->v1, e2->v0, bTestCoincidentBones)); // backwards if (m2) Swap(e2->v0, e2->v1); // link if (m1 || m2) { checkSlow(e2->next == e2); checkSlow(e2->prev == e2); e2->next = e1->next; e2->prev = e1; e2->next->prev = e2; e2->prev->next = e2; } } } } } void SkeletalSimplifier::FSimplifierMeshManager::VisitEdges(TFunctionRef EdgeVisitor) { TArray< SimpVertType*, TInlineAllocator<64> > adjVerts; if (NumSrcVerts == 0 || NumSrcTris == 0) { //Avoid trying to compute an empty mesh return; } // clear the mark2 flags. We use these to determine if we have visited a vert group. for (int32 i = 0; i < NumSrcVerts; ++i) { SimpVertType* v0 = &VertArray[i]; v0->DisableFlags(SIMP_MARK2); } for (int i = 0; i < NumSrcVerts; i++) { SimpVertType* v0 = &VertArray[i]; checkSlow(v0 != NULL); check(v0->adjTris.Num() > 0); // we have already visited this vertex group if (v0->TestFlags(SIMP_MARK2)) { continue; } if (v0->TestFlags(SIMP_REMOVED)) { continue; } //Find all the verts that are adjacent to any vert in this group. adjVerts.Reset(); SimpVertType* v0Smallest = v0; { SimpVertType* v = v0; do { for (TriIterator triIter = v->adjTris.Begin(); triIter != v->adjTris.End(); ++triIter) { for (int j = 0; j < 3; j++) { SimpVertType* TriVert = (*triIter)->verts[j]; if (TriVert != v) { adjVerts.AddUnique(TriVert); } } } v = v->next; if (v0Smallest > v) { v0Smallest = v; } } while (v != v0); } for (SimpVertType* v1 : adjVerts) { // visit edges that are incoming to this vertex group // note, we may end up visiting a few edges twice. if (v0Smallest < v1) { // set if this edge is boundary // find faces that share v0 and v1 v0->EnableAdjTriFlagsGroup(SIMP_MARK1); v1->DisableAdjTriFlagsGroup(SIMP_MARK1); int32 AdjFaceCount = 0; SimpVertType* vert = v0; do { for (TriIterator j = vert->adjTris.Begin(); j != vert->adjTris.End(); ++j) { SimpTriType* tri = *j; AdjFaceCount += tri->TestFlags(SIMP_MARK1) ? 0 : 1; } vert = vert->next; } while (vert != v0); // reset v0-group flag. v0->DisableAdjTriFlagsGroup(SIMP_MARK1); // process this edge. EdgeVisitor(v0, v1, AdjFaceCount); } } // visited this vert and all the incoming edges. v0->EnableFlagsGroup(SIMP_MARK2); } for (int32 i = 0; i < NumSrcVerts; ++i) { SimpVertType* v0 = &VertArray[i]; v0->DisableFlags(SIMP_MARK2); } } //[TODO] convert this to use the VisitEdges method. void SkeletalSimplifier::FSimplifierMeshManager::FlagBoundary(const ESimpElementFlags Flag) { TArray< SimpVertType*, TInlineAllocator<64> > adjVerts; if (NumSrcVerts == 0 || NumSrcTris == 0) { //Avoid trying to compute an empty mesh return; } for (int i = 0; i < NumSrcVerts; i++) { SimpVertType* v0 = &VertArray[i]; checkSlow(v0 != NULL); check(v0->adjTris.Num() > 0); // not sure if this test is valid. if (v0->TestFlags(Flag)) { // we must have visited this vert already in a vert group continue; } //Find all the verts that are adjacent to any vert in this group. adjVerts.Reset(); //the scope below replaces v0->FindAdjacentVertsGroup(adjVerts); { SimpVertType* v = v0; do { for (TriIterator triIter = v->adjTris.Begin(); triIter != v->adjTris.End(); ++triIter) { for (int j = 0; j < 3; j++) { SimpVertType* TriVert = (*triIter)->verts[j]; if (TriVert != v) { adjVerts.AddUnique(TriVert); } } } v = v->next; } while (v != v0); } for (SimpVertType* v1 : adjVerts) { if (v0 < v1) { // set if this edge is boundary // find faces that share v0 and v1 v0->EnableAdjTriFlagsGroup(SIMP_MARK1); v1->DisableAdjTriFlagsGroup(SIMP_MARK1); int faceCount = 0; SimpVertType* vert = v0; do { for (TriIterator j = vert->adjTris.Begin(); j != vert->adjTris.End(); ++j) { SimpTriType* tri = *j; faceCount += tri->TestFlags(SIMP_MARK1) ? 0 : 1; } vert = vert->next; } while (vert != v0); // reset v0-group flag. v0->DisableAdjTriFlagsGroup(SIMP_MARK1); if (faceCount == 1) { // only one face on this edge v0->EnableFlagsGroup(Flag); v1->EnableFlagsGroup(Flag); } } } } } void SkeletalSimplifier::FSimplifierMeshManager::FlagBoxCorners(const ESimpElementFlags Flag) { TArray VisitedMask; VisitedMask.AddZeroed(NumSrcVerts); for (int32 i = 0; i < NumSrcVerts; i++) { if (VisitedMask[i] != 0) { continue; } // Collect all the face normals associated with this vertgroup TArray> FaceNormals; SimpVertType* Vert = GetVertPtr(i); SimpVertType* seedVert = Vert; do { for (TriIterator triIter = Vert->adjTris.Begin(); triIter != Vert->adjTris.End(); ++triIter) { bool bIsDuplicate = false; SimpTriType* tri = *triIter; FVector Nrml = (FVector)tri->GetNormal(); for (int32 fnIdx = 0; fnIdx < FaceNormals.Num(); ++fnIdx) { FVector ExistingNormal = FaceNormals[fnIdx]; ExistingNormal.Normalize(); float DotValue = FVector::DotProduct(ExistingNormal, Nrml); if (1.f - DotValue < 0.133975f) // 30 degrees. { // we already have this vector. // could be a corner bIsDuplicate = true; FaceNormals[fnIdx] += Nrml; continue; } } if (!bIsDuplicate) { FaceNormals.Add(Nrml); } } // mark as visited. uint32 Idx = GetVertIndex(Vert); VisitedMask[Idx] = 1; Vert = Vert->next; } while (Vert != seedVert); int32 FaceCount = FaceNormals.Num(); if ( FaceNormals.Num() == 3 ) { FVector& A = FaceNormals[0]; FVector& B = FaceNormals[1]; FVector& C = FaceNormals[2]; A.Normalize(); B.Normalize(); C.Normalize(); float AdotB = FVector::DotProduct(A, B); float BdotC = FVector::DotProduct(B, C); float AdotC = FVector::DotProduct(A, C); if (FMath::Abs(AdotB) < 0.259f && FMath::Abs(BdotC) < 0.259f && FMath::Abs(AdotC) < 0.259f) // 15-degrees off normal { Vert->EnableFlagsGroup(Flag); } } } } void SkeletalSimplifier::FSimplifierMeshManager::FlagEdges(const TFunction IsDifferent, const ESimpElementFlags Flag) { int32 NumEdges = EdgeArray.Num(); for (int32 i = 0; i < NumEdges; ++i) { SimpEdgeType& Edge = EdgeArray[i]; if (IsDifferent(Edge.v0, Edge.v1)) { Edge.EnableFlags(Flag); Edge.v0->EnableFlags(Flag); Edge.v1->EnableFlags(Flag); } } for (int i = 0; i < NumSrcVerts; i++) { SimpVertType* v = &VertArray[i]; SimpVertType* v1 = v; if (v1 == nullptr || v1->TestFlags(Flag)) { continue; } // only one vert in this group if (v1->next == v1) { continue; } bool bAddedFlag = false; do { if (IsDifferent(v, v->next)) { // we only need to mark one of the vertices in this vertex group since the lock state will be propagated to the others. v->EnableFlags(Flag); bAddedFlag = true; } v = v->next; } while (v != v1); // add the locked state to all the vertices in this group. if (bAddedFlag) { v = v1; do { v->EnableFlags(Flag); v = v->next; } while (v != v1); } } } void SkeletalSimplifier::FSimplifierMeshManager::GetAdjacentTopology(const SimpVertType* VertPtr, TriPtrArray& DirtyTris, VertPtrArray& DirtyVerts, EdgePtrArray& DirtyEdges) { // need this cast because the const version is missing on the vert.. SimpVertType* v = const_cast(VertPtr); // Gather pointers to all the triangles that share this vert. // Update all tris touching collapse edge. for (TriIterator triIter = v->adjTris.Begin(); triIter != v->adjTris.End(); ++triIter) { DirtyTris.AddUnique(*triIter); } // Gather all verts that are adjacent to this one. TArray< SimpVertType*, TInlineAllocator<64> > adjVerts; v->FindAdjacentVerts(adjVerts); // Gather verts that are adjacent to VertPtr for (int i = 0, Num = adjVerts.Num(); i < Num; i++) { DirtyVerts.AddUnique(adjVerts[i]); } // Gather verts that are adjacent to VertPtr for (int i = 0, Num = adjVerts.Num(); i < Num; i++) { adjVerts[i]->EnableFlags(SIMP_MARK2); } // update the costs of all edges connected to any face adjacent to v for (int i = 0, iMax = adjVerts.Num(); i < iMax; ++i) { SimpVertType* AdjVert = adjVerts[i]; AdjVert->EnableAdjVertFlags(SIMP_MARK1); for (TriIterator triIter = AdjVert->adjTris.Begin(); triIter != AdjVert->adjTris.End(); ++triIter) { SimpTriType* tri = *triIter; for (int k = 0; k < 3; k++) { SimpVertType* vert = tri->verts[k]; if (vert->TestFlags(SIMP_MARK1) && !vert->TestFlags(SIMP_MARK2) && vert != AdjVert) { SimpEdgeType* edge = FindEdge(AdjVert, vert); DirtyEdges.AddUnique(edge); } vert->DisableFlags(SIMP_MARK1); } } AdjVert->DisableFlags(SIMP_MARK2); } } void SkeletalSimplifier::FSimplifierMeshManager::GetAdjacentTopology(const SimpEdgeType& GroupedEdge, TriPtrArray& DirtyTris, VertPtrArray& DirtyVerts, EdgePtrArray& DirtyEdges) { // Find the parts of the mesh that will be 'dirty' after the // edge collapse. const SimpVertType* v = GroupedEdge.v0; do { GetAdjacentTopology(v, DirtyTris, DirtyVerts, DirtyEdges); v = v->next; } while (v != GroupedEdge.v0); v = GroupedEdge.v1; do { GetAdjacentTopology(v, DirtyTris, DirtyVerts, DirtyEdges); v = v->next; } while (v != GroupedEdge.v1); } int32 SkeletalSimplifier::FSimplifierMeshManager::RemoveEdgeIfInvalid(EdgePtrArray& CandidateEdges, IdxArray& RemovedEdgeIdxArray) { const uint32 NumCandidateEdges = CandidateEdges.Num(); for (uint32 i = 0; i < NumCandidateEdges; ++i) { SimpEdgeType* EdgePtr = CandidateEdges[i]; // Edge has already been removed.. if (!EdgePtr) continue; // Verify the edge has an adjacent face. auto HasAdjacentFace = [](SimpEdgeType* e)->bool { // Verify that this is truly an edge of a triangle e->v0->EnableAdjVertFlags(SIMP_MARK1); e->v1->DisableAdjVertFlags(SIMP_MARK1); if (e->v0->TestFlags(SIMP_MARK1)) { // Invalid edge results from collapsing a bridge tri // There are no actual triangles connecting these verts e->v0->DisableAdjVertFlags(SIMP_MARK1); return false; } return true; }; // remove invalid faces from the hash, edge group and flag. if ( IsInvalid(EdgePtr) || !HasAdjacentFace(EdgePtr) ) // one of the verts touches zero triangles { // unlinks the edge from the edge group, remove from edge hash, add removed flag. const uint32 Idx = RemoveEdge(*EdgePtr); // Record the index of the edge we remove. if (Idx < INVALID_EDGE_ID) { RemovedEdgeIdxArray.AddUnique(Idx); } CandidateEdges[i] = NULL; } } return RemovedEdgeIdxArray.Num(); } uint32 SkeletalSimplifier::FSimplifierMeshManager::RemoveEdge(const SimpVertType* VertAPtr, const SimpVertType* VertBPtr) { auto HashAndIdx = GetEdgeHashPair(VertAPtr, VertBPtr); uint32 Idx = HashAndIdx.Key; // Early out if this edge doesn't exist. if (Idx == INVALID_EDGE_ID) { return Idx; } SimpEdgeType& Edge = EdgeArray[Idx]; if (Edge.TestFlags(SIMP_REMOVED)) { Idx = INVALID_EDGE_ID; } else { // mark as removed Edge.EnableFlags(SIMP_REMOVED); EdgeVertIdHashMap.Remove(HashAndIdx.Value, Idx); } // remove this edge from its edge group Edge.prev->next = Edge.next; Edge.next->prev = Edge.prev; Edge.next = &Edge; Edge.prev = &Edge; // return the Idx return Idx; } uint32 SkeletalSimplifier::FSimplifierMeshManager::RemoveEdge(SimpEdgeType& Edge) { // remove this edge from its edge group Edge.prev->next = Edge.next; Edge.next->prev = Edge.prev; Edge.next = &Edge; Edge.prev = &Edge; uint32 Idx = GetEdgeIndex(&Edge); if (Edge.TestFlags(SIMP_REMOVED)) { Idx = INVALID_EDGE_ID; } else { // mark as removed Edge.EnableFlags(SIMP_REMOVED); uint32 Hash = HashEdge(Edge.v0, Edge.v1); EdgeVertIdHashMap.Remove(Hash, Idx); } // return the Idx return Idx; } uint32 SkeletalSimplifier::FSimplifierMeshManager::ReplaceVertInEdge(const SimpVertType* VertAPtr, const SimpVertType* VertBPtr, SimpVertType* VertAprimePtr) { TPair HashAndIdx = GetEdgeHashPair(VertAPtr, VertBPtr); const uint32 Idx = HashAndIdx.Key; const uint32 HashValue = HashAndIdx.Value; checkSlow(Idx != INVALID_EDGE_ID); SimpEdgeType* edge = &EdgeArray[Idx]; EdgeVertIdHashMap.Remove(HashValue, Idx); EdgeVertIdHashMap.Add(HashEdge(VertAprimePtr, VertBPtr), Idx); if (edge->v0 == VertAPtr) edge->v0 = VertAprimePtr; else edge->v1 = VertAprimePtr; return Idx; } int32 SkeletalSimplifier::FSimplifierMeshManager::RemoveIfDegenerate(TriPtrArray& CandidateTrisPtrArray) { int32 NumRemoved = 0; // remove degenerate triangles // not sure why this happens for (SimpTriType* CandidateTriPtr : CandidateTrisPtrArray) { if (CandidateTriPtr->TestFlags(SIMP_REMOVED)) continue; const FVector& p0 = (FVector)CandidateTriPtr->verts[0]->GetPos(); const FVector& p1 = (FVector)CandidateTriPtr->verts[1]->GetPos(); const FVector& p2 = (FVector)CandidateTriPtr->verts[2]->GetPos(); const FVector n = (p2 - p0) ^ (p1 - p0); if (n.SizeSquared() == 0.0f) { NumRemoved++; CandidateTriPtr->EnableFlags(SIMP_REMOVED); // remove references to tri for (int j = 0; j < 3; j++) { SimpVertType* vert = CandidateTriPtr->verts[j]; vert->adjTris.Remove(CandidateTriPtr); // orphaned verts are removed below } } } ReducedNumTris -= NumRemoved; return NumRemoved; } int32 SkeletalSimplifier::FSimplifierMeshManager::RemoveDegenerateTris() { TriPtrArray TriPtrs; ResizeArray(TriPtrs, NumSrcTris); { for (int32 i = 0, IMax = NumSrcTris; i < IMax; ++i) { TriPtrs[i] = &TriArray[i]; } } return RemoveIfDegenerate(TriPtrs); } int32 SkeletalSimplifier::FSimplifierMeshManager::RemoveIfDegenerate(VertPtrArray& CandidateVertPtrArray) { int32 NumRemoved = 0; // remove orphaned verts for (SimpVertType* VertPtr : CandidateVertPtrArray) { if (VertPtr->TestFlags(SIMP_REMOVED)) continue; if (VertPtr->adjTris.Num() == 0) { NumRemoved++; VertPtr->EnableFlags(SIMP_REMOVED); // ungroup VertPtr->prev->next = VertPtr->next; VertPtr->next->prev = VertPtr->prev; VertPtr->next = VertPtr; VertPtr->prev = VertPtr; } } ReducedNumVerts -= NumRemoved; return NumRemoved; } int32 SkeletalSimplifier::FSimplifierMeshManager::RemoveDegenerateVerts() { VertPtrArray VertPtrs; ResizeArray(VertPtrs, NumSrcVerts); { for (int32 i = 0, IMax = NumSrcVerts; i < IMax; ++i) { VertPtrs[i] = &VertArray[i]; } } return RemoveIfDegenerate(VertPtrs); } int32 SkeletalSimplifier::FSimplifierMeshManager::RemoveIfDegenerate(EdgePtrArray& CandidateEdges, IdxArray& RemovedEdgeIdxArray) { const uint32 NumCandidateEdges = CandidateEdges.Num(); // add all grouped edges for (uint32 i = 0; i < NumCandidateEdges; i++) { SimpEdgeType* edge = CandidateEdges[i]; if (edge->TestFlags(SIMP_REMOVED)) continue; SimpEdgeType* e = edge; do { CandidateEdges.AddUnique(e); e = e->next; } while (e != edge); } // remove dead edges from our edge hash. for (uint32 i = 0, Num = CandidateEdges.Num(); i < Num; i++) { SimpEdgeType* edge = CandidateEdges[i]; if (edge->TestFlags(SIMP_REMOVED)) continue; if (edge->v0 == edge->v1) { edge->EnableFlags(SIMP_REMOVED); // djh 8/3/18. not sure why this happens uint32 Idx = RemoveEdge(*edge); if (Idx < INVALID_EDGE_ID) { RemovedEdgeIdxArray.AddUnique(Idx); } } else if (edge->v0->TestFlags(SIMP_REMOVED) || edge->v1->TestFlags(SIMP_REMOVED)) { uint32 Idx = RemoveEdge(*edge); if (Idx < INVALID_EDGE_ID) { RemovedEdgeIdxArray.AddUnique(Idx); } } } return RemovedEdgeIdxArray.Num(); } void SkeletalSimplifier::FSimplifierMeshManager::UpdateVertexAttriuteIDs(EdgePtrArray& InCoincidentEdges) { // some of the edges may be null / removed. Need to filter these out. EdgePtrArray CoincidentEdges; for (SimpEdgeType* EdgePtr : InCoincidentEdges) { if (!EdgePtr || !EdgePtr->v0 || !EdgePtr->v1 || IsRemoved(EdgePtr)) { continue; } CoincidentEdges.Add(EdgePtr); } int32 NumCoincidentEdges = CoincidentEdges.Num(); if (NumCoincidentEdges == 0) { return; } if (NumCoincidentEdges > 2) { // this is the collapse of a non-manifold edge. Not going to track the element Ids in this badness. return; } // Update the Attribute element IDs on the loose v0 verts (since v0 collapses onto v1) // Loose verts updated any non-split element IDs to those of the v1. if (NumCoincidentEdges == 1) { SimpEdgeType* EdgePtr = CoincidentEdges[0]; auto v0IDs = EdgePtr->v0->vert.BasicAttributes.ElementIDs; auto v1IDs = EdgePtr->v1->vert.BasicAttributes.ElementIDs; // loop over the v0 loose verts and update any non-split element IDs to v1ID. SimpVertType* v0 = EdgePtr->v0; SimpVertType* vCurrent = v0; while (vCurrent->next != v0) { vCurrent = vCurrent->next; // create a mask that is zero where the elements are the same as the v0 elements. // those elements should be merged with their v1 counterparts, retaining the v1 ids. auto MaskIDs = vCurrent->vert.BasicAttributes.ElementIDs - v0IDs; // copy element ids from v1IDs to vCurrent where the mask is off (zero) vCurrent->vert.BasicAttributes.ElementIDs.MaskedCopy(MaskIDs, v1IDs); } } if (NumCoincidentEdges == 2) { // the edge is split. There are two verts at each location SimpVertType* v0[2]; SimpVertType* v1[2]; for (int i = 0; i < 2; ++i) { SimpEdgeType* EdgePtr = CoincidentEdges[i]; v0[i] = EdgePtr->v0; v1[i] = EdgePtr->v1; } // force attrs that are split on v0 to be split on v1. If an attribute is split on v0 and not on v1 // then this copies the ids from v0 to v1 for this attribute. // Why do this? When an attribute seam starts on a mesh, one vertex may have a split and the other not. // This can even happen in the middle of a seam, for example to neighboring UV patches might agree at a single // point. // An Example helps: Say color ID is split on the v0 vertex but not the v1 vertex // v0[0] ColorID = 7, v0[1] ColorID =8 v1[0] ColorID = 11, v1[1] ColorID = 11 // // this should result in copying that split to the v1 vertex. => v1[0] ColorID =7 v1[1] ColorID = 8 // if (v1[0] != v1[1] && v0[0] != v0[1]) // only apply this to the case of two distinct edges. { // Identify the elements that are not split on the v0 vertex (zeros of mask) auto v0MaskIDs = v0[0]->vert.BasicAttributes.ElementIDs - v0[1]->vert.BasicAttributes.ElementIDs; // Identify the elements that are not split on the v1 vertex ( zero of mask ) auto v1MaskIDs = v1[0]->vert.BasicAttributes.ElementIDs - v1[1]->vert.BasicAttributes.ElementIDs; // Copy values from v0 IDs where v1Mask is off (zero) and v0 mask is on (non-zero) v1[0]->vert.BasicAttributes.ElementIDs.MaskedCopy(v1MaskIDs, v0MaskIDs, v0[0]->vert.BasicAttributes.ElementIDs); v1[1]->vert.BasicAttributes.ElementIDs.MaskedCopy(v1MaskIDs, v0MaskIDs, v0[1]->vert.BasicAttributes.ElementIDs); } // Update the Attribute element IDs on the loose v0 verts (since v0 collapses onto v1) // Loose verts updated any non-split element IDs to those of the v1. for (int i = 0; i < 2; ++i) { SimpEdgeType* EdgePtr = CoincidentEdges[i]; auto v0IDs = v0[i]->vert.BasicAttributes.ElementIDs; auto v1IDs = v1[i]->vert.BasicAttributes.ElementIDs; SimpVertType* vCurrent = v0[i]; while (vCurrent->next != v0[i]) { vCurrent = vCurrent->next; if (vCurrent != v0[0] && vCurrent != v0[1]) // only look at the v0 loose verts { // create a mask where the elements are the same auto MaskIDs = vCurrent->vert.BasicAttributes.ElementIDs - v0IDs; // copy elements from V1IDs to tmp where the mask is off (zero) vCurrent->vert.BasicAttributes.ElementIDs.MaskedCopy(MaskIDs, v1IDs); } } } } } bool SkeletalSimplifier::FSimplifierMeshManager::CollapseEdge(SimpEdgeType * EdgePtr, IdxArray& RemovedEdgeIdxArray) { SimpVertType* v0 = EdgePtr->v0; SimpVertType* v1 = EdgePtr->v1; // Collapse the edge uv by moving vertex v0 onto v1 checkSlow(v0 && v1); checkSlow(EdgePtr == FindEdge(v0, v1)); checkSlow(v0->adjTris.Num() > 0); checkSlow(v1->adjTris.Num() > 0); // Because another edge in the same edge group may share a vertex with this edge // and it might have already been collapsed, we can't do this check //checkSlow(! (v0->TestFlags(SIMP_LOCKED) && v1->TestFlags(SIMP_LOCKED)) ); // Verify that this is truly an edge of a triangle v0->EnableAdjVertFlags(SIMP_MARK1); v1->DisableAdjVertFlags(SIMP_MARK1); if (v0->TestFlags(SIMP_MARK1)) { // Invalid edge results from collapsing a bridge tri // There are no actual triangles connecting these verts v0->DisableAdjVertFlags(SIMP_MARK1); EdgePtr->EnableFlags(SIMP_REMOVED); const uint32 Idx = RemoveEdge(*EdgePtr); if (Idx < INVALID_EDGE_ID) { RemovedEdgeIdxArray.AddUnique(Idx); } // return false because the was no real edge to collapse return false; } // update edges from v0 to v1 // Note, the position and other attributes have already been corrected // to have the same values. Here we are just propagating any locked state. if (v0->TestFlags(SIMP_LOCKED)) v1->EnableFlags(SIMP_LOCKED); // this version of the vertex will be removed after the collapse v0->DisableFlags(SIMP_LOCKED); // we already shared the locked state with the remaining vertex // Update 'other'-u edges to 'other'-v edges ( where other != v) for (TriIterator triIter = v0->adjTris.Begin(); triIter != v0->adjTris.End(); ++triIter) { SimpTriType* TriPtr = *triIter; for (int j = 0; j < 3; j++) { SimpVertType* VertPtr = TriPtr->verts[j]; if (VertPtr->TestFlags(SIMP_MARK1)) { // replace v0-vert with v1-vert // first remove v1-vert if it already exists ( it shouldn't..) { uint32 Idx = RemoveEdge(VertPtr, v1); if (Idx < INVALID_EDGE_ID) { RemovedEdgeIdxArray.AddUnique(Idx); } ReplaceVertInEdge(v0, VertPtr, v1); } VertPtr->DisableFlags(SIMP_MARK1); } } } // For faces with verts: v0, v1, other // remove the v0-other edges. v0->EnableAdjVertFlags(SIMP_MARK1); v0->DisableFlags(SIMP_MARK1); v1->DisableFlags(SIMP_MARK1); for (TriIterator triIter = v1->adjTris.Begin(); triIter != v1->adjTris.End(); ++triIter) { SimpTriType* TriPtr = *triIter; for (int j = 0; j < 3; j++) { SimpVertType* VertPtr = TriPtr->verts[j]; if (VertPtr->TestFlags(SIMP_MARK1)) { const uint32 Idx = RemoveEdge(v0, VertPtr); if (Idx < INVALID_EDGE_ID) { RemovedEdgeIdxArray.AddUnique(Idx); } // VertPtr->DisableFlags(SIMP_MARK1); } } } v1->DisableAdjVertFlags(SIMP_MARK1); // Remove collapsed triangles, and fix-up the others that now use v instead of u triangles TriPtrArray v0AdjTris; { uint32 i = 0; ResizeArray(v0AdjTris, v0->adjTris.Num()); for (TriIterator triIter = v0->adjTris.Begin(); triIter != v0->adjTris.End(); ++triIter) { v0AdjTris[i] = *triIter; i++; } } for (int32 i = 0, iMax = v0AdjTris.Num(); i < iMax; ++i) { SimpTriType* TriPtr = v0AdjTris[i]; checkSlow(!TriPtr->TestFlags(SIMP_REMOVED)); checkSlow(TriPtr->HasVertex(v0)); if (TriPtr->HasVertex(v1)) // tri shared by v0 and v1.. { // delete triangles on edge uv ReducedNumTris--; RemoveTri(*TriPtr); } else { // update triangles to have v1 instead of v0 ReplaceTriVertex(*TriPtr, *v0, *v1); } } // remove modified verts and tris from cache v1->EnableAdjVertFlags(SIMP_MARK1); for (TriIterator triIter = v1->adjTris.Begin(); triIter != v1->adjTris.End(); ++triIter) { SimpTriType* TriPtr = *triIter; for (int i = 0; i < 3; i++) { SimpVertType* VertPtr = TriPtr->verts[i]; if (VertPtr->TestFlags(SIMP_MARK1)) { VertPtr->DisableFlags(SIMP_MARK1); } } } // mark v0 as dead. v0->adjTris.Clear(); // u has been removed v0->EnableFlags(SIMP_REMOVED); // Remove the actual edge. const uint32 Idx = RemoveEdge(*EdgePtr); if (Idx < INVALID_EDGE_ID) { RemovedEdgeIdxArray.AddUnique(Idx); } // record the reduced number of verts ReducedNumVerts--; return true; } void SkeletalSimplifier::FSimplifierMeshManager::OutputMesh(MeshVertType* verts, uint32* indexes, TArray* LockedVerts) { int32 NumValidVerts = 0; for (int32 i = 0; i < NumSrcVerts; i++) NumValidVerts += VertArray[i].TestFlags(SIMP_REMOVED) ? 0 : 1; check(NumValidVerts <= ReducedNumVerts); FHashTable HashTable(4096, NumValidVerts); int32 numV = 0; int32 numI = 0; for (int32 i = 0; i < NumSrcTris; i++) { if (TriArray[i].TestFlags(SIMP_REMOVED)) continue; // TODO this is sloppy. There should be no duped verts. Alias by index. for (int j = 0; j < 3; j++) { SimpVertType* vert = TriArray[i].verts[j]; checkSlow(!vert->TestFlags(SIMP_REMOVED)); checkSlow(vert->adjTris.Num() != 0); const FVector& p = (FVector)vert->GetPos(); uint32 hash = HashPoint(p); uint32 f; for (f = HashTable.First(hash); HashTable.IsValid(f); f = HashTable.Next(f)) { if (vert->vert == verts[f]) break; } if (!HashTable.IsValid(f)) { // export the id of the locked vert. if (LockedVerts != NULL && vert->TestFlags(SIMP_LOCKED)) { LockedVerts->Push(numV); } HashTable.Add(hash, numV); verts[numV] = vert->vert; indexes[numI++] = numV; numV++; } else { indexes[numI++] = f; } } } #if 0 check(numV <= NumValidVerts); check(numI <= numTris * 3); numVerts = numV; numTris = numI / 3; #endif } int32 SkeletalSimplifier::FSimplifierMeshManager::CountDegeneratesTris() const { int32 DegenerateCount = 0; // remove degenerate triangles // not sure why this happens for (int i = 0; i < NumSrcTris; i++) { const SimpTriType* tri = &TriArray[i]; if (tri->TestFlags(SIMP_REMOVED)) continue; const FVector& p0 = (FVector)tri->verts[0]->GetPos(); const FVector& p1 = (FVector)tri->verts[1]->GetPos(); const FVector& p2 = (FVector)tri->verts[2]->GetPos(); const FVector n = (p2 - p0) ^ (p1 - p0); if (n.SizeSquared() == 0.0f) { DegenerateCount++; } } return DegenerateCount; } int32 SkeletalSimplifier::FSimplifierMeshManager::CountDegenerateEdges() const { int32 DegenerateCount = 0; const SkeletalSimplifier::SimpEdgeType* Edges = EdgeArray.GetData(); int32 NumEdges = EdgeArray.Num(); for (int32 i = 0; i < NumEdges; ++i) { const SkeletalSimplifier::SimpEdgeType& Edge = Edges[i]; if (Edge.TestFlags(SIMP_REMOVED)) continue; if (Edge.v0 == Edge.v1) { DegenerateCount++; } } return DegenerateCount; } #if 0 // disabled because static analysis insists that NonManifoldEdgeCounter.EdgeCount != 0 is always false. float SkeletalSimplifier::FSimplifierMeshManager::FractionNonManifoldEdges(bool bLockNonManifoldEdges) { FNonManifoldEdgeCounter NonManifoldEdgeCounter; NonManifoldEdgeCounter.EdgeCount = 0; NonManifoldEdgeCounter.NumNonManifoldEdges = 0; NonManifoldEdgeCounter.bLockNonManifoldEdges = bLockNonManifoldEdges; VisitEdges(NonManifoldEdgeCounter); float FractionBadEdges = 0.f; if (NonManifoldEdgeCounter.EdgeCount != 0) { FractionBadEdges = float(NonManifoldEdgeCounter.NumNonManifoldEdges) / NonManifoldEdgeCounter.EdgeCount; } return FractionBadEdges; } #else float SkeletalSimplifier::FSimplifierMeshManager::FractionNonManifoldEdges(bool bLockNonManifoldEdges) { int32 NumVisitedEdges = 0; int32 NumNonManifoldEdges = 0; TArray< SimpVertType*, TInlineAllocator<64> > adjVerts; if (NumSrcVerts == 0 || NumSrcTris == 0) { //Avoid trying to compute an empty mesh return 0.f; } // clear the mark2 flags. We use these to determine if we have visited a vert group. for (int32 i = 0; i < NumSrcVerts; ++i) { SimpVertType* v0 = &VertArray[i]; v0->DisableFlags(SIMP_MARK2); } for (int i = 0; i < NumSrcVerts; i++) { SimpVertType* v0 = &VertArray[i]; checkSlow(v0 != NULL); check(v0->adjTris.Num() > 0); // we have already visited this vertex group if (v0->TestFlags(SIMP_MARK2)) { continue; } if (v0->TestFlags(SIMP_REMOVED)) { continue; } //Find all the verts that are adjacent to any vert in this group. adjVerts.Reset(); SimpVertType* v0Smallest = v0; { SimpVertType* v = v0; do { for (TriIterator triIter = v->adjTris.Begin(); triIter != v->adjTris.End(); ++triIter) { for (int j = 0; j < 3; j++) { SimpVertType* TriVert = (*triIter)->verts[j]; if (TriVert != v) { adjVerts.AddUnique(TriVert); } } } v = v->next; if (v0Smallest > v) { v0Smallest = v; } } while (v != v0); } for (SimpVertType* v1 : adjVerts) { // visit edges that are incoming to this vertex group // note, we may end up visiting a few edges twice. if (v0Smallest < v1) { // set if this edge is boundary // find faces that share v0 and v1 v0->EnableAdjTriFlagsGroup(SIMP_MARK1); v1->DisableAdjTriFlagsGroup(SIMP_MARK1); int32 AdjFaceCount = 0; SimpVertType* vert = v0; do { for (TriIterator j = vert->adjTris.Begin(); j != vert->adjTris.End(); ++j) { SimpTriType* tri = *j; AdjFaceCount += tri->TestFlags(SIMP_MARK1) ? 0 : 1; } vert = vert->next; } while (vert != v0); // reset v0-group flag. v0->DisableAdjTriFlagsGroup(SIMP_MARK1); // process this edge. { NumVisitedEdges++; if (AdjFaceCount > 2) { NumNonManifoldEdges++; if (bLockNonManifoldEdges) { // lock these verts. v0->EnableFlagsGroup(SIMP_LOCKED); v1->EnableFlagsGroup(SIMP_LOCKED); } } } } } // visited this vert and all the incoming edges. v0->EnableFlagsGroup(SIMP_MARK2); } for (int32 i = 0; i < NumSrcVerts; ++i) { SimpVertType* v0 = &VertArray[i]; v0->DisableFlags(SIMP_MARK2); } return float(NumNonManifoldEdges) / (float(NumVisitedEdges) +0.01f); } #endif