// ----------------------------------------------------------------------- // // 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.Data; using Animation.TriangleNet.Tools; using Animation.TriangleNet.Topology; /// /// Provides methods for mesh quality enforcement and testing. /// class QualityMesher { IPredicates predicates; Queue badsubsegs; BadTriQueue queue; Mesh mesh; Behavior behavior; NewLocation newLocation; ILog logger; // Stores the vertices of the triangle that contains newvertex // in SplitTriangle method. Triangle newvertex_tri; public QualityMesher(Mesh mesh, Configuration config) { logger = Log.Instance; badsubsegs = new Queue(); queue = new BadTriQueue(); this.mesh = mesh; this.predicates = config.Predicates(); this.behavior = mesh.behavior; newLocation = new NewLocation(mesh, predicates); newvertex_tri = new Triangle(); } /// /// Apply quality constraints to a mesh. /// /// The quality constraints. /// A value indicating, if the refined mesh should be Conforming Delaunay. public void Apply(QualityOptions quality, bool delaunay = false) { // Copy quality options if (quality != null) { behavior.Quality = true; behavior.MinAngle = quality.MinimumAngle; behavior.MaxAngle = quality.MaximumAngle; behavior.MaxArea = quality.MaximumArea; behavior.UserTest = quality.UserTest; behavior.VarArea = quality.VariableArea; behavior.ConformingDelaunay = behavior.ConformingDelaunay || delaunay; mesh.steinerleft = quality.SteinerPoints == 0 ? -1 : quality.SteinerPoints; } // TODO: remove if (!behavior.Poly) { // Be careful not to allocate space for element area constraints that // will never be assigned any value (other than the default -1.0). behavior.VarArea = false; } // 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) { mesh.checksegments = true; } if (behavior.Quality && mesh.triangles.Count > 0) { // Enforce angle and area constraints. EnforceQuality(); } } /// /// Add a bad subsegment to the queue. /// /// Bad subsegment. public void AddBadSubseg(BadSubseg badseg) { badsubsegs.Enqueue(badseg); } #region Check /// /// Check a subsegment to see if it is encroached; add it to the list if it is. /// /// The subsegment to check. /// Returns a nonzero value if the subsegment is encroached. /// /// A subsegment is encroached if there is a vertex in its diametral lens. /// For Ruppert's algorithm (-D switch), the "diametral lens" is the /// diametral circle. For Chew's algorithm (default), the diametral lens is /// just big enough to enclose two isosceles triangles whose bases are the /// subsegment. Each of the two isosceles triangles has two angles equal /// to 'b.minangle'. /// /// Chew's algorithm does not require diametral lenses at all--but they save /// time. Any vertex inside a subsegment's diametral lens implies that the /// triangle adjoining the subsegment will be too skinny, so it's only a /// matter of time before the encroaching vertex is deleted by Chew's /// algorithm. It's faster to simply not insert the doomed vertex in the /// first place, which is why I use diametral lenses with Chew's algorithm. /// public int CheckSeg4Encroach(ref Osub testsubseg) { Otri neighbortri = default(Otri); Osub testsym = default(Osub); BadSubseg encroachedseg; double dotproduct; int encroached; int sides; Vertex eorg, edest, eapex; encroached = 0; sides = 0; eorg = testsubseg.Org(); edest = testsubseg.Dest(); // Check one neighbor of the subsegment. testsubseg.Pivot(ref neighbortri); // Does the neighbor exist, or is this a boundary edge? if (neighbortri.tri.id != Mesh.DUMMY) { sides++; // Find a vertex opposite this subsegment. eapex = neighbortri.Apex(); // Check whether the apex is in the diametral lens of the subsegment // (the diametral circle if 'conformdel' is set). A dot product // of two sides of the triangle is used to check whether the angle // at the apex is greater than (180 - 2 'minangle') degrees (for // lenses; 90 degrees for diametral circles). dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) + (eorg.y - eapex.y) * (edest.y - eapex.y); if (dotproduct < 0.0) { if (behavior.ConformingDelaunay || (dotproduct * dotproduct >= (2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) * ((eorg.x - eapex.x) * (eorg.x - eapex.x) + (eorg.y - eapex.y) * (eorg.y - eapex.y)) * ((edest.x - eapex.x) * (edest.x - eapex.x) + (edest.y - eapex.y) * (edest.y - eapex.y)))) { encroached = 1; } } } // Check the other neighbor of the subsegment. testsubseg.Sym(ref testsym); testsym.Pivot(ref neighbortri); // Does the neighbor exist, or is this a boundary edge? if (neighbortri.tri.id != Mesh.DUMMY) { sides++; // Find the other vertex opposite this subsegment. eapex = neighbortri.Apex(); // Check whether the apex is in the diametral lens of the subsegment // (or the diametral circle, if 'conformdel' is set). dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) + (eorg.y - eapex.y) * (edest.y - eapex.y); if (dotproduct < 0.0) { if (behavior.ConformingDelaunay || (dotproduct * dotproduct >= (2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) * ((eorg.x - eapex.x) * (eorg.x - eapex.x) + (eorg.y - eapex.y) * (eorg.y - eapex.y)) * ((edest.x - eapex.x) * (edest.x - eapex.x) + (edest.y - eapex.y) * (edest.y - eapex.y)))) { encroached += 2; } } } if (encroached > 0 && (behavior.NoBisect == 0 || ((behavior.NoBisect == 1) && (sides == 2)))) { // Add the subsegment to the list of encroached subsegments. // Be sure to get the orientation right. encroachedseg = new BadSubseg(); if (encroached == 1) { encroachedseg.subseg = testsubseg; encroachedseg.org = eorg; encroachedseg.dest = edest; } else { encroachedseg.subseg = testsym; encroachedseg.org = edest; encroachedseg.dest = eorg; } badsubsegs.Enqueue(encroachedseg); } return encroached; } /// /// Test a triangle for quality and size. /// /// Triangle to check. /// /// Tests a triangle to see if it satisfies the minimum angle condition and /// the maximum area condition. Triangles that aren't up to spec are added /// to the bad triangle queue. /// public void TestTriangle(ref Otri testtri) { Otri tri1 = default(Otri), tri2 = default(Otri); Osub testsub = default(Osub); Vertex torg, tdest, tapex; Vertex base1, base2; Vertex org1, dest1, org2, dest2; Vertex joinvertex; double dxod, dyod, dxda, dyda, dxao, dyao; double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2; double apexlen, orglen, destlen, minedge; double angle; double area; double dist1, dist2; double maxangle; torg = testtri.Org(); tdest = testtri.Dest(); tapex = testtri.Apex(); dxod = torg.x - tdest.x; dyod = torg.y - tdest.y; dxda = tdest.x - tapex.x; dyda = tdest.y - tapex.y; dxao = tapex.x - torg.x; dyao = tapex.y - torg.y; dxod2 = dxod * dxod; dyod2 = dyod * dyod; dxda2 = dxda * dxda; dyda2 = dyda * dyda; dxao2 = dxao * dxao; dyao2 = dyao * dyao; // Find the lengths of the triangle's three edges. apexlen = dxod2 + dyod2; orglen = dxda2 + dyda2; destlen = dxao2 + dyao2; if ((apexlen < orglen) && (apexlen < destlen)) { // The edge opposite the apex is shortest. minedge = apexlen; // Find the square of the cosine of the angle at the apex. angle = dxda * dxao + dyda * dyao; angle = angle * angle / (orglen * destlen); base1 = torg; base2 = tdest; testtri.Copy(ref tri1); } else if (orglen < destlen) { // The edge opposite the origin is shortest. minedge = orglen; // Find the square of the cosine of the angle at the origin. angle = dxod * dxao + dyod * dyao; angle = angle * angle / (apexlen * destlen); base1 = tdest; base2 = tapex; testtri.Lnext(ref tri1); } else { // The edge opposite the destination is shortest. minedge = destlen; // Find the square of the cosine of the angle at the destination. angle = dxod * dxda + dyod * dyda; angle = angle * angle / (apexlen * orglen); base1 = tapex; base2 = torg; testtri.Lprev(ref tri1); } if (behavior.VarArea || behavior.fixedArea || (behavior.UserTest != null)) { // Check whether the area is larger than permitted. area = 0.5 * (dxod * dyda - dyod * dxda); if (behavior.fixedArea && (area > behavior.MaxArea)) { // Add this triangle to the list of bad triangles. queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); return; } // Nonpositive area constraints are treated as unconstrained. if ((behavior.VarArea) && (area > testtri.tri.area) && (testtri.tri.area > 0.0)) { // Add this triangle to the list of bad triangles. queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); return; } // Check whether the user thinks this triangle is too large. if ((behavior.UserTest != null) && behavior.UserTest(testtri.tri, area)) { queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); return; } } // find the maximum edge and accordingly the pqr orientation if ((apexlen > orglen) && (apexlen > destlen)) { // The edge opposite the apex is longest. // maxedge = apexlen; // Find the cosine of the angle at the apex. maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen * destlen)); } else if (orglen > destlen) { // The edge opposite the origin is longest. // maxedge = orglen; // Find the cosine of the angle at the origin. maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen * destlen)); } else { // The edge opposite the destination is longest. // maxedge = destlen; // Find the cosine of the angle at the destination. maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen * orglen)); } // Check whether the angle is smaller than permitted. if ((angle > behavior.goodAngle) || (maxangle < behavior.maxGoodAngle && behavior.MaxAngle != 0.0)) { // Use the rules of Miller, Pav, and Walkington to decide that certain // triangles should not be split, even if they have bad angles. // A skinny triangle is not split if its shortest edge subtends a // small input angle, and both endpoints of the edge lie on a // concentric circular shell. For convenience, I make a small // adjustment to that rule: I check if the endpoints of the edge // both lie in segment interiors, equidistant from the apex where // the two segments meet. // First, check if both points lie in segment interiors. if ((base1.type == VertexType.SegmentVertex) && (base2.type == VertexType.SegmentVertex)) { // Check if both points lie in a common segment. If they do, the // skinny triangle is enqueued to be split as usual. tri1.Pivot(ref testsub); if (testsub.seg.hash == Mesh.DUMMY) { // No common segment. Find a subsegment that contains 'torg'. tri1.Copy(ref tri2); do { tri1.Oprev(); tri1.Pivot(ref testsub); } while (testsub.seg.hash == Mesh.DUMMY); // Find the endpoints of the containing segment. org1 = testsub.SegOrg(); dest1 = testsub.SegDest(); // Find a subsegment that contains 'tdest'. do { tri2.Dnext(); tri2.Pivot(ref testsub); } while (testsub.seg.hash == Mesh.DUMMY); // Find the endpoints of the containing segment. org2 = testsub.SegOrg(); dest2 = testsub.SegDest(); // Check if the two containing segments have an endpoint in common. joinvertex = null; if ((dest1.x == org2.x) && (dest1.y == org2.y)) { joinvertex = dest1; } else if ((org1.x == dest2.x) && (org1.y == dest2.y)) { joinvertex = org1; } if (joinvertex != null) { // Compute the distance from the common endpoint (of the two // segments) to each of the endpoints of the shortest edge. dist1 = ((base1.x - joinvertex.x) * (base1.x - joinvertex.x) + (base1.y - joinvertex.y) * (base1.y - joinvertex.y)); dist2 = ((base2.x - joinvertex.x) * (base2.x - joinvertex.x) + (base2.y - joinvertex.y) * (base2.y - joinvertex.y)); // If the two distances are equal, don't split the triangle. if ((dist1 < 1.001 * dist2) && (dist1 > 0.999 * dist2)) { // Return now to avoid enqueueing the bad triangle. return; } } } } // Add this triangle to the list of bad triangles. queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); } } #endregion #region Maintanance /// /// Traverse the entire list of subsegments, and check each to see if it /// is encroached. If so, add it to the list. /// private void TallyEncs() { Osub subsegloop = default(Osub); subsegloop.orient = 0; foreach (var seg in mesh.subsegs.Values) { subsegloop.seg = seg; // If the segment is encroached, add it to the list. CheckSeg4Encroach(ref subsegloop); } } /// /// Split all the encroached subsegments. /// /// A flag that specifies whether one should take /// note of new bad triangles that result from inserting vertices to repair /// encroached subsegments. /// /// 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. /// 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; double segmentlength, nearestpoweroftwo; double split; double 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.subseg; 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.org) && (edest == seg.dest)) { // 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.Pivot(ref enctri); enctri.Lnext(ref testtri); testtri.Pivot(ref testsh); acuteorg = testsh.seg.hash != Mesh.DUMMY; // Is the destination shared with another segment? testtri.Lnext(); testtri.Pivot(ref testsh); acutedest = testsh.seg.hash != Mesh.DUMMY; // 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.Pivot(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.tri.id != Mesh.DUMMY) { // Is the destination shared with another segment? testtri.Lnext(); testtri.Pivot(ref testsh); acutedest2 = testsh.seg.hash != Mesh.DUMMY; acutedest = acutedest || acutedest2; // Is the origin shared with another segment? testtri.Lnext(); testtri.Pivot(ref testsh); acuteorg2 = testsh.seg.hash != Mesh.DUMMY; 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.Lprev(); } } } // Use the concentric circles if exactly one endpoint is shared // with another adjacent segment. if (acuteorg || acutedest) { segmentlength = Math.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.0; while (segmentlength > 3.0 * nearestpoweroftwo) { nearestpoweroftwo *= 2.0; } while (segmentlength < 1.5 * nearestpoweroftwo) { nearestpoweroftwo *= 0.5; } // Where do we split the segment? split = nearestpoweroftwo / segmentlength; if (acutedest) { split = 1.0 - split; } } else { // If we're not worried about adjacent segments, split // this segment in the middle. split = 0.5; } // Create the new vertex (interpolate coordinates). newvertex = new Vertex( eorg.x + split * (edest.x - eorg.x), eorg.y + split * (edest.y - eorg.y), currentenc.seg.boundary #if USE_ATTRIBS , mesh.nextras #endif ); newvertex.type = VertexType.SegmentVertex; newvertex.hash = mesh.hash_vtx++; newvertex.id = newvertex.hash; mesh.vertices.Add(newvertex.hash, newvertex); #if USE_ATTRIBS // Interpolate attributes. for (int i = 0; i < mesh.nextras; i++) { newvertex.attributes[i] = eorg.attributes[i] + split * (edest.attributes[i] - eorg.attributes[i]); } #endif #if USE_Z newvertex.z = eorg.z + split * (edest.z - eorg.z); #endif 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 = predicates.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 (!double.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.Next(); 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.org = null; } } /// /// Test every triangle in the mesh for quality measures. /// private void TallyFaces() { Otri triangleloop = default(Otri); triangleloop.orient = 0; foreach (var tri in mesh.triangles) { triangleloop.tri = tri; // If the triangle is bad, enqueue it. TestTriangle(ref triangleloop); } } /// /// Inserts a vertex at the circumcenter of a triangle. Deletes /// the newly inserted vertex if it encroaches upon a segment. /// /// private void SplitTriangle(BadTriangle badtri) { Otri badotri = default(Otri); Vertex borg, bdest, bapex; Point newloc; // Location of the new vertex double 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.tri) && (borg == badtri.org) && (bdest == badtri.dest) && (bapex == badtri.apex)) { 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 = predicates.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 (Log.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 #if USE_ATTRIBS , mesh.nextras #endif ); newvertex.type = VertexType.FreeVertex; // 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.Lprev(); } // Assign triangle for attributes interpolation. newvertex.tri.tri = newvertex_tri; // 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; #if USE_ATTRIBS if (mesh.nextras > 0) { Interpolation.InterpolateAttributes(newvertex, newvertex.tri.tri, mesh.nextras); } #endif #if USE_Z Interpolation.InterpolateZ(newvertex, newvertex.tri.tri); #endif 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 (Log.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."); } } } /// /// Remove all the encroached subsegments and bad triangles from the triangulation. /// private void EnforceQuality() { BadTriangle badtri; // Test all segments to see if they're encroached. TallyEncs(); // Fix encroached subsegments without noting bad triangles. SplitEncSegs(false); // At this point, if we haven't run out of Steiner points, the // triangulation should be (conforming) Delaunay. // Next, we worry about enforcing triangle quality. if ((behavior.MinAngle > 0.0) || behavior.VarArea || behavior.fixedArea || behavior.UserTest != null) { // TODO: Reset queue? (Or is it always empty at this point) // Test all triangles to see if they're bad. TallyFaces(); mesh.checkquality = true; while ((queue.Count > 0) && (mesh.steinerleft != 0)) { // Fix one bad triangle by inserting a vertex at its circumcenter. badtri = queue.Dequeue(); SplitTriangle(badtri); if (badsubsegs.Count > 0) { // Put bad triangle back in queue for another try later. queue.Enqueue(badtri); // Fix any encroached subsegments that resulted. // Record any new bad triangles that result. SplitEncSegs(true); } } } // At this point, if the "-D" switch was selected and we haven't run out // of Steiner points, the triangulation should be (conforming) Delaunay // and have no low-quality triangles. // Might we have run out of Steiner points too soon? if (Log.Verbose && behavior.ConformingDelaunay && (badsubsegs.Count > 0) && (mesh.steinerleft == 0)) { logger.Warning("I ran out of Steiner points, but the mesh has encroached subsegments, " + "and therefore might not be truly Delaunay. If the Delaunay property is important " + "to you, try increasing the number of Steiner points.", "Quality.EnforceQuality()"); } } #endregion } }