738 lines
17 KiB
C++
738 lines
17 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "MeshBoundaryLoops.h"
|
|
|
|
using namespace UE::Geometry;
|
|
|
|
int FMeshBoundaryLoops::GetMaxVerticesLoopIndex() const
|
|
{
|
|
int j = 0;
|
|
for (int i = 1; i < Loops.Num(); ++i)
|
|
{
|
|
if (Loops[i].Vertices.Num() > Loops[j].Vertices.Num())
|
|
{
|
|
j = i;
|
|
}
|
|
}
|
|
return j;
|
|
}
|
|
|
|
|
|
|
|
int FMeshBoundaryLoops::GetLongestLoopIndex() const
|
|
{
|
|
int32 LongestLoopIdx = -1;
|
|
double LongestLoopLen = 0;
|
|
for (int i = 0; i < Loops.Num(); ++i)
|
|
{
|
|
int32 LoopNum = Loops[i].Vertices.Num();
|
|
double LoopLen = 0;
|
|
for (int32 k = 0; k < LoopNum; ++k)
|
|
{
|
|
FVector3d NextPos = Mesh->GetVertex(Loops[i].Vertices[(k+1)%LoopNum]);
|
|
LoopLen += Distance(Mesh->GetVertex(Loops[i].Vertices[k]), NextPos);
|
|
}
|
|
if (LoopLen > LongestLoopLen)
|
|
{
|
|
LongestLoopLen = LoopLen;
|
|
LongestLoopIdx = i;
|
|
}
|
|
}
|
|
return LongestLoopIdx;
|
|
}
|
|
|
|
|
|
FIndex2i FMeshBoundaryLoops::FindVertexInLoop(int VertexID) const
|
|
{
|
|
int N = Loops.Num();
|
|
for (int li = 0; li < N; ++li)
|
|
{
|
|
int idx = Loops[li].FindVertexIndex(VertexID);
|
|
if (idx >= 0)
|
|
{
|
|
return FIndex2i(li, idx);
|
|
}
|
|
}
|
|
return FIndex2i::Invalid();
|
|
}
|
|
|
|
|
|
|
|
int FMeshBoundaryLoops::FindLoopContainingVertex(int VertexID) const
|
|
{
|
|
int N = Loops.Num();
|
|
for (int li = 0; li < N; ++li)
|
|
{
|
|
if (Loops[li].Vertices.Contains(VertexID))
|
|
{
|
|
return li;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
int FMeshBoundaryLoops::FindLoopContainingEdge(int EdgeID) const
|
|
{
|
|
int N = Loops.Num();
|
|
for (int li = 0; li < N; ++li)
|
|
{
|
|
if (Loops[li].Edges.Contains(EdgeID))
|
|
{
|
|
return li;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FMeshBoundaryLoops::Compute()
|
|
{
|
|
// This algorithm assumes that triangles are oriented consistently,
|
|
// so closed boundary-loop can be followed by walking edges in-order
|
|
|
|
Loops.Reset(); Spans.Reset();
|
|
bSawOpenSpans = bFellBackToSpansOnFailure = false;
|
|
|
|
// early-out if we don't actually have boundaries
|
|
if (Mesh->IsClosed())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int NE = Mesh->MaxEdgeID();
|
|
|
|
// Temporary memory used to indicate when we have "used" an edge.
|
|
TArray<bool> used_edge;
|
|
used_edge.Init(false, Mesh->MaxEdgeID());
|
|
|
|
// current loop is stored here, cleared after each loop extracted
|
|
TArray<int> loop_edges;
|
|
TArray<int> loop_verts;
|
|
TArray<int> bowties;
|
|
|
|
// Temp buffer for reading back all boundary edges of a vertex.
|
|
// probably always small but : pathological cases it could be large...
|
|
TArray<int> all_e;
|
|
all_e.Reserve(32);
|
|
|
|
// [TODO] might make sense to precompute some things here, like num_be for each bdry vtx?
|
|
|
|
// process all edges of mesh
|
|
for (int eid = 0; eid < NE; ++eid)
|
|
{
|
|
if (Mesh->IsEdge(eid) == false)
|
|
{
|
|
continue;
|
|
}
|
|
if (used_edge[eid] == true)
|
|
{
|
|
continue;
|
|
}
|
|
if (Mesh->IsBoundaryEdge(eid) == false)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (EdgeFilterFunc != nullptr && EdgeFilterFunc(eid) == false)
|
|
{
|
|
used_edge[eid] = true;
|
|
continue;
|
|
}
|
|
|
|
// ok this is start of a boundary chain
|
|
int eStart = eid;
|
|
used_edge[eStart] = true;
|
|
loop_edges.Add(eStart);
|
|
|
|
int eCur = eid;
|
|
|
|
// follow the chain in order of oriented edges
|
|
bool bClosed = false;
|
|
bool bIsOpenSpan = false;
|
|
while (!bClosed)
|
|
{
|
|
FIndex2i ev = Mesh->GetOrientedBoundaryEdgeV(eCur);
|
|
int cure_a = ev.A, cure_b = ev.B;
|
|
if (bIsOpenSpan)
|
|
{
|
|
cure_a = ev.B; cure_b = ev.A;
|
|
}
|
|
else
|
|
{
|
|
loop_verts.Add(cure_a);
|
|
}
|
|
|
|
int e0 = -1, e1 = 1;
|
|
int bdry_nbrs = Mesh->GetVtxBoundaryEdges(cure_b, e0, e1);
|
|
|
|
// have to filter this list, if we are filtering. this is ugly.
|
|
if (EdgeFilterFunc != nullptr)
|
|
{
|
|
if (bdry_nbrs > 2)
|
|
{
|
|
// we may repreat this below...irritating...
|
|
all_e.Reset();
|
|
int num_be = Mesh->GetAllVtxBoundaryEdges(cure_b, all_e);
|
|
num_be = BufferUtil::CountValid(all_e, EdgeFilterFunc, num_be);
|
|
}
|
|
else
|
|
{
|
|
if (EdgeFilterFunc(e0) == false) bdry_nbrs--;
|
|
if (EdgeFilterFunc(e1) == false) bdry_nbrs--;
|
|
}
|
|
}
|
|
|
|
|
|
if (bdry_nbrs < 2)
|
|
{ // hit an 'endpoint' vertex (should only happen when Filter is on...)
|
|
if (SpanBehavior == ESpanBehaviors::Abort)
|
|
{
|
|
bSawOpenSpans = true;
|
|
goto CATASTROPHIC_ABORT;
|
|
}
|
|
if (bIsOpenSpan)
|
|
{
|
|
bClosed = true;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
bIsOpenSpan = true; // begin open span
|
|
eCur = loop_edges[0]; // restart at other end of loop
|
|
Algo::Reverse(loop_edges); // do this so we can push to front
|
|
continue;
|
|
}
|
|
}
|
|
|
|
int eNext = -1;
|
|
|
|
if (bdry_nbrs > 2)
|
|
{
|
|
// found "bowtie" vertex...things just got complicated!
|
|
|
|
if (cure_b == loop_verts[0])
|
|
{
|
|
// The "end" of the current edge is the same as the start vertex.
|
|
// This means we can close the loop here. Might as well!
|
|
eNext = -2; // sentinel value used below
|
|
|
|
}
|
|
else
|
|
{
|
|
// try to find an unused outgoing edge that is oriented properly.
|
|
// This could create sub-loops, we will handle those later
|
|
all_e.Reset();
|
|
int num_be = Mesh->GetAllVtxBoundaryEdges(cure_b, all_e);
|
|
check(num_be == bdry_nbrs);
|
|
|
|
if (EdgeFilterFunc != nullptr)
|
|
{
|
|
num_be = BufferUtil::FilterInPlace(all_e, EdgeFilterFunc, num_be);
|
|
}
|
|
|
|
// Try to pick the best "turn left" vertex.
|
|
eNext = FindLeftTurnEdge(eCur, cure_b, all_e, num_be, used_edge);
|
|
if (eNext == -1)
|
|
{
|
|
if (FailureBehavior == EFailureBehaviors::Abort || SpanBehavior == ESpanBehaviors::Abort)
|
|
{
|
|
goto CATASTROPHIC_ABORT;
|
|
}
|
|
|
|
// ok, we are stuck. all we can do now is terminate this loop and keep it as a span
|
|
if (bIsOpenSpan)
|
|
{
|
|
bClosed = true;
|
|
}
|
|
else
|
|
{
|
|
bIsOpenSpan = true;
|
|
bClosed = true;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (bowties.Contains(cure_b) == false)
|
|
{
|
|
bowties.Add(cure_b);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// walk forward to next available edge
|
|
check(e0 == eCur || e1 == eCur);
|
|
eNext = (e0 == eCur) ? e1 : e0;
|
|
}
|
|
|
|
if (eNext == -2)
|
|
{
|
|
// found a bowtie vert that is the same as start-of-loop, so we
|
|
// are just closing it off explicitly
|
|
bClosed = true;
|
|
}
|
|
else if (eNext == eStart)
|
|
{
|
|
// found edge at start of loop, so loop is done.
|
|
bClosed = true;
|
|
}
|
|
else if (used_edge[eNext] != false)
|
|
{
|
|
// disaster case - the next edge is already used, but it is not the start of our loop
|
|
// All we can do is convert to open span and terminate
|
|
if (FailureBehavior == EFailureBehaviors::Abort || SpanBehavior == ESpanBehaviors::Abort)
|
|
{
|
|
goto CATASTROPHIC_ABORT;
|
|
}
|
|
bIsOpenSpan = true;
|
|
bClosed = true;
|
|
|
|
}
|
|
else
|
|
{
|
|
// push onto accumulated list
|
|
check(used_edge[eNext] == false);
|
|
loop_edges.Add(eNext);
|
|
used_edge[eNext] = true;
|
|
eCur = eNext;
|
|
}
|
|
}
|
|
|
|
if (bIsOpenSpan)
|
|
{
|
|
bSawOpenSpans = true;
|
|
if (SpanBehavior == ESpanBehaviors::Compute)
|
|
{
|
|
Algo::Reverse(loop_edges); // orient properly
|
|
FEdgeSpan& NewSpan = Spans[Spans.Emplace()];
|
|
NewSpan.InitializeFromEdges(Mesh, loop_edges);
|
|
}
|
|
}
|
|
else if (bowties.Num() > 0)
|
|
{
|
|
// if we saw a bowtie vertex, we might need to break up this loop,
|
|
// so call ExtractSubloops
|
|
Subloops subloops;
|
|
bool bSubloopsOK = ExtractSubloops(loop_verts, loop_edges, bowties, subloops);
|
|
if (bSubloopsOK == false)
|
|
{
|
|
if (FailureBehavior == EFailureBehaviors::Abort)
|
|
{
|
|
goto CATASTROPHIC_ABORT;
|
|
}
|
|
|
|
if (subloops.Spans.Num() > 0)
|
|
{
|
|
bFellBackToSpansOnFailure = true;
|
|
for (FEdgeSpan& span : subloops.Spans)
|
|
{
|
|
Spans.Add(span);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (FEdgeLoop& loop : subloops.Loops)
|
|
{
|
|
Loops.Add(loop);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// clean simple loop, convert to EdgeLoop instance
|
|
FEdgeLoop& NewLoop = Loops[Loops.Emplace()];
|
|
NewLoop.Initialize(Mesh, loop_verts, loop_edges);
|
|
}
|
|
|
|
// reset these lists
|
|
loop_edges.Reset();
|
|
loop_verts.Reset();
|
|
bowties.Reset();
|
|
}
|
|
|
|
return true;
|
|
|
|
CATASTROPHIC_ABORT:
|
|
bAborted = true;
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
FVector3d FMeshBoundaryLoops::GetVertexNormal(int vid)
|
|
{
|
|
FVector3d n = FVector3d::Zero();
|
|
for (int ti : Mesh->VtxTrianglesItr(vid))
|
|
{
|
|
n += Mesh->GetTriNormal(ti);
|
|
}
|
|
Normalize(n);
|
|
return n;
|
|
}
|
|
|
|
|
|
|
|
|
|
// ok, bdry_edges[0...bdry_edges_count] contains the boundary edges coming out of bowtie_v.
|
|
// We want to pick the best one to continue the loop that came in to bowtie_v on incoming_e.
|
|
// If the loops are all sane, then we will get the smallest loops by "turning left" at bowtie_v.
|
|
// So, we compute the tangent plane at bowtie_v, and then the signed angle for each
|
|
// viable edge in this plane.
|
|
//
|
|
// [TODO] handle degenerate edges. what do we do then? Currently will only chose
|
|
// degenerate edge if there are no other options (I think...)
|
|
int FMeshBoundaryLoops::FindLeftTurnEdge(int incoming_e, int bowtie_v, TArray<int>& bdry_edges, int bdry_edges_count, TArray<bool>& used_edges)
|
|
{
|
|
// compute normal and edge [a,bowtie]
|
|
FVector3d n = GetVertexNormal(bowtie_v);
|
|
//int other_v = Mesh->edge_other_v(incoming_e, bowtie_v);
|
|
FIndex2i ev = Mesh->GetEdgeV(incoming_e);
|
|
int other_v = (ev.A == bowtie_v) ? ev.B : ev.A;
|
|
FVector3d ab = Mesh->GetVertex(bowtie_v) - Mesh->GetVertex(other_v);
|
|
|
|
// our winner
|
|
int best_e = -1;
|
|
double best_angle = TNumericLimits<double>::Max();
|
|
|
|
for (int i = 0; i < bdry_edges_count; ++i)
|
|
{
|
|
int bdry_eid = bdry_edges[i];
|
|
if (used_edges[bdry_eid] == true)
|
|
{
|
|
continue; // this edge is already used
|
|
}
|
|
|
|
FIndex2i bdry_ev = Mesh->GetOrientedBoundaryEdgeV(bdry_eid);
|
|
if (bdry_ev.A != bowtie_v)
|
|
{
|
|
continue; // have to be able to chain to end of current edge, orientation-wise
|
|
}
|
|
|
|
// compute projected angle
|
|
FVector3d bc = Mesh->GetVertex(bdry_ev.B) - Mesh->GetVertex(bowtie_v);
|
|
double fAngleS = -VectorUtil::PlaneAngleSignedD(ab, bc, n);
|
|
|
|
// turn left!
|
|
if (best_angle == TNumericLimits<double>::Max() || fAngleS < best_angle)
|
|
{
|
|
best_angle = fAngleS;
|
|
best_e = bdry_eid;
|
|
}
|
|
}
|
|
|
|
// Note w/ bowtie vertices and open spans, best_e CAN be invalid (== -1)
|
|
return best_e;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This is called when loopV contains one or more "bowtie" vertices.
|
|
// These vertices *might* be duplicated in loopV (but not necessarily)
|
|
// If they are, we have to break loopV into subloops that don't contain duplicates.
|
|
//
|
|
// The list bowties contains all the possible duplicates
|
|
// (all v in bowties occur in loopV at least once)
|
|
//
|
|
// Currently loopE is not used, and the returned EdgeLoop objects do not have their Edges
|
|
// arrays initialized. Perhaps to improve in future.
|
|
//
|
|
// An unhandled case to think about is where we have a sequence [..A..B..A..B..] where
|
|
// A and B are bowties. In this case there are no A->A or B->B subloops. What should
|
|
// we do here??
|
|
bool FMeshBoundaryLoops::ExtractSubloops(TArray<int>& loopV, TArray<int>& loopE, TArray<int>& bowties, Subloops& SubloopsOut)
|
|
{
|
|
Subloops& subs = SubloopsOut;
|
|
|
|
// figure out which bowties we saw are actually duplicated in loopV
|
|
TArray<int> dupes;
|
|
for (int bv : bowties)
|
|
{
|
|
if (CountInList(loopV, bv) > 1)
|
|
{
|
|
dupes.Add(bv);
|
|
}
|
|
}
|
|
|
|
// we might not actually have any duplicates, if we got luck. Early out in that case
|
|
if (dupes.Num() == 0)
|
|
{
|
|
FEdgeLoop& NewLoop = subs.Loops[subs.Loops.Emplace()];
|
|
NewLoop.Initialize(Mesh, loopV, loopE, &bowties);
|
|
return true;
|
|
}
|
|
|
|
// This loop extracts subloops until we have dealt with all the
|
|
// duplicate vertices in loopV
|
|
while (dupes.Num() > 0)
|
|
{
|
|
|
|
// Find shortest "simple" loop, ie a loop from a bowtie to itself that
|
|
// does not contain any other bowties. This is an independent loop.
|
|
// We're doing a lot of extra work here if we only have one element in dupes...
|
|
int bi = 0, bv = 0;
|
|
int start_i = -1, end_i = -1;
|
|
int bv_shortest = -1; int shortest = TNumericLimits<int>::Max();
|
|
for (; bi < dupes.Num(); ++bi)
|
|
{
|
|
bv = dupes[bi];
|
|
if (IsSimpleBowtieLoop(loopV, dupes, bv, start_i, end_i))
|
|
{
|
|
int len = CountSpan(loopV, start_i, end_i);
|
|
if (len < shortest)
|
|
{
|
|
bv_shortest = bv;
|
|
shortest = len;
|
|
}
|
|
}
|
|
}
|
|
|
|
// failed to find a simple loop. Not sure what to do in this situation.
|
|
// If we don't want to throw, all we can do is convert the remaining
|
|
// loop to a span and return.
|
|
// (Or should we keep it as a loop and set flag??)
|
|
if (bv_shortest == -1)
|
|
{
|
|
if (FailureBehavior == EFailureBehaviors::Abort)
|
|
{
|
|
FailureBowties = dupes;
|
|
bAborted = true;
|
|
return false;
|
|
}
|
|
|
|
VerticesTemp.Reset();
|
|
for (int i = 0; i < loopV.Num(); ++i)
|
|
{
|
|
if (loopV[i] != -1)
|
|
{
|
|
VerticesTemp.Add(loopV[i]);
|
|
}
|
|
}
|
|
|
|
FEdgeSpan& NewSpan = subs.Spans[subs.Spans.Emplace()];
|
|
NewSpan.InitializeFromVertices(Mesh, VerticesTemp, false);
|
|
NewSpan.SetBowtieVertices(bowties);
|
|
return false;
|
|
}
|
|
|
|
if (bv != bv_shortest)
|
|
{
|
|
bv = bv_shortest;
|
|
// running again just to get start_i and end_i...
|
|
IsSimpleBowtieLoop(loopV, dupes, bv, start_i, end_i);
|
|
}
|
|
|
|
check(loopV[start_i] == bv && loopV[end_i] == bv);
|
|
|
|
FEdgeLoop& NewLoop = subs.Loops[subs.Loops.Emplace()];
|
|
|
|
VerticesTemp.Reset();
|
|
ExtractSpan(loopV, start_i, end_i, true, VerticesTemp);
|
|
NewLoop.InitializeFromVertices(Mesh, VerticesTemp, false);
|
|
NewLoop.SetBowtieVertices(bowties);
|
|
|
|
// If there are no more duplicates of this bowtie, we can treat
|
|
// it like a regular vertex now
|
|
if (CountInList(loopV, bv) < 2)
|
|
{
|
|
dupes.Remove(bv);
|
|
}
|
|
}
|
|
|
|
// Should have one loop left that contains duplicates.
|
|
// Extract this as a separate loop
|
|
int nLeft = 0;
|
|
for (int i = 0; i < loopV.Num(); ++i)
|
|
{
|
|
if (loopV[i] != -1)
|
|
nLeft++;
|
|
}
|
|
if (nLeft > 0)
|
|
{
|
|
FEdgeLoop& NewLoop = subs.Loops[subs.Loops.Emplace()];
|
|
|
|
VerticesTemp.Reset();
|
|
for (int i = 0; i < loopV.Num(); ++i)
|
|
{
|
|
if (loopV[i] != -1)
|
|
{
|
|
VerticesTemp.Add(loopV[i]);
|
|
}
|
|
}
|
|
NewLoop.InitializeFromVertices(Mesh, VerticesTemp, false);
|
|
NewLoop.SetBowtieVertices(bowties);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Check if the loop from bowtieV to bowtieV inside loopV contains any other bowtie verts.
|
|
// Also returns start and end indices in loopV of "clean" loop
|
|
// Note that start may be < end, if the "clean" loop wraps around the end
|
|
bool FMeshBoundaryLoops::IsSimpleBowtieLoop(const TArray<int>& LoopVerts, const TArray<int>& BowtieVerts, int BowtieVertex, int& start_i, int& end_i)
|
|
{
|
|
// find two indices of bowtie vert
|
|
start_i = FindIndex(LoopVerts, 0, BowtieVertex);
|
|
end_i = FindIndex(LoopVerts, start_i + 1, BowtieVertex);
|
|
|
|
if (IsSimplePath(LoopVerts, BowtieVerts, BowtieVertex, start_i, end_i))
|
|
{
|
|
return true;
|
|
}
|
|
else if (IsSimplePath(LoopVerts, BowtieVerts, BowtieVertex, end_i, start_i))
|
|
{
|
|
int tmp = start_i; start_i = end_i; end_i = tmp;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false; // not a simple bowtie loop!
|
|
}
|
|
}
|
|
|
|
|
|
// check if forward path from loopV[i1] to loopV[i2] contains any bowtie verts other than bowtieV
|
|
bool FMeshBoundaryLoops::IsSimplePath(const TArray<int>& LoopVerts, const TArray<int>& BowtieVerts, int BowtieVertex, int i1, int i2)
|
|
{
|
|
int N = LoopVerts.Num();
|
|
for (int i = i1; i != i2; i = (i + 1) % N)
|
|
{
|
|
int vi = LoopVerts[i];
|
|
if (vi == -1)
|
|
{
|
|
continue; // skip removed vertices
|
|
}
|
|
if (vi != BowtieVertex && BowtieVerts.Contains(vi))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// Read out the span from loop[i0] to loop [i1-1] into an array.
|
|
// If bMarkInvalid, then these values are set to -1 in loop
|
|
void FMeshBoundaryLoops::ExtractSpan(TArray<int>& Loop, int i0, int i1, bool bMarkInvalid, TArray<int>& OutSpan)
|
|
{
|
|
int num = CountSpan(Loop, i0, i1);
|
|
OutSpan.SetNum(num);
|
|
int ai = 0;
|
|
int N = Loop.Num();
|
|
for (int i = i0; i != i1; i = (i + 1) % N)
|
|
{
|
|
if (Loop[i] != -1)
|
|
{
|
|
OutSpan[ai++] = Loop[i];
|
|
if (bMarkInvalid)
|
|
{
|
|
Loop[i] = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// count number of valid vertices in l between loop[i0] and loop[i1-1]
|
|
int FMeshBoundaryLoops::CountSpan(const TArray<int>& Loop, int i0, int i1)
|
|
{
|
|
int c = 0;
|
|
int N = Loop.Num();
|
|
for (int i = i0; i != i1; i = (i + 1) % N)
|
|
{
|
|
if (Loop[i] != -1)
|
|
{
|
|
c++;
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
// find the index of item in loop, starting at start index
|
|
int FMeshBoundaryLoops::FindIndex(const TArray<int>& Loop, int Start, int Item)
|
|
{
|
|
for (int i = Start; i < Loop.Num(); ++i)
|
|
{
|
|
if (Loop[i] == Item)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// count number of times item appears in loop
|
|
int FMeshBoundaryLoops::CountInList(const TArray<int>& Loop, int Item)
|
|
{
|
|
int c = 0;
|
|
for (int i = 0; i < Loop.Num(); ++i)
|
|
{
|
|
if (Loop[i] == Item)
|
|
{
|
|
c++;
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
int FMeshBoundaryLoops::FindLoopTrianglesHint(const TArray<int>& HintTris) const
|
|
{
|
|
TSet<int> HintEdges;
|
|
for (int TriangleID : HintTris)
|
|
{
|
|
if (Mesh->IsTriangle(TriangleID) == false)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FIndex3i TriangleEdges = Mesh->GetTriEdges(TriangleID);
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
if (Mesh->IsBoundaryEdge(TriangleEdges[j]))
|
|
{
|
|
HintEdges.Add(TriangleEdges[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return FindLoopEdgesHint(HintEdges);
|
|
}
|
|
|
|
|
|
int FMeshBoundaryLoops::FindLoopEdgesHint(const TSet<int>& HintEdges) const
|
|
{
|
|
int NumLoops = GetLoopCount();
|
|
int BestLoop = -1;
|
|
int MaxVotes = 0;
|
|
for (int LoopIndex = 0; LoopIndex < NumLoops; ++LoopIndex)
|
|
{
|
|
int Votes = 0;
|
|
const FEdgeLoop& CurrentLoop = Loops[LoopIndex];
|
|
for (int EdgeID : CurrentLoop.Edges)
|
|
{
|
|
if (HintEdges.Contains(EdgeID))
|
|
{
|
|
++Votes;
|
|
}
|
|
}
|
|
|
|
if (Votes > MaxVotes)
|
|
{
|
|
BestLoop = LoopIndex;
|
|
MaxVotes = Votes;
|
|
}
|
|
}
|
|
|
|
return BestLoop;
|
|
}
|
|
|