// Copyright Epic Games, Inc. All Rights Reserved. #include "ConstrainedDelaunay2.h" #include "CompGeom/Delaunay2.h" #include "Async/ParallelFor.h" #include "Curve/PolygonIntersectionUtils.h" using namespace UE::Geometry; //namespace //{ //#define DEBUG_FILE_DUMPING 1 //#ifndef DEBUG_FILE_DUMPING // void DumpDelaunayInputForDebugAsOBJ(const FConstrainedDelaunay2d& Delaunay, const FString& PathBase) // { // } // void DumpDelaunayTriangulationForDebug(const FConstrainedDelaunay2d& Delaunay, const FString& PathBase) // { // } //#else //#include // static int num = 0; // template // void DumpDelaunayInputForDebugAsOBJ(const TConstrainedDelaunay2& Delaunay, const FString& PathBase) // //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 < Delaunay.Vertices.Num(); VertexIdx++) // { // const TVector2& Vertex = Delaunay.Vertices[VertexIdx]; // f << "v " << Vertex.X << " " << Vertex.Y << " 0" << std::endl; // } // for (int32 VertexIdx = 0; VertexIdx < Delaunay.Vertices.Num(); VertexIdx++) // { // const TVector2& Vertex = Delaunay.Vertices[VertexIdx]; // f << "v " << Vertex.X << " " << Vertex.Y << " .5" << std::endl; // } // for (const FIndex2i& Edge : Delaunay.Edges) // { // f << "f " << Edge.A + 1 << " " << Edge.B + 1 << " " << 1 + Edge.A + Delaunay.Vertices.Num() << std::endl; // } // f.close(); // } // //void DumpTriangulationForDebug(const FDynamicGraph2d& Graph, const TArray& Triangles, const FString& PathBase) // template // void DumpDelaunayTriangulationForDebug(const TConstrainedDelaunay2& Delaunay, const FString& PathBase) // { // num++; // FString Path = PathBase + FString::FromInt(num) + ".obj"; // std::ofstream f(*Path); // for (int32 VertexIdx = 0; VertexIdx < Delaunay.Vertices.Num(); VertexIdx++) // { // const TVector2& Vertex = Delaunay.Vertices[VertexIdx]; // f << "v " << Vertex.X << " " << Vertex.Y << " 0" << std::endl; // } // for (const FIndex3i& Tri : Delaunay.Triangles) // { // f << "f " << 1 + Tri.A << " " << 1 + Tri.B << " " << 1 + Tri.C << std::endl; // } // f.close(); // } //#endif //} void AddOrderedEdge(TMap, bool>& EdgeMap, int VertA, int VertB) { bool bReversed = VertA > VertB; if (bReversed) { Swap(VertA, VertB); } EdgeMap.Add(TPair(VertA, VertB), bReversed); } /** * Compute the change in winding number from crossing an oriented edge connecting VertA to VertB * * @param EdgeMap Map of known edges & orientations * @param VertA First vertex on edge * @param VertB Second vertex on edge * @return -1 if reverse edge (B-A) found, 1 if forward edge (A-B) found, 0 otherwise */ int WindingAcross(const TMap, bool>& EdgeMap, int VertA, int VertB) { bool bReversed = VertA > VertB; if (bReversed) { Swap(VertA, VertB); } TPair EdgeKey(VertA, VertB); const bool *bFoundReversed = EdgeMap.Find(EdgeKey); if (!bFoundReversed) { return 0; } bool bSameDir = bReversed == *bFoundReversed; return bSameDir ? 1 : -1; } /** * Check if any edge in edge map connects VertA to VertB (or VertB to VertA) * * @param EdgeMap Map of known edges & orientations * @param VertA First vertex on edge * @param VertB Second vertex on edge * @return true if VertA and VertB are connected by an edge (in either direction) */ bool HasUnorderedEdge(const TMap, bool>& EdgeMap, int VertA, int VertB) { return EdgeMap.Contains(TPair(FMath::Min(VertA, VertB), FMath::Max(VertA, VertB))); } namespace ConstrainedDelaunay2Internal { template void SplitBowtiesHelper(TArray>& NeedUpdates, TArray>& Vertices, TArray& Keep, const TArray& Indices, const TArray& Adj) { int TriNum = Adj.Num(); int32 OrigNumVertices = Vertices.Num(); // track all wedge verts that are seen by walking local tris auto OtherEdgeOnTri = [&Indices](int VertID, int TriID, int EdgeIdx) { int StepNext = 1; if (Indices[TriID][EdgeIdx] == VertID) { StepNext = 2; } return (EdgeIdx + StepNext) % 3; }; // helper to find new edge idx of the edge you crossed to go from FromTriID over to ToTriID auto CrossEdge = [&Adj](int FromTriID, int ToTriID) { for (int AdjEdgeIdx = 0; AdjEdgeIdx < 3; AdjEdgeIdx++) { if (Adj[ToTriID][AdjEdgeIdx] == FromTriID) { return AdjEdgeIdx; } } checkSlow(false); return -1; }; auto GetVertSubIdx = [&Indices](int VertID, int TriID) { for (int VertSubIdx = 0; VertSubIdx < 3; VertSubIdx++) { if (Indices[TriID][VertSubIdx] == VertID) { return VertSubIdx; } } checkSlow(false); return -1; }; auto Walk = [&Adj, &OtherEdgeOnTri](int VertID, int TriID, int EdgeSubIdx) { int OtherEdgeSubIdx = OtherEdgeOnTri(VertID, TriID, EdgeSubIdx); int AdjTri = Adj[TriID][OtherEdgeSubIdx]; return AdjTri; }; TArray Seen, SeenSource; Seen.SetNumZeroed(TriNum * 3); SeenSource.SetNumZeroed(Vertices.Num()); for (int TriID = 0; TriID < TriNum; TriID++) { if (Keep[TriID] != 1) { continue; } for (int SubIdx = 0, OtherSubIdx = 2; SubIdx < 3; OtherSubIdx = SubIdx++) { int WedgeIdx = TriID * 3 + SubIdx; int VertID = Indices[TriID][SubIdx]; if (Seen[WedgeIdx]) // already been walked over & therefore covered by previous pass { continue; } // if seen source but haven't seen specific wedge, then we need to duplicate the vertex and re-link bool bSeenSource = SeenSource[VertID]; int NewVertID = -1; if (bSeenSource) { TVector2 VertexToCopy = Vertices[VertID]; NewVertID = Vertices.Add(VertexToCopy); } // process all triangles starting from the given tri ID and edge idx; return true if looped, false otherwise auto WalkAll = [&TriNum, &Indices, &Seen, &SeenSource, &bSeenSource, &NeedUpdates, &NewVertID, &Keep, &CrossEdge, &GetVertSubIdx, &Walk](int WalkVertID, int WalkTriID, int WalkSubIdx) { int StartTriID = WalkTriID; int SafetyCounter = 0; while (true) { int VertSubIdx = GetVertSubIdx(WalkVertID, WalkTriID); int WalkWedgeIdx = WalkTriID * 3 + VertSubIdx; ensure(!Seen[WalkWedgeIdx]); checkSlow(Indices[WalkTriID][VertSubIdx] == WalkVertID); Seen[WalkWedgeIdx] = true; if (bSeenSource) { NeedUpdates.Add(TPair(WalkWedgeIdx, NewVertID)); } int NextTriID = Walk(WalkVertID, WalkTriID, WalkSubIdx); if (NextTriID < 0 || Keep[NextTriID] != 1) { return false; } WalkSubIdx = CrossEdge(WalkTriID, NextTriID); WalkTriID = NextTriID; if (WalkTriID == StartTriID) { return true; } check(SafetyCounter++ < TriNum * 2); // infinite loop catcher } }; bool bLooped = WalkAll(VertID, TriID, SubIdx); if (!bLooped) { // if it didn't loop around, also walk the other direction int OtherWayTriID = Walk(VertID, TriID, OtherSubIdx); if (OtherWayTriID >= 0 && Keep[OtherWayTriID] == 1) { int OtherWayTriSubIdx = CrossEdge(TriID, OtherWayTriID); ensure(!WalkAll(VertID, OtherWayTriID, OtherWayTriSubIdx)); } } SeenSource[VertID] = true; } } } void BuildFinalTriangles(TArray& Triangles, TArray>& NeedUpdates, int& AddedVerticesStartIndex, TArray& Keep, const TArray& Indices, int OrigNumVertices, bool bOutputCCW) { int TriNum = Indices.Num(); // function to build output triangles out of an indices array // normally called directly on the const indices from the CDT, but will be called on an updated copy if bowtie splits happen auto BuildTriangles = [&Triangles, &TriNum, &Keep, &bOutputCCW](const TArray& IndicesIn) { for (int i = 0; i < TriNum; i++) { if (Keep[i] > 0) { FIndex3i& Tri = Triangles.Emplace_GetRef(IndicesIn[i].A, IndicesIn[i].B, IndicesIn[i].C); if (!bOutputCCW) { Swap(Tri.B, Tri.C); } } } }; if (NeedUpdates.Num() > 0) { TArray UpdatedIndices = Indices; for (const TPair& Update : NeedUpdates) { int TriID = (int)Update.Key / 3; int SubIdx = Update.Key % 3; UpdatedIndices[TriID][SubIdx] = Update.Value; } AddedVerticesStartIndex = OrigNumVertices; BuildTriangles(UpdatedIndices); } else { BuildTriangles(Indices); } } } template bool TConstrainedDelaunay2::AddWithIntersectionResolution(const TPolygon2& Polygon) { TGeneralPolygon2 GPolygon(Polygon); return AddWithIntersectionResolution(GPolygon); } template bool TConstrainedDelaunay2::AddWithIntersectionResolution(const TGeneralPolygon2& GPolygon) { TGeneralPolygon2 Empty; TUnionPolygon2Polygon2, RealType> Union(GPolygon, Empty); bool bResolveSuccess = Union.ComputeResult(); if (bResolveSuccess) { for (const TGeneralPolygon2& ResultGPoly : Union.Result) { Add(ResultGPoly); } } return bResolveSuccess; } template bool TConstrainedDelaunay2::Triangulate(TFunctionRef>&, const FIndex3i&)> KeepTriangle) { Triangles.Empty(); FDelaunay2 Delaunay; Delaunay.bAutomaticallyFixEdgesToDuplicateVertices = true; if (!Delaunay.Triangulate(Vertices)) { return false; } Delaunay.bKeepFastEdgeAdjacencyData = true; bool bEdgesFailed = !Delaunay.ConstrainEdges(Vertices, Edges); bool bHoleEdgesFailed = !Delaunay.ConstrainEdges(Vertices, HoleEdges); bool bBoundaryTrackingFailure = bEdgesFailed || bHoleEdgesFailed; TArray Indices, Adj; Delaunay.GetTrianglesAndAdjacency(Indices, Adj); int TriNum = Adj.Num(); TArray Keep; // values: 0->unprocessed (delete), 1->yes keep, -1->processed, delete Keep.SetNumZeroed(TriNum); ParallelFor(TriNum, [this, &KeepTriangle, &Indices, &Keep](int32 Index) { bool bKeepTri = KeepTriangle(Vertices, Indices[Index]); Keep[Index] = bKeepTri ? 1 : -1; }); TArray> NeedUpdates; // stores all wedge indices and the corresponding new vertices they require int32 OrigNumVertices = Vertices.Num(); if (bSplitBowties) { ConstrainedDelaunay2Internal::SplitBowtiesHelper(NeedUpdates, Vertices, Keep, Indices, Adj); } ConstrainedDelaunay2Internal::BuildFinalTriangles(Triangles, NeedUpdates, AddedVerticesStartIndex, Keep, Indices, OrigNumVertices, bOutputCCW); return !bBoundaryTrackingFailure; } template bool TConstrainedDelaunay2::Triangulate() { Triangles.Empty(); check(FillRule <= EFillRule::Odd || bOrientedEdges); FDelaunay2 Delaunay; Delaunay.bAutomaticallyFixEdgesToDuplicateVertices = true; if (!Delaunay.Triangulate(Vertices)) { return false; } Delaunay.bKeepFastEdgeAdjacencyData = true; Delaunay.bValidateEdges = false; // edge validation will be done manually later Delaunay.ConstrainEdges(Vertices, Edges); Delaunay.ConstrainEdges(Vertices, HoleEdges); TMap, bool> BoundaryMap, HoleMap; // tracks all the boundary edges as they are added, so we can later flood fill across them for inside/outside decisions TMap, bool>* EdgeAndHoleMaps[2] = { &BoundaryMap, &HoleMap }; bool bBoundaryTrackingFailure = false; TArray* InputEdgesAndHoles[2] = { &Edges, &HoleEdges }; for (int EdgeOrHole = 0; EdgeOrHole < 2; EdgeOrHole++) { TArray& Input = *InputEdgesAndHoles[EdgeOrHole]; TMap, bool>& InputMap = *EdgeAndHoleMaps[EdgeOrHole]; for (FIndex2i Edge : Input) { Delaunay.FixDuplicatesOnEdge(Edge); if (!Delaunay.HasEdge(Edge, false)) { // Note the failed edge; we will try to proceed anyway, just without this edge. This can happen for example if the edge is exactly on top of another edge bBoundaryTrackingFailure = true; } else { AddOrderedEdge(InputMap, Edge.A, Edge.B); } } } TArray Indices, Adj; Delaunay.GetTrianglesAndAdjacency(Indices, Adj); int TriNum = Adj.Num(); TArray Keep; // values: 0->unprocessed (delete), 1->yes keep, -1->processed, delete Keep.SetNumZeroed(TriNum); TArray> ToWalkQ; // Pair of tri index, winding number // seed the queue with all triangles that are on the boundary of the convex hull // note: need *all* not just *one* because of the strategy of refusing to cross hole edges; if using pure winding number classification would just need one boundary triangle to start for (int TriIdx = 0; TriIdx < TriNum; TriIdx++) { for (int SubIdx = 0, NextIdx = 2; SubIdx < 3; NextIdx = SubIdx++) { if (Adj[TriIdx][NextIdx] < 0) // on hull { int VertA = Indices[TriIdx][SubIdx], VertB = Indices[TriIdx][NextIdx]; if (HasUnorderedEdge(HoleMap, VertA, VertB)) { continue; // cannot cross hole edges } // note we negate the winding across for these hull triangles because we're not actually crossing the edge; we're already on the 'inside' of the hull edge int Winding = -WindingAcross(BoundaryMap, VertA, VertB); ToWalkQ.Add(TPair(TriIdx, Winding)); Keep[TriIdx] = ClassifyFromRule(Winding) ? 1 : -1; break; // don't check any more edges once in queue } } } int SelIdx = 0; // Index of item to Pop next; used to make the traversal less depth-first in shape, so a little more robust to bad data while (ToWalkQ.Num()) { SelIdx = (SelIdx + 1) % ToWalkQ.Num(); TPair TriWithWinding = ToWalkQ[SelIdx]; ToWalkQ.RemoveAtSwap(SelIdx); int TriIdx = TriWithWinding.Key; int LastWinding = TriWithWinding.Value; for (int SubIdx = 0, NextIdx = 2; SubIdx < 3; NextIdx = SubIdx++) { int VertA = Indices[TriIdx][SubIdx], VertB = Indices[TriIdx][NextIdx]; if (HasUnorderedEdge(HoleMap, VertA, VertB)) { continue; // cannot cross hole edges } int AdjTri = Adj[TriIdx][NextIdx]; if (AdjTri >= 0 && Keep[AdjTri] == 0) { int WindingChange = WindingAcross(BoundaryMap, VertA, VertB); int Winding = LastWinding + WindingChange; ToWalkQ.Add(TPair(AdjTri, Winding)); Keep[AdjTri] = ClassifyFromRule(Winding) ? 1 : -1; } } } TArray> NeedUpdates; // stores all wedge indices and the corresponding new vertices they require int32 OrigNumVertices = Vertices.Num(); if (bSplitBowties) { ConstrainedDelaunay2Internal::SplitBowtiesHelper(NeedUpdates, Vertices, Keep, Indices, Adj); } ConstrainedDelaunay2Internal::BuildFinalTriangles(Triangles, NeedUpdates, AddedVerticesStartIndex, Keep, Indices, OrigNumVertices, bOutputCCW); return !bBoundaryTrackingFailure; } template TArray GEOMETRYALGORITHMS_API UE::Geometry::ConstrainedDelaunayTriangulate(const TGeneralPolygon2& GeneralPolygon) { TConstrainedDelaunay2 Triangulation; Triangulation.FillRule = TConstrainedDelaunay2::EFillRule::Positive; Triangulation.Add(GeneralPolygon); Triangulation.Triangulate(); return Triangulation.Triangles; } template TArray GEOMETRYALGORITHMS_API UE::Geometry::ConstrainedDelaunayTriangulateWithVertices(const TGeneralPolygon2& GeneralPolygon, TArray>& OutVertices) { TConstrainedDelaunay2 Triangulation; Triangulation.FillRule = TConstrainedDelaunay2::EFillRule::Positive; Triangulation.Add(GeneralPolygon); Triangulation.Triangulate(); OutVertices = MoveTemp(Triangulation.Vertices); return Triangulation.Triangles; } namespace UE { namespace Geometry { template TArray GEOMETRYALGORITHMS_API UE::Geometry::ConstrainedDelaunayTriangulate(const TGeneralPolygon2& GeneralPolygon); template TArray GEOMETRYALGORITHMS_API UE::Geometry::ConstrainedDelaunayTriangulate(const TGeneralPolygon2& GeneralPolygon); template TArray GEOMETRYALGORITHMS_API UE::Geometry::ConstrainedDelaunayTriangulateWithVertices(const TGeneralPolygon2& GeneralPolygon, TArray>& Vertices); template TArray GEOMETRYALGORITHMS_API UE::Geometry::ConstrainedDelaunayTriangulateWithVertices(const TGeneralPolygon2& GeneralPolygon, TArray>& Vertices); template struct TConstrainedDelaunay2; template struct TConstrainedDelaunay2; } // end namespace UE::Geometry } // end namespace UE