1227 lines
26 KiB
C++
1227 lines
26 KiB
C++
// Copyright (C) 2009 Nine Realms, Inc
|
|
//
|
|
|
|
#include "ProxyLODSimplifier.h"
|
|
|
|
#include "ProxyLODQuadric.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
|
|
|
|
//=============
|
|
// FMeshSimplifier
|
|
//=============
|
|
ProxyLOD::FQuadricMeshSimplifier::FQuadricMeshSimplifier(const MeshVertType* Verts, uint32 NumVerts, const uint32* Indexes, uint32 NumIndexes, float CoAlignmentLimit)
|
|
:
|
|
numSVerts(NumVerts),
|
|
numSTris(NumIndexes / 3),
|
|
numVerts(numSVerts),
|
|
numTris(numSTris),
|
|
coAlignmentLimit(CoAlignmentLimit),
|
|
edgeHash(1 << FMath::Min(16u, FMath::FloorLog2(NumVerts))),
|
|
quadricCache(numSVerts, numSTris)
|
|
{
|
|
// SCOPE_LOG_TIME(TEXT("UE_ProxyLOD_Simplifier_Constructor"), nullptr);
|
|
vertFlagLock = 0;
|
|
triFlagLock = 0;
|
|
|
|
for (uint32 i = 0; i < NumAttributes; i++)
|
|
{
|
|
attributeWeights[i] = 1.0f;
|
|
}
|
|
|
|
sVerts = new SimpVertType[numSVerts];
|
|
sTris = new SimpTriType[numSTris];
|
|
|
|
// The cache uses pointer-based arithmetic based on sVerts and sTris.
|
|
|
|
quadricCache.RegisterCache(sVerts, sTris);
|
|
|
|
// Deep copy the verts
|
|
|
|
Parallel_For(FIntRange(0, NumVerts), [&Verts, this](const FIntRange& Range)
|
|
{
|
|
for (int32 i = Range.begin(), I = Range.end(); i < I; ++i)
|
|
{
|
|
sVerts[i].vert = Verts[i];
|
|
}
|
|
});
|
|
|
|
// Register the verts with the tris
|
|
|
|
Parallel_For(FIntRange(0, numSTris), [&Indexes, this](const FIntRange& Range)
|
|
{
|
|
for (int32 i = Range.begin(), I = Range.end(); i < I; ++i)
|
|
{
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
sTris[i].verts[j] = &sVerts[Indexes[3 * i + j]];
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
// -----------
|
|
|
|
// Register each tri with the vert.
|
|
|
|
for (int32 i = 0; i < numSTris; ++i)
|
|
{
|
|
for (int32 j = 0; j < 3; ++j)
|
|
{
|
|
sTris[i].verts[j]->adjTris.Add(&sTris[i]);
|
|
}
|
|
}
|
|
|
|
|
|
// -----------
|
|
|
|
// Group the verts that share the same location.
|
|
|
|
GroupVerts();
|
|
|
|
// Populate the TArray of edges.
|
|
|
|
int maxEdgeSize = FMath::Min(3 * numSTris, 3 * numSVerts - 6);
|
|
edges.Empty(maxEdgeSize);
|
|
for (int i = 0; i < numSVerts; i++)
|
|
{
|
|
InitVert(&sVerts[i]);
|
|
}
|
|
|
|
// Guessed wrong on num edges. Array was resized so fix up pointers.
|
|
|
|
if (edges.Num() > maxEdgeSize)
|
|
{
|
|
Parallel_For(FIntRange(0, edges.Num()), [this](const FIntRange& Range)
|
|
{
|
|
for (int32 i = Range.begin(), I = Range.end(); i < I; ++i)
|
|
{
|
|
SimpEdgeType& edge = edges[i];
|
|
edge.next = &edge;
|
|
edge.prev = &edge;
|
|
}
|
|
});
|
|
}
|
|
|
|
FTaskGroup TaskGroup;
|
|
|
|
TaskGroup.Run([&, this]
|
|
{
|
|
GroupEdges();
|
|
});
|
|
|
|
TaskGroup.RunAndWait([&, this]
|
|
{
|
|
this->edgeHash.Resize(this->edges.Num());
|
|
{
|
|
TArray<int32> HashValues;
|
|
ResizeArray(HashValues, this->edges.Num());
|
|
|
|
Parallel_For(FIntRange(0, this->edges.Num()),
|
|
[&HashValues, this](const FIntRange& Range)
|
|
{
|
|
for (int32 i = Range.begin(), I = Range.end(); i < I; ++i)
|
|
{
|
|
HashValues[i] = HashEdge(this->edges[i].v0, this->edges[i].v1);
|
|
}
|
|
});
|
|
|
|
for (int i = 0; i < this->edges.Num(); i++)
|
|
{
|
|
this->edgeHash.Add(HashValues[i], i);
|
|
}
|
|
}
|
|
|
|
this->edgeHeap.Resize(this->edges.Num(), this->edges.Num());
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
ProxyLOD::FQuadricMeshSimplifier::~FQuadricMeshSimplifier()
|
|
{
|
|
delete[] sVerts;
|
|
delete[] sTris;
|
|
}
|
|
|
|
|
|
void ProxyLOD::FQuadricMeshSimplifier::SetAttributeWeights(const float* weights)
|
|
{
|
|
for (uint32 i = 0; i < NumAttributes; i++)
|
|
{
|
|
attributeWeights[i] = weights[i];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void ProxyLOD::FQuadricMeshSimplifier::SetBoundaryLocked()
|
|
{
|
|
TArray< SimpVertType*, TInlineAllocator<64> > adjVerts;
|
|
|
|
for (int i = 0; i < numSVerts; i++)
|
|
{
|
|
|
|
SimpVertType* v0 = &sVerts[i];
|
|
checkSlow(v0 != NULL);
|
|
check(v0->adjTris.Num() > 0);
|
|
|
|
adjVerts.Reset();
|
|
v0->FindAdjacentVertsGroup(adjVerts);
|
|
|
|
for (SimpVertType* v1 : adjVerts)
|
|
{
|
|
if (v0 < v1)
|
|
{
|
|
LockTriFlags(SIMP_MARK1);
|
|
|
|
// 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);
|
|
|
|
v0->DisableAdjTriFlagsGroup(SIMP_MARK1);
|
|
|
|
if (faceCount == 1)
|
|
{
|
|
// only one face on this edge
|
|
v0->EnableFlagsGroup(SIMP_LOCKED);
|
|
v1->EnableFlagsGroup(SIMP_LOCKED);
|
|
}
|
|
|
|
UnlockTriFlags(SIMP_MARK1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProxyLOD::FQuadricMeshSimplifier::InitVert(SimpVertType* v)
|
|
{
|
|
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 ProxyLOD::FQuadricMeshSimplifier::GroupVerts()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ProxyLOD::FQuadricMeshSimplifier::GroupVerts)
|
|
|
|
// group verts that share a point
|
|
FHashTable HashTable(1 << FMath::Min(16u, FMath::FloorLog2(numSVerts / 2)), numSVerts);
|
|
|
|
TArray<uint32> HashValues;
|
|
ResizeArray(HashValues, numSVerts);
|
|
{
|
|
// Compute the hash values
|
|
|
|
Parallel_For(FIntRange(0, numSVerts),
|
|
[&HashValues, this](const FIntRange& Range)
|
|
{
|
|
const auto& Verts = this->sVerts;
|
|
for (int32 i = Range.begin(), I = Range.end(); i < I; ++i)
|
|
{
|
|
HashValues[i] = HashPoint((FVector)Verts[i].GetPos());
|
|
}
|
|
}
|
|
);
|
|
|
|
// insert the hash values.
|
|
for (int i = 0; i < numSVerts; i++)
|
|
{
|
|
HashTable.Add(HashValues[i], i);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < numSVerts; i++)
|
|
{
|
|
// already grouped
|
|
if (sVerts[i].next != &sVerts[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// find any matching verts
|
|
const uint32 hash = HashValues[i];
|
|
SimpVertType* v1 = &sVerts[i];
|
|
|
|
for (int j = HashTable.First(hash); HashTable.IsValid(j); j = HashTable.Next(j))
|
|
{
|
|
|
|
SimpVertType* v2 = &sVerts[j];
|
|
|
|
if (v1 == v2)
|
|
continue;
|
|
|
|
// link
|
|
if (v1->GetPos() == v2->GetPos())
|
|
{
|
|
checkSlow(v2->next == v2);
|
|
checkSlow(v2->prev == v2);
|
|
|
|
v2->next = v1->next;
|
|
v2->prev = v1;
|
|
v2->next->prev = v2;
|
|
v2->prev->next = v2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProxyLOD::FQuadricMeshSimplifier::GroupEdges()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ProxyLOD::FQuadricMeshSimplifier::GroupEdges)
|
|
|
|
FHashTable HashTable(1 << FMath::Min(16u, FMath::FloorLog2(edges.Num() / 2)), edges.Num());
|
|
|
|
TArray<uint32> HashValues;
|
|
ResizeArray(HashValues, edges.Num());
|
|
|
|
Parallel_For(FIntRange(0, edges.Num()),
|
|
[&HashValues, this](const FIntRange& Range)
|
|
{
|
|
const auto& Edges = this->edges;
|
|
for (int32 i = Range.begin(), I = Range.end(); 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 (int i = 0; i < edges.Num(); i++)
|
|
{
|
|
HashTable.Add(HashValues[i], i);
|
|
}
|
|
|
|
for (int i = 0; i < edges.Num(); 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 &&
|
|
e1->v1 == e2->v1)
|
|
||
|
|
(e1->v0->GetPos() == e2->v0->GetPos() &&
|
|
e1->v1->GetPos() == e2->v1->GetPos());
|
|
|
|
bool m2 =
|
|
(e1->v0 == e2->v1 &&
|
|
e1->v1 == e2->v0)
|
|
||
|
|
(e1->v0->GetPos() == e2->v1->GetPos() &&
|
|
e1->v1->GetPos() == e2->v0->GetPos());
|
|
|
|
// 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 ProxyLOD::FQuadricMeshSimplifier::InitCosts()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ProxyLOD::FQuadricMeshSimplifier::InitCosts)
|
|
|
|
// SCOPE_LOG_TIME(TEXT("UE_ProxyLOD_Simplifier_InitCosts"), nullptr);
|
|
for (int i = 0; i < edges.Num(); i++)
|
|
{
|
|
float cost = ComputeEdgeCollapseCost(&edges[i]);
|
|
checkSlow(FMath::IsFinite(cost));
|
|
edgeHeap.Add(cost, i);
|
|
}
|
|
}
|
|
|
|
|
|
void ProxyLOD::FQuadricMeshSimplifier::RemoveEdge(SimpEdgeType* edge)
|
|
{
|
|
if (edge->TestFlags(SIMP_REMOVED))
|
|
{
|
|
checkSlow(edge->next == edge);
|
|
checkSlow(edge->prev == edge);
|
|
return;
|
|
}
|
|
|
|
uint32 hash = HashEdge(edge->v0, edge->v1);
|
|
for (uint32 i = edgeHash.First(hash); edgeHash.IsValid(i); i = edgeHash.Next(i))
|
|
{
|
|
if (&edges[i] == edge)
|
|
{
|
|
edgeHash.Remove(hash, i);
|
|
edgeHeap.Remove(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// remove edge
|
|
edge->EnableFlags(SIMP_REMOVED);
|
|
|
|
// ungroup edge
|
|
edge->prev->next = edge->next;
|
|
edge->next->prev = edge->prev;
|
|
edge->next = edge;
|
|
edge->prev = edge;
|
|
}
|
|
|
|
void ProxyLOD::FQuadricMeshSimplifier::ReplaceEdgeVert(const SimpVertType* oldV, const SimpVertType* otherV, SimpVertType* newV)
|
|
{
|
|
uint32 hash = HashEdge(oldV, otherV);
|
|
uint32 index;
|
|
for (index = edgeHash.First(hash); edgeHash.IsValid(index); index = edgeHash.Next(index))
|
|
{
|
|
if ((edges[index].v0 == oldV && edges[index].v1 == otherV) ||
|
|
(edges[index].v1 == oldV && edges[index].v0 == otherV))
|
|
break;
|
|
}
|
|
|
|
checkSlow(index != -1);
|
|
SimpEdgeType* edge = &edges[index];
|
|
|
|
edgeHash.Remove(hash, index);
|
|
|
|
SimpEdgeType* ExistingEdge = FindEdge(newV, otherV);
|
|
if (ExistingEdge)
|
|
{
|
|
// Not entirely sure why this happens. I believe these are invalid edges from bridge tris.
|
|
RemoveEdge(ExistingEdge);
|
|
}
|
|
|
|
if (newV)
|
|
{
|
|
edgeHash.Add(HashEdge(newV, otherV), index);
|
|
|
|
if (edge->v0 == oldV)
|
|
edge->v0 = newV;
|
|
else
|
|
edge->v1 = newV;
|
|
}
|
|
else
|
|
{
|
|
// remove edge
|
|
edge->EnableFlags(SIMP_REMOVED);
|
|
|
|
// ungroup old edge
|
|
edge->prev->next = edge->next;
|
|
edge->next->prev = edge->prev;
|
|
edge->next = edge;
|
|
edge->prev = edge;
|
|
|
|
edgeHeap.Remove(index);
|
|
}
|
|
}
|
|
|
|
|
|
void ProxyLOD::FQuadricMeshSimplifier::CollapseEdgeVert(const SimpVertType* oldV, const SimpVertType* otherV, SimpVertType* newV)
|
|
{
|
|
uint32 hash = HashEdge(oldV, otherV);
|
|
uint32 index;
|
|
for (index = edgeHash.First(hash); edgeHash.IsValid(index); index = edgeHash.Next(index))
|
|
{
|
|
if ((edges[index].v0 == oldV && edges[index].v1 == otherV) ||
|
|
(edges[index].v1 == oldV && edges[index].v0 == otherV))
|
|
break;
|
|
}
|
|
|
|
checkSlow(index != -1);
|
|
SimpEdgeType* edge = &edges[index];
|
|
|
|
edgeHash.Remove(hash, index);
|
|
edgeHeap.Remove(index);
|
|
|
|
// remove edge
|
|
edge->EnableFlags(SIMP_REMOVED);
|
|
|
|
// ungroup old edge
|
|
edge->prev->next = edge->next;
|
|
edge->next->prev = edge->prev;
|
|
edge->next = edge;
|
|
edge->prev = edge;
|
|
}
|
|
|
|
|
|
void ProxyLOD::FQuadricMeshSimplifier::GatherUpdates(SimpVertType* v)
|
|
{
|
|
// Update all tris touching collapse edge.
|
|
for (TriIterator i = v->adjTris.Begin(); i != v->adjTris.End(); ++i)
|
|
{
|
|
updateTris.AddUnique(*i);
|
|
}
|
|
|
|
TArray< SimpVertType*, TInlineAllocator<64> > adjVerts;
|
|
v->FindAdjacentVerts(adjVerts);
|
|
|
|
LockVertFlags(SIMP_MARK1 | SIMP_MARK2);
|
|
|
|
// Update verts from tris adjacent to collapsed edge
|
|
for (int i = 0, Num = adjVerts.Num(); i < Num; i++)
|
|
{
|
|
updateVerts.AddUnique(adjVerts[i]);
|
|
|
|
adjVerts[i]->EnableFlags(SIMP_MARK2);
|
|
}
|
|
|
|
// update the costs of all edges connected to any face adjacent to v
|
|
for (int i = 0, Num = adjVerts.Num(); i < Num; i++)
|
|
{
|
|
adjVerts[i]->EnableAdjVertFlags(SIMP_MARK1);
|
|
|
|
for (TriIterator j = adjVerts[i]->adjTris.Begin(); j != adjVerts[i]->adjTris.End(); ++j)
|
|
{
|
|
SimpTriType* tri = *j;
|
|
for (int k = 0; k < 3; k++)
|
|
{
|
|
SimpVertType* vert = tri->verts[k];
|
|
if (vert->TestFlags(SIMP_MARK1) && !vert->TestFlags(SIMP_MARK2))
|
|
{
|
|
SimpEdgeType* edge = FindEdge(adjVerts[i], vert);
|
|
updateEdges.AddUnique(edge);
|
|
}
|
|
vert->DisableFlags(SIMP_MARK1);
|
|
}
|
|
}
|
|
adjVerts[i]->DisableFlags(SIMP_MARK2);
|
|
}
|
|
|
|
UnlockVertFlags(SIMP_MARK1 | SIMP_MARK2);
|
|
}
|
|
|
|
|
|
template <typename VertexType>
|
|
struct OptimizerTrait
|
|
{
|
|
typedef ProxyLOD::TQuadricAttrOptimizer<ProxyLOD::FQuadricMeshSimplifier::NumAttributes > OptimizerType;
|
|
};
|
|
|
|
|
|
FVector ProxyLOD::FQuadricMeshSimplifier::ComputeNewVertsPos(SimpEdgeType* edge, TArray< MeshVertType, TInlineAllocator<16> >& newVerts, TArray< QuadricType, TInlineAllocator<16> >& quadrics, FQuadric& edgeQuadric)
|
|
{
|
|
SimpEdgeType* e;
|
|
SimpVertType* v;
|
|
|
|
|
|
typename OptimizerTrait<MeshVertType>::OptimizerType optimizer;
|
|
//TQuadricAttrOptimizer<TQuadricMeshSimplifier<T>::NumAttributes > optimizer;
|
|
|
|
LockVertFlags(SIMP_MARK1);
|
|
|
|
edge->v0->EnableFlagsGroup(SIMP_MARK1);
|
|
edge->v1->EnableFlagsGroup(SIMP_MARK1);
|
|
|
|
// add edges
|
|
e = edge;
|
|
do {
|
|
checkSlow(e == FindEdge(e->v0, e->v1));
|
|
checkSlow(e->v0->adjTris.Num() > 0);
|
|
checkSlow(e->v1->adjTris.Num() > 0);
|
|
|
|
newVerts.Add(e->v0->vert);
|
|
|
|
QuadricType quadric;
|
|
quadric = GetQuadric(e->v0);
|
|
quadric += GetQuadric(e->v1);
|
|
quadrics.Add(quadric);
|
|
optimizer.AddQuadric(quadric);
|
|
|
|
e->v0->DisableFlags(SIMP_MARK1);
|
|
e->v1->DisableFlags(SIMP_MARK1);
|
|
|
|
e = e->next;
|
|
} while (e != edge);
|
|
|
|
// add remainder verts
|
|
v = edge->v0;
|
|
do {
|
|
if (v->TestFlags(SIMP_MARK1))
|
|
{
|
|
newVerts.Add(v->vert);
|
|
|
|
QuadricType quadric;
|
|
quadric = GetQuadric(v);
|
|
quadrics.Add(quadric);
|
|
optimizer.AddQuadric(quadric);
|
|
|
|
v->DisableFlags(SIMP_MARK1);
|
|
}
|
|
v = v->next;
|
|
} while (v != edge->v0);
|
|
|
|
v = edge->v1;
|
|
do {
|
|
if (v->TestFlags(SIMP_MARK1))
|
|
{
|
|
newVerts.Add(v->vert);
|
|
|
|
QuadricType quadric;
|
|
quadric = GetQuadric(v);
|
|
quadrics.Add(quadric);
|
|
optimizer.AddQuadric(quadric);
|
|
|
|
v->DisableFlags(SIMP_MARK1);
|
|
}
|
|
v = v->next;
|
|
} while (v != edge->v1);
|
|
|
|
UnlockVertFlags(SIMP_MARK1);
|
|
|
|
check(quadrics.Num() <= 256);
|
|
|
|
edgeQuadric.Zero();
|
|
|
|
v = edge->v0;
|
|
do {
|
|
edgeQuadric += GetEdgeQuadric(v);
|
|
v = v->next;
|
|
} while (v != edge->v0);
|
|
|
|
v = edge->v1;
|
|
do {
|
|
edgeQuadric += GetEdgeQuadric(v);
|
|
v = v->next;
|
|
} while (v != edge->v1);
|
|
|
|
optimizer.AddQuadric(edgeQuadric);
|
|
|
|
FVector newPos;
|
|
{
|
|
bool bLocked0 = edge->v0->TestFlags(SIMP_LOCKED);
|
|
bool bLocked1 = edge->v1->TestFlags(SIMP_LOCKED);
|
|
//checkSlow( !bLocked0 || !bLocked1 ); // can't have both locked
|
|
|
|
// find position
|
|
if (bLocked0)
|
|
{
|
|
// v0 position
|
|
newPos = (FVector)edge->v0->GetPos();
|
|
}
|
|
else if (bLocked1)
|
|
{
|
|
// v1 position
|
|
newPos = (FVector)edge->v1->GetPos();
|
|
}
|
|
else
|
|
{
|
|
// optimal position
|
|
bool valid = optimizer.Optimize(newPos);
|
|
if (!valid)
|
|
{
|
|
// Couldn't find optimal so choose middle
|
|
newPos = (FVector)(edge->v0->GetPos() + edge->v1->GetPos()) * 0.5f;
|
|
}
|
|
}
|
|
}
|
|
|
|
return newPos;
|
|
|
|
}
|
|
|
|
|
|
float ProxyLOD::FQuadricMeshSimplifier::ComputeNewVerts(SimpEdgeType* edge, TArray< MeshVertType, TInlineAllocator<16> >& newVerts)
|
|
{
|
|
|
|
FQuadric edgeQuadric;
|
|
TArray< QuadricType, TInlineAllocator<16> > quadrics;
|
|
FVector newPos = ComputeNewVertsPos(edge, newVerts, quadrics, edgeQuadric);
|
|
|
|
|
|
float cost = 0.0f;
|
|
|
|
for (int i = 0; i < quadrics.Num(); i++)
|
|
{
|
|
newVerts[i].GetPos() = (FVector3f)newPos;
|
|
|
|
if (quadrics[i].a > 1e-8)
|
|
{
|
|
// calculate vert attributes from the new position
|
|
quadrics[i].CalcAttributes((FVector)newVerts[i].GetPos(), newVerts[i].GetAttributes(), attributeWeights);
|
|
newVerts[i].Correct();
|
|
}
|
|
|
|
// sum cost of new verts
|
|
cost += quadrics[i].Evaluate((FVector)newVerts[i].GetPos(), newVerts[i].GetAttributes(), attributeWeights);
|
|
}
|
|
|
|
cost += edgeQuadric.Evaluate(newPos);
|
|
|
|
return cost;
|
|
}
|
|
|
|
|
|
|
|
float ProxyLOD::FQuadricMeshSimplifier::ComputeEdgeCollapseCost(SimpEdgeType* edge)
|
|
{
|
|
TArray< MeshVertType, TInlineAllocator<16> > newVerts;
|
|
|
|
#if 1
|
|
// djh
|
|
|
|
if (edge->v0->TestFlags(SIMP_LOCKED) && edge->v1->TestFlags(SIMP_LOCKED))
|
|
{
|
|
return FLT_MAX;
|
|
}
|
|
#endif
|
|
float cost = ComputeNewVerts(edge, newVerts);
|
|
|
|
const FVector& newPos = (FVector)newVerts[0].GetPos();
|
|
|
|
// add penalties
|
|
// the below penalty code works with groups so no need to worry about remainder verts
|
|
SimpVertType* u = edge->v0;
|
|
SimpVertType* v = edge->v1;
|
|
SimpVertType* vert;
|
|
|
|
float penalty = 0.0f;
|
|
|
|
{
|
|
//const int degreeLimit = 24;
|
|
//const float degreePenalty = 100.0f;
|
|
|
|
int degree = 0;
|
|
|
|
// u
|
|
vert = u;
|
|
do {
|
|
degree += vert->adjTris.Num();
|
|
vert = vert->next;
|
|
} while (vert != u);
|
|
|
|
// v
|
|
vert = v;
|
|
do {
|
|
degree += vert->adjTris.Num();
|
|
vert = vert->next;
|
|
} while (vert != v);
|
|
|
|
if (degree > degreeLimit)
|
|
penalty += degreePenalty * (degree - degreeLimit);
|
|
}
|
|
|
|
|
|
{
|
|
|
|
const float penaltyToPreventEdgeFolding = invalidPenalty;
|
|
|
|
LockTriFlags(SIMP_MARK1);
|
|
|
|
v->EnableAdjTriFlagsGroup(SIMP_MARK1);
|
|
|
|
|
|
// u
|
|
vert = u;
|
|
do {
|
|
for (TriIterator i = vert->adjTris.Begin(); i != vert->adjTris.End(); ++i)
|
|
{
|
|
SimpTriType* tri = *i;
|
|
if (!tri->TestFlags(SIMP_MARK1))
|
|
{
|
|
// djh
|
|
if (!tri->ReplaceVertexIsValid(vert, (FVector3f)newPos))
|
|
{
|
|
penalty += penaltyToPreventEdgeFolding;
|
|
|
|
}
|
|
//penalty += tri->ReplaceVertexIsValid(vert, newPos, minDotProduct) ? 0.0f : penaltyToPreventEdgeFolding;
|
|
}
|
|
tri->DisableFlags(SIMP_MARK1);
|
|
}
|
|
vert = vert->next;
|
|
} while (vert != u);
|
|
|
|
// v
|
|
vert = v;
|
|
do {
|
|
for (TriIterator i = vert->adjTris.Begin(); i != vert->adjTris.End(); ++i)
|
|
{
|
|
SimpTriType* tri = *i;
|
|
if (tri->TestFlags(SIMP_MARK1))
|
|
{
|
|
if (!tri->ReplaceVertexIsValid(vert, (FVector3f)newPos))
|
|
{
|
|
penalty += penaltyToPreventEdgeFolding;
|
|
|
|
}
|
|
//penalty += tri->ReplaceVertexIsValid(vert, newPos, minDotProduct) ? 0.0f : penaltyToPreventEdgeFolding;
|
|
}
|
|
tri->DisableFlags(SIMP_MARK1);
|
|
}
|
|
vert = vert->next;
|
|
} while (vert != v);
|
|
|
|
UnlockTriFlags(SIMP_MARK1);
|
|
}
|
|
|
|
return cost + penalty;
|
|
}
|
|
|
|
void ProxyLOD::FQuadricMeshSimplifier::Collapse(SimpEdgeType* edge)
|
|
{
|
|
SimpVertType* u = edge->v0;
|
|
SimpVertType* v = edge->v1;
|
|
|
|
// Collapse the edge uv by moving vertex u onto v
|
|
checkSlow(u && v);
|
|
checkSlow(edge == FindEdge(u, v));
|
|
checkSlow(u->adjTris.Num() > 0);
|
|
checkSlow(v->adjTris.Num() > 0);
|
|
checkSlow(!u->TestFlags(SIMP_LOCKED) || !v->TestFlags(SIMP_LOCKED));
|
|
|
|
|
|
if (u->TestFlags(SIMP_LOCKED))
|
|
v->EnableFlags(SIMP_LOCKED);
|
|
|
|
LockVertFlags(SIMP_MARK1);
|
|
|
|
// update edges from u to v
|
|
u->EnableAdjVertFlags(SIMP_MARK1);
|
|
v->DisableAdjVertFlags(SIMP_MARK1);
|
|
|
|
if (u->TestFlags(SIMP_MARK1))
|
|
{
|
|
// Invalid edge, results from collapsing a bridge tri
|
|
// There are no actual triangles connecting these verts
|
|
u->DisableAdjVertFlags(SIMP_MARK1);
|
|
UnlockVertFlags(SIMP_MARK1);
|
|
return;
|
|
}
|
|
|
|
for (TriIterator i = u->adjTris.Begin(); i != u->adjTris.End(); ++i)
|
|
{
|
|
SimpTriType* tri = *i;
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
SimpVertType* vert = tri->verts[j];
|
|
if (vert->TestFlags(SIMP_MARK1))
|
|
{
|
|
ReplaceEdgeVert(u, vert, v);
|
|
vert->DisableFlags(SIMP_MARK1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove dead edges
|
|
u->EnableAdjVertFlags(SIMP_MARK1);
|
|
u->DisableFlags(SIMP_MARK1);
|
|
v->DisableFlags(SIMP_MARK1);
|
|
|
|
for (TriIterator i = v->adjTris.Begin(); i != v->adjTris.End(); ++i)
|
|
{
|
|
SimpTriType* tri = *i;
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
SimpVertType* vert = tri->verts[j];
|
|
if (vert->TestFlags(SIMP_MARK1))
|
|
{
|
|
ReplaceEdgeVert(u, vert, NULL);
|
|
vert->DisableFlags(SIMP_MARK1);
|
|
}
|
|
}
|
|
}
|
|
|
|
u->DisableAdjVertFlags(SIMP_MARK1);
|
|
|
|
// fixup triangles
|
|
for (TriIterator i = u->adjTris.Begin(); i != u->adjTris.End(); ++i)
|
|
{
|
|
SimpTriType* tri = *i;
|
|
checkSlow(!tri->TestFlags(SIMP_REMOVED));
|
|
checkSlow(tri->HasVertex(u));
|
|
|
|
if (tri->HasVertex(v))
|
|
{
|
|
// delete triangles on edge uv
|
|
numTris--;
|
|
tri->EnableFlags(SIMP_REMOVED);
|
|
quadricCache.DirtyTriQuadric(tri);
|
|
|
|
// remove references to tri
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
SimpVertType* vert = tri->verts[j];
|
|
checkSlow(!vert->TestFlags(SIMP_REMOVED));
|
|
if (vert != u)
|
|
{
|
|
vert->adjTris.Remove(tri);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// update triangles to have v instead of u
|
|
tri->ReplaceVertex(u, v);
|
|
v->adjTris.Add(tri);
|
|
}
|
|
}
|
|
|
|
// remove modified verts and tris from cache
|
|
v->EnableAdjVertFlags(SIMP_MARK1);
|
|
for (TriIterator i = v->adjTris.Begin(); i != v->adjTris.End(); ++i)
|
|
{
|
|
SimpTriType* tri = *i;
|
|
quadricCache.DirtyTriQuadric(tri);
|
|
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
SimpVertType* vert = tri->verts[j];
|
|
if (vert->TestFlags(SIMP_MARK1))
|
|
{
|
|
quadricCache.DirtyVertQuadric(vert);
|
|
vert->DisableFlags(SIMP_MARK1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
UnlockVertFlags(SIMP_MARK1);
|
|
|
|
u->adjTris.Clear(); // u has been removed
|
|
u->EnableFlags(SIMP_REMOVED);
|
|
numVerts--;
|
|
}
|
|
|
|
|
|
void ProxyLOD::FQuadricMeshSimplifier::UpdateTris()
|
|
{
|
|
// remove degenerate triangles
|
|
// not sure why this happens
|
|
for (SimpTriType* tri : updateTris)
|
|
{
|
|
if (tri->TestFlags(SIMP_REMOVED))
|
|
continue;
|
|
|
|
quadricCache.DirtyTriQuadric(tri);
|
|
|
|
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)
|
|
{
|
|
numTris--;
|
|
tri->EnableFlags(SIMP_REMOVED);
|
|
|
|
// remove references to tri
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
SimpVertType* vert = tri->verts[j];
|
|
vert->adjTris.Remove(tri);
|
|
// orphaned verts are removed below
|
|
}
|
|
}
|
|
}
|
|
updateTris.Reset();
|
|
}
|
|
|
|
|
|
void ProxyLOD::FQuadricMeshSimplifier::UpdateVerts()
|
|
{
|
|
// remove orphaned verts
|
|
for (SimpVertType* vert : updateVerts)
|
|
{
|
|
if (vert->TestFlags(SIMP_REMOVED))
|
|
continue;
|
|
|
|
quadricCache.DirtyVertQuadric(vert);
|
|
quadricCache.DirtEdgeQuadric(vert);
|
|
|
|
|
|
if (vert->adjTris.Num() == 0)
|
|
{
|
|
numVerts--;
|
|
vert->EnableFlags(SIMP_REMOVED);
|
|
|
|
// ungroup
|
|
vert->prev->next = vert->next;
|
|
vert->next->prev = vert->prev;
|
|
vert->next = vert;
|
|
vert->prev = vert;
|
|
}
|
|
}
|
|
updateVerts.Reset();
|
|
}
|
|
|
|
|
|
void ProxyLOD::FQuadricMeshSimplifier::UpdateEdges()
|
|
{
|
|
const uint32 NumEdges = updateEdges.Num();
|
|
|
|
// add all grouped edges
|
|
for (uint32 i = 0; i < NumEdges; i++)
|
|
{
|
|
SimpEdgeType* edge = updateEdges[i];
|
|
|
|
if (edge->TestFlags(SIMP_REMOVED))
|
|
continue;
|
|
|
|
SimpEdgeType* e = edge;
|
|
do {
|
|
updateEdges.AddUnique(e);
|
|
e = e->next;
|
|
} while (e != edge);
|
|
}
|
|
|
|
// remove dead edges from our edge hash.
|
|
for (uint32 i = 0, Num = updateEdges.Num(); i < Num; i++)
|
|
{
|
|
SimpEdgeType* edge = updateEdges[i];
|
|
|
|
if (edge->TestFlags(SIMP_REMOVED))
|
|
continue;
|
|
|
|
if (edge->v0->TestFlags(SIMP_REMOVED) ||
|
|
edge->v1->TestFlags(SIMP_REMOVED))
|
|
{
|
|
RemoveEdge(edge);
|
|
}
|
|
}
|
|
|
|
// Fix edge groups
|
|
{
|
|
FHashTable HashTable(128, NumEdges);
|
|
|
|
// ungroup edges
|
|
for (uint32 i = 0, Num = updateEdges.Num(); i < Num; i++)
|
|
{
|
|
SimpEdgeType* edge = updateEdges[i];
|
|
|
|
if (edge->TestFlags(SIMP_REMOVED))
|
|
continue;
|
|
|
|
edge->next = edge;
|
|
edge->prev = edge;
|
|
|
|
HashTable.Add(HashPoint((FVector)edge->v0->GetPos()) ^ HashPoint((FVector)edge->v1->GetPos()), i);
|
|
}
|
|
|
|
// regroup edges
|
|
for (uint32 i = 0, Num = updateEdges.Num(); i < Num; i++)
|
|
{
|
|
SimpEdgeType* edge = updateEdges[i];
|
|
|
|
if (edge->TestFlags(SIMP_REMOVED))
|
|
continue;
|
|
|
|
// already grouped
|
|
if (edge->next != edge)
|
|
continue;
|
|
|
|
// find any matching edges
|
|
uint32 hash = HashPoint((FVector)edge->v0->GetPos()) ^ HashPoint((FVector)edge->v1->GetPos());
|
|
SimpEdgeType* e1 = updateEdges[i];
|
|
for (uint32 j = HashTable.First(hash); HashTable.IsValid(j); j = HashTable.Next(j))
|
|
{
|
|
|
|
SimpEdgeType* e2 = updateEdges[j];
|
|
|
|
if (e1 == e2)
|
|
continue;
|
|
|
|
bool m1 =
|
|
(e1->v0 == e2->v0 &&
|
|
e1->v1 == e2->v1)
|
|
||
|
|
(e1->v0->GetPos() == e2->v0->GetPos() &&
|
|
e1->v1->GetPos() == e2->v1->GetPos());
|
|
|
|
bool m2 =
|
|
(e1->v0 == e2->v1 &&
|
|
e1->v1 == e2->v0)
|
|
||
|
|
(e1->v0->GetPos() == e2->v1->GetPos() &&
|
|
e1->v1->GetPos() == e2->v0->GetPos());
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// update edges
|
|
for (uint32 i = 0; i < NumEdges; i++)
|
|
{
|
|
SimpEdgeType* edge = updateEdges[i];
|
|
|
|
if (edge->TestFlags(SIMP_REMOVED))
|
|
continue;
|
|
|
|
float cost = ComputeEdgeCollapseCost(edge);
|
|
|
|
SimpEdgeType* e = edge;
|
|
do {
|
|
uint32 EdgeIndex = GetEdgeIndex(e);
|
|
if (edgeHeap.IsPresent(EdgeIndex))
|
|
{
|
|
edgeHeap.Update(cost, EdgeIndex);
|
|
}
|
|
e = e->next;
|
|
} while (e != edge);
|
|
}
|
|
updateEdges.Reset();
|
|
}
|
|
|
|
int32 ProxyLOD::FQuadricMeshSimplifier::CountDegenerates() const
|
|
{
|
|
int32 DegenerateCount = 0;
|
|
// remove degenerate triangles
|
|
// not sure why this happens
|
|
for (int i = 0; i < numSTris; i++)
|
|
{
|
|
SimpTriType* tri = &sTris[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;
|
|
}
|
|
|
|
|
|
void ProxyLOD::FQuadricMeshSimplifier::OutputMesh(MeshVertType* verts, uint32* indexes, TArray<int32>* LockedVerts, int* OutNumVertices, int* OutNumIndices) const
|
|
{
|
|
FHashTable HashTable(4096, GetNumVerts());
|
|
|
|
#if 1
|
|
int count = 0;
|
|
for (int i = 0; i < numSVerts; i++)
|
|
count += sVerts[i].TestFlags(SIMP_REMOVED) ? 0 : 1;
|
|
|
|
check(numVerts == count);
|
|
#endif
|
|
|
|
int numV = 0;
|
|
int numI = 0;
|
|
|
|
for (int i = 0; i < numSTris; i++)
|
|
{
|
|
if (sTris[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 = sTris[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;
|
|
}
|
|
}
|
|
}
|
|
|
|
check(numV <= numVerts);
|
|
check(numI <= numTris * 3);
|
|
|
|
if (OutNumVertices)
|
|
{
|
|
*OutNumVertices = numV;
|
|
}
|
|
|
|
if (OutNumIndices)
|
|
{
|
|
*OutNumIndices = numI;
|
|
}
|
|
} |