332 lines
10 KiB
C++
332 lines
10 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Operations/WeldEdgeSequence.h"
|
|
|
|
#include "DynamicMesh/DynamicMesh3.h"
|
|
#include "DynamicMesh/DynamicMeshAttributeSet.h"
|
|
#include "DynamicMeshEditor.h"
|
|
|
|
using namespace UE::Geometry;
|
|
|
|
FWeldEdgeSequence::EWeldResult FWeldEdgeSequence::Weld()
|
|
{
|
|
// As soon as any of these helper functions return a non-OK value,
|
|
// we want to forward that to the user and stop operating.
|
|
|
|
EWeldResult Result = EWeldResult::Ok;
|
|
|
|
if ((Result = CheckInput()) != EWeldResult::Ok)
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
if ((Result = SplitSmallerSpan()) != EWeldResult::Ok)
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
if ((Result = WeldEdgeSequence()) != EWeldResult::Ok)
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
|
|
/** Protected Functions */
|
|
|
|
FWeldEdgeSequence::EWeldResult FWeldEdgeSequence::CheckInput()
|
|
{
|
|
// Selected edges must be boundary edges
|
|
for (int Edge : EdgeSpanToDiscard.Edges)
|
|
{
|
|
if (!ensure(Mesh->IsEdge(Edge)) || !Mesh->IsBoundaryEdge(Edge))
|
|
{
|
|
return EWeldResult::Failed_EdgesNotBoundaryEdges;
|
|
}
|
|
}
|
|
|
|
for (int Edge : EdgeSpanToKeep.Edges)
|
|
{
|
|
if (!ensure(Mesh->IsEdge(Edge)) || !Mesh->IsBoundaryEdge(Edge))
|
|
{
|
|
return EWeldResult::Failed_EdgesNotBoundaryEdges;
|
|
}
|
|
}
|
|
|
|
// Ensure that the two input spans are oriented according to mesh boundary
|
|
// Guaranteed to be on boundary after two for loops above
|
|
EdgeSpanToDiscard.SetCorrectOrientation();
|
|
EdgeSpanToKeep.SetCorrectOrientation();
|
|
|
|
return EWeldResult::Ok;
|
|
}
|
|
|
|
FWeldEdgeSequence::EWeldResult FWeldEdgeSequence::SplitSmallerSpan()
|
|
{
|
|
return SplitEdgesToEqualizeSpanLengths(*Mesh, EdgeSpanToKeep, EdgeSpanToDiscard);
|
|
}
|
|
|
|
FWeldEdgeSequence::EWeldResult UE::Geometry::FWeldEdgeSequence::SplitEdgesToEqualizeSpanLengths(FDynamicMesh3& Mesh, FEdgeSpan& Span1, FEdgeSpan& Span2)
|
|
{
|
|
// For each new vertex that must be created:
|
|
// The longest simple edge is found and split
|
|
// The newly generated vertex is inserted into the span
|
|
// The newly generated edge is inserted into the span
|
|
// TODO: Could improve this by sorting lengths and keeping those updated as we split (i.e. using
|
|
// a priority queue)
|
|
|
|
FEdgeSpan& SpanToSplit = (Span1.Vertices.Num() < Span2.Vertices.Num()) ? Span1 : Span2;
|
|
int TotalSplits = FMath::Abs(Span1.Vertices.Num() - Span2.Vertices.Num());
|
|
for (int SplitCount = 0; SplitCount < TotalSplits; ++SplitCount)
|
|
{
|
|
double MaxLength = 0.0;
|
|
int LongestEID = -1;
|
|
int LongestIndex = -1;
|
|
|
|
// Find longest edge and store length, ID, and index
|
|
for (int EdgeIndex = 0; EdgeIndex < SpanToSplit.Edges.Num(); ++EdgeIndex)
|
|
{
|
|
FVector3d VertA = Mesh.GetVertex(Mesh.GetEdge(SpanToSplit.Edges[EdgeIndex]).Vert.A);
|
|
FVector3d VertB = Mesh.GetVertex(Mesh.GetEdge(SpanToSplit.Edges[EdgeIndex]).Vert.B);
|
|
double EdgeLength = DistanceSquared(VertA, VertB);
|
|
|
|
if (MaxLength < EdgeLength)
|
|
{
|
|
MaxLength = EdgeLength;
|
|
LongestEID = SpanToSplit.Edges[EdgeIndex];
|
|
LongestIndex = EdgeIndex;
|
|
}
|
|
}
|
|
|
|
// Split longest edge
|
|
FDynamicMesh3::FEdgeSplitInfo SplitInfo;
|
|
EMeshResult Result = Mesh.SplitEdge(LongestEID, SplitInfo);
|
|
if (Result != EMeshResult::Ok)
|
|
{
|
|
return EWeldResult::Failed_CannotSplitEdge;
|
|
}
|
|
|
|
// Correctly insert new vertex (between vertices of split edge)
|
|
SpanToSplit.Vertices.Insert(SplitInfo.NewVertex, LongestIndex + 1);
|
|
|
|
// Correctly insert new edge
|
|
// OriginalVertices.B is the non-new vertex of the newly inserted edge- use this
|
|
// to determine whether the edge goes before or after the original in our span
|
|
if (SplitInfo.OriginalVertices.B == SpanToSplit.Vertices[LongestIndex])
|
|
{
|
|
SpanToSplit.Edges.Insert(SplitInfo.NewEdges.A, LongestIndex);
|
|
}
|
|
else
|
|
{
|
|
SpanToSplit.Edges.Insert(SplitInfo.NewEdges.A, LongestIndex + 1);
|
|
}
|
|
}
|
|
|
|
return EWeldResult::Ok;
|
|
}
|
|
|
|
// No longer used, as it fails to consider the case of intervening triangles in the middle
|
|
// of the sequence, if the edges are the boundary of a band.
|
|
FWeldEdgeSequence::EWeldResult FWeldEdgeSequence::CheckForAndCollapseSideTriangles()
|
|
{
|
|
// Checks for and deletes edge between VertA and VertB
|
|
auto CheckForAndHandleEdge = [this](int VertA, int VertB)
|
|
{
|
|
int Edge = Mesh->FindEdge(VertA, VertB);
|
|
if (Edge != IndexConstants::InvalidID)
|
|
{
|
|
if (bAllowIntermediateTriangleDeletion == false)
|
|
{
|
|
return EWeldResult::Failed_TriangleDeletionDisabled;
|
|
}
|
|
|
|
FIndex2i TrianglePair = Mesh->GetEdgeT(Edge);
|
|
EMeshResult Result = Mesh->RemoveTriangle(TrianglePair.A);
|
|
if (Result != EMeshResult::Ok)
|
|
{
|
|
return EWeldResult::Failed_CannotDeleteTriangle;
|
|
}
|
|
|
|
if (Mesh->IsTriangle(TrianglePair.B))
|
|
{
|
|
Result = Mesh->RemoveTriangle(TrianglePair.B);
|
|
if (Result != EMeshResult::Ok)
|
|
{
|
|
return EWeldResult::Failed_CannotDeleteTriangle;
|
|
}
|
|
}
|
|
}
|
|
|
|
return EWeldResult::Ok;
|
|
};
|
|
|
|
EWeldResult Result = CheckForAndHandleEdge(EdgeSpanToDiscard.Vertices[0], EdgeSpanToKeep.Vertices.Last());
|
|
if (Result != EWeldResult::Ok)
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
Result = CheckForAndHandleEdge(EdgeSpanToDiscard.Vertices.Last(), EdgeSpanToKeep.Vertices[0]);
|
|
if (Result != EWeldResult::Ok)
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
return EWeldResult::Ok;
|
|
}
|
|
|
|
FWeldEdgeSequence::EWeldResult FWeldEdgeSequence::WeldEdgeSequence()
|
|
{
|
|
if (!ensure(EdgeSpanToDiscard.Edges.Num() == EdgeSpanToKeep.Edges.Num()
|
|
&& EdgeSpanToDiscard.Vertices.Num() == EdgeSpanToDiscard.Edges.Num() + 1
|
|
&& EdgeSpanToKeep.Vertices.Num() == EdgeSpanToKeep.Edges.Num() + 1))
|
|
{
|
|
return EWeldResult::Failed_Other;
|
|
}
|
|
|
|
// There are certain pathological cases in which one edge weld could delete one of the
|
|
// next edge paired verts before we can use its location and attribute values for interpolation.
|
|
// For example in the following diagram, welding ab to de will delete the triangle bce, but we
|
|
// still need to update vertex f:
|
|
// a_b_c
|
|
// \|/
|
|
// d_e_f
|
|
// |\|\|
|
|
//
|
|
// This is only possible if there is an edge between b and e (so that the triangle can be deleted), and if
|
|
// there is not another triangle holding on to c. We can handle this case by collapsing a bit out of order-
|
|
// if we know there's an edge at this pair but there is not an edge at the next pair, we can do the next
|
|
// pair first, and we know the same issue won't occur there. On the flip side if there is an edge at the
|
|
// next pair too, then the next vert can't be destroyed by collapsing this one.
|
|
|
|
// For this and other edge cases, it is safer to do the welding vert by vert instead of edge by edge.
|
|
// As another example, in this diagram, after merging ab to de, bc no longer exists, but c still needs
|
|
// welding to f:
|
|
// a_b_c
|
|
// |\|/|
|
|
// d_e_f
|
|
|
|
auto ProcessVidPair = [this](int32 KeepVid, int32 DiscardVid, int32 KeepVertIndex) -> EWeldResult
|
|
{
|
|
FDynamicMesh3::FMergeVerticesInfo MergeInfo;
|
|
EMeshResult Result = Mesh->MergeVertices(KeepVid, DiscardVid, InterpolationT, MergeInfo);
|
|
|
|
if (Result == EMeshResult::Failed_CollapseTriangle
|
|
|| Result == EMeshResult::Failed_CollapseQuad
|
|
|| Result == EMeshResult::Failed_FoundDuplicateTriangle)
|
|
{
|
|
// Currently collapse doesn't allow us to collapse away an isolated triangle, quad,
|
|
// or double sided triangle. We can deal with this case, however, simply by deleting
|
|
// them.
|
|
// TODO: Should maybe have an option for this in CollapseEdge/MergeVertices
|
|
int32 Eid = Mesh->FindEdge(KeepVid, DiscardVid);
|
|
if (!ensure(Mesh->IsEdge(Eid)))
|
|
{
|
|
return EWeldResult::Failed_Other;
|
|
}
|
|
FIndex2i TidsToDelete = Mesh->GetEdgeT(Eid);
|
|
Mesh->RemoveTriangle(TidsToDelete.A);
|
|
if (TidsToDelete.B != IndexConstants::InvalidID)
|
|
{
|
|
Mesh->RemoveTriangle(TidsToDelete.B);
|
|
}
|
|
}
|
|
else if (Result == EMeshResult::Failed_InvalidNeighbourhood && bAllowFailedMerge == true)
|
|
{
|
|
// If we're allowed to, we just place the edges together without welding.
|
|
FVector3d Destination = Lerp(Mesh->GetVertex(KeepVid), Mesh->GetVertex(DiscardVid), InterpolationT);
|
|
Mesh->SetVertex(KeepVid, Destination);
|
|
Mesh->SetVertex(DiscardVid, Destination);
|
|
|
|
// Maybe it's unfortunate that we have to output unmerged edges instead of vertices, but
|
|
// theoretically the edges on either side were not successfully welded.
|
|
auto AddEdgeAtKeepIndex = [this](int32 KeepEidIndex)
|
|
{
|
|
int32 DiscardEidIndex = EdgeSpanToDiscard.Edges.Num() - 1 - KeepEidIndex;
|
|
int32 KeepEid = EdgeSpanToKeep.Edges[KeepEidIndex];
|
|
int32 DiscardEid = EdgeSpanToDiscard.Edges[DiscardEidIndex];
|
|
if (Mesh->IsEdge(KeepEid) && Mesh->IsEdge(DiscardEid))
|
|
{
|
|
UnmergedEdgePairsOut.Add(TPair<int, int>(KeepEid, DiscardEid));
|
|
}
|
|
};
|
|
if (KeepVertIndex > 0)
|
|
{
|
|
AddEdgeAtKeepIndex(KeepVertIndex - 1);
|
|
}
|
|
if (KeepVertIndex < EdgeSpanToKeep.Edges.Num())
|
|
{
|
|
AddEdgeAtKeepIndex(KeepVertIndex);
|
|
}
|
|
}
|
|
else if (Result != EMeshResult::Ok)
|
|
{
|
|
return EWeldResult::Failed_Other;
|
|
}
|
|
|
|
return EWeldResult::Ok;
|
|
};
|
|
|
|
for (int KeepVertIndex = 0; KeepVertIndex < EdgeSpanToKeep.Vertices.Num(); ++KeepVertIndex)
|
|
{
|
|
// The spans are oriented opposite directions, so iterate in opposite order
|
|
int32 DiscardVertIndex = EdgeSpanToDiscard.Vertices.Num() - 1 - KeepVertIndex;
|
|
int32 KeepVid = EdgeSpanToKeep.Vertices[KeepVertIndex];
|
|
int32 DiscardVid = EdgeSpanToDiscard.Vertices[DiscardVertIndex];
|
|
|
|
if (KeepVid == DiscardVid)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!ensure(Mesh->IsVertex(KeepVid) && Mesh->IsVertex(DiscardVid)))
|
|
{
|
|
// This shouldn't happen due to our out-of-order collapse strategy, see above
|
|
continue;
|
|
}
|
|
|
|
// See above for why we consider processing the next vid first
|
|
bool bProcessedNext = false;
|
|
int32 InterveningEdge = Mesh->FindEdge(KeepVid, DiscardVid);
|
|
if (InterveningEdge != IndexConstants::InvalidID
|
|
&& KeepVertIndex < EdgeSpanToKeep.Vertices.Num()-1)
|
|
{
|
|
int32 NextKeepVid = EdgeSpanToKeep.Vertices[KeepVertIndex + 1];
|
|
int32 NextDiscardVid = EdgeSpanToDiscard.Vertices[DiscardVertIndex - 1];
|
|
if (NextKeepVid == NextDiscardVid)
|
|
{
|
|
// Consider ourselves to have dealt with the next vertex
|
|
bProcessedNext = true;
|
|
}
|
|
else if (ensure(Mesh->IsVertex(NextKeepVid) && Mesh->IsVertex(NextDiscardVid))
|
|
&& Mesh->FindEdge(NextKeepVid, NextDiscardVid) == IndexConstants::InvalidID)
|
|
{
|
|
// This is a safe vert pair to collapse, since it doesn't have intervening edges.
|
|
// And if it did, it wouldn't be in danger of losing a vert due to our collapse.
|
|
EWeldResult Result = ProcessVidPair(NextKeepVid, NextDiscardVid, KeepVertIndex + 1);
|
|
if (Result != EWeldResult::Ok)
|
|
{
|
|
return Result;
|
|
}
|
|
bProcessedNext = true;
|
|
}
|
|
}
|
|
|
|
EWeldResult Result = ProcessVidPair(KeepVid, DiscardVid, KeepVertIndex);
|
|
if (Result != EWeldResult::Ok)
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
if (bProcessedNext)
|
|
{
|
|
++KeepVertIndex;
|
|
}
|
|
}//end iterating through vertices
|
|
|
|
return EWeldResult::Ok;
|
|
}
|