private async Task <Partition> Subdivide(int lidx, int ridx) { Debug.Assert(lidx != ridx); if (ridx - lidx == 1) //2 points { QuadEdge edge = QuadEdge.MakeEdge(this._points[lidx], this._points[lidx + 1]); return(new Partition { Left = edge, Right = edge.Inverse }); } if (ridx - lidx == 2) //3 points { QuadEdge a = QuadEdge.MakeEdge(this._points[lidx], this._points[lidx + 1]); QuadEdge b = QuadEdge.MakeEdge(this._points[lidx + 1], this._points[lidx + 2]); QuadEdge.Splice(a.Inverse, b); if (Point.IsCounterClockWise(this._points[lidx], this._points[lidx + 1], this._points[lidx + 2])) { QuadEdge c = QuadEdge.ConnectLeft(b, a); return(new Partition { Left = a, Right = b.Inverse }); } if (Point.IsCounterClockWise(this._points[lidx], this._points[lidx + 2], this._points[lidx + 1])) { QuadEdge c = QuadEdge.ConnectLeft(b, a); return(new Partition { Left = c.Inverse, Right = c }); } return(new Partition { Left = a, Right = b.Inverse }); } int midx = (lidx + ridx) / 2; Task <Partition> leftTask = Subdivide(lidx, midx); Task <Partition> rightTask = Subdivide(midx + 1, ridx); return(Merge(await leftTask, await rightTask)); }
/// <summary> /// Triangulate the set of points using a divide and conquer approach. /// </summary> private QuadEdge <T>[] Triangulate(Vec3[] pts) { QuadEdge <T> a, b, c, nextCand; // Only two points -> One edge if (pts.Length == 2) { a = QuadEdge <T> .MakeEdge(pts[0], pts[1]); return(new QuadEdge <T>[] { a, a.Sym }); } // Only tree points else if (pts.Length == 3) { a = QuadEdge <T> .MakeEdge(pts[0], pts[1]); b = QuadEdge <T> .MakeEdge(pts[1], pts[2]); QuadEdge <T> .Splice(a.Sym, b); // Closing triangle if (Geometry.Ccw(pts[0], pts[1], pts[2])) { c = QuadEdge <T> .Connect(b, a); return(new QuadEdge <T>[] { a, b.Sym }); } else if (Geometry.Ccw(pts[0], pts[2], pts[1])) { c = QuadEdge <T> .Connect(b, a); return(new QuadEdge <T>[] { c.Sym, c }); } else { // Points are collinear return(new QuadEdge <T>[] { a, b.Sym }); } } // SPLITTING // Divide them halfsize recursively int halfLength = (pts.Length + 1) / 2; QuadEdge <T>[] left = Triangulate(pts.Take(halfLength).ToArray()); QuadEdge <T>[] right = Triangulate(pts.Skip(halfLength).ToArray()); // MERGING // From left to right QuadEdge <T> ldo = left[0]; QuadEdge <T> ldi = left[1]; QuadEdge <T> rdi = right[0]; QuadEdge <T> rdo = right[1]; // Compute lower common tangent to be able to merge both triangulations bool crossEdgeNotFound = true; while (crossEdgeNotFound) { if (Geometry.LeftOf(rdi.Origin, ldi)) { ldi = ldi.Lnext; } else if (Geometry.RightOf(ldi.Origin, rdi)) { rdi = rdi.Rprev; } else { crossEdgeNotFound = false; } } // Start merging // 1) Creation of the baseEdge quad edge (See Fig.21) QuadEdge <T> baseEdge = QuadEdge <T> .Connect(rdi.Sym, ldi); if (ldi.Origin == ldo.Origin) { ldo = baseEdge.Sym; } if (rdi.Origin == rdo.Origin) { rdo = baseEdge; } // 2) Rising bubble (See Fig. 22) bool upperCommonTangentNotFound = true; while (upperCommonTangentNotFound) { // Locate the first L site (lCand.Destination) to be encountered // by the rising bubble, and delete L edges out of baseEdge.Destination // that fail the circle test. QuadEdge <T> lCand = baseEdge.Sym.Onext; if (IsValid(lCand, baseEdge)) { while (Geometry.InCircumCercle2D(lCand.Onext.Destination, baseEdge.Destination, baseEdge.Origin, lCand.Destination)) { nextCand = lCand.Onext; QuadEdge <T> .Delete(lCand); lCand = nextCand; } } // Same for the right part (Symetrically) QuadEdge <T> rCand = baseEdge.Oprev; if (IsValid(rCand, baseEdge)) { while (Geometry.InCircumCercle2D(rCand.Oprev.Destination, baseEdge.Destination, baseEdge.Origin, rCand.Destination)) { nextCand = rCand.Oprev; QuadEdge <T> .Delete(rCand); rCand = nextCand; } } // Upper common tangent is baseEdge if (!IsValid(lCand, baseEdge) && !IsValid(rCand, baseEdge)) { upperCommonTangentNotFound = false; } // Construct new cross edge between left and right // The next cross edge is to be connected to either lcand.Dest or rCand.Dest // If both are valid, then choose the appropriate one using the // Geometry.InCircumCercle2D test else if (!IsValid(lCand, baseEdge) || ( IsValid(rCand, baseEdge) && Geometry.InCircumCercle2D(rCand.Destination, lCand.Destination, lCand.Origin, rCand.Origin) ) ) { // Cross edge baseEdge added from rCand.Destination to basel.Destination baseEdge = QuadEdge <T> .Connect(rCand, baseEdge.Sym); } else { // Cross edge baseEdge added from baseEdge.Origin to lCand.Destination baseEdge = QuadEdge <T> .Connect(baseEdge.Sym, lCand.Sym); } } return(new QuadEdge <T>[] { ldo, rdo }); }
/// <summary> /// Insert a new site inside an existing delaunay triangulation. New site /// must be inside the convex hull of previoulsy added sites. /// Set <paramref name="safe"/> to true to first test if new site is correct. /// </summary> /// <param name="newPos">The position to of new site</param> /// <param name="edge">Edge used to start locate process. Can be used to speed up search.</param> /// <param name="safe">If true, check if <paramref name="newPos"/> inside the convex hull.</param> public bool Insert(Vec3 newPos, QuadEdge <T> edge = null, bool safe = false) { if (safe) { bool result = InsideConvexHull(newPos); if (!result) { // Cannot add site not already inside the convex hull return(false); } } // Start somewhere if no hint if (edge == null) { edge = _leftRightEdges[1]; } // Locate edge (must be inside the boundary) QuadEdge <T> foundE = Locate(newPos, edge, safe: false); // Site already triangulated if (Geometry.AlmostEquals(foundE.Origin, newPos) || Geometry.AlmostEquals(foundE.Destination, newPos)) { return(false); } // On an edge ? if (Geometry.AlmostColinear(foundE.Origin, foundE.Destination, newPos)) { var temp = foundE.Oprev; QuadEdge <T> .Delete(foundE); foundE = temp; } // Create new edge to connect new site to neighbors QuadEdge <T> baseE = QuadEdge <T> .MakeEdge(foundE.Origin, newPos); Vec3 first = baseE.Origin; QuadEdge <T> .Splice(baseE, foundE); // Up to 4 vertices if new site on an edge do { baseE = QuadEdge <T> .Connect(foundE, baseE.Sym); foundE = baseE.Oprev; } while (foundE.Destination != first); // Fill star shaped polygon and swap suspect edges // Adding a new point can break old condition about InCircle test foundE = baseE.Oprev; bool shouldExit = false; do { var tempE = foundE.Oprev; if (Geometry.RightOf(tempE.Destination, foundE) && Geometry.InCircumCercle2D(newPos, foundE.Origin, tempE.Destination, foundE.Destination)) { QuadEdge <T> .Swap(foundE); // tempE != foundE.Oprev after swap foundE = foundE.Oprev; } else if (foundE.Origin == first) { // No more suspect edge ... exit shouldExit = true; } else { // Get next suspect edge from top to bottom foundE = foundE.Onext.Lprev; } } while (!shouldExit); return(true); }