// ----------------------------------------------------------------------- // // 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 TriangleNet { using TriangleNet.Data; using System; using TriangleNet.Geometry; using System.Collections.Generic; /// /// Carves holes into the triangulation. /// class Carver { Mesh mesh; public Carver(Mesh mesh) { this.mesh = mesh; } /// /// 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; // Find a triangle handle on the hull. hulltri.triangle = Mesh.dummytri; hulltri.orient = 0; hulltri.SymSelf(); // 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.SegPivot(ref hullsubseg); if (hullsubseg.seg == Mesh.dummysub) { // The triangle is not protected; infect it. if (!hulltri.IsInfected()) { hulltri.Infect(); mesh.viri.Add(hulltri.triangle); } } 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.mark == 0) { horg.mark = 1; } if (hdest.mark == 0) { hdest.mark = 1; } } } } // To find the next hull edge, go clockwise around the next vertex. hulltri.LnextSelf(); hulltri.Oprev(ref nexttri); while (nexttri.triangle != Mesh.dummytri) { nexttri.Copy(ref hulltri); hulltri.Oprev(ref nexttri); } } while (!hulltri.Equal(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; bool killorg; // Loop through all the infected triangles, spreading the virus to // their neighbors, then to their neighbors' neighbors. for (int i = 0; i < mesh.viri.Count; i++) { // WARNING: Don't use foreach, mesh.viri list may get modified. testtri.triangle = mesh.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.SegPivot(ref neighborsubseg); // Check if the neighbor is nonexistent or already infected. if ((neighbor.triangle == Mesh.dummytri) || neighbor.IsInfected()) { if (neighborsubseg.seg != Mesh.dummysub) { // 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.triangle != Mesh.dummytri) { // Make sure the subsegment doesn't get deallocated again // later when the infected neighbor is visited. neighbor.Uninfect(); neighbor.SegDissolve(); neighbor.Infect(); } } } else { // The neighbor exists and is not infected. if (neighborsubseg.seg == Mesh.dummysub) { // There is no subsegment protecting the neighbor, so // the neighbor becomes infected. neighbor.Infect(); // Ensure that the neighbor's neighbors will be infected. mesh.viri.Add(neighbor.triangle); } else { // The neighbor is protected by a subsegment. // Remove this triangle from the subsegment. neighborsubseg.TriDissolve(); // 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.mark == 0) { norg.mark = 1; } if (ndest.mark == 0) { ndest.mark = 1; } } } } // Remark the triangle as infected, so it doesn't get added to the // virus pool again. testtri.Infect(); } foreach (var virus in mesh.viri) { testtri.triangle = 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.triangle != Mesh.dummytri) && (!neighbor.Equal(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.OnextSelf(); } // If we reached a boundary, we must walk clockwise as well. if (neighbor.triangle == Mesh.dummytri) { // Walk clockwise about the vertex. testtri.Oprev(ref neighbor); // Stop upon reaching a boundary. while (neighbor.triangle != Mesh.dummytri) { 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.OprevSelf(); } } 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.triangle == Mesh.dummytri) { // 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(); // 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.triangle); } // Empty the virus pool. mesh.viri.Clear(); } /// /// Spread regional attributes and/or area constraints (from a .poly file) /// throughout the mesh. /// /// /// /// /// This procedure operates in two phases. The first phase spreads an /// attribute and/or an area constraint through a (segment-bounded) region. /// The triangles are marked to ensure that each triangle is added to the /// virus pool only once, so the procedure will terminate. /// /// The second phase uninfects all infected triangles, returning them to /// normal. /// void RegionPlague(double attribute, double area) { Otri testtri = default(Otri); Otri neighbor = default(Otri); Osub neighborsubseg = default(Osub); Behavior behavior = mesh.behavior; // Loop through all the infected triangles, spreading the attribute // and/or area constraint to their neighbors, then to their neighbors' // neighbors. for (int i = 0; i < mesh.viri.Count; i++) { // WARNING: Don't use foreach, mesh.viri list may get modified. testtri.triangle = mesh.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(); if (behavior.RegionAttrib) { // Set an attribute (Note: the attributes array was resized before). testtri.triangle.attributes[mesh.eextras] = attribute; } if (behavior.VarArea) { // Set an area constraint. testtri.triangle.area = area; } // 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.SegPivot(ref neighborsubseg); // Make sure the neighbor exists, is not already infected, and // isn't protected by a subsegment. if ((neighbor.triangle != Mesh.dummytri) && !neighbor.IsInfected() && (neighborsubseg.seg == Mesh.dummysub)) { // Infect the neighbor. neighbor.Infect(); // Ensure that the neighbor's neighbors will be infected. mesh.viri.Add(neighbor.triangle); } } // Remark the triangle as infected, so it doesn't get added to the // virus pool again. testtri.Infect(); } // Uninfect all triangles. foreach (var virus in mesh.viri) { testtri.triangle = virus; testtri.Uninfect(); } // Empty the virus pool. mesh.viri.Clear(); } /// /// 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. /// public void CarveHoles() { Otri searchtri = default(Otri); Otri tri = default(Otri); Vertex searchorg, searchdest; LocateResult intersect; Otri[] regionTris = null; 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.triangle = Mesh.dummytri; searchtri.orient = 0; searchtri.SymSelf(); // 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 (Primitives.CounterClockwise(searchorg, searchdest, hole) > 0.0) { // Find a triangle that contains the hole. intersect = mesh.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(); mesh.viri.Add(searchtri.triangle); } } } } } // 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) { regionTris = new Otri[mesh.regions.Count]; int i = 0; // Find the starting triangle for each region. foreach (var region in mesh.regions) { regionTris[i].triangle = Mesh.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.triangle = Mesh.dummytri; searchtri.orient = 0; searchtri.SymSelf(); // 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 (Primitives.CounterClockwise(searchorg, searchdest, region.point) > 0.0) { // Find a triangle that contains the region point. intersect = mesh.Locate(region.point, ref searchtri); if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) { // Record the triangle for processing after the // holes have been carved. searchtri.Copy(ref regionTris[i]); } } } i++; } } if (mesh.viri.Count > 0) { // Carve the holes and concavities. Plague(); } // The virus pool should be empty now. if (regionTris != null) { if (mesh.behavior.RegionAttrib) { // Make the triangle's attributes larger. double[] attributes = new double[mesh.eextras + 1]; // Assign every triangle a regional attribute of zero. tri.orient = 0; foreach (var t in mesh.triangles.Values) { Array.Copy(tri.triangle.attributes, attributes, mesh.eextras); tri.triangle = t; tri.triangle.attributes = attributes; } } for (int i = 0; i < regionTris.Length; i++) { if (regionTris[i].triangle != Mesh.dummytri) { // Make sure the triangle under consideration still exists. // It may have been eaten by the virus. if (!Otri.IsDead(regionTris[i].triangle)) { // Put one triangle in the virus pool. regionTris[i].Infect(); mesh.viri.Add(regionTris[i].triangle); // Apply one region's attribute and/or area constraint. RegionPlague(mesh.regions[i].Attribute, mesh.regions[i].Area); // The virus pool should be empty now. } } } if (mesh.behavior.RegionAttrib) { // Note the fact that each triangle has an additional attribute. mesh.eextras++; } } // Free up memory (virus pool should be empty anyway). mesh.viri.Clear(); } } }