Files
UnrealEngine/Engine/Source/Runtime/Datasmith/CADKernel/Base/Private/Topo/Topomaker.cpp
2025-05-18 13:04:45 +08:00

1556 lines
41 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Topo/Topomaker.h"
#include "Core/Chrono.h"
#include "Core/Session.h"
#include "Geo/Curves/RestrictionCurve.h"
#include "Topo/Body.h"
#include "Topo/FaceAnalyzer.h"
#include "Topo/Model.h"
#include "Topo/Shell.h"
#include "Topo/TopologicalEdge.h"
#include "Topo/TopologicalLink.h"
#include "Topo/TopologicalFace.h"
#include "Topo/TopologicalLoop.h"
#include "Topo/TopologicalVertex.h"
#include "Topo/TopologicalShapeEntity.h"
#include "UI/Display.h"
#include "UI/Message.h"
#include "Utils/Util.h"
namespace UE::CADKernel
{
namespace SewOption
{
static bool IsForceJoining(ESewOption SewOptions)
{
return (SewOptions & ESewOption::ForceJoining) == ESewOption::ForceJoining;
}
static bool IsRemoveThinFaces(ESewOption SewOptions)
{
return (SewOptions & ESewOption::RemoveThinFaces) == ESewOption::RemoveThinFaces;
}
static bool IsRemoveDuplicatedFaces(ESewOption SewOptions)
{
return (SewOptions & ESewOption::RemoveDuplicatedFaces) == ESewOption::RemoveDuplicatedFaces;
}
} // namespace
namespace TopomakerTools
{
/**
* Merge Border Vertices with other vertices.
* @param Vertices: the initial array of active vertices to process, this array is updated at the end of the process
*/
void MergeCoincidentVertices(TArray<FTopologicalVertex*>& VerticesToMerge, double Tolerance)
{
const double SquareTolerance = FMath::Square(Tolerance);
const double WeigthTolerance = 3 * Tolerance;
int32 VertexNum = (int32)VerticesToMerge.Num();
TArray<double> VerticesWeight;
VerticesWeight.Reserve(VertexNum);
TArray<int32> SortedVertexIndices;
SortedVertexIndices.Reserve(VertexNum);
#ifdef DEBUG_MERGE_COINCIDENT_VERTICES
F3DDebugSession _(TEXT("Merge Coincident Vertices"));
for (const FTopologicalVertex* Vertex : VerticesToMerge)
{
F3DDebugSession A(*FString::Printf(TEXT("Vertex %d"), Vertex->GetId()));
Display(*Vertex);
}
#endif
for (FTopologicalVertex* Vertex : VerticesToMerge)
{
VerticesWeight.Add(FVectorUtil::DiagonalAxisCoordinate(Vertex->GetCoordinates()));
}
for (int32 Index = 0; Index < VerticesToMerge.Num(); ++Index)
{
SortedVertexIndices.Add(Index);
}
SortedVertexIndices.Sort([&VerticesWeight](const int32& Index1, const int32& Index2) { return VerticesWeight[Index1] < VerticesWeight[Index2]; });
for (int32 IndexI = 0; IndexI < VertexNum; ++IndexI)
{
FTopologicalVertex* Vertex = VerticesToMerge[SortedVertexIndices[IndexI]];
if (Vertex->HasMarker1())
{
continue;
}
ensureCADKernel(Vertex->IsActiveEntity());
Vertex->SetMarker1();
double VertexWeigth = VerticesWeight[SortedVertexIndices[IndexI]];
FVector Barycenter = Vertex->GetBarycenter();
for (int32 IndexJ = IndexI + 1; IndexJ < VertexNum; ++IndexJ)
{
FTopologicalVertex* OtherVertex = VerticesToMerge[SortedVertexIndices[IndexJ]];
if (OtherVertex->HasMarker1())
{
continue;
}
double OtherVertexWeigth = VerticesWeight[SortedVertexIndices[IndexJ]];
if ((OtherVertexWeigth - VertexWeigth) > WeigthTolerance)
{
break;
}
double DistanceSqr = OtherVertex->GetLinkActiveEntity()->SquareDistance(Barycenter);
if (DistanceSqr < SquareTolerance)
{
OtherVertex->SetMarker1();
Vertex->Link(*OtherVertex);
Barycenter = Vertex->GetBarycenter();
}
}
}
for (FTopologicalVertex* Vertex : VerticesToMerge)
{
Vertex->ResetMarker1();
}
TArray<FTopologicalVertex*> ActiveVertices;
ActiveVertices.Reserve(VertexNum);
for (FTopologicalVertex* Vertex : VerticesToMerge)
{
FTopologicalVertex& ActiveVertex = *Vertex->GetLinkActiveEntity();
if (ActiveVertex.HasMarker1())
{
continue;
}
ActiveVertex.SetMarker1();
ActiveVertices.Add(&ActiveVertex);
}
for (FTopologicalVertex* Vertex : ActiveVertices)
{
Vertex->ResetMarker1();
}
Swap(ActiveVertices, VerticesToMerge);
}
FTopologicalVertex* SplitAndLink(FTopologicalVertex& StartVertex, FTopologicalEdge& EdgeToLink, FTopologicalEdge& EdgeToSplit, double SquareSewTolerance, double SquareMinEdgeLength)
{
FTopologicalVertex* VertexToLink = EdgeToLink.GetOtherVertex(StartVertex);
FTopologicalVertex* EndVertex = EdgeToSplit.GetOtherVertex(StartVertex);
double SquareDistanceToVertexToLink = EndVertex->SquareDistanceBetweenBarycenters(*VertexToLink);
if (SquareDistanceToVertexToLink < SquareMinEdgeLength)
{
VertexToLink->Link(*EndVertex);
EdgeToLink.Link(EdgeToSplit);
return &VertexToLink->GetLinkActiveEntity().Get();
}
double SquareDistanceToStartVertex = StartVertex.SquareDistanceBetweenBarycenters(*VertexToLink);
if (SquareDistanceToStartVertex < SquareMinEdgeLength)
{
VertexToLink->Link(StartVertex);
EdgeToLink.SetAsDegenerated();
return &VertexToLink->GetLinkActiveEntity().Get();
}
FVector ProjectedPoint;
double UProjectedPoint = EdgeToSplit.ProjectPoint(VertexToLink->GetBarycenter(), ProjectedPoint);
double SquareDistanceToProjectedPoint = FVector::DistSquared(ProjectedPoint, VertexToLink->GetBarycenter());
if (SquareDistanceToProjectedPoint > SquareSewTolerance)
{
return nullptr;
}
double SquareDistanceToOtherPoint = FVector::DistSquared(ProjectedPoint, EndVertex->GetBarycenter());
if (FMath::IsNearlyEqual(UProjectedPoint, EdgeToSplit.GetBoundary().GetMax(), UE_DOUBLE_SMALL_NUMBER) || SquareDistanceToOtherPoint < SquareMinEdgeLength)
{
// the new point is closed to the extremity, a degenerated edge will be created, so the edges are joined
VertexToLink->Link(*EndVertex);
EdgeToLink.Link(EdgeToSplit);
return &VertexToLink->GetLinkActiveEntity().Get();
}
SquareDistanceToProjectedPoint = FVector::DistSquared(ProjectedPoint, StartVertex.GetBarycenter());
if (FMath::IsNearlyEqual(UProjectedPoint, EdgeToSplit.GetBoundary().GetMin(), UE_DOUBLE_SMALL_NUMBER) || SquareDistanceToProjectedPoint < SquareMinEdgeLength)
{
VertexToLink->Link(StartVertex);
EdgeToLink.SetAsDegenerated();
return &VertexToLink->GetLinkActiveEntity().Get();
}
TArray<FTopologicalVertex*> NewTwinVertices;
if (EdgeToSplit.GetTwinEntityCount() > 1)
{
TArray<FTopologicalEdge*> TwinEdges = EdgeToSplit.GetTwinEntities();
EdgeToSplit.UnlinkTwinEntities();
for (FTopologicalEdge* TwinEdge : TwinEdges)
{
if (TwinEdge != &EdgeToSplit)
{
FTopologicalVertex* NewVertex = SplitAndLink(StartVertex, EdgeToLink, *TwinEdge, SquareSewTolerance, SquareMinEdgeLength);
if (NewVertex)
{
NewTwinVertices.Add(NewVertex);
}
}
}
}
// JoinParallelEdges process all edges connected to startVertex (ConnectedEdges).
// Connected edges must remain compliant i.e. all edges of ConnectedEdges must be connected to StartVertex
// EdgeToSplit->SplitAt() must keep EdgeToSplit connected to StartVertex
bool bKeepStartVertexConnectivity = (StartVertex.GetLink() == EdgeToSplit.GetStartVertex()->GetLink());
TSharedPtr<FTopologicalEdge> NewEdge;
FTopologicalVertex* NewVertex = EdgeToSplit.SplitAt(UProjectedPoint, ProjectedPoint, bKeepStartVertexConnectivity, NewEdge);
if (!NewVertex)
{
return nullptr;
}
VertexToLink->Link(*NewVertex);
EdgeToLink.Link(EdgeToSplit);
if (NewTwinVertices.Num() > 0)
{
for (FTopologicalVertex* Vertex : NewTwinVertices)
{
NewVertex->Link(*Vertex);
}
}
return NewVertex;
}
FTopologicalVertex* StitchParallelEdgesFrom(FTopologicalVertex* Vertex, double SewTolerance, double MinEdgeLength, bool bProhibitSewingEdgesOfSameFace)
{
double SquareTolerance = FMath::Square(SewTolerance);
double SquareMinEdgeLength = FMath::Square(MinEdgeLength);
TArray<FTopologicalEdge*> ConnectedEdges;
Vertex->GetConnectedEdges(ConnectedEdges);
const int32 ConnectedEdgeCount = ConnectedEdges.Num();
if (ConnectedEdgeCount == 1)
{
return nullptr;
}
#ifdef DEBUG_STITCH_PARALLEL_EDGES_FROM
{
F3DDebugSession B(*FString::Printf(TEXT("Vertex %d"), Vertex->GetId()));
{
F3DDebugSession A(*FString::Printf(TEXT("Vertex %d"), Vertex->GetId()));
Display(*Vertex);
}
for (const FTopologicalEdge* Edge : ConnectedEdges)
{
F3DDebugSession A(*FString::Printf(TEXT("Edge %d"), Edge->GetId()));
int32 TwinCount = Edge->GetTwinEntityCount();
EVisuProperty Property = EVisuProperty::RedCurve;
switch (TwinCount)
{
case 1:
Property = EVisuProperty::BorderEdge;
break;
case 2:
Property = EVisuProperty::BlueCurve;
break;
default:
break;
}
Display(*Edge, Property);
}
Wait();
}
#endif
for (int32 EdgeI = 0; EdgeI < ConnectedEdgeCount - 1; ++EdgeI)
{
FTopologicalEdge* Edge = ConnectedEdges[EdgeI];
ensureCADKernel(Edge->GetLoop() != nullptr);
if (Edge->IsDegenerated())
{
continue;
}
if (!Edge->IsActiveEntity())
{
continue;
}
bool bFirstEdgeBorder = Edge->IsBorder();
for (int32 EdgeJ = EdgeI + 1; EdgeJ < ConnectedEdgeCount; ++EdgeJ)
{
FTopologicalEdge* SecondEdge = ConnectedEdges[EdgeJ];
if (SecondEdge->IsDegenerated())
{
continue;
}
if (!SecondEdge->IsActiveEntity())
{
continue;
}
bool bSecondEdgeBorder = SecondEdge->IsBorder();
// Process only if at least one edge is Border
if (!bFirstEdgeBorder && !bSecondEdgeBorder)
{
continue;
}
if(bProhibitSewingEdgesOfSameFace)
{
bool bVoidSelection = false;
for (FTopologicalEdge* FirstEdgeTwin : Edge->GetTwinEntities())
{
FTopologicalFace* FirstEdgeFace = FirstEdgeTwin->GetFace();
for (FTopologicalEdge* SecondEdgeTwin : SecondEdge->GetTwinEntities())
{
if (FirstEdgeFace == SecondEdgeTwin->GetFace())
{
bVoidSelection = true;
break;
}
}
}
if (bVoidSelection)
{
continue;
}
}
FVector StartTangentEdge = Edge->GetTangentAt(*Vertex);
FVector StartTangentOtherEdge = SecondEdge->GetTangentAt(*Vertex);
double CosAngle = FVectorUtil::ComputeCosinus(StartTangentEdge, StartTangentOtherEdge);
if (CosAngle < UE_DOUBLE_HALF_SQRT_3) // cos(30 deg)
{
continue;
}
#ifdef DEBUG_STITCH_PARALLEL_EDGES_FROM
{
F3DDebugSession B(*FString::Printf(TEXT("StitchParallelEdges")));
{
F3DDebugSession A(*FString::Printf(TEXT("Edge %d"), Edge->GetId()));
Display(*Edge);
}
{
F3DDebugSession A(*FString::Printf(TEXT("SecondEdge %d"), SecondEdge->GetId()));
Display(*SecondEdge, EVisuProperty::RedCurve);
}
Wait();
}
#endif
FTopologicalVertex& EndVertex = *Edge->GetOtherVertex(*Vertex)->GetLinkActiveEntity();
FTopologicalVertex& SecondEdgeEndVertex = *SecondEdge->GetOtherVertex(*Vertex)->GetLinkActiveEntity();
if (&EndVertex == &SecondEdgeEndVertex && Edge->IsLinkableTo(*SecondEdge, MinEdgeLength))
{
// should not happen but, just in case
Edge->Link(*SecondEdge);
}
else
{
double EdgeLength = Edge->Length();
double SecondEdgeLength = SecondEdge->Length();
FTopologicalVertex* OtherVertex = nullptr;
FTopologicalVertex* NewVertex = nullptr;
if (EdgeLength < SecondEdgeLength)
{
OtherVertex = &SecondEdgeEndVertex;
NewVertex = SplitAndLink(*Vertex, *Edge, *SecondEdge, SquareTolerance, SquareMinEdgeLength);
}
else
{
OtherVertex = &EndVertex;
NewVertex = SplitAndLink(*Vertex, *SecondEdge, *Edge, SquareTolerance, SquareMinEdgeLength);
}
if (NewVertex && !Vertex->IsLinkedTo(*NewVertex) && !OtherVertex->IsLinkedTo(*NewVertex))
{
return NewVertex;
}
}
}
}
return nullptr;
}
void StitchParallelEdges(TArray<FTopologicalVertex*>& VerticesToProcess, double SewTolerance, double MinEdgeLength, bool bProhibitSewingEdgesOfSameFace)
{
double SquareTolerance = FMath::Square(SewTolerance);
double SquareMinEdgeLength = FMath::Square(MinEdgeLength);
FTimePoint StartTime = FChrono::Now();
for (int32 VertexI = 0; VertexI < VerticesToProcess.Num(); ++VertexI)
{
FTopologicalVertex* Vertex = VerticesToProcess[VertexI];
if (!Vertex || Vertex->IsDeleted() || !Vertex->IsBorderVertex())
{
continue;
}
while (Vertex)
{
Vertex = StitchParallelEdgesFrom(Vertex, SewTolerance, MinEdgeLength, bProhibitSewingEdgesOfSameFace);
}
}
FDuration StepDuration = FChrono::Elapse(StartTime);
FChrono::PrintClockElapse(EVerboseLevel::Log, TEXT(" "), TEXT("Stitch Parallel Edges"), StepDuration);
}
/**
* First step, trivial edge merge i.e. couple of edges with same extremity active vertices
*/
void MergeCoincidentEdges(FTopologicalEdge* Edge, double MinEdgeLength)
{
const FTopologicalVertex& StartVertex = *Edge->GetStartVertex()->GetLinkActiveEntity();
const FTopologicalVertex& EndVertex = *Edge->GetEndVertex()->GetLinkActiveEntity();
if (&StartVertex == &EndVertex && Edge->Length() < MinEdgeLength)
{
if (Edge->GetTwinEntityCount() > 1)
{
FMessage::Printf(Debug, TEXT("Face %d Edge %d was self connected\n"), Edge->GetFace()->GetId(), Edge->GetId());
Edge->GetStartVertex()->UnlinkTo(*Edge->GetEndVertex());
}
else
{
Edge->SetAsDegenerated();
FMessage::Printf(Debug, TEXT("Face %d Edge %d is set as degenerated\n"), Edge->GetFace()->GetId(), Edge->GetId());
}
return;
}
TArray<FTopologicalEdge*> ConnectedEdges;
StartVertex.GetConnectedEdges(EndVertex, ConnectedEdges);
const int32 ConnectedEdgeCount = ConnectedEdges.Num();
if (ConnectedEdgeCount == 1)
{
return;
}
const bool bFirstEdgeBorder = Edge->IsBorder();
for (FTopologicalEdge* SecondEdge : ConnectedEdges)
{
if (SecondEdge == Edge || !SecondEdge->IsActiveEntity() || SecondEdge->IsDegenerated())
{
continue;
}
const bool bSecondEdgeBorder = Edge->IsBorder();
// Process only if at least one edge is Border
if (!bFirstEdgeBorder && !bSecondEdgeBorder)
{
continue;
}
if (Edge->GetFace() != SecondEdge->GetFace() && Edge->IsLinkableTo(*SecondEdge, MinEdgeLength))
{
Edge->Link(*SecondEdge);
}
}
}
void MergeCoincidentEdges(TArray<FTopologicalEdge*>& EdgesToProcess, double MinEdgeLength)
{
for (FTopologicalEdge* Edge : EdgesToProcess)
{
if (Edge->IsDegenerated() || !Edge->IsBorder())
{
continue;
}
MergeCoincidentEdges(Edge, MinEdgeLength);
}
}
void MergeCoincidentEdges(TArray<FTopologicalVertex*>& VerticesToProcess, double MinEdgeLength)
{
FTimePoint StartTime = FChrono::Now();
for (FTopologicalVertex* Vertex : VerticesToProcess)
{
if (Vertex->IsDeleted() || !Vertex->IsActiveEntity() || Vertex->HasMarker1())
{
continue;
}
Vertex->SetMarker1();
TArray<FTopologicalEdge*> ConnectedEdges;
Vertex->GetConnectedEdges(ConnectedEdges);
const int32 ConnectedEdgeCount = ConnectedEdges.Num();
if (ConnectedEdgeCount == 1)
{
continue;
}
for (int32 EdgeI = 0; EdgeI < ConnectedEdgeCount - 1; ++EdgeI)
{
FTopologicalEdge* Edge = ConnectedEdges[EdgeI];
if (!Edge->IsActiveEntity() || Edge->IsDegenerated())
{
continue;
}
MergeCoincidentEdges(Edge, MinEdgeLength);
}
}
for (FTopologicalVertex* Vertex : VerticesToProcess)
{
Vertex->ResetMarker1();
}
FDuration StepDuration = FChrono::Elapse(StartTime);
FChrono::PrintClockElapse(EVerboseLevel::Log, TEXT(" "), TEXT("Merge coincident edges"), StepDuration);
}
void GetVertices(const TArray<FTopologicalEdge*>& InEdges, TArray<FTopologicalVertex*>& OutVertices)
{
OutVertices.Reset(InEdges.Num());
for (const FTopologicalEdge* Edge : InEdges)
{
if (!Edge || Edge->IsDeleted())
{
continue;
}
Edge->GetStartVertex()->GetLinkActiveEntity()->ResetMarker1();
Edge->GetEndVertex()->GetLinkActiveEntity()->ResetMarker1();
}
for (const FTopologicalEdge* Edge : InEdges)
{
if (!Edge || Edge->IsDeleted())
{
continue;
}
if (!Edge->GetStartVertex()->GetLinkActiveEntity()->HasMarker1())
{
Edge->GetStartVertex()->GetLinkActiveEntity()->SetMarker1();
OutVertices.Add(&*Edge->GetStartVertex()->GetLinkActiveEntity());
}
if (!Edge->GetEndVertex()->GetLinkActiveEntity()->HasMarker1())
{
Edge->GetEndVertex()->GetLinkActiveEntity()->SetMarker1();
OutVertices.Add(&*Edge->GetEndVertex()->GetLinkActiveEntity());
}
}
for (FTopologicalVertex* Vertex : OutVertices)
{
Vertex->ResetMarker1();
}
}
} // namespace TopomakerTools
FTopomaker::FTopomaker(FSession& InSession, const FTopomakerOptions& InOptions)
: Session(InSession)
{
SetTolerance(InOptions);
int32 ShellCount = 0;
for (const TSharedPtr<FBody>& Body : Session.GetModel().GetBodies())
{
ShellCount += Body->GetShells().Num();
}
Shells.Reserve(ShellCount);
for (const TSharedPtr<FBody>& Body : Session.GetModel().GetBodies())
{
Body->CompleteMetaData();
for (const TSharedPtr<FShell>& Shell : Body->GetShells())
{
Shell->PropagateBodyOrientation();
Shells.Add(Shell.Get());
}
}
InitFaces();
}
FTopomaker::FTopomaker(FSession& InSession, const TArray<TSharedPtr<FShell>>& InShells, const FTopomakerOptions& InOptions)
: Session(InSession)
{
SetTolerance(InOptions);
Shells.Reserve(InShells.Num());
for (const TSharedPtr<FShell>& Shell : InShells)
{
Shell->PropagateBodyOrientation();
Shells.Add(Shell.Get());
}
InitFaces();
}
FTopomaker::FTopomaker(FSession& InSession, const TArray<TSharedPtr<FTopologicalFace>>& InFaces, const FTopomakerOptions& InOptions)
: Session(InSession)
{
SetTolerance(InOptions);
Faces.Reserve(InFaces.Num());
for (const TSharedPtr<FTopologicalFace>& Face : InFaces)
{
Faces.Add(Face);
}
}
void FTopomaker::InitFaces()
{
int32 FaceCount = 0;
for (FShell* Shell : Shells)
{
FaceCount += Shell->FaceCount();
}
Faces.Reserve(FaceCount);
for (FShell* Shell : Shells)
{
for (const FOrientedFace& Face : Shell->GetFaces())
{
Faces.Add(Face.Entity);
}
}
for (const TSharedPtr<FTopologicalFace>& Face : Faces)
{
Face->ResetMarkers();
}
}
void FTopomaker::EmptyShells()
{
for (FShell* Shell : Shells)
{
Shell->RemoveFaces();
}
}
void FTopomaker::Sew()
{
FTimePoint StartJoinTime = FChrono::Now();
TArray<FTopologicalVertex*> BorderVertices;
GetBorderVertices(BorderVertices);
TopomakerTools::MergeCoincidentVertices(BorderVertices, SewTolerance);
CheckSelfConnectedEdge(EdgeLengthTolerance, BorderVertices);
// basic case: merge pair of edges connected together at both extremities i.e. pair of edges with same extremity active vertices.
// #cadkernel_check: Commenting the line below prevent the creation of the mesh in UE-228246. Why?
TopomakerTools::MergeCoincidentEdges(BorderVertices, EdgeLengthTolerance);
// advance case: two partially coincident edges connected at one extremity i.e. coincident along the shortest edge
BorderVertices.Reset();
GetBorderVertices(BorderVertices);
TopomakerTools::StitchParallelEdges(BorderVertices, SewTolerance, EdgeLengthTolerance, /*bProhibitSewingEdgesOfSameFace*/ false);
const bool bForceJoining = SewOption::IsForceJoining(SewOptions);
if (bForceJoining)
{
BorderVertices.Empty(BorderVertices.Num());
GetBorderVertices(BorderVertices);
TopomakerTools::MergeCoincidentVertices(BorderVertices, SewToleranceToForceJoin);
CheckSelfConnectedEdge(EdgeLengthTolerance, BorderVertices);
// re process with new edges from MergeUnconnectedSuccessiveEdges and new merged vertices (if bForceJoining)
BorderVertices.Reset();
GetBorderVertices(BorderVertices);
TopomakerTools::MergeCoincidentEdges(BorderVertices, LargeEdgeLengthTolerance);
// advance case: two partially coincident edges connected at one extremity i.e. coincident along the shortest edge
BorderVertices.Reset();
GetBorderVertices(BorderVertices);
TopomakerTools::StitchParallelEdges(BorderVertices, SewToleranceToForceJoin, LargeEdgeLengthTolerance, /*bProhibitSewingEdgesOfSameFace*/ true);
}
if (SewOption::IsRemoveDuplicatedFaces(SewOptions))
{
RemoveDuplicatedFaces();
}
if (SewOption::IsRemoveThinFaces(SewOptions))
{
RemoveThinFaces();
// advance case: two partially coincident edges connected at one extremity i.e. coincident along the shortest edge
BorderVertices.Reset();
GetBorderVertices(BorderVertices);
TopomakerTools::MergeCoincidentEdges(BorderVertices, bForceJoining ? LargeEdgeLengthTolerance : EdgeLengthTolerance);
TopomakerTools::StitchParallelEdges(BorderVertices, bForceJoining ? SewToleranceToForceJoin : SewTolerance, bForceJoining ? LargeEdgeLengthTolerance : EdgeLengthTolerance, /*bProhibitSewingEdgesOfSameFace*/ true);
}
ResetMarkersOfFaces();
#ifdef CADKERNEL_DEV
Report.SewDuration = FChrono::Elapse(StartJoinTime);
FChrono::PrintClockElapse(EVerboseLevel::Log, TEXT(""), TEXT("Sew"), Report.SewDuration);
#endif
}
void FTopomaker::GetVertices(TArray<FTopologicalVertex*>& Vertices)
{
Vertices.Empty(10 * Faces.Num());
for (TSharedPtr<FTopologicalFace> Face : Faces)
{
if (Face->IsDeletedOrDegenerated())
{
continue;
}
for (TSharedPtr<FTopologicalLoop> Loop : Face->GetLoops())
{
for (const FOrientedEdge& OrientedEdge : Loop->GetEdges())
{
const TSharedPtr<FTopologicalEdge>& Edge = OrientedEdge.Entity;
if (!Edge->GetStartVertex()->GetLinkActiveEntity()->HasMarker1())
{
Edge->GetStartVertex()->GetLinkActiveEntity()->SetMarker1();
Vertices.Add(&*Edge->GetStartVertex()->GetLinkActiveEntity());
}
if (!Edge->GetEndVertex()->GetLinkActiveEntity()->HasMarker1())
{
Edge->GetEndVertex()->GetLinkActiveEntity()->SetMarker1();
Vertices.Add(&*Edge->GetEndVertex()->GetLinkActiveEntity());
}
}
}
}
for (FTopologicalVertex* Vertex : Vertices)
{
Vertex->ResetMarker1();
}
}
void FTopomaker::GetBorderVertices(TArray<FTopologicalVertex*>& BorderVertices)
{
TArray<FTopologicalVertex*> Vertices;
GetVertices(Vertices);
BorderVertices.Empty(Vertices.Num());
for (FTopologicalVertex* Vertex : Vertices)
{
if (Vertex->IsBorderVertex())
{
BorderVertices.Add(Vertex);
}
}
}
void FTopomaker::MergeUnconnectedSuccessiveEdges()
{
FTimePoint StartTime = FChrono::Now();
for (TSharedPtr<FTopologicalFace> FacePtr : Faces)
{
if (!FacePtr.IsValid())
{
continue;
}
FTopologicalFace* Face = FacePtr.Get();
TArray<TArray<FOrientedEdge>> ArrayOfCandidates;
ArrayOfCandidates.Reserve(10);
// For each loop, find unconnected successive edges...
for (TSharedPtr<FTopologicalLoop> Loop : Face->GetLoops())
{
TArray<FOrientedEdge>& Edges = Loop->GetEdges();
int32 EdgeCount = Edges.Num();
// Find the starting edge i.e. the next edge of the first edge that its ending vertex is connecting to 3 or more edges
// The algorithm start to the last edge of the loop, if it verifies the criteria then the first edge is the edges[0]
int32 EndIndex = EdgeCount;
{
TSharedPtr<FTopologicalVertex> EndVertex;
do
{
EndIndex--;
EndVertex = Edges[EndIndex].Direction == EOrientation::Front ? Edges[EndIndex].Entity->GetEndVertex() : Edges[EndIndex].Entity->GetStartVertex();
} while (EndVertex->ConnectedEdgeCount() == 2 && EndIndex > 0);
}
EndIndex++;
// First step
// For the loop, find all arrays of successive unconnected edges
TArray<FOrientedEdge>* Candidates = &ArrayOfCandidates.Emplace_GetRef();
Candidates->Reserve(10);
bool bCanStop = false;
for (int32 Index = EndIndex; bCanStop == (Index != EndIndex); ++Index)
{
if (Index == EdgeCount)
{
Index = 0;
}
bCanStop = true;
FOrientedEdge& Edge = Edges[Index];
if (Edge.Entity->GetTwinEntityCount() == 1)
{
TSharedPtr<FTopologicalVertex> EndVertex = Edge.Direction == EOrientation::Front ? Edge.Entity->GetEndVertex() : Edge.Entity->GetStartVertex();
TArray<FTopologicalEdge*> ConnectedEdges;
EndVertex->GetConnectedEdges(ConnectedEdges);
bool bEdgeIsNotTheLast = false;
if (ConnectedEdges.Num() == 2)
{
// check if the edges are tangents
FVector StartTangentEdge = ConnectedEdges[0]->GetTangentAt(*EndVertex);
FVector StartTangentOtherEdge = ConnectedEdges[1]->GetTangentAt(*EndVertex);
double CosAngle = FVectorUtil::ComputeCosinus(StartTangentEdge, StartTangentOtherEdge);
if (CosAngle < -UE_DOUBLE_HALF_SQRT_3) // Cos(30 deg)
{
bEdgeIsNotTheLast = true;
}
}
if (bEdgeIsNotTheLast || Candidates->Num() > 0)
{
Candidates->Add(Edges[Index]);
}
if (!bEdgeIsNotTheLast && Candidates->Num() > 0)
{
Candidates = &ArrayOfCandidates.Emplace_GetRef();
Candidates->Reserve(10);
}
}
}
}
// Second step,
// Each array of edges are merged to generated a single edge, the loop is updated
for (TArray<FOrientedEdge>& Candidates : ArrayOfCandidates)
{
if (Candidates.Num())
{
TSharedRef<FTopologicalVertex> StartVertex = Candidates[0].Direction == EOrientation::Front ? Candidates[0].Entity->GetStartVertex() : Candidates[0].Entity->GetEndVertex();
TSharedRef<FTopologicalVertex> EndVertex = Candidates.Last().Direction == EOrientation::Front ? Candidates.Last().Entity->GetEndVertex() : Candidates.Last().Entity->GetStartVertex();
FTopologicalEdge::CreateEdgeByMergingEdges(LargeEdgeLengthTolerance, Candidates, StartVertex, EndVertex);
}
}
}
FDuration StepDuration = FChrono::Elapse(StartTime);
FChrono::PrintClockElapse(EVerboseLevel::Log, TEXT(" "), TEXT("Merge unconnected adjacent edges"), StepDuration);
}
void FTopomaker::RemoveIsolatedEdges()
{
FTimePoint StartTime = FChrono::Now();
TArray<FTopologicalEdge*> IsolatedEdges;
TArray<FTopologicalVertex*> Vertices;
GetVertices(Vertices);
for (const FTopologicalVertex* Vertex : Vertices)
{
for (const FTopologicalVertex* TwinVertex : Vertex->GetTwinEntities())
{
TArray<FTopologicalEdge*> Edges = Vertex->GetDirectConnectedEdges();
for (FTopologicalEdge* Edge : Edges)
{
if (Edge->GetLoop() == nullptr)
{
IsolatedEdges.Add(Edge);
}
}
}
}
FDuration StepDuration = FChrono::Elapse(StartTime);
FChrono::PrintClockElapse(EVerboseLevel::Log, TEXT(" "), TEXT("Remove Isolated Edges"), StepDuration);
FMessage::Printf(EVerboseLevel::Log, TEXT("\n\nIsolatedEdges count %d\n\n\n"), IsolatedEdges.Num());
}
void SplitVertexLinkByShell(FTopologicalVertex* InVertex)
{
TMap<const FShell*, TArray<FTopologicalVertex*>> ShellToVertices;
const TArray<FTopologicalVertex*>& Twins = InVertex->GetTwinEntities();
ShellToVertices.Reserve(Twins.Num());
for (FTopologicalVertex* TwinVertex : Twins)
{
const FShell* OtherShell = (const FShell*)TwinVertex->GetDirectConnectedEdges()[0]->GetFace()->GetHost();
TArray<FTopologicalVertex*>& Vertices = ShellToVertices.FindOrAdd(OtherShell);
Vertices.Add(TwinVertex);
}
#ifdef DEBUG_Split_Vertex_Link_By_Shell
{
F3DDebugSession B(*FString::Printf(TEXT("Shells")));
for (TPair<const FShell*, TArray<FTopologicalVertex*>>& Pair : ShellToVertices)
{
F3DDebugSession B(*FString::Printf(TEXT("Shell to vertex")));
{
F3DDebugSession B(*FString::Printf(TEXT("Shell")));
Display(*Pair.Key);
}
TArray<FTopologicalVertex*>& Vertices = Pair.Value;
for (const FTopologicalVertex* Vertex : Vertices)
{
{
F3DDebugSession A(*FString::Printf(TEXT("Vertex %d"), Vertex->GetId()));
Display(*Vertex);
Display(*Vertex->GetDirectConnectedEdges()[0], EVisuProperty::RedPoint);
Display(*Vertex->GetFace());
}
}
}
Wait();
}
#endif
InVertex->UnlinkTwinEntities();
for (TPair<const FShell*, TArray<FTopologicalVertex*>>& Pair : ShellToVertices)
{
TArray<FTopologicalVertex*>& Vertices = Pair.Value;
if (Vertices.Num() > 1)
{
FTopologicalVertex* FirstVertex = Vertices[0];
for (FTopologicalVertex* TwinVertex : Vertices)
{
FirstVertex->Link(*TwinVertex);
}
}
}
}
void FTopomaker::UnlinkNonManifoldVertex()
{
TArray<FTopologicalVertex*> Vertices;
GetVertices(Vertices);
TArray<FTopologicalVertex*> VerticesToSplit;
VerticesToSplit.Reserve(Vertices.Num());
for (FTopologicalVertex* Vertex : Vertices)
{
const TArray<FTopologicalVertex*>& Twins = Vertex->GetTwinEntities();
const FShell* Shell = (const FShell*) Twins[0]->GetFace()->GetHost();
for (const FTopologicalVertex* TwinVertex : Twins)
{
const FShell* OtherShell = (const FShell*)TwinVertex->GetDirectConnectedEdges()[0]->GetFace()->GetHost();
if (OtherShell != Shell)
{
TSharedPtr<FTopologicalVertex> ActiveVertex = Vertex->GetLinkActiveEntity();
if(!ActiveVertex->IsProcessed())
{
VerticesToSplit.Add(ActiveVertex.Get());
ActiveVertex->SetProcessedMarker();
}
}
}
}
for (const FTopologicalVertex* Vertex : VerticesToSplit)
{
Vertex->ResetProcessedMarker();
}
for (FTopologicalVertex* Vertex : VerticesToSplit)
{
SplitVertexLinkByShell(Vertex);
}
}
void FindBorderVertex(const TArray<FTopologicalEdge*>& Edges, TArray<FTopologicalVertex*>& OutBorderVertices)
{
OutBorderVertices.Reserve(Edges.Num());
TFunction<void(FTopologicalVertex&)> AddVertex = [&](FTopologicalVertex& Vertex)
{
if (!Vertex.GetLinkActiveEntity()->HasMarker1())
{
OutBorderVertices.Add(&*Vertex.GetLinkActiveEntity());
Vertex.GetLinkActiveEntity()->SetMarker1();
}
};
for (FTopologicalEdge* Edge : Edges)
{
if (Edge->IsBorder())
{
AddVertex(*Edge->GetStartVertex());
AddVertex(*Edge->GetEndVertex());
}
}
for (FTopologicalVertex* Vertex : OutBorderVertices)
{
Vertex->ResetMarker1();
}
}
void FTopomaker::RemoveThinFaces()
{
FTimePoint StartTime = FChrono::Now();
#ifdef DEBUG_THIN_FACE
F3DDebugSession _(TEXT("RemoveThinFaces"));
#endif
TArray<FTopologicalEdge*> BorderEdges;
TArray<FTopologicalVertex*> BorderVertices;
// Find thin faces
for (TSharedPtr<FTopologicalFace> Face : Faces)
{
if (Face->GetLoops().Num() == 0)
{
Face->SetDeletedMarker();
continue;
}
FFaceAnalyzer Analyer(*Face, ThinFaceWidth);
double GapSize = 0;
if (Analyer.IsThinFace(GapSize))
{
#ifdef DEBUG_THIN_FACE
{
F3DDebugSession _(TEXT("Thin Face"));
Display(*Face);
Wait();
}
#endif
Face->Remove(&BorderEdges);
Face->SetDeletedMarker();
TopomakerTools::GetVertices(BorderEdges, BorderVertices);
TopomakerTools::MergeCoincidentVertices(BorderVertices, GapSize * 1.2);
TopomakerTools::MergeCoincidentEdges(BorderEdges, GapSize * 1.2);
BorderEdges.Reset();
#ifdef CADKERNEL_DEV
Report.AddThinFace();
#endif
}
}
#ifdef CADKERNEL_DEV
Report.RemoveThinFacesDuration = FChrono::Elapse(StartTime);
FChrono::PrintClockElapse(EVerboseLevel::Log, TEXT(""), TEXT(".RemoveThinFaces"), Report.RemoveThinFacesDuration = FChrono::Elapse(StartTime));
#endif
}
void FTopomaker::SetSelfConnectedEdgeDegenerated(TArray<FTopologicalVertex*>& Vertices)
{
for (const FTopologicalVertex* Vertex : Vertices)
{
for (const FTopologicalVertex* TwinVertex : Vertex->GetTwinEntities())
{
for (FTopologicalEdge* Edge : TwinVertex->GetDirectConnectedEdges())
{
if (Edge->GetStartVertex()->IsLinkedTo(Edge->GetEndVertex()))
{
if (!Edge->IsDegenerated() && Edge->Length() < 2 * SewTolerance)
{
Edge->SetAsDegenerated();
}
}
}
}
}
}
void FTopomaker::CheckSelfConnectedEdge(double MaxLengthOfDegeneratedEdge, TArray<FTopologicalVertex*>& OutBorderVertices)
{
FTimePoint StartTime = FChrono::Now();
TFunction<void(FTopologicalVertex&)> AddVertexIfBorder = [&](FTopologicalVertex& Vertex)
{
TSharedRef<FTopologicalVertex> ActiveVertex = Vertex.GetLinkActiveEntity();
if (ActiveVertex->IsBorderVertex())
{
OutBorderVertices.Add(&*ActiveVertex);
}
};
FMessage::Printf(Log, TEXT(" Self connected edges\n"));
for (TSharedPtr<FTopologicalFace> Face : Faces)
{
for (TSharedPtr<FTopologicalLoop> Loop : Face->GetLoops())
{
for (const FOrientedEdge& OrientedEdge : Loop->GetEdges())
{
TSharedPtr<FTopologicalEdge> Edge = OrientedEdge.Entity;
if (Edge->GetStartVertex()->IsLinkedTo(Edge->GetEndVertex()))
{
if (!Edge->IsDegenerated() && Edge->Length() < MaxLengthOfDegeneratedEdge)
{
if (Edge->GetTwinEntityCount() > 1)
{
FMessage::Printf(Debug, TEXT("Face %d Edge %d was self connected\n"), Face->GetId(), Edge->GetId());
Edge->GetStartVertex()->UnlinkTo(*Edge->GetEndVertex());
}
else
{
Edge->SetAsDegenerated();
FMessage::Printf(Debug, TEXT("Face %d Edge %d is set as degenerated\n"), Face->GetId(), Edge->GetId());
}
}
}
}
}
}
FDuration StepDuration = FChrono::Elapse(StartTime);
FChrono::PrintClockElapse(EVerboseLevel::Log, TEXT(" "), TEXT("Unconnect Self connected edges"), StepDuration);
}
void FTopomaker::SplitIntoConnectedShells()
{
FTimePoint StartTime = FChrono::Now();
DeleteNonmanifoldLink();
// Processed1 : Surfaces added in CandidateSurfacesForMesh
int32 TopologicalFaceCount = Faces.Num();
// Is closed ?
// Is one shell ?
TArray<FFaceSubset> SubShells;
int32 ProcessFaceCount = 0;
TArray<FTopologicalFace*> Front;
TFunction<void(const FTopologicalFace&, FFaceSubset&)> GetNeighboringFaces = [&](const FTopologicalFace& Face, FFaceSubset& Shell)
{
for (const TSharedPtr<FTopologicalLoop>& Loop : Face.GetLoops())
{
for (const FOrientedEdge& OrientedEdge : Loop->GetEdges())
{
const TSharedPtr<FTopologicalEdge>& Edge = OrientedEdge.Entity;
if (Edge->GetTwinEntityCount() == 1)
{
if (!Edge->IsDegenerated())
{
Shell.BorderEdgeCount++;
}
continue;
}
if (Edge->GetTwinEntityCount() > 2)
{
Shell.NonManifoldEdgeCount++;
}
for (FTopologicalEdge* NextEdge : Edge->GetTwinEntities())
{
FTopologicalFace* NextFace = NextEdge->GetFace();
if ((NextFace == nullptr) || NextFace->IsNotToOrAlreadyProcess())
{
continue;
}
NextFace->SetProcessedMarker();
Front.Add(NextFace);
}
}
}
};
TFunction<void(FFaceSubset&)> PropagateFront = [&](FFaceSubset& Shell)
{
while (Front.Num())
{
FTopologicalFace* Face = Front.Pop();
if (Face == nullptr)
{
continue;
}
Shell.Faces.Add(Face);
GetNeighboringFaces(*Face, Shell);
}
};
SetToProcessMarkerOfFaces();
for (const TSharedPtr<FTopologicalFace>& Face : Faces)
{
if (Face->IsProcessedDeletedOrDegenerated())
{
continue;
}
FFaceSubset& Shell = SubShells.Emplace_GetRef();
Shell.Faces.Reserve(TopologicalFaceCount - ProcessFaceCount);
Front.Empty(TopologicalFaceCount);
Face->SetProcessedMarker();
Front.Add(Face.Get());
PropagateFront(Shell);
ProcessFaceCount += Shell.Faces.Num();
if (ProcessFaceCount == TopologicalFaceCount)
{
break;
}
}
ResetMarkersOfFaces();
// for each FaceSubset, find the main shell
for (FFaceSubset& FaceSubset : SubShells)
{
TMap<FTopologicalShapeEntity*, int32> BodyToFaceCount;
TMap<FTopologicalShapeEntity*, int32> ShellToFaceCount;
TMap<uint32, int32> ColorToFaceCount;
TMap<FString, int32> NameToFaceCount;
for (FTopologicalFace* Face : FaceSubset.Faces)
{
FTopologicalShapeEntity* Shell = Face->GetHost();
FTopologicalShapeEntity* Body = Shell->GetHost();
ShellToFaceCount.FindOrAdd(Shell)++;
BodyToFaceCount.FindOrAdd(Body)++;
ColorToFaceCount.FindOrAdd(Face->GetColorId())++;
NameToFaceCount.FindOrAdd(Face->GetName())++;
}
FaceSubset.SetMainShell(ShellToFaceCount);
FaceSubset.SetMainBody(BodyToFaceCount);
FaceSubset.SetMainName(NameToFaceCount);
FaceSubset.SetMainColor(ColorToFaceCount);
}
EmptyShells();
// for each FaceSubset, process the Shell
for (FFaceSubset FaceSubset : SubShells)
{
if (FaceSubset.MainShell != nullptr)
{
FShell* Shell = (FShell*)FaceSubset.MainShell;
#ifdef CADKERNEL_DEBUG
ensure(Shell->GetFaces().Num() == 0);
#endif
Shell->Add(FaceSubset.Faces);
}
else
{
FBody* Body = (FBody*)FaceSubset.MainBody;
if (Body == nullptr)
{
TSharedRef<FBody> SharedBody = FEntity::MakeShared<FBody>();
Session.GetModel().Add(SharedBody);
Body = &SharedBody.Get();
Session.SpawnEntityIdent(*Body);
Body->SetName(FaceSubset.MainName);
Body->SetColorId(FaceSubset.MainColor);
Body->SetHostId(Body->GetId());
}
TSharedRef<FShell> Shell = FEntity::MakeShared<FShell>();
Shells.Add(&*Shell);
Body->AddShell(Shell);
Session.SpawnEntityIdent(*Shell);
Shell->Add(FaceSubset.Faces);
Shell->SetName(FaceSubset.MainName);
Shell->SetColorId(FaceSubset.MainColor);
Shell->SetHostId(Shell->GetId());
}
}
UnlinkFromOther();
UnlinkNonManifoldVertex();
RemoveEmptyShells();
Session.GetModel().RemoveEmptyBodies();
FDuration StepDuration = FChrono::Elapse(StartTime);
FChrono::PrintClockElapse(EVerboseLevel::Log, TEXT(""), TEXT("Split"), StepDuration);
}
void FTopomaker::RemoveEmptyShells()
{
#ifdef CADKERNEL_DEV
{
FModel& Model = Session.GetModel();
for (const TSharedPtr<FBody>& Body : Model.GetBodies())
{
ensureCADKernel(!Body->HasMarker1());
}
}
#endif
TArray<FBody*> Bodies;
Bodies.Reserve(Shells.Num());
TArray<FShell*> NewShells;
for (FShell* Shell : Shells)
{
if (Shell->FaceCount() == 0)
{
FBody* Body = (FBody*)Shell->GetHost();
if (Body != nullptr && !Body->HasMarker1())
{
Body->SetMarker1();
Bodies.Add(Body);
}
Shell->SetDeletedMarker();
}
else
{
NewShells.Add(Shell);
}
}
Swap(Shells, NewShells);
for (FBody* Body : Bodies)
{
Body->RemoveEmptyShell();
Body->ResetMarkers();
}
}
void FTopomaker::OrientShells()
{
FTimePoint StartTime = FChrono::Now();
for (FShell* Shell : Shells)
{
int32 FaceSwapCount = Shell->Orient();
#ifdef CADKERNEL_DEV
Report.AddSwappedFaceCount(FaceSwapCount);
#endif
}
#ifdef CADKERNEL_DEV
Report.OrientationDuration = FChrono::Elapse(StartTime);
FChrono::PrintClockElapse(EVerboseLevel::Log, TEXT(""), TEXT("Orient"), Report.OrientationDuration);
#endif
}
void FTopomaker::RemoveDuplicatedFaces()
{
FTimePoint StartTime = FChrono::Now();
#ifdef DEBUG_REMOVE_DUPLICATED_FACES
F3DDebugSession _(TEXT("DOUBLE FACE"));
#endif
TArray<FTopologicalFace*> NonManifoldFaces;
NonManifoldFaces.Reserve(Faces.Num() / 10);
for (TSharedPtr<FTopologicalFace>& FacePtr : Faces)
{
FTopologicalFace& Face = *FacePtr;
if (Face.IsDeleted())
{
continue;
}
if (Face.IsANonManifoldFace())
{
if (Face.IsAFullyNonManifoldFace())
{
if (Face.IsADuplicatedFace())
{
#ifdef DEBUG_REMOVE_DUPLICATED_FACES
F3DDebugSession A(*FString::Printf(TEXT("Duplicated 1 %d"), Face.GetId()));
Display(Face);
#endif
Face.Delete();
#ifdef CADKERNEL_DEV
Report.AddDuplicatedFace();
#endif
}
}
else
{
NonManifoldFaces.Add(&Face);
}
}
}
// Step 2: Process of Face with NonManifold and without border edges
for (FTopologicalFace* Face : NonManifoldFaces)
{
if (Face->IsDeleted() || !Face->IsANonManifoldFace())
{
continue;
}
if (!Face->IsABorderFace())
{
if (Face->IsADuplicatedFace())
{
#ifdef DEBUG_REMOVE_DUPLICATED_FACES
F3DDebugSession A(*FString::Printf(TEXT("Duplicated 2 %d"), Face->GetId()));
Display(*Face);
#endif
Face->Delete();
#ifdef CADKERNEL_DEV
Report.AddDuplicatedFace();
#endif
}
}
}
// Step 3: Process of the remaining non manifold Face
for (FTopologicalFace* Face : NonManifoldFaces)
{
if (Face->IsDeleted() || !Face->IsANonManifoldFace())
{
continue;
}
if (Face->IsADuplicatedFace())
{
#ifdef DEBUG_REMOVE_DUPLICATED_FACES
F3DDebugSession A(*FString::Printf(TEXT("Border %d"), Face->GetId()));
Display(*Face);
#endif
Face->Delete();
#ifdef CADKERNEL_DEV
Report.AddNearlyDuplicatedFace();
#endif
}
}
#ifdef DEBUG_REMOVE_DUPLICATED_FACES
// Step 4: display the remaining Face
for (FTopologicalFace* Face : NonManifoldFaces)
{
if (Face->IsDeleted() || !Face->IsANonManifoldFace())
{
continue;
}
F3DDebugSession A(*FString::Printf(TEXT("Remaining %d"), Face->GetId()));
Display(*Face);
}
#endif
#ifdef CADKERNEL_DEV
Report.OrientationDuration = FChrono::Elapse(StartTime);
FChrono::PrintClockElapse(EVerboseLevel::Log, TEXT(""), TEXT("RemoveDuplicatedFaces"), Report.RemoveDuplicatedFacesDuration);
#endif
}
void FTopomaker::SetToProcessMarkerOfFaces()
{
for (const TSharedPtr<FTopologicalFace>& Face : Faces)
{
if (!Face->IsDeletedOrDegenerated())
{
Face->SetToProcessMarker();
}
}
}
void FTopomaker::ResetMarkersOfFaces()
{
for (const TSharedPtr<FTopologicalFace>& Face : Faces)
{
Face->ResetMarkers();
}
}
void FTopomaker::DeleteNonmanifoldLink()
{
for (const TSharedPtr<FTopologicalFace>& Face : Faces)
{
Face->DeleteNonmanifoldLink();
}
}
void FTopomaker::UnlinkFromOther()
{
TFunction<void(TArray<FTopologicalVertex*>&)> MergeCoincidents = [&Tolerance = Tolerance](TArray<FTopologicalVertex*>& Vertices)
{
TArray<FTopologicalVertex*> ActiveVerticesToLink;
ActiveVerticesToLink.Reserve(Vertices.Num());
for (FTopologicalVertex* Vertex : Vertices)
{
if (!Vertex->IsProcessed())
{
ActiveVerticesToLink.Add(&Vertex->GetLinkActiveEntity().Get());
Vertex->SetProcessedMarker();
}
}
for (FTopologicalVertex* Vertex : Vertices)
{
Vertex->ResetProcessedMarker();
}
TopomakerTools::MergeCoincidentVertices(ActiveVerticesToLink, Tolerance);
};
TArray<FTopologicalVertex*> VerticesToLink;
for (FShell* Shell : Shells)
{
VerticesToLink.Reset();
Shell->UnlinkFromOther(VerticesToLink);
MergeCoincidents(VerticesToLink);
}
if(Shells.IsEmpty())
{
VerticesToLink.Reset();
TArray<FTopologicalFace*> FacePtrs;
for (TSharedPtr<FTopologicalFace> Face : Faces)
{
if (!Face->IsDeleted())
{
FacePtrs.Add(Face.Get());
}
}
ShellTools::UnlinkFromOther(FacePtrs, VerticesToLink);
MergeCoincidents(VerticesToLink);
}
}
}