// Copyright Epic Games, Inc. All Rights Reserved. #include "ConstrainedDelaunay2.h" #include "ThirdParty/GTEngine/Mathematics/GteConstrainedDelaunay2.h" #include "ThirdParty/GTEngine/Mathematics/GteBSNumber.h" #include "ThirdParty/GTEngine/Mathematics/GteUIntegerFP32.h" #include "Async/ParallelFor.h" #include 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 std::vector& Indices, const std::vector& Adj) { int TriNum = int(Adj.size() / 3); 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 * 3 + 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 * 3 + AdjEdgeIdx] == FromTriID) { return AdjEdgeIdx; } } ensure(false); return -1; }; auto GetVertSubIdx = [&Indices](int VertID, int TriID) { for (int VertSubIdx = 0; VertSubIdx < 3; VertSubIdx++) { if (Indices[TriID * 3 + VertSubIdx] == VertID) { return VertSubIdx; } } ensure(false); return -1; }; auto Walk = [&Adj, &OtherEdgeOnTri](int VertID, int TriID, int EdgeSubIdx) { int OtherEdgeSubIdx = OtherEdgeOnTri(VertID, TriID, EdgeSubIdx); int AdjTri = Adj[TriID * 3 + 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[WedgeIdx]; 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[WalkWedgeIdx] == 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 std::vector& Indices, int OrigNumVertices, bool bOutputCCW) { int TriNum = int(Indices.size() / 3); // 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 std::vector& IndicesIn) { for (int i = 0; i < TriNum; i++) { if (Keep[i] > 0) { FIndex3i& Tri = Triangles.Emplace_GetRef(IndicesIn[i * 3], IndicesIn[i * 3 + 1], IndicesIn[i * 3 + 2]); if (!bOutputCCW) { Swap(Tri.B, Tri.C); } } } }; if (NeedUpdates.Num() > 0) { std::vector UpdatedIndices = Indices; for (const TPair& Update : NeedUpdates) { UpdatedIndices[Update.Key] = Update.Value; } AddedVerticesStartIndex = OrigNumVertices; BuildTriangles(UpdatedIndices); } else { BuildTriangles(Indices); } } } template bool TConstrainedDelaunay2::Triangulate(TFunctionRef>&, const FIndex3i&)> KeepTriangle) { Triangles.Empty(); gte::ConstrainedDelaunay2>> Delaunay; // Value of 263 is from comment in GTEngine/Mathematics/GteDelaunay2.h std::vector> InputVertices; for (int i = 0; i < Vertices.Num(); i++) { InputVertices.push_back(gte::Vector2 {{Vertices[i].X, Vertices[i].Y}}); } if (!Delaunay(Vertices.Num(), &InputVertices[0], 0)) { return false; } const std::vector& Duplicates = Delaunay.GetDuplicates(); std::vector OutEdges; bool bBoundaryTrackingFailure = false; TArray* InputEdgesAndHoles[2] = { &Edges, &HoleEdges }; for (int EdgeOrHole = 0; EdgeOrHole < 2; EdgeOrHole++) { TArray& Input = *InputEdgesAndHoles[EdgeOrHole]; for (const FIndex2i& Edge : Input) { int A = Duplicates[Edge.A]; int B = Duplicates[Edge.B]; if (!Delaunay.Insert({{A, B}}, OutEdges)) { // Note the failed edge; we will try to proceed anyway, just without this edge. Hopefully the CDT is robust and this never happens! ensureMsgf(false, TEXT("CDT edge insertion failed")); bBoundaryTrackingFailure = true; } } } const std::vector& Indices = Delaunay.GetIndices(); const std::vector& Adj = Delaunay.GetAdjacencies(); int TriNum = int(Adj.size() / 3); TArray Keep; // values: 0->unprocessed (delete), 1->yes keep, -1->processed, delete Keep.SetNumZeroed(TriNum); ParallelFor(TriNum, [this, &KeepTriangle, &Indices, &Keep](int32 Index) { int32 TriIndStart = Index * 3; bool bKeepTri = KeepTriangle(Vertices, FIndex3i(Indices[TriIndStart], Indices[TriIndStart + 1], Indices[TriIndStart + 2])); 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); gte::ConstrainedDelaunay2>> Delaunay; // Value of 263 is from comment in GTEngine/Mathematics/GteDelaunay2.h std::vector> InputVertices; for (int i = 0; i < Vertices.Num(); i++) { InputVertices.push_back(gte::Vector2 {{Vertices[i].X, Vertices[i].Y}}); } if (!Delaunay(Vertices.Num(), &InputVertices[0], 0)) { return false; } const std::vector& Duplicates = Delaunay.GetDuplicates(); std::vector OutEdges; 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 (const FIndex2i& Edge : Input) { int A = Duplicates[Edge.A]; int B = Duplicates[Edge.B]; if (!Delaunay.Insert({{A, B}}, OutEdges)) { // Note the failed edge; we will try to proceed anyway, just without this edge. Hopefully the CDT is robust and this never happens! ensureMsgf(false, TEXT("CDT edge insertion failed")); bBoundaryTrackingFailure = true; } else { for (size_t i = 0; i + 1 < OutEdges.size(); i++) { AddOrderedEdge(InputMap, OutEdges[i], OutEdges[i + 1]); } } } } const std::vector& Indices = Delaunay.GetIndices(); const std::vector& Adj = Delaunay.GetAdjacencies(); int TriNum = int(Adj.size() / 3); 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++) { int BaseIdx = TriIdx * 3; for (int SubIdx = 0, NextIdx = 2; SubIdx < 3; NextIdx = SubIdx++) { if (Adj[BaseIdx + NextIdx] < 0) // on hull { int VertA = Indices[BaseIdx + SubIdx], VertB = Indices[BaseIdx + 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 BaseIdx = TriWithWinding.Key * 3; int LastWinding = TriWithWinding.Value; for (int SubIdx = 0, NextIdx = 2; SubIdx < 3; NextIdx = SubIdx++) { int VertA = Indices[BaseIdx + SubIdx], VertB = Indices[BaseIdx + NextIdx]; if (HasUnorderedEdge(HoleMap, VertA, VertB)) { continue; // cannot cross hole edges } int AdjTri = Adj[BaseIdx + 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