You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb Semion.Piskarev #jira none #preflight 62a8efbadb45e64426a03d12 [CL 20811431 by Nickolas Drake in ue5-main branch]
222 lines
6.2 KiB
C++
222 lines
6.2 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 = CheckForAndCollapseSideTriangles()) != 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 (Mesh->IsBoundaryEdge(Edge) == false)
|
|
{
|
|
return EWeldResult::Failed_EdgesNotBoundaryEdges;
|
|
}
|
|
}
|
|
|
|
for (int Edge : EdgeSpanToKeep.Edges)
|
|
{
|
|
if (Mesh->IsBoundaryEdge(Edge) == false)
|
|
{
|
|
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()
|
|
{
|
|
// 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
|
|
|
|
FEdgeSpan& SpanToSplit = (EdgeSpanToDiscard.Vertices.Num() < EdgeSpanToKeep.Vertices.Num()) ? EdgeSpanToDiscard : EdgeSpanToKeep;
|
|
int TotalSplits = FMath::Abs(EdgeSpanToDiscard.Vertices.Num() - EdgeSpanToKeep.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;
|
|
}
|
|
|
|
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()))
|
|
{
|
|
return EWeldResult::Failed_Other;
|
|
}
|
|
|
|
for (int EdgePairIndex = 0; EdgePairIndex < EdgeSpanToDiscard.Edges.Num(); ++EdgePairIndex)
|
|
{
|
|
int EdgeA = EdgeSpanToDiscard.Edges[EdgeSpanToDiscard.Edges.Num() - 1 - EdgePairIndex];
|
|
int EdgeB = EdgeSpanToKeep.Edges[EdgePairIndex];
|
|
|
|
// We need to check this because MergeEdges() can implicitly
|
|
// merge adjacent edges when adjacent edges share a vertex.
|
|
if (Mesh->IsEdge(EdgeA) && Mesh->IsEdge(EdgeB))
|
|
{
|
|
FDynamicMesh3::FMergeEdgesInfo MergeInfo;
|
|
EMeshResult Result = Mesh->MergeEdges(EdgeB, EdgeA, MergeInfo);
|
|
if (Result != EMeshResult::Ok)
|
|
{
|
|
if (Result == EMeshResult::Failed_InvalidNeighbourhood && bAllowFailedMerge == true)
|
|
{
|
|
// Get properly oriented vertex pairs for edges which can't be merged
|
|
FIndex2i EdgeAVerts = Mesh->GetEdgeV(EdgeA);
|
|
FIndex2i EdgeBVerts = Mesh->GetEdgeV(EdgeB);
|
|
IndexUtil::OrientTriEdge(EdgeAVerts.A, EdgeAVerts.B, Mesh->GetTriangle(Mesh->GetEdgeT(EdgeA).A));
|
|
IndexUtil::OrientTriEdge(EdgeBVerts.A, EdgeBVerts.B, Mesh->GetTriangle(Mesh->GetEdgeT(EdgeB).A));
|
|
|
|
// Move verts from EdgeA to EdgeB, leaving an invisible seam
|
|
Mesh->SetVertex(EdgeAVerts.A, Mesh->GetVertex(EdgeBVerts.B));
|
|
Mesh->SetVertex(EdgeAVerts.B, Mesh->GetVertex(EdgeBVerts.A));
|
|
|
|
// Add eids to UnmergedEdgePairsOut so user can easily identify offending edges
|
|
UnmergedEdgePairsOut.Add(TPair<int, int>(EdgeA, EdgeB));
|
|
}
|
|
else
|
|
{
|
|
return EWeldResult::Failed_Other;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return EWeldResult::Ok;
|
|
}
|