// 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& VerticesToMerge, double Tolerance) { const double SquareTolerance = FMath::Square(Tolerance); const double WeigthTolerance = 3 * Tolerance; int32 VertexNum = (int32)VerticesToMerge.Num(); TArray VerticesWeight; VerticesWeight.Reserve(VertexNum); TArray 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 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 NewTwinVertices; if (EdgeToSplit.GetTwinEntityCount() > 1) { TArray 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 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 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& 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 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& EdgesToProcess, double MinEdgeLength) { for (FTopologicalEdge* Edge : EdgesToProcess) { if (Edge->IsDegenerated() || !Edge->IsBorder()) { continue; } MergeCoincidentEdges(Edge, MinEdgeLength); } } void MergeCoincidentEdges(TArray& VerticesToProcess, double MinEdgeLength) { FTimePoint StartTime = FChrono::Now(); for (FTopologicalVertex* Vertex : VerticesToProcess) { if (Vertex->IsDeleted() || !Vertex->IsActiveEntity() || Vertex->HasMarker1()) { continue; } Vertex->SetMarker1(); TArray 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& InEdges, TArray& 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& Body : Session.GetModel().GetBodies()) { ShellCount += Body->GetShells().Num(); } Shells.Reserve(ShellCount); for (const TSharedPtr& Body : Session.GetModel().GetBodies()) { Body->CompleteMetaData(); for (const TSharedPtr& Shell : Body->GetShells()) { Shell->PropagateBodyOrientation(); Shells.Add(Shell.Get()); } } InitFaces(); } FTopomaker::FTopomaker(FSession& InSession, const TArray>& InShells, const FTopomakerOptions& InOptions) : Session(InSession) { SetTolerance(InOptions); Shells.Reserve(InShells.Num()); for (const TSharedPtr& Shell : InShells) { Shell->PropagateBodyOrientation(); Shells.Add(Shell.Get()); } InitFaces(); } FTopomaker::FTopomaker(FSession& InSession, const TArray>& InFaces, const FTopomakerOptions& InOptions) : Session(InSession) { SetTolerance(InOptions); Faces.Reserve(InFaces.Num()); for (const TSharedPtr& 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& Face : Faces) { Face->ResetMarkers(); } } void FTopomaker::EmptyShells() { for (FShell* Shell : Shells) { Shell->RemoveFaces(); } } void FTopomaker::Sew() { FTimePoint StartJoinTime = FChrono::Now(); TArray 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& Vertices) { Vertices.Empty(10 * Faces.Num()); for (TSharedPtr Face : Faces) { if (Face->IsDeletedOrDegenerated()) { continue; } for (TSharedPtr Loop : Face->GetLoops()) { for (const FOrientedEdge& OrientedEdge : Loop->GetEdges()) { const TSharedPtr& 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& BorderVertices) { TArray 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 FacePtr : Faces) { if (!FacePtr.IsValid()) { continue; } FTopologicalFace* Face = FacePtr.Get(); TArray> ArrayOfCandidates; ArrayOfCandidates.Reserve(10); // For each loop, find unconnected successive edges... for (TSharedPtr Loop : Face->GetLoops()) { TArray& 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 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* 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 EndVertex = Edge.Direction == EOrientation::Front ? Edge.Entity->GetEndVertex() : Edge.Entity->GetStartVertex(); TArray 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& Candidates : ArrayOfCandidates) { if (Candidates.Num()) { TSharedRef StartVertex = Candidates[0].Direction == EOrientation::Front ? Candidates[0].Entity->GetStartVertex() : Candidates[0].Entity->GetEndVertex(); TSharedRef 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 IsolatedEdges; TArray Vertices; GetVertices(Vertices); for (const FTopologicalVertex* Vertex : Vertices) { for (const FTopologicalVertex* TwinVertex : Vertex->GetTwinEntities()) { TArray 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> ShellToVertices; const TArray& Twins = InVertex->GetTwinEntities(); ShellToVertices.Reserve(Twins.Num()); for (FTopologicalVertex* TwinVertex : Twins) { const FShell* OtherShell = (const FShell*)TwinVertex->GetDirectConnectedEdges()[0]->GetFace()->GetHost(); TArray& Vertices = ShellToVertices.FindOrAdd(OtherShell); Vertices.Add(TwinVertex); } #ifdef DEBUG_Split_Vertex_Link_By_Shell { F3DDebugSession B(*FString::Printf(TEXT("Shells"))); for (TPair>& Pair : ShellToVertices) { F3DDebugSession B(*FString::Printf(TEXT("Shell to vertex"))); { F3DDebugSession B(*FString::Printf(TEXT("Shell"))); Display(*Pair.Key); } TArray& 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>& Pair : ShellToVertices) { TArray& Vertices = Pair.Value; if (Vertices.Num() > 1) { FTopologicalVertex* FirstVertex = Vertices[0]; for (FTopologicalVertex* TwinVertex : Vertices) { FirstVertex->Link(*TwinVertex); } } } } void FTopomaker::UnlinkNonManifoldVertex() { TArray Vertices; GetVertices(Vertices); TArray VerticesToSplit; VerticesToSplit.Reserve(Vertices.Num()); for (FTopologicalVertex* Vertex : Vertices) { const TArray& 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 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& Edges, TArray& OutBorderVertices) { OutBorderVertices.Reserve(Edges.Num()); TFunction 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 BorderEdges; TArray BorderVertices; // Find thin faces for (TSharedPtr 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& 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& OutBorderVertices) { FTimePoint StartTime = FChrono::Now(); TFunction AddVertexIfBorder = [&](FTopologicalVertex& Vertex) { TSharedRef ActiveVertex = Vertex.GetLinkActiveEntity(); if (ActiveVertex->IsBorderVertex()) { OutBorderVertices.Add(&*ActiveVertex); } }; FMessage::Printf(Log, TEXT(" Self connected edges\n")); for (TSharedPtr Face : Faces) { for (TSharedPtr Loop : Face->GetLoops()) { for (const FOrientedEdge& OrientedEdge : Loop->GetEdges()) { TSharedPtr 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 SubShells; int32 ProcessFaceCount = 0; TArray Front; TFunction GetNeighboringFaces = [&](const FTopologicalFace& Face, FFaceSubset& Shell) { for (const TSharedPtr& Loop : Face.GetLoops()) { for (const FOrientedEdge& OrientedEdge : Loop->GetEdges()) { const TSharedPtr& 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 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& 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 BodyToFaceCount; TMap ShellToFaceCount; TMap ColorToFaceCount; TMap 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 SharedBody = FEntity::MakeShared(); Session.GetModel().Add(SharedBody); Body = &SharedBody.Get(); Session.SpawnEntityIdent(*Body); Body->SetName(FaceSubset.MainName); Body->SetColorId(FaceSubset.MainColor); Body->SetHostId(Body->GetId()); } TSharedRef Shell = FEntity::MakeShared(); 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& Body : Model.GetBodies()) { ensureCADKernel(!Body->HasMarker1()); } } #endif TArray Bodies; Bodies.Reserve(Shells.Num()); TArray 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 NonManifoldFaces; NonManifoldFaces.Reserve(Faces.Num() / 10); for (TSharedPtr& 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& Face : Faces) { if (!Face->IsDeletedOrDegenerated()) { Face->SetToProcessMarker(); } } } void FTopomaker::ResetMarkersOfFaces() { for (const TSharedPtr& Face : Faces) { Face->ResetMarkers(); } } void FTopomaker::DeleteNonmanifoldLink() { for (const TSharedPtr& Face : Faces) { Face->DeleteNonmanifoldLink(); } } void FTopomaker::UnlinkFromOther() { TFunction&)> MergeCoincidents = [&Tolerance = Tolerance](TArray& Vertices) { TArray 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 VerticesToLink; for (FShell* Shell : Shells) { VerticesToLink.Reset(); Shell->UnlinkFromOther(VerticesToLink); MergeCoincidents(VerticesToLink); } if(Shells.IsEmpty()) { VerticesToLink.Reset(); TArray FacePtrs; for (TSharedPtr Face : Faces) { if (!Face->IsDeleted()) { FacePtrs.Add(Face.Get()); } } ShellTools::UnlinkFromOther(FacePtrs, VerticesToLink); MergeCoincidents(VerticesToLink); } } }