// Copyright Epic Games, Inc. All Rights Reserved. // Port of geometry3Sharp Arrangement2d #include "Arrangement2d.h" #include "CompGeom/Delaunay2.h" using namespace UE::Geometry; namespace { //#define DEBUG_FILE_DUMPING 1 #ifndef DEBUG_FILE_DUMPING void DumpGraphForDebug(const FDynamicGraph2d& Graph, const FString& PathBase) { } void DumpGraphForDebugAsOBJ(const FDynamicGraph2d& Graph, const FString& PathBase) { } void DumpTriangulationForDebug(const FDynamicGraph2d& Graph, const TArray& Triangles, const FString& PathBase) { } #else #include static int num = 0; void DumpGraphForDebug(const FDynamicGraph2d& Graph, const FString& PathBase) { num++; FString Path = PathBase + FString::FromInt(num) + ".txt"; std::ofstream f(*Path); for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++) { const FVector2d& Vertex = Graph.GetVertex(VertexIdx); f << "v " << Vertex.X << " " << Vertex.Y << " " << std::endl; } for (const FDynamicGraph::FEdge& Edge : Graph.Edges()) { f << "e " << Edge.A << " " << Edge.B << std::endl; } f.close(); } void DumpGraphForDebugAsOBJ(const FDynamicGraph2d& Graph, const FString& PathBase) { num++; FString Path = PathBase + FString::FromInt(num) + ".obj"; std::ofstream f(*Path); for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++) { const FVector2d& Vertex = Graph.GetVertex(VertexIdx); f << "v " << Vertex.X << " " << Vertex.Y << " 0" << std::endl; } for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++) { const FVector2d& Vertex = Graph.GetVertex(VertexIdx); f << "v " << Vertex.X << " " << Vertex.Y << " .5" << std::endl; } for (const FDynamicGraph::FEdge& Edge : Graph.Edges()) { f << "f " << Edge.A + 1 << " " << Edge.B + 1 << " " << 1 + Edge.A + Graph.MaxVertexID() << std::endl; } f.close(); } void DumpTriangulationForDebug(const FDynamicGraph2d& Graph, const TArray& Triangles, const FString& PathBase) { num++; FString Path = PathBase + FString::FromInt(num) + ".obj"; std::ofstream f(*Path); for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++) { const FVector2d& Vertex = Graph.GetVertex(VertexIdx); f << "v " << Vertex.X << " " << Vertex.Y << " 0" << std::endl; } for (const FIntVector& Tri : Triangles) { f << "f " << 1 + Tri.X << " " << 1 + Tri.Y << " " << 1 + Tri.Z << std::endl; } f.close(); } #endif } bool FArrangement2d::AttemptTriangulate(TArray& Triangles, TArray& SkippedEdges, int32 BoundaryEdgeGroupID) { // TODO: Currently this just constructs an FIndex3i array and then copies it to an FIntVector array; if we need to construct FIntVector versions faster, we could directly construct them here instead // However this would require writing FIntVector versions of the GetTriangles functions in FDelaunay2; ideally we would instead mainly call the FIndex3i variant TArray TrianglesInd3i; bool bResult = TriangulateInternal(TrianglesInd3i, true, BoundaryEdgeGroupID, false, -1, true, &SkippedEdges); Triangles.SetNumUninitialized(TrianglesInd3i.Num()); for (int32 Idx = 0; Idx < TrianglesInd3i.Num(); Idx++) { Triangles[Idx] = (FIntVector)TrianglesInd3i[Idx]; } return bResult; } bool FArrangement2d::TriangulateInternal(TArray& Triangles, bool bHasBoundaryGroupID, int32 BoundaryEdgeGroupID, bool bHasHoleGroupID, int32 HoleEdgeGroupID, bool bLegacyKeepTrianglesIfBoundaryNotFound, TArray* SkippedEdges) { Triangles.Empty(); // A flat array of the vertices, copied out of the graph TArray InputVertices; // If there are unused vertices, define a vertex index remapping so the delaunay code won't have to understand that bool bNeedsRemap = Graph.MaxVertexID() != Graph.VertexCount(); TArray InputIndices, OutputIndices; if (bNeedsRemap) { InputIndices.SetNumZeroed(Graph.MaxVertexID()); OutputIndices.SetNumZeroed(Graph.VertexCount()); int TotalOffset = 0; for (int i = 0; i < Graph.MaxVertexID(); i++) { if (Graph.IsVertex(i)) { OutputIndices[i - TotalOffset] = i; InputIndices[i] = i - TotalOffset; FVector2d Vertex = Graph.GetVertex(i); InputVertices.Add(Vertex); } else { InputIndices[i] = -1; TotalOffset++; } } } else { // no index remapping needed; just copy the vertices for (int i = 0; i < Graph.MaxVertexID(); i++) { FVector2d Vertex = Graph.GetVertex(i); InputVertices.Add(Vertex); } } FDelaunay2 Delaunay; Delaunay.bAutomaticallyFixEdgesToDuplicateVertices = false; // Arrangement will remove duplicates already if (!Delaunay.Triangulate(InputVertices)) { return false; } Delaunay.bValidateEdges = false; Delaunay.bKeepFastEdgeAdjacencyData = true; bool bInsertConstraintFailure = false; bool bBoundaryTrackingFailure = false; TArray AllEdges, BoundaryEdges, HoleEdges; for (int EdgeIdx : Graph.EdgeIndices()) { FDynamicGraph::FEdge Edge = Graph.GetEdge(EdgeIdx); if (bNeedsRemap) { Edge.A = InputIndices[Edge.A]; Edge.B = InputIndices[Edge.B]; } FIndex2i& AddedEdge = AllEdges.Emplace_GetRef(Edge.A, Edge.B); if (bHasBoundaryGroupID && Edge.Group == BoundaryEdgeGroupID) { BoundaryEdges.Add(AddedEdge); } if (bHasHoleGroupID && Edge.Group == HoleEdgeGroupID) { HoleEdges.Add(AddedEdge); } } Delaunay.ConstrainEdges(InputVertices, AllEdges); // Verify all edges after all constraints are in -- to ensure that inserted edges were also not removed by subsequent edge insertion for (int EdgeIdx : Graph.EdgeIndices()) { FDynamicGraph::FEdge Edge = Graph.GetEdge(EdgeIdx); if (bNeedsRemap) { Edge.A = InputIndices[Edge.A]; Edge.B = InputIndices[Edge.B]; } if (!Delaunay.HasEdge(FIndex2i(Edge.A, Edge.B), false)) { bInsertConstraintFailure = true; if (SkippedEdges) { SkippedEdges->Add(EdgeIdx); } if (Edge.Group == BoundaryEdgeGroupID) { bBoundaryTrackingFailure = true; } } } if (bHasBoundaryGroupID && (!bLegacyKeepTrianglesIfBoundaryNotFound || (!bBoundaryTrackingFailure && BoundaryEdges.Num() > 0))) { if (bHasHoleGroupID) { Delaunay.GetFilledTriangles(Triangles, BoundaryEdges, HoleEdges); } else { Triangles = Delaunay.GetFilledTriangles(BoundaryEdges, FDelaunay2::EFillMode::Solid); } } else { Triangles = Delaunay.GetTriangles(); } if (bNeedsRemap) { for (FIndex3i& Face : Triangles) { Face.A = OutputIndices[Face.A]; Face.B = OutputIndices[Face.B]; Face.C = OutputIndices[Face.C]; } } return !bInsertConstraintFailure; } bool FArrangement2d::AttemptTriangulate(TArray& Triangles, TArray& SkippedEdges, int32 BoundaryEdgeGroupID) { return TriangulateInternal(Triangles, true, BoundaryEdgeGroupID, false, -1, true, &SkippedEdges); } bool FArrangement2d::TriangulateWithBoundaryAndHoles(TArray& Triangles, int32 BoundaryEdgeGroupID, int32 HoleEdgeGroupID) { return TriangulateInternal(Triangles, true, BoundaryEdgeGroupID, true, HoleEdgeGroupID, false, nullptr); } bool FArrangement2d::TriangulateWithBoundary(TArray& Triangles, int32 BoundaryEdgeGroupID) { return TriangulateInternal(Triangles, true, BoundaryEdgeGroupID, false, -1, false, nullptr); } bool FArrangement2d::Triangulate(TArray& Triangles) { return TriangulateInternal(Triangles, false, -1, false, -1, false, nullptr); }