/// <summary> /// Find a triangle or edge containing a given point. /// </summary> /// <param name="searchpoint">The point to locate.</param> /// <param name="searchtri">The triangle to start the search at.</param> /// <returns>Location information.</returns> /// <remarks> /// Searching begins from one of: the input 'searchtri', a recently /// encountered triangle 'recenttri', or from a triangle chosen from a /// random sample. The choice is made by determining which triangle's /// origin is closest to the point we are searching for. Normally, /// 'searchtri' should be a handle on the convex hull of the triangulation. /// /// Details on the random sampling method can be found in the Mucke, Saias, /// and Zhu paper cited in the header of this code. /// /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. /// /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' /// is a handle whose origin is the existing vertex. /// /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a /// handle whose primary edge is the edge on which the point lies. /// /// Returns INTRIANGLE if the point lies strictly within a triangle. /// 'searchtri' is a handle on the triangle that contains the point. /// /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a /// handle whose primary edge the point is to the right of. This might /// occur when the circumcenter of a triangle falls just slightly outside /// the mesh due to floating-point roundoff error. It also occurs when /// seeking a hole or region point that a foolish user has placed outside /// the mesh. /// /// WARNING: This routine is designed for convex triangulations, and will /// not generally work after the holes and concavities have been carved. /// </remarks> public LocateResult Locate(Point searchpoint, ref Otri searchtri) { Otri sampletri = default(Otri); Vertex torg, tdest; float searchdist, dist; float ahead; // Record the distance from the suggested starting triangle to the // point we seek. torg = searchtri.Org(); searchdist = (searchpoint.X - torg.x) * (searchpoint.X - torg.x) + (searchpoint.Y - torg.y) * (searchpoint.Y - torg.y); // If a recently encountered triangle has been recorded and has not been // deallocated, test it as a good starting point. if (recenttri.triangle != null) { if (!Otri.IsDead(recenttri.triangle)) { torg = recenttri.Org(); if ((torg.x == searchpoint.X) && (torg.y == searchpoint.Y)) { recenttri.Copy(ref searchtri); return(LocateResult.OnVertex); } dist = (searchpoint.X - torg.x) * (searchpoint.X - torg.x) + (searchpoint.Y - torg.y) * (searchpoint.Y - torg.y); if (dist < searchdist) { recenttri.Copy(ref searchtri); searchdist = dist; } } } // TODO: Improve sampling. sampler.Update(mesh); int[] samples = sampler.GetSamples(mesh); foreach (var key in samples) { sampletri.triangle = mesh.triangles[key]; if (!Otri.IsDead(sampletri.triangle)) { torg = sampletri.Org(); dist = (searchpoint.X - torg.x) * (searchpoint.X - torg.x) + (searchpoint.Y - torg.y) * (searchpoint.Y - torg.y); if (dist < searchdist) { sampletri.Copy(ref searchtri); searchdist = dist; } } } // Where are we? torg = searchtri.Org(); tdest = searchtri.Dest(); // Check the starting triangle's vertices. if ((torg.x == searchpoint.X) && (torg.y == searchpoint.Y)) { return(LocateResult.OnVertex); } if ((tdest.x == searchpoint.X) && (tdest.y == searchpoint.Y)) { searchtri.LnextSelf(); return(LocateResult.OnVertex); } // Orient 'searchtri' to fit the preconditions of calling preciselocate(). ahead = Primitives.CounterClockwise(torg, tdest, searchpoint); if (ahead < 0.0) { // Turn around so that 'searchpoint' is to the left of the // edge specified by 'searchtri'. searchtri.SymSelf(); } else if (ahead == 0.0) { // Check if 'searchpoint' is between 'torg' and 'tdest'. if (((torg.x < searchpoint.X) == (searchpoint.X < tdest.x)) && ((torg.y < searchpoint.Y) == (searchpoint.Y < tdest.y))) { return(LocateResult.OnEdge); } } return(PreciseLocate(searchpoint, ref searchtri, false)); }
public void CarveHoles() { Otri otri = new Otri(); Triangle[] triangleArray = null; if (!this.mesh.behavior.Convex) { this.InfectHull(); } if (!this.mesh.behavior.NoHoles) { foreach (Point hole in this.mesh.holes) { if (!this.mesh.bounds.Contains(hole)) { continue; } otri.triangle = Mesh.dummytri; otri.orient = 0; otri.SymSelf(); if (Primitives.CounterClockwise(otri.Org(), otri.Dest(), hole) <= 0 || this.mesh.locator.Locate(hole, ref otri) == LocateResult.Outside || otri.IsInfected()) { continue; } otri.Infect(); this.viri.Add(otri.triangle); } } if (this.mesh.regions.Count > 0) { int num = 0; triangleArray = new Triangle[this.mesh.regions.Count]; foreach (RegionPointer region in this.mesh.regions) { triangleArray[num] = Mesh.dummytri; if (this.mesh.bounds.Contains(region.point)) { otri.triangle = Mesh.dummytri; otri.orient = 0; otri.SymSelf(); if (Primitives.CounterClockwise(otri.Org(), otri.Dest(), region.point) > 0 && this.mesh.locator.Locate(region.point, ref otri) != LocateResult.Outside && !otri.IsInfected()) { triangleArray[num] = otri.triangle; triangleArray[num].region = region.id; } } num++; } } if (this.viri.Count > 0) { this.Plague(); } if (triangleArray != null) { RegionIterator regionIterator = new RegionIterator(this.mesh); for (int i = 0; i < (int)triangleArray.Length; i++) { if (triangleArray[i] != Mesh.dummytri && !Otri.IsDead(triangleArray[i])) { regionIterator.Process(triangleArray[i]); } } } this.viri.Clear(); }
/// <summary> /// Find a triangle or edge containing a given point. /// </summary> /// <param name="searchpoint">The point to locate.</param> /// <param name="searchtri">The triangle to start the search at.</param> /// <param name="stopatsubsegment"> If 'stopatsubsegment' is set, the search /// will stop if it tries to walk through a subsegment, and will return OUTSIDE.</param> /// <returns>Location information.</returns> /// <remarks> /// Begins its search from 'searchtri'. It is important that 'searchtri' /// be a handle with the property that 'searchpoint' is strictly to the left /// of the edge denoted by 'searchtri', or is collinear with that edge and /// does not intersect that edge. (In particular, 'searchpoint' should not /// be the origin or destination of that edge.) /// /// These conditions are imposed because preciselocate() is normally used in /// one of two situations: /// /// (1) To try to find the location to insert a new point. Normally, we /// know an edge that the point is strictly to the left of. In the /// incremental Delaunay algorithm, that edge is a bounding box edge. /// In Ruppert's Delaunay refinement algorithm for quality meshing, /// that edge is the shortest edge of the triangle whose circumcenter /// is being inserted. /// /// (2) To try to find an existing point. In this case, any edge on the /// convex hull is a good starting edge. You must screen out the /// possibility that the vertex sought is an endpoint of the starting /// edge before you call preciselocate(). /// /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. /// /// This implementation differs from that given by Guibas and Stolfi. It /// walks from triangle to triangle, crossing an edge only if 'searchpoint' /// is on the other side of the line containing that edge. After entering /// a triangle, there are two edges by which one can leave that triangle. /// If both edges are valid ('searchpoint' is on the other side of both /// edges), one of the two is chosen by drawing a line perpendicular to /// the entry edge (whose endpoints are 'forg' and 'fdest') passing through /// 'fapex'. Depending on which side of this perpendicular 'searchpoint' /// falls on, an exit edge is chosen. /// /// This implementation is empirically faster than the Guibas and Stolfi /// point location routine (which I originally used), which tends to spiral /// in toward its target. /// /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' /// is a handle whose origin is the existing vertex. /// /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a /// handle whose primary edge is the edge on which the point lies. /// /// Returns INTRIANGLE if the point lies strictly within a triangle. /// 'searchtri' is a handle on the triangle that contains the point. /// /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a /// handle whose primary edge the point is to the right of. This might /// occur when the circumcenter of a triangle falls just slightly outside /// the mesh due to floating-point roundoff error. It also occurs when /// seeking a hole or region point that a foolish user has placed outside /// the mesh. /// /// WARNING: This routine is designed for convex triangulations, and will /// not generally work after the holes and concavities have been carved. /// However, it can still be used to find the circumcenter of a triangle, as /// long as the search is begun from the triangle in question.</remarks> public LocateResult PreciseLocate(Point searchpoint, ref Otri searchtri, bool stopatsubsegment) { Otri backtracktri = default(Otri); Osub checkedge = default(Osub); Vertex forg, fdest, fapex; float orgorient, destorient; bool moveleft; // Where are we? forg = searchtri.Org(); fdest = searchtri.Dest(); fapex = searchtri.Apex(); while (true) { // Check whether the apex is the point we seek. if ((fapex.x == searchpoint.X) && (fapex.y == searchpoint.Y)) { searchtri.LprevSelf(); return(LocateResult.OnVertex); } // Does the point lie on the other side of the line defined by the // triangle edge opposite the triangle's destination? destorient = Primitives.CounterClockwise(forg, fapex, searchpoint); // Does the point lie on the other side of the line defined by the // triangle edge opposite the triangle's origin? orgorient = Primitives.CounterClockwise(fapex, fdest, searchpoint); if (destorient > 0.0) { if (orgorient > 0.0) { // Move left if the inner product of (fapex - searchpoint) and // (fdest - forg) is positive. This is equivalent to drawing // a line perpendicular to the line (forg, fdest) and passing // through 'fapex', and determining which side of this line // 'searchpoint' falls on. moveleft = (fapex.x - searchpoint.X) * (fdest.x - forg.x) + (fapex.y - searchpoint.Y) * (fdest.y - forg.y) > 0.0; } else { moveleft = true; } } else { if (orgorient > 0.0) { moveleft = false; } else { // The point we seek must be on the boundary of or inside this // triangle. if (destorient == 0.0) { searchtri.LprevSelf(); return(LocateResult.OnEdge); } if (orgorient == 0.0) { searchtri.LnextSelf(); return(LocateResult.OnEdge); } return(LocateResult.InTriangle); } } // Move to another triangle. Leave a trace 'backtracktri' in case // floating-point roundoff or some such bogey causes us to walk // off a boundary of the triangulation. if (moveleft) { searchtri.Lprev(ref backtracktri); fdest = fapex; } else { searchtri.Lnext(ref backtracktri); forg = fapex; } backtracktri.Sym(ref searchtri); if (mesh.checksegments && stopatsubsegment) { // Check for walking through a subsegment. backtracktri.SegPivot(ref checkedge); if (checkedge.seg != Mesh.dummysub) { // Go back to the last triangle. backtracktri.Copy(ref searchtri); return(LocateResult.Outside); } } // Check for walking right out of the triangulation. if (searchtri.triangle == Mesh.dummytri) { // Go back to the last triangle. backtracktri.Copy(ref searchtri); return(LocateResult.Outside); } fapex = searchtri.Apex(); } }
/// <summary> /// Inserts a vertex at the circumcenter of a triangle. Deletes /// the newly inserted vertex if it encroaches upon a segment. /// </summary> /// <param name="badtri"></param> private void SplitTriangle(BadTriangle badtri) { Otri badotri = default(Otri); Vertex borg, bdest, bapex; Point newloc; // Location of the new vertex float xi = 0, eta = 0; InsertVertexResult success; bool errorflag; badotri = badtri.poortri; borg = badotri.Org(); bdest = badotri.Dest(); bapex = badotri.Apex(); // Make sure that this triangle is still the same triangle it was // when it was tested and determined to be of bad quality. // Subsequent transformations may have made it a different triangle. if (!Otri.IsDead(badotri.triangle) && (borg == badtri.triangorg) && (bdest == badtri.triangdest) && (bapex == badtri.triangapex)) { errorflag = false; // Create a new vertex at the triangle's circumcenter. // Using the original (simpler) Steiner point location method // for mesh refinement. // TODO: NewLocation doesn't work for refinement. Why? Maybe // reset VertexType? if (behavior.fixedArea || behavior.VarArea) { newloc = Primitives.FindCircumcenter(borg, bdest, bapex, ref xi, ref eta, behavior.offconstant); } else { newloc = newLocation.FindLocation(borg, bdest, bapex, ref xi, ref eta, true, badotri); } // Check whether the new vertex lies on a triangle vertex. if (((newloc.x == borg.x) && (newloc.y == borg.y)) || ((newloc.x == bdest.x) && (newloc.y == bdest.y)) || ((newloc.x == bapex.x) && (newloc.y == bapex.y))) { if (Behavior.Verbose) { logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()"); errorflag = true; } } else { // The new vertex must be in the interior, and therefore is a // free vertex with a marker of zero. Vertex newvertex = new Vertex(newloc.x, newloc.y, 0, mesh.nextras); newvertex.type = VertexType.FreeVertex; for (int i = 0; i < mesh.nextras; i++) { // Interpolate the vertex attributes at the circumcenter. newvertex.attributes[i] = borg.attributes[i] + xi * (bdest.attributes[i] - borg.attributes[i]) + eta * (bapex.attributes[i] - borg.attributes[i]); } // Ensure that the handle 'badotri' does not represent the longest // edge of the triangle. This ensures that the circumcenter must // fall to the left of this edge, so point location will work. // (If the angle org-apex-dest exceeds 90 degrees, then the // circumcenter lies outside the org-dest edge, and eta is // negative. Roundoff error might prevent eta from being // negative when it should be, so I test eta against xi.) if (eta < xi) { badotri.LprevSelf(); } // Insert the circumcenter, searching from the edge of the triangle, // and maintain the Delaunay property of the triangulation. Osub tmp = default(Osub); success = mesh.InsertVertex(newvertex, ref badotri, ref tmp, true, true); if (success == InsertVertexResult.Successful) { newvertex.hash = mesh.hash_vtx++; newvertex.id = newvertex.hash; mesh.vertices.Add(newvertex.hash, newvertex); if (mesh.steinerleft > 0) { mesh.steinerleft--; } } else if (success == InsertVertexResult.Encroaching) { // If the newly inserted vertex encroaches upon a subsegment, // delete the new vertex. mesh.UndoVertex(); } else if (success == InsertVertexResult.Violating) { // Failed to insert the new vertex, but some subsegment was // marked as being encroached. } else { // success == DUPLICATEVERTEX // Couldn't insert the new vertex because a vertex is already there. if (Behavior.Verbose) { logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()"); errorflag = true; } } } if (errorflag) { logger.Error("The new vertex is at the circumcenter of triangle: This probably " + "means that I am trying to refine triangles to a smaller size than can be " + "accommodated by the finite precision of floating point arithmetic.", "Quality.SplitTriangle()"); throw new Exception("The new vertex is at the circumcenter of triangle."); } } }
/// <summary> /// Test the mesh for topological consistency. /// </summary> public bool CheckMesh() { Otri tri = default(Otri); Otri oppotri = default(Otri), oppooppotri = default(Otri); Vertex triorg, tridest, triapex; Vertex oppoorg, oppodest; int horrors; bool saveexact; // Temporarily turn on exact arithmetic if it's off. saveexact = Behavior.NoExact; Behavior.NoExact = false; horrors = 0; // Run through the list of triangles, checking each one. foreach (var t in mesh.triangles.Values) { tri.triangle = t; // Check all three edges of the triangle. for (tri.orient = 0; tri.orient < 3; tri.orient++) { triorg = tri.Org(); tridest = tri.Dest(); if (tri.orient == 0) { // Only test for inversion once. // Test if the triangle is flat or inverted. triapex = tri.Apex(); if (Primitives.CounterClockwise(triorg, tridest, triapex) <= 0.0) { logger.Warning("Triangle is flat or inverted.", "Quality.CheckMesh()"); horrors++; } } // Find the neighboring triangle on this edge. tri.Sym(ref oppotri); if (oppotri.triangle != Mesh.dummytri) { // Check that the triangle's neighbor knows it's a neighbor. oppotri.Sym(ref oppooppotri); if ((tri.triangle != oppooppotri.triangle) || (tri.orient != oppooppotri.orient)) { if (tri.triangle == oppooppotri.triangle) { logger.Warning("Asymmetric triangle-triangle bond: (Right triangle, wrong orientation)", "Quality.CheckMesh()"); } horrors++; } // Check that both triangles agree on the identities // of their shared vertices. oppoorg = oppotri.Org(); oppodest = oppotri.Dest(); if ((triorg != oppodest) || (tridest != oppoorg)) { logger.Warning("Mismatched edge coordinates between two triangles.", "Quality.CheckMesh()"); horrors++; } } } } // Check for unconnected vertices mesh.MakeVertexMap(); foreach (var v in mesh.vertices.Values) { if (v.tri.triangle == null) { logger.Warning("Vertex (ID " + v.id + ") not connected to mesh (duplicate input vertex?)", "Quality.CheckMesh()"); } } if (horrors == 0) // && Behavior.Verbose { logger.Info("Mesh topology appears to be consistent."); } // Restore the status of exact arithmetic. Behavior.NoExact = saveexact; return(horrors == 0); }
/// <summary> /// Split all the encroached subsegments. /// </summary> /// <param name="triflaws">A flag that specifies whether one should take /// note of new bad triangles that result from inserting vertices to repair /// encroached subsegments.</param> /// <remarks> /// Each encroached subsegment is repaired by splitting it - inserting a /// vertex at or near its midpoint. Newly inserted vertices may encroach /// upon other subsegments; these are also repaired. /// </remarks> private void SplitEncSegs(bool triflaws) { Otri enctri = default(Otri); Otri testtri = default(Otri); Osub testsh = default(Osub); Osub currentenc = default(Osub); BadSubseg seg; Vertex eorg, edest, eapex; Vertex newvertex; InsertVertexResult success; float segmentlength, nearestpoweroftwo; float split; float multiplier, divisor; bool acuteorg, acuteorg2, acutedest, acutedest2; // Note that steinerleft == -1 if an unlimited number // of Steiner points is allowed. while (badsubsegs.Count > 0) { if (mesh.steinerleft == 0) { break; } seg = badsubsegs.Dequeue(); currentenc = seg.encsubseg; eorg = currentenc.Org(); edest = currentenc.Dest(); // Make sure that this segment is still the same segment it was // when it was determined to be encroached. If the segment was // enqueued multiple times (because several newly inserted // vertices encroached it), it may have already been split. if (!Osub.IsDead(currentenc.seg) && (eorg == seg.subsegorg) && (edest == seg.subsegdest)) { // To decide where to split a segment, we need to know if the // segment shares an endpoint with an adjacent segment. // The concern is that, if we simply split every encroached // segment in its center, two adjacent segments with a small // angle between them might lead to an infinite loop; each // vertex added to split one segment will encroach upon the // other segment, which must then be split with a vertex that // will encroach upon the first segment, and so on forever. // To avoid this, imagine a set of concentric circles, whose // radii are powers of two, about each segment endpoint. // These concentric circles determine where the segment is // split. (If both endpoints are shared with adjacent // segments, split the segment in the middle, and apply the // concentric circles for later splittings.) // Is the origin shared with another segment? currentenc.TriPivot(ref enctri); enctri.Lnext(ref testtri); testtri.SegPivot(ref testsh); acuteorg = testsh.seg != Mesh.dummysub; // Is the destination shared with another segment? testtri.LnextSelf(); testtri.SegPivot(ref testsh); acutedest = testsh.seg != Mesh.dummysub; // If we're using Chew's algorithm (rather than Ruppert's) // to define encroachment, delete free vertices from the // subsegment's diametral circle. if (!behavior.ConformingDelaunay && !acuteorg && !acutedest) { eapex = enctri.Apex(); while ((eapex.type == VertexType.FreeVertex) && ((eorg.x - eapex.x) * (edest.x - eapex.x) + (eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0)) { mesh.DeleteVertex(ref testtri); currentenc.TriPivot(ref enctri); eapex = enctri.Apex(); enctri.Lprev(ref testtri); } } // Now, check the other side of the segment, if there's a triangle there. enctri.Sym(ref testtri); if (testtri.triangle != Mesh.dummytri) { // Is the destination shared with another segment? testtri.LnextSelf(); testtri.SegPivot(ref testsh); acutedest2 = testsh.seg != Mesh.dummysub; acutedest = acutedest || acutedest2; // Is the origin shared with another segment? testtri.LnextSelf(); testtri.SegPivot(ref testsh); acuteorg2 = testsh.seg != Mesh.dummysub; acuteorg = acuteorg || acuteorg2; // Delete free vertices from the subsegment's diametral circle. if (!behavior.ConformingDelaunay && !acuteorg2 && !acutedest2) { eapex = testtri.Org(); while ((eapex.type == VertexType.FreeVertex) && ((eorg.x - eapex.x) * (edest.x - eapex.x) + (eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0)) { mesh.DeleteVertex(ref testtri); enctri.Sym(ref testtri); eapex = testtri.Apex(); testtri.LprevSelf(); } } } // Use the concentric circles if exactly one endpoint is shared // with another adjacent segment. if (acuteorg || acutedest) { segmentlength = UnityEngine.Mathf.Sqrt((edest.x - eorg.x) * (edest.x - eorg.x) + (edest.y - eorg.y) * (edest.y - eorg.y)); // Find the power of two that most evenly splits the segment. // The worst case is a 2:1 ratio between subsegment lengths. nearestpoweroftwo = 1.0f; while (segmentlength > 3.0f * nearestpoweroftwo) { nearestpoweroftwo *= 2.0f; } while (segmentlength < 1.5f * nearestpoweroftwo) { nearestpoweroftwo *= 0.5f; } // Where do we split the segment? split = nearestpoweroftwo / segmentlength; if (acutedest) { split = 1.0f - split; } } else { // If we're not worried about adjacent segments, split // this segment in the middle. split = 0.5f; } // Create the new vertex (interpolate coordinates). newvertex = new Vertex( eorg.x + split * (edest.x - eorg.x), eorg.y + split * (edest.y - eorg.y), currentenc.Mark(), mesh.nextras); newvertex.type = VertexType.SegmentVertex; newvertex.hash = mesh.hash_vtx++; newvertex.id = newvertex.hash; mesh.vertices.Add(newvertex.hash, newvertex); // Interpolate attributes. for (int i = 0; i < mesh.nextras; i++) { newvertex.attributes[i] = eorg.attributes[i] + split * (edest.attributes[i] - eorg.attributes[i]); } if (!Behavior.NoExact) { // Roundoff in the above calculation may yield a 'newvertex' // that is not precisely collinear with 'eorg' and 'edest'. // Improve collinearity by one step of iterative refinement. multiplier = Primitives.CounterClockwise(eorg, edest, newvertex); divisor = ((eorg.x - edest.x) * (eorg.x - edest.x) + (eorg.y - edest.y) * (eorg.y - edest.y)); if ((multiplier != 0.0) && (divisor != 0.0)) { multiplier = multiplier / divisor; // Watch out for NANs. if (!float.IsNaN(multiplier)) { newvertex.x += multiplier * (edest.y - eorg.y); newvertex.y += multiplier * (eorg.x - edest.x); } } } // Check whether the new vertex lies on an endpoint. if (((newvertex.x == eorg.x) && (newvertex.y == eorg.y)) || ((newvertex.x == edest.x) && (newvertex.y == edest.y))) { logger.Error("Ran out of precision: I attempted to split a" + " segment to a smaller size than can be accommodated by" + " the finite precision of floating point arithmetic.", "Quality.SplitEncSegs()"); throw new Exception("Ran out of precision"); } // Insert the splitting vertex. This should always succeed. success = mesh.InsertVertex(newvertex, ref enctri, ref currentenc, true, triflaws); if ((success != InsertVertexResult.Successful) && (success != InsertVertexResult.Encroaching)) { logger.Error("Failure to split a segment.", "Quality.SplitEncSegs()"); throw new Exception("Failure to split a segment."); } if (mesh.steinerleft > 0) { mesh.steinerleft--; } // Check the two new subsegments to see if they're encroached. CheckSeg4Encroach(ref currentenc); currentenc.NextSelf(); CheckSeg4Encroach(ref currentenc); } // Set subsegment's origin to NULL. This makes it possible to detect dead // badsubsegs when traversing the list of all badsubsegs. seg.subsegorg = null; } }
/// <summary> /// Ensure that the mesh is (constrained) Delaunay. /// </summary> public bool CheckDelaunay() { Otri loop = default(Otri); Otri oppotri = default(Otri); Osub opposubseg = default(Osub); Vertex triorg, tridest, triapex; Vertex oppoapex; bool shouldbedelaunay; int horrors; bool saveexact; // Temporarily turn on exact arithmetic if it's off. saveexact = Behavior.NoExact; Behavior.NoExact = false; horrors = 0; // Run through the list of triangles, checking each one. foreach (var tri in mesh.triangles.Values) { loop.triangle = tri; // Check all three edges of the triangle. for (loop.orient = 0; loop.orient < 3; loop.orient++) { triorg = loop.Org(); tridest = loop.Dest(); triapex = loop.Apex(); loop.Sym(ref oppotri); oppoapex = oppotri.Apex(); // Only test that the edge is locally Delaunay if there is an // adjoining triangle whose pointer is larger (to ensure that // each pair isn't tested twice). shouldbedelaunay = (oppotri.triangle != Mesh.dummytri) && !Otri.IsDead(oppotri.triangle) && loop.triangle.id < oppotri.triangle.id && (triorg != mesh.infvertex1) && (triorg != mesh.infvertex2) && (triorg != mesh.infvertex3) && (tridest != mesh.infvertex1) && (tridest != mesh.infvertex2) && (tridest != mesh.infvertex3) && (triapex != mesh.infvertex1) && (triapex != mesh.infvertex2) && (triapex != mesh.infvertex3) && (oppoapex != mesh.infvertex1) && (oppoapex != mesh.infvertex2) && (oppoapex != mesh.infvertex3); if (mesh.checksegments && shouldbedelaunay) { // If a subsegment separates the triangles, then the edge is // constrained, so no local Delaunay test should be done. loop.SegPivot(ref opposubseg); if (opposubseg.seg != Mesh.dummysub) { shouldbedelaunay = false; } } if (shouldbedelaunay) { if (Primitives.NonRegular(triorg, tridest, triapex, oppoapex) > 0.0) { logger.Warning(String.Format("Non-regular pair of triangles found (IDs {0}/{1}).", loop.triangle.id, oppotri.triangle.id), "Quality.CheckDelaunay()"); horrors++; } } } } if (horrors == 0) // && Behavior.Verbose { logger.Info("Mesh is Delaunay."); } // Restore the status of exact arithmetic. Behavior.NoExact = saveexact; return(horrors == 0); }