// ----------------------------------------------------------------------- // // Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html // Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ // // ----------------------------------------------------------------------- namespace UnityEngine.U2D.Animation.TriangleNet .Meshing { using System; using System.Collections.Generic; using Animation.TriangleNet.Geometry; using Animation.TriangleNet.Logging; using Animation.TriangleNet.Meshing.Iterators; using Animation.TriangleNet.Topology; internal class ConstraintMesher { IPredicates predicates; Mesh mesh; Behavior behavior; TriangleLocator locator; List viri; ILog logger; public ConstraintMesher(Mesh mesh, Configuration config) { this.mesh = mesh; this.predicates = config.Predicates(); this.behavior = mesh.behavior; this.locator = mesh.locator; this.viri = new List(); logger = Log.Instance; } /// /// Insert segments into the mesh. /// /// The polygon. /// Constraint options. public void Apply(IPolygon input, ConstraintOptions options) { behavior.Poly = input.Segments.Count > 0; // Copy constraint options if (options != null) { behavior.ConformingDelaunay = options.ConformingDelaunay; behavior.Convex = options.Convex; behavior.NoBisect = options.SegmentSplitting; if (behavior.ConformingDelaunay) { behavior.Quality = true; } } //if (input.EdgeMarkers != null) //{ // behavior.UseBoundaryMarkers = true; //} behavior.useRegions = input.Regions.Count > 0; // Ensure that no vertex can be mistaken for a triangular bounding // box vertex in insertvertex(). mesh.infvertex1 = null; mesh.infvertex2 = null; mesh.infvertex3 = null; if (behavior.useSegments) { // Segments will be introduced next. mesh.checksegments = true; // Insert PSLG segments and/or convex hull segments. FormSkeleton(input); } if (behavior.Poly && (mesh.triangles.Count > 0)) { // Copy holes and regions mesh.holes.AddRange(input.Holes); mesh.regions.AddRange(input.Regions); // Carve out holes and concavities. CarveHoles(); } } /// /// Find the holes and infect them. Find the area constraints and infect /// them. Infect the convex hull. Spread the infection and kill triangles. /// Spread the area constraints. /// private void CarveHoles() { Otri searchtri = default(Otri); Vertex searchorg, searchdest; LocateResult intersect; Triangle[] regionTris = null; var dummytri = mesh.dummytri; if (!mesh.behavior.Convex) { // Mark as infected any unprotected triangles on the boundary. // This is one way by which concavities are created. InfectHull(); } if (!mesh.behavior.NoHoles) { // Infect each triangle in which a hole lies. foreach (var hole in mesh.holes) { // Ignore holes that aren't within the bounds of the mesh. if (mesh.bounds.Contains(hole)) { // Start searching from some triangle on the outer boundary. searchtri.tri = dummytri; searchtri.orient = 0; searchtri.Sym(); // Ensure that the hole is to the left of this boundary edge; // otherwise, locate() will falsely report that the hole // falls within the starting triangle. searchorg = searchtri.Org(); searchdest = searchtri.Dest(); if (predicates.CounterClockwise(searchorg, searchdest, hole) > 0.0) { // Find a triangle that contains the hole. intersect = mesh.locator.Locate(hole, ref searchtri); if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) { // Infect the triangle. This is done by marking the triangle // as infected and including the triangle in the virus pool. searchtri.Infect(); viri.Add(searchtri.tri); } } } } } // Now, we have to find all the regions BEFORE we carve the holes, because locate() won't // work when the triangulation is no longer convex. (Incidentally, this is the reason why // regional attributes and area constraints can't be used when refining a preexisting mesh, // which might not be convex; they can only be used with a freshly triangulated PSLG.) if (mesh.regions.Count > 0) { int i = 0; regionTris = new Triangle[mesh.regions.Count]; // Find the starting triangle for each region. foreach (var region in mesh.regions) { regionTris[i] = dummytri; // Ignore region points that aren't within the bounds of the mesh. if (mesh.bounds.Contains(region.point)) { // Start searching from some triangle on the outer boundary. searchtri.tri = dummytri; searchtri.orient = 0; searchtri.Sym(); // Ensure that the region point is to the left of this boundary // edge; otherwise, locate() will falsely report that the // region point falls within the starting triangle. searchorg = searchtri.Org(); searchdest = searchtri.Dest(); if (predicates.CounterClockwise(searchorg, searchdest, region.point) > 0.0) { // Find a triangle that contains the region point. intersect = mesh.locator.Locate(region.point, ref searchtri); if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) { // Record the triangle for processing after the // holes have been carved. regionTris[i] = searchtri.tri; regionTris[i].label = region.id; regionTris[i].area = region.area; } } } i++; } } if (viri.Count > 0) { // Carve the holes and concavities. Plague(); } if (regionTris != null) { var iterator = new RegionIterator(mesh); for (int i = 0; i < regionTris.Length; i++) { if (regionTris[i].id != Mesh.DUMMY) { // Make sure the triangle under consideration still exists. // It may have been eaten by the virus. if (!Otri.IsDead(regionTris[i])) { // Apply one region's attribute and/or area constraint. iterator.Process(regionTris[i]); } } } } // Free up memory (virus pool should be empty anyway). viri.Clear(); } /// /// Create the segments of a triangulation, including PSLG segments and edges /// on the convex hull. /// private void FormSkeleton(IPolygon input) { // The segment endpoints. Vertex p, q; mesh.insegments = 0; if (behavior.Poly) { // If the input vertices are collinear, there is no triangulation, // so don't try to insert segments. if (mesh.triangles.Count == 0) { return; } // If segments are to be inserted, compute a mapping // from vertices to triangles. if (input.Segments.Count > 0) { mesh.MakeVertexMap(); } // Read and insert the segments. foreach (var seg in input.Segments) { mesh.insegments++; p = seg.GetVertex(0); q = seg.GetVertex(1); if ((p.x == q.x) && (p.y == q.y)) { if (Log.Verbose) { logger.Warning("Endpoints of segment (IDs " + p.id + "/" + q.id + ") are coincident.", "Mesh.FormSkeleton()"); } } else { InsertSegment(p, q, seg.Label); } } } if (behavior.Convex || !behavior.Poly) { // Enclose the convex hull with subsegments. MarkHull(); } } #region Carving holes /// /// Virally infect all of the triangles of the convex hull that are not /// protected by subsegments. Where there are subsegments, set boundary /// markers as appropriate. /// private void InfectHull() { Otri hulltri = default(Otri); Otri nexttri = default(Otri); Otri starttri = default(Otri); Osub hullsubseg = default(Osub); Vertex horg, hdest; var dummytri = mesh.dummytri; // Find a triangle handle on the hull. hulltri.tri = dummytri; hulltri.orient = 0; hulltri.Sym(); // Remember where we started so we know when to stop. hulltri.Copy(ref starttri); // Go once counterclockwise around the convex hull. do { // Ignore triangles that are already infected. if (!hulltri.IsInfected()) { // Is the triangle protected by a subsegment? hulltri.Pivot(ref hullsubseg); if (hullsubseg.seg.hash == Mesh.DUMMY) { // The triangle is not protected; infect it. if (!hulltri.IsInfected()) { hulltri.Infect(); viri.Add(hulltri.tri); } } else { // The triangle is protected; set boundary markers if appropriate. if (hullsubseg.seg.boundary == 0) { hullsubseg.seg.boundary = 1; horg = hulltri.Org(); hdest = hulltri.Dest(); if (horg.label == 0) { horg.label = 1; } if (hdest.label == 0) { hdest.label = 1; } } } } // To find the next hull edge, go clockwise around the next vertex. hulltri.Lnext(); hulltri.Oprev(ref nexttri); while (nexttri.tri.id != Mesh.DUMMY) { nexttri.Copy(ref hulltri); hulltri.Oprev(ref nexttri); } } while (!hulltri.Equals(starttri)); } /// /// Spread the virus from all infected triangles to any neighbors not /// protected by subsegments. Delete all infected triangles. /// /// /// This is the procedure that actually creates holes and concavities. /// /// This procedure operates in two phases. The first phase identifies all /// the triangles that will die, and marks them as infected. They are /// marked to ensure that each triangle is added to the virus pool only /// once, so the procedure will terminate. /// /// The second phase actually eliminates the infected triangles. It also /// eliminates orphaned vertices. /// void Plague() { Otri testtri = default(Otri); Otri neighbor = default(Otri); Osub neighborsubseg = default(Osub); Vertex testvertex; Vertex norg, ndest; var dummysub = mesh.dummysub; var dummytri = mesh.dummytri; bool killorg; // Loop through all the infected triangles, spreading the virus to // their neighbors, then to their neighbors' neighbors. for (int i = 0; i < viri.Count; i++) { // WARNING: Don't use foreach, mesh.viri list may get modified. testtri.tri = viri[i]; // A triangle is marked as infected by messing with one of its pointers // to subsegments, setting it to an illegal value. Hence, we have to // temporarily uninfect this triangle so that we can examine its // adjacent subsegments. // TODO: Not true in the C# version (so we could skip this). testtri.Uninfect(); // Check each of the triangle's three neighbors. for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) { // Find the neighbor. testtri.Sym(ref neighbor); // Check for a subsegment between the triangle and its neighbor. testtri.Pivot(ref neighborsubseg); // Check if the neighbor is nonexistent or already infected. if ((neighbor.tri.id == Mesh.DUMMY) || neighbor.IsInfected()) { if (neighborsubseg.seg.hash != Mesh.DUMMY) { // There is a subsegment separating the triangle from its // neighbor, but both triangles are dying, so the subsegment // dies too. mesh.SubsegDealloc(neighborsubseg.seg); if (neighbor.tri.id != Mesh.DUMMY) { // Make sure the subsegment doesn't get deallocated again // later when the infected neighbor is visited. neighbor.Uninfect(); neighbor.SegDissolve(dummysub); neighbor.Infect(); } } } else { // The neighbor exists and is not infected. if (neighborsubseg.seg.hash == Mesh.DUMMY) { // There is no subsegment protecting the neighbor, so // the neighbor becomes infected. neighbor.Infect(); // Ensure that the neighbor's neighbors will be infected. viri.Add(neighbor.tri); } else { // The neighbor is protected by a subsegment. // Remove this triangle from the subsegment. neighborsubseg.TriDissolve(dummytri); // The subsegment becomes a boundary. Set markers accordingly. if (neighborsubseg.seg.boundary == 0) { neighborsubseg.seg.boundary = 1; } norg = neighbor.Org(); ndest = neighbor.Dest(); if (norg.label == 0) { norg.label = 1; } if (ndest.label == 0) { ndest.label = 1; } } } } // Remark the triangle as infected, so it doesn't get added to the // virus pool again. testtri.Infect(); } foreach (var virus in viri) { testtri.tri = virus; // Check each of the three corners of the triangle for elimination. // This is done by walking around each vertex, checking if it is // still connected to at least one live triangle. for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) { testvertex = testtri.Org(); // Check if the vertex has already been tested. if (testvertex != null) { killorg = true; // Mark the corner of the triangle as having been tested. testtri.SetOrg(null); // Walk counterclockwise about the vertex. testtri.Onext(ref neighbor); // Stop upon reaching a boundary or the starting triangle. while ((neighbor.tri.id != Mesh.DUMMY) && (!neighbor.Equals(testtri))) { if (neighbor.IsInfected()) { // Mark the corner of this triangle as having been tested. neighbor.SetOrg(null); } else { // A live triangle. The vertex survives. killorg = false; } // Walk counterclockwise about the vertex. neighbor.Onext(); } // If we reached a boundary, we must walk clockwise as well. if (neighbor.tri.id == Mesh.DUMMY) { // Walk clockwise about the vertex. testtri.Oprev(ref neighbor); // Stop upon reaching a boundary. while (neighbor.tri.id != Mesh.DUMMY) { if (neighbor.IsInfected()) { // Mark the corner of this triangle as having been tested. neighbor.SetOrg(null); } else { // A live triangle. The vertex survives. killorg = false; } // Walk clockwise about the vertex. neighbor.Oprev(); } } if (killorg) { // Deleting vertex testvertex.type = VertexType.UndeadVertex; mesh.undeads++; } } } // Record changes in the number of boundary edges, and disconnect // dead triangles from their neighbors. for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) { testtri.Sym(ref neighbor); if (neighbor.tri.id == Mesh.DUMMY) { // There is no neighboring triangle on this edge, so this edge // is a boundary edge. This triangle is being deleted, so this // boundary edge is deleted. mesh.hullsize--; } else { // Disconnect the triangle from its neighbor. neighbor.Dissolve(dummytri); // There is a neighboring triangle on this edge, so this edge // becomes a boundary edge when this triangle is deleted. mesh.hullsize++; } } // Return the dead triangle to the pool of triangles. mesh.TriangleDealloc(testtri.tri); } // Empty the virus pool. viri.Clear(); } #endregion #region Segment insertion /// /// Find the first triangle on the path from one point to another. /// /// /// /// /// The return value notes whether the destination or apex of the found /// triangle is collinear with the two points in question. /// /// 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. /// 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 = predicates.CounterClockwise(searchpoint, startvertex, leftvertex); leftflag = leftccw > 0.0; // Is 'searchpoint' to the right? rightccw = predicates.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.tri.id == Mesh.DUMMY) { leftflag = false; } else { rightflag = false; } } while (leftflag) { // Turn left until satisfied. searchtri.Onext(); if (searchtri.tri.id == Mesh.DUMMY) { 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 = predicates.CounterClockwise(searchpoint, startvertex, leftvertex); leftflag = leftccw > 0.0; } while (rightflag) { // Turn right until satisfied. searchtri.Oprev(); if (searchtri.tri.id == Mesh.DUMMY) { 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 = predicates.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; } } /// /// Find the intersection of an existing segment and a segment that is being /// inserted. Insert a vertex at the intersection, splitting an existing subsegment. /// /// /// /// /// /// The segment being inserted connects the apex of splittri to endpoint2. /// splitsubseg is the subsegment being split, and MUST adjoin splittri. /// Hence, endpoints of the subsegment being split are the origin and /// destination of splittri. /// /// On completion, splittri is a handle having the newly inserted /// intersection point as its origin, and endpoint1 as its destination. /// private void SegmentIntersection(ref Otri splittri, ref Osub splitsubseg, Vertex endpoint2) { Osub opposubseg = default(Osub); Vertex endpoint1; Vertex torg, tdest; Vertex leftvertex, rightvertex; Vertex newvertex; InsertVertexResult success; var dummysub = mesh.dummysub; double ex, ey; double tx, ty; double etx, ety; double split, denom; // Find the other three segment endpoints. endpoint1 = splittri.Apex(); torg = splittri.Org(); tdest = splittri.Dest(); // Segment intersection formulae; see the Antonio reference. tx = tdest.x - torg.x; ty = tdest.y - torg.y; ex = endpoint2.x - endpoint1.x; ey = endpoint2.y - endpoint1.y; etx = torg.x - endpoint2.x; ety = torg.y - endpoint2.y; denom = ty * ex - tx * ey; if (denom == 0.0) { logger.Error("Attempt to find intersection of parallel segments.", "Mesh.SegmentIntersection()"); throw new Exception("Attempt to find intersection of parallel segments."); } split = (ey * etx - ex * ety) / denom; // Create the new vertex. newvertex = new Vertex( torg.x + split * (tdest.x - torg.x), torg.y + split * (tdest.y - torg.y), splitsubseg.seg.boundary #if USE_ATTRIBS , mesh.nextras #endif ); newvertex.hash = mesh.hash_vtx++; newvertex.id = newvertex.hash; #if USE_ATTRIBS // Interpolate its attributes. for (int i = 0; i < mesh.nextras; i++) { newvertex.attributes[i] = torg.attributes[i] + split * (tdest.attributes[i] - torg.attributes[i]); } #endif #if USE_Z newvertex.z = torg.z + split * (tdest.z - torg.z); #endif mesh.vertices.Add(newvertex.hash, newvertex); // Insert the intersection vertex. This should always succeed. success = mesh.InsertVertex(newvertex, ref splittri, ref splitsubseg, false, false); if (success != InsertVertexResult.Successful) { logger.Error("Failure to split a segment.", "Mesh.SegmentIntersection()"); throw new Exception("Failure to split a segment."); } // Record a triangle whose origin is the new vertex. newvertex.tri = splittri; if (mesh.steinerleft > 0) { mesh.steinerleft--; } // Divide the segment into two, and correct the segment endpoints. splitsubseg.Sym(); splitsubseg.Pivot(ref opposubseg); splitsubseg.Dissolve(dummysub); opposubseg.Dissolve(dummysub); do { splitsubseg.SetSegOrg(newvertex); splitsubseg.Next(); } while (splitsubseg.seg.hash != Mesh.DUMMY); do { opposubseg.SetSegOrg(newvertex); opposubseg.Next(); } while (opposubseg.seg.hash != Mesh.DUMMY); // Inserting the vertex may have caused edge flips. We wish to rediscover // the edge connecting endpoint1 to the new intersection vertex. FindDirection(ref splittri, endpoint1); rightvertex = splittri.Dest(); leftvertex = splittri.Apex(); if ((leftvertex.x == endpoint1.x) && (leftvertex.y == endpoint1.y)) { splittri.Onext(); } else if ((rightvertex.x != endpoint1.x) || (rightvertex.y != endpoint1.y)) { logger.Error("Topological inconsistency after splitting a segment.", "Mesh.SegmentIntersection()"); throw new Exception("Topological inconsistency after splitting a segment."); } // 'splittri' should have destination endpoint1. } /// /// Scout the first triangle on the path from one endpoint to another, and check /// for completion (reaching the second endpoint), a collinear vertex, or the /// intersection of two segments. /// /// /// /// /// Returns true if the entire segment is successfully inserted, and false /// if the job must be finished by ConstrainedEdge(). /// /// If the first triangle on the path has the second endpoint as its /// destination or apex, a subsegment is inserted and the job is done. /// /// If the first triangle on the path has a destination or apex that lies on /// the segment, a subsegment is inserted connecting the first endpoint to /// the collinear vertex, and the search is continued from the collinear /// vertex. /// /// If the first triangle on the path has a subsegment opposite its origin, /// then there is a segment that intersects the segment being inserted. /// Their intersection vertex is inserted, splitting the subsegment. /// private bool ScoutSegment(ref Otri searchtri, Vertex endpoint2, int newmark) { Otri crosstri = default(Otri); Osub crosssubseg = default(Osub); Vertex leftvertex, rightvertex; FindDirectionResult collinear; collinear = FindDirection(ref searchtri, endpoint2); rightvertex = searchtri.Dest(); leftvertex = searchtri.Apex(); if (((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y)) || ((rightvertex.x == endpoint2.x) && (rightvertex.y == endpoint2.y))) { // The segment is already an edge in the mesh. if ((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y)) { searchtri.Lprev(); } // Insert a subsegment, if there isn't already one there. mesh.InsertSubseg(ref searchtri, newmark); return true; } else if (collinear == FindDirectionResult.Leftcollinear) { // We've collided with a vertex between the segment's endpoints. // Make the collinear vertex be the triangle's origin. searchtri.Lprev(); mesh.InsertSubseg(ref searchtri, newmark); // Insert the remainder of the segment. return ScoutSegment(ref searchtri, endpoint2, newmark); } else if (collinear == FindDirectionResult.Rightcollinear) { // We've collided with a vertex between the segment's endpoints. mesh.InsertSubseg(ref searchtri, newmark); // Make the collinear vertex be the triangle's origin. searchtri.Lnext(); // Insert the remainder of the segment. return ScoutSegment(ref searchtri, endpoint2, newmark); } else { searchtri.Lnext(ref crosstri); crosstri.Pivot(ref crosssubseg); // Check for a crossing segment. if (crosssubseg.seg.hash == Mesh.DUMMY) { return false; } else { // Insert a vertex at the intersection. SegmentIntersection(ref crosstri, ref crosssubseg, endpoint2); crosstri.Copy(ref searchtri); mesh.InsertSubseg(ref searchtri, newmark); // Insert the remainder of the segment. return ScoutSegment(ref searchtri, endpoint2, newmark); } } } /// /// Enforce the Delaunay condition at an edge, fanning out recursively from /// an existing vertex. Pay special attention to stacking inverted triangles. /// /// /// Indicates whether or not fixuptri is to the left of /// the segment being inserted. (Imagine that the segment is pointing up from /// endpoint1 to endpoint2.) /// /// This is a support routine for inserting segments into a constrained /// Delaunay triangulation. /// /// The origin of fixuptri is treated as if it has just been inserted, and /// the local Delaunay condition needs to be enforced. It is only enforced /// in one sector, however, that being the angular range defined by /// fixuptri. /// /// This routine also needs to make decisions regarding the "stacking" of /// triangles. (Read the description of ConstrainedEdge() below before /// reading on here, so you understand the algorithm.) If the position of /// the new vertex (the origin of fixuptri) indicates that the vertex before /// it on the polygon is a reflex vertex, then "stack" the triangle by /// doing nothing. (fixuptri is an inverted triangle, which is how stacked /// triangles are identified.) /// /// Otherwise, check whether the vertex before that was a reflex vertex. /// If so, perform an edge flip, thereby eliminating an inverted triangle /// (popping it off the stack). The edge flip may result in the creation /// of a new inverted triangle, depending on whether or not the new vertex /// is visible to the vertex three edges behind on the polygon. /// /// If neither of the two vertices behind the new vertex are reflex /// vertices, fixuptri and fartri, the triangle opposite it, are not /// inverted; hence, ensure that the edge between them is locally Delaunay. /// private void DelaunayFixup(ref Otri fixuptri, bool leftside) { Otri neartri = default(Otri); Otri fartri = default(Otri); Osub faredge = default(Osub); Vertex nearvertex, leftvertex, rightvertex, farvertex; fixuptri.Lnext(ref neartri); neartri.Sym(ref fartri); // Check if the edge opposite the origin of fixuptri can be flipped. if (fartri.tri.id == Mesh.DUMMY) { return; } neartri.Pivot(ref faredge); if (faredge.seg.hash != Mesh.DUMMY) { return; } // Find all the relevant vertices. nearvertex = neartri.Apex(); leftvertex = neartri.Org(); rightvertex = neartri.Dest(); farvertex = fartri.Apex(); // Check whether the previous polygon vertex is a reflex vertex. if (leftside) { if (predicates.CounterClockwise(nearvertex, leftvertex, farvertex) <= 0.0) { // leftvertex is a reflex vertex too. Nothing can // be done until a convex section is found. return; } } else { if (predicates.CounterClockwise(farvertex, rightvertex, nearvertex) <= 0.0) { // rightvertex is a reflex vertex too. Nothing can // be done until a convex section is found. return; } } if (predicates.CounterClockwise(rightvertex, leftvertex, farvertex) > 0.0) { // fartri is not an inverted triangle, and farvertex is not a reflex // vertex. As there are no reflex vertices, fixuptri isn't an // inverted triangle, either. Hence, test the edge between the // triangles to ensure it is locally Delaunay. if (predicates.InCircle(leftvertex, farvertex, rightvertex, nearvertex) <= 0.0) { return; } // Not locally Delaunay; go on to an edge flip. } // else fartri is inverted; remove it from the stack by flipping. mesh.Flip(ref neartri); fixuptri.Lprev(); // Restore the origin of fixuptri after the flip. // Recursively process the two triangles that result from the flip. DelaunayFixup(ref fixuptri, leftside); DelaunayFixup(ref fartri, leftside); } /// /// Force a segment into a constrained Delaunay triangulation by deleting the /// triangles it intersects, and triangulating the polygons that form on each /// side of it. /// /// /// /// /// /// Generates a single subsegment connecting 'endpoint1' to 'endpoint2'. /// The triangle 'starttri' has 'endpoint1' as its origin. 'newmark' is the /// boundary marker of the segment. /// /// To insert a segment, every triangle whose interior intersects the /// segment is deleted. The union of these deleted triangles is a polygon /// (which is not necessarily monotone, but is close enough), which is /// divided into two polygons by the new segment. This routine's task is /// to generate the Delaunay triangulation of these two polygons. /// /// You might think of this routine's behavior as a two-step process. The /// first step is to walk from endpoint1 to endpoint2, flipping each edge /// encountered. This step creates a fan of edges connected to endpoint1, /// including the desired edge to endpoint2. The second step enforces the /// Delaunay condition on each side of the segment in an incremental manner: /// proceeding along the polygon from endpoint1 to endpoint2 (this is done /// independently on each side of the segment), each vertex is "enforced" /// as if it had just been inserted, but affecting only the previous /// vertices. The result is the same as if the vertices had been inserted /// in the order they appear on the polygon, so the result is Delaunay. /// /// In truth, ConstrainedEdge() interleaves these two steps. The procedure /// walks from endpoint1 to endpoint2, and each time an edge is encountered /// and flipped, the newly exposed vertex (at the far end of the flipped /// edge) is "enforced" upon the previously flipped edges, usually affecting /// only one side of the polygon (depending upon which side of the segment /// the vertex falls on). /// /// The algorithm is complicated by the need to handle polygons that are not /// convex. Although the polygon is not necessarily monotone, it can be /// triangulated in a manner similar to the stack-based algorithms for /// monotone polygons. For each reflex vertex (local concavity) of the /// polygon, there will be an inverted triangle formed by one of the edge /// flips. (An inverted triangle is one with negative area - that is, its /// vertices are arranged in clockwise order - and is best thought of as a /// wrinkle in the fabric of the mesh.) Each inverted triangle can be /// thought of as a reflex vertex pushed on the stack, waiting to be fixed /// later. /// /// A reflex vertex is popped from the stack when a vertex is inserted that /// is visible to the reflex vertex. (However, if the vertex behind the /// reflex vertex is not visible to the reflex vertex, a new inverted /// triangle will take its place on the stack.) These details are handled /// by the DelaunayFixup() routine above. /// private void ConstrainedEdge(ref Otri starttri, Vertex endpoint2, int newmark) { Otri fixuptri = default(Otri), fixuptri2 = default(Otri); Osub crosssubseg = default(Osub); Vertex endpoint1; Vertex farvertex; double area; bool collision; bool done; endpoint1 = starttri.Org(); starttri.Lnext(ref fixuptri); mesh.Flip(ref fixuptri); // 'collision' indicates whether we have found a vertex directly // between endpoint1 and endpoint2. collision = false; done = false; do { farvertex = fixuptri.Org(); // 'farvertex' is the extreme point of the polygon we are "digging" // to get from endpoint1 to endpoint2. if ((farvertex.x == endpoint2.x) && (farvertex.y == endpoint2.y)) { fixuptri.Oprev(ref fixuptri2); // Enforce the Delaunay condition around endpoint2. DelaunayFixup(ref fixuptri, false); DelaunayFixup(ref fixuptri2, true); done = true; } else { // Check whether farvertex is to the left or right of the segment being // inserted, to decide which edge of fixuptri to dig through next. area = predicates.CounterClockwise(endpoint1, endpoint2, farvertex); if (area == 0.0) { // We've collided with a vertex between endpoint1 and endpoint2. collision = true; fixuptri.Oprev(ref fixuptri2); // Enforce the Delaunay condition around farvertex. DelaunayFixup(ref fixuptri, false); DelaunayFixup(ref fixuptri2, true); done = true; } else { if (area > 0.0) { // farvertex is to the left of the segment. fixuptri.Oprev(ref fixuptri2); // Enforce the Delaunay condition around farvertex, on the // left side of the segment only. DelaunayFixup(ref fixuptri2, true); // Flip the edge that crosses the segment. After the edge is // flipped, one of its endpoints is the fan vertex, and the // destination of fixuptri is the fan vertex. fixuptri.Lprev(); } else { // farvertex is to the right of the segment. DelaunayFixup(ref fixuptri, false); // Flip the edge that crosses the segment. After the edge is // flipped, one of its endpoints is the fan vertex, and the // destination of fixuptri is the fan vertex. fixuptri.Oprev(); } // Check for two intersecting segments. fixuptri.Pivot(ref crosssubseg); if (crosssubseg.seg.hash == Mesh.DUMMY) { mesh.Flip(ref fixuptri); // May create inverted triangle at left. } else { // We've collided with a segment between endpoint1 and endpoint2. collision = true; // Insert a vertex at the intersection. SegmentIntersection(ref fixuptri, ref crosssubseg, endpoint2); done = true; } } } } while (!done); // Insert a subsegment to make the segment permanent. mesh.InsertSubseg(ref fixuptri, newmark); // If there was a collision with an interceding vertex, install another // segment connecting that vertex with endpoint2. if (collision) { // Insert the remainder of the segment. if (!ScoutSegment(ref fixuptri, endpoint2, newmark)) { ConstrainedEdge(ref fixuptri, endpoint2, newmark); } } } /// /// Insert a PSLG segment into a triangulation. /// /// /// /// private void InsertSegment(Vertex endpoint1, Vertex endpoint2, int newmark) { Otri searchtri1 = default(Otri), searchtri2 = default(Otri); Vertex checkvertex = null; var dummytri = mesh.dummytri; // Find a triangle whose origin is the segment's first endpoint. searchtri1 = endpoint1.tri; if (searchtri1.tri != null) { checkvertex = searchtri1.Org(); } if (checkvertex != endpoint1) { // Find a boundary triangle to search from. searchtri1.tri = dummytri; searchtri1.orient = 0; searchtri1.Sym(); // Search for the segment's first endpoint by point location. if (locator.Locate(endpoint1, ref searchtri1) != LocateResult.OnVertex) { logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().1"); throw new Exception("Unable to locate PSLG vertex in triangulation."); } } // Remember this triangle to improve subsequent point location. locator.Update(ref searchtri1); // Scout the beginnings of a path from the first endpoint // toward the second. if (ScoutSegment(ref searchtri1, endpoint2, newmark)) { // The segment was easily inserted. return; } // The first endpoint may have changed if a collision with an intervening // vertex on the segment occurred. endpoint1 = searchtri1.Org(); // Find a triangle whose origin is the segment's second endpoint. checkvertex = null; searchtri2 = endpoint2.tri; if (searchtri2.tri != null) { checkvertex = searchtri2.Org(); } if (checkvertex != endpoint2) { // Find a boundary triangle to search from. searchtri2.tri = dummytri; searchtri2.orient = 0; searchtri2.Sym(); // Search for the segment's second endpoint by point location. if (locator.Locate(endpoint2, ref searchtri2) != LocateResult.OnVertex) { logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().2"); throw new Exception("Unable to locate PSLG vertex in triangulation."); } } // Remember this triangle to improve subsequent point location. locator.Update(ref searchtri2); // Scout the beginnings of a path from the second endpoint // toward the first. if (ScoutSegment(ref searchtri2, endpoint1, newmark)) { // The segment was easily inserted. return; } // The second endpoint may have changed if a collision with an intervening // vertex on the segment occurred. endpoint2 = searchtri2.Org(); // Insert the segment directly into the triangulation. ConstrainedEdge(ref searchtri1, endpoint2, newmark); } /// /// Cover the convex hull of a triangulation with subsegments. /// private void MarkHull() { Otri hulltri = default(Otri); Otri nexttri = default(Otri); Otri starttri = default(Otri); // Find a triangle handle on the hull. hulltri.tri = mesh.dummytri; hulltri.orient = 0; hulltri.Sym(); // Remember where we started so we know when to stop. hulltri.Copy(ref starttri); // Go once counterclockwise around the convex hull. do { // Create a subsegment if there isn't already one here. mesh.InsertSubseg(ref hulltri, 1); // To find the next hull edge, go clockwise around the next vertex. hulltri.Lnext(); hulltri.Oprev(ref nexttri); while (nexttri.tri.id != Mesh.DUMMY) { nexttri.Copy(ref hulltri); hulltri.Oprev(ref nexttri); } } while (!hulltri.Equals(starttri)); } #endregion } }