/// <summary> /// Find the Delaunay triangulation of a polygon that has a certain "nice" shape. /// This includes the polygons that result from deletion of a vertex or insertion /// of a segment. /// </summary> /// <param name="firstedge">The primary edge of the first triangle.</param> /// <param name="lastedge">The primary edge of the last triangle.</param> /// <param name="edgecount">The number of sides of the polygon, including its /// base.</param> /// <param name="doflip">A flag, wether to perform the last flip.</param> /// <param name="triflaws">A flag that determines whether the new triangles should /// be tested for quality, and enqueued if they are bad.</param> /// <remarks> // This is a conceptually difficult routine. The starting assumption is // that we have a polygon with n sides. n - 1 of these sides are currently // represented as edges in the mesh. One side, called the "base", need not // be. // // Inside the polygon is a structure I call a "fan", consisting of n - 1 // triangles that share a common origin. For each of these triangles, the // edge opposite the origin is one of the sides of the polygon. The // primary edge of each triangle is the edge directed from the origin to // the destination; note that this is not the same edge that is a side of // the polygon. 'firstedge' is the primary edge of the first triangle. // From there, the triangles follow in counterclockwise order about the // polygon, until 'lastedge', the primary edge of the last triangle. // 'firstedge' and 'lastedge' are probably connected to other triangles // beyond the extremes of the fan, but their identity is not important, as // long as the fan remains connected to them. // // Imagine the polygon oriented so that its base is at the bottom. This // puts 'firstedge' on the far right, and 'lastedge' on the far left. // The right vertex of the base is the destination of 'firstedge', and the // left vertex of the base is the apex of 'lastedge'. // // The challenge now is to find the right sequence of edge flips to // transform the fan into a Delaunay triangulation of the polygon. Each // edge flip effectively removes one triangle from the fan, committing it // to the polygon. The resulting polygon has one fewer edge. If 'doflip' // is set, the final flip will be performed, resulting in a fan of one // (useless?) triangle. If 'doflip' is not set, the final flip is not // performed, resulting in a fan of two triangles, and an unfinished // triangular polygon that is not yet filled out with a single triangle. // On completion of the routine, 'lastedge' is the last remaining triangle, // or the leftmost of the last two. // // Although the flips are performed in the order described above, the // decisions about what flips to perform are made in precisely the reverse // order. The recursive triangulatepolygon() procedure makes a decision, // uses up to two recursive calls to triangulate the "subproblems" // (polygons with fewer edges), and then performs an edge flip. // // The "decision" it makes is which vertex of the polygon should be // connected to the base. This decision is made by testing every possible // vertex. Once the best vertex is found, the two edges that connect this // vertex to the base become the bases for two smaller polygons. These // are triangulated recursively. Unfortunately, this approach can take // O(n^2) time not only in the worst case, but in many common cases. It's // rarely a big deal for vertex deletion, where n is rarely larger than // ten, but it could be a big deal for segment insertion, especially if // there's a lot of long segments that each cut many triangles. I ought to // code a faster algorithm some day. /// </remarks> private void TriangulatePolygon(Otri firstedge, Otri lastedge, int edgecount, bool doflip, bool triflaws) { Otri testtri = default(Otri); Otri besttri = default(Otri); Otri tempedge = default(Otri); Vertex leftbasevertex, rightbasevertex; Vertex testvertex; Vertex bestvertex; int bestnumber = 1; // Identify the base vertices. leftbasevertex = lastedge.Apex(); rightbasevertex = firstedge.Dest(); // Find the best vertex to connect the base to. firstedge.Onext(ref besttri); bestvertex = besttri.Dest(); besttri.Copy(ref testtri); for (int i = 2; i <= edgecount - 2; i++) { testtri.OnextSelf(); testvertex = testtri.Dest(); // Is this a better vertex? if (Primitives.InCircle(leftbasevertex, rightbasevertex, bestvertex, testvertex) > 0.0) { testtri.Copy(ref besttri); bestvertex = testvertex; bestnumber = i; } } if (bestnumber > 1) { // Recursively triangulate the smaller polygon on the right. besttri.Oprev(ref tempedge); TriangulatePolygon(firstedge, tempedge, bestnumber + 1, true, triflaws); } if (bestnumber < edgecount - 2) { // Recursively triangulate the smaller polygon on the left. besttri.Sym(ref tempedge); TriangulatePolygon(besttri, lastedge, edgecount - bestnumber, true, triflaws); // Find 'besttri' again; it may have been lost to edge flips. tempedge.Sym(ref besttri); } if (doflip) { // Do one final edge flip. Flip(ref besttri); if (triflaws) { // Check the quality of the newly committed triangle. besttri.Sym(ref testtri); quality.TestTriangle(ref testtri); } } // Return the base triangle. besttri.Copy(ref lastedge); }
/// <summary> /// Delete a vertex from a Delaunay triangulation, ensuring that the /// triangulation remains Delaunay. /// </summary> /// <param name="deltri"></param> /// <remarks>The origin of 'deltri' is deleted. The union of the triangles /// adjacent to this vertex is a polygon, for which the Delaunay triangulation /// is found. Two triangles are removed from the mesh. /// /// Only interior vertices that do not lie on segments or boundaries /// may be deleted. /// </remarks> internal void DeleteVertex(ref Otri deltri) { Otri countingtri = default(Otri); Otri firstedge = default(Otri), lastedge = default(Otri); Otri deltriright = default(Otri); Otri lefttri = default(Otri), righttri = default(Otri); Otri leftcasing = default(Otri), rightcasing = default(Otri); Osub leftsubseg = default(Osub), rightsubseg = default(Osub); Vertex delvertex; Vertex neworg; int edgecount; delvertex = deltri.Org(); VertexDealloc(delvertex); // Count the degree of the vertex being deleted. deltri.Onext(ref countingtri); edgecount = 1; while (!deltri.Equal(countingtri)) { edgecount++; countingtri.OnextSelf(); } if (edgecount > 3) { // Triangulate the polygon defined by the union of all triangles // adjacent to the vertex being deleted. Check the quality of // the resulting triangles. deltri.Onext(ref firstedge); deltri.Oprev(ref lastedge); TriangulatePolygon(firstedge, lastedge, edgecount, false, behavior.NoBisect == 0); } // Splice out two triangles. deltri.Lprev(ref deltriright); deltri.Dnext(ref lefttri); lefttri.Sym(ref leftcasing); deltriright.Oprev(ref righttri); righttri.Sym(ref rightcasing); deltri.Bond(ref leftcasing); deltriright.Bond(ref rightcasing); lefttri.SegPivot(ref leftsubseg); if (leftsubseg.seg != Mesh.dummysub) { deltri.SegBond(ref leftsubseg); } righttri.SegPivot(ref rightsubseg); if (rightsubseg.seg != Mesh.dummysub) { deltriright.SegBond(ref rightsubseg); } // Set the new origin of 'deltri' and check its quality. neworg = lefttri.Org(); deltri.SetOrg(neworg); if (behavior.NoBisect == 0) { quality.TestTriangle(ref deltri); } // Delete the two spliced-out triangles. TriangleDealloc(lefttri.triangle); TriangleDealloc(righttri.triangle); }
/// <summary> /// Find the first triangle on the path from one point to another. /// </summary> /// <param name="searchtri"></param> /// <param name="searchpoint"></param> /// <returns> /// The return value notes whether the destination or apex of the found /// triangle is collinear with the two points in question.</returns> /// <remarks> /// Finds the triangle that intersects a line segment drawn from the /// origin of 'searchtri' to the point 'searchpoint', and returns the result /// in 'searchtri'. The origin of 'searchtri' does not change, even though /// the triangle returned may differ from the one passed in. This routine /// is used to find the direction to move in to get from one point to /// another. /// </remarks> private FindDirectionResult FindDirection(ref Otri searchtri, Vertex searchpoint) { Otri checktri = default(Otri); Vertex startvertex; Vertex leftvertex, rightvertex; double leftccw, rightccw; bool leftflag, rightflag; startvertex = searchtri.Org(); rightvertex = searchtri.Dest(); leftvertex = searchtri.Apex(); // Is 'searchpoint' to the left? leftccw = Primitives.CounterClockwise(searchpoint, startvertex, leftvertex); leftflag = leftccw > 0.0; // Is 'searchpoint' to the right? rightccw = Primitives.CounterClockwise(startvertex, searchpoint, rightvertex); rightflag = rightccw > 0.0; if (leftflag && rightflag) { // 'searchtri' faces directly away from 'searchpoint'. We could go left // or right. Ask whether it's a triangle or a boundary on the left. searchtri.Onext(ref checktri); if (checktri.triangle == Mesh.dummytri) { leftflag = false; } else { rightflag = false; } } while (leftflag) { // Turn left until satisfied. searchtri.OnextSelf(); if (searchtri.triangle == Mesh.dummytri) { logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().1"); throw new Exception("Unable to find a triangle on path."); } leftvertex = searchtri.Apex(); rightccw = leftccw; leftccw = Primitives.CounterClockwise(searchpoint, startvertex, leftvertex); leftflag = leftccw > 0.0; } while (rightflag) { // Turn right until satisfied. searchtri.OprevSelf(); if (searchtri.triangle == Mesh.dummytri) { logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().2"); throw new Exception("Unable to find a triangle on path."); } rightvertex = searchtri.Dest(); leftccw = rightccw; rightccw = Primitives.CounterClockwise(startvertex, searchpoint, rightvertex); rightflag = rightccw > 0.0; } if (leftccw == 0.0) { return FindDirectionResult.Leftcollinear; } else if (rightccw == 0.0) { return FindDirectionResult.Rightcollinear; } else { return FindDirectionResult.Within; } }