Files
UnrealEngine/Engine/Plugins/Experimental/SkeletalReduction/Source/Private/SkeletalSimplifierMeshManager.cpp
2025-05-18 13:04:45 +08:00

2004 lines
48 KiB
C++

// 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<int32> 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<SimpVertType>& Verts)
{
const int32 NumVerts = Verts.Num();
// group verts that share a point
FHashTable HashTable(1 << FMath::Min(16u, FMath::FloorLog2(NumVerts / 2)), NumVerts);
TArray<uint32> 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<SimpVertType>& 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<SimpVertType>& Verts, const int32 NumTris, TArray<SimpEdgeType>& 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<SimpVertType*>(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<uint32> 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<FVertAndID, TInlineAllocator<5>> 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<void(SimpVertType*, SimpVertType*, int32)> 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<int32> 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<FVector, TInlineAllocator<6>> 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<bool(const SimpVertType*, const SimpVertType*)> 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<SimpVertType*>(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<uint32, uint32> 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<int32>* 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