SplayNode FrontLocate(SplayNode splayroot, Otri bottommost, Vertex searchvertex, ref Otri searchtri, ref bool farright) { bool farrightflag; bottommost.Copy(ref searchtri); splayroot = Splay(splayroot, searchvertex, ref searchtri); farrightflag = false; while (!farrightflag && RightOfHyperbola(ref searchtri, searchvertex)) { searchtri.OnextSelf(); farrightflag = searchtri.Equal(bottommost); } farright = farrightflag; return splayroot; }
/// <summary> /// Removes ghost triangles. /// </summary> /// <param name="startghost"></param> /// <returns>Number of vertices on the hull.</returns> int RemoveGhosts(ref Otri startghost) { Otri searchedge = default(Otri); Otri dissolveedge = default(Otri); Otri deadtriangle = default(Otri); Vertex markorg; int hullsize; bool noPoly = !mesh.behavior.Poly; // Find an edge on the convex hull to start point location from. startghost.Lprev(ref searchedge); searchedge.SymSelf(); Mesh.dummytri.neighbors[0] = searchedge; // Remove the bounding box and count the convex hull edges. startghost.Copy(ref dissolveedge); hullsize = 0; do { hullsize++; dissolveedge.Lnext(ref deadtriangle); dissolveedge.LprevSelf(); dissolveedge.SymSelf(); // If no PSLG is involved, set the boundary markers of all the vertices // on the convex hull. If a PSLG is used, this step is done later. if (noPoly) { // Watch out for the case where all the input vertices are collinear. if (dissolveedge.triangle != Mesh.dummytri) { markorg = dissolveedge.Org(); if (markorg.mark == 0) { markorg.mark = 1; } } } // Remove a bounding triangle from a convex hull triangle. dissolveedge.Dissolve(); // Find the next bounding triangle. deadtriangle.Sym(ref dissolveedge); // Delete the bounding triangle. mesh.TriangleDealloc(deadtriangle.triangle); } while (!dissolveedge.Equal(startghost)); return hullsize; }
SplayNode SplayInsert(SplayNode splayroot, Otri newkey, Point searchpoint) { SplayNode newsplaynode; newsplaynode = new SplayNode(); //poolalloc(m.splaynodes); splaynodes.Add(newsplaynode); newkey.Copy(ref newsplaynode.keyedge); newsplaynode.keydest = newkey.Dest(); if (splayroot == null) { newsplaynode.lchild = null; newsplaynode.rchild = null; } else if (RightOfHyperbola(ref splayroot.keyedge, searchpoint)) { newsplaynode.lchild = splayroot; newsplaynode.rchild = splayroot.rchild; splayroot.rchild = null; } else { newsplaynode.lchild = splayroot.lchild; newsplaynode.rchild = splayroot; splayroot.lchild = null; } return newsplaynode; }
/// <summary> /// Insert a vertex into a Delaunay triangulation, performing flips as necessary /// to maintain the Delaunay property. /// </summary> /// <param name="newvertex">The point to be inserted.</param> /// <param name="searchtri">The triangle to start the search.</param> /// <param name="splitseg">Segment to split.</param> /// <param name="segmentflaws">Check for creation of encroached subsegments.</param> /// <param name="triflaws">Check for creation of bad quality triangles.</param> /// <returns>If a duplicate vertex or violated segment does not prevent the /// vertex from being inserted, the return value will be ENCROACHINGVERTEX if /// the vertex encroaches upon a subsegment (and checking is enabled), or /// SUCCESSFULVERTEX otherwise. In either case, 'searchtri' is set to a handle /// whose origin is the newly inserted vertex.</returns> /// <remarks> /// The point 'newvertex' is located. If 'searchtri.triangle' is not NULL, /// the search for the containing triangle begins from 'searchtri'. If /// 'searchtri.triangle' is NULL, a full point location procedure is called. /// If 'insertvertex' is found inside a triangle, the triangle is split into /// three; if 'insertvertex' lies on an edge, the edge is split in two, /// thereby splitting the two adjacent triangles into four. Edge flips are /// used to restore the Delaunay property. If 'insertvertex' lies on an /// existing vertex, no action is taken, and the value DUPLICATEVERTEX is /// returned. On return, 'searchtri' is set to a handle whose origin is the /// existing vertex. /// /// InsertVertex() does not use flip() for reasons of speed; some /// information can be reused from edge flip to edge flip, like the /// locations of subsegments. /// /// Param 'splitseg': Normally, the parameter 'splitseg' is set to NULL, /// implying that no subsegment should be split. In this case, if 'insertvertex' /// is found to lie on a segment, no action is taken, and the value VIOLATINGVERTEX /// is returned. On return, 'searchtri' is set to a handle whose primary edge is the /// violated subsegment. /// If the calling routine wishes to split a subsegment by inserting a vertex in it, /// the parameter 'splitseg' should be that subsegment. In this case, 'searchtri' /// MUST be the triangle handle reached by pivoting from that subsegment; no point /// location is done. /// /// Param 'segmentflaws': Flags that indicate whether or not there should /// be checks for the creation of encroached subsegments. If a newly inserted /// vertex encroaches upon subsegments, these subsegments are added to the list /// of subsegments to be split if 'segmentflaws' is set. /// /// Param 'triflaws': Flags that indicate whether or not there should be /// checks for the creation of bad quality triangles. If bad triangles are /// created, these are added to the queue if 'triflaws' is set. /// </remarks> internal InsertVertexResult InsertVertex(Vertex newvertex, ref Otri searchtri, ref Osub splitseg, bool segmentflaws, bool triflaws) { Otri horiz = default(Otri); Otri top = default(Otri); Otri botleft = default(Otri), botright = default(Otri); Otri topleft = default(Otri), topright = default(Otri); Otri newbotleft = default(Otri), newbotright = default(Otri); Otri newtopright = default(Otri); Otri botlcasing = default(Otri), botrcasing = default(Otri); Otri toplcasing = default(Otri), toprcasing = default(Otri); Otri testtri = default(Otri); Osub botlsubseg = default(Osub), botrsubseg = default(Osub); Osub toplsubseg = default(Osub), toprsubseg = default(Osub); Osub brokensubseg = default(Osub); Osub checksubseg = default(Osub); Osub rightsubseg = default(Osub); Osub newsubseg = default(Osub); BadSubseg encroached; //FlipStacker newflip; Vertex first; Vertex leftvertex, rightvertex, botvertex, topvertex, farvertex; Vertex segmentorg, segmentdest; int region; double area; InsertVertexResult success; LocateResult intersect; bool doflip; bool mirrorflag; bool enq; if (splitseg.seg == null) { // Find the location of the vertex to be inserted. Check if a good // starting triangle has already been provided by the caller. if (searchtri.triangle == dummytri) { // Find a boundary triangle. horiz.triangle = dummytri; horiz.orient = 0; horiz.SymSelf(); // Search for a triangle containing 'newvertex'. intersect = locator.Locate(newvertex, ref horiz); } else { // Start searching from the triangle provided by the caller. searchtri.Copy(ref horiz); intersect = locator.PreciseLocate(newvertex, ref horiz, true); } } else { // The calling routine provides the subsegment in which // the vertex is inserted. searchtri.Copy(ref horiz); intersect = LocateResult.OnEdge; } if (intersect == LocateResult.OnVertex) { // There's already a vertex there. Return in 'searchtri' a triangle // whose origin is the existing vertex. horiz.Copy(ref searchtri); locator.Update(ref horiz); return InsertVertexResult.Duplicate; } if ((intersect == LocateResult.OnEdge) || (intersect == LocateResult.Outside)) { // The vertex falls on an edge or boundary. if (checksegments && (splitseg.seg == null)) { // Check whether the vertex falls on a subsegment. horiz.SegPivot(ref brokensubseg); if (brokensubseg.seg != dummysub) { // The vertex falls on a subsegment, and hence will not be inserted. if (segmentflaws) { enq = behavior.NoBisect != 2; if (enq && (behavior.NoBisect == 1)) { // This subsegment may be split only if it is an // internal boundary. horiz.Sym(ref testtri); enq = testtri.triangle != dummytri; } if (enq) { // Add the subsegment to the list of encroached subsegments. encroached = new BadSubseg(); encroached.encsubseg = brokensubseg; encroached.subsegorg = brokensubseg.Org(); encroached.subsegdest = brokensubseg.Dest(); quality.AddBadSubseg(encroached); } } // Return a handle whose primary edge contains the vertex, // which has not been inserted. horiz.Copy(ref searchtri); locator.Update(ref horiz); return InsertVertexResult.Violating; } } // Insert the vertex on an edge, dividing one triangle into two (if // the edge lies on a boundary) or two triangles into four. horiz.Lprev(ref botright); botright.Sym(ref botrcasing); horiz.Sym(ref topright); // Is there a second triangle? (Or does this edge lie on a boundary?) mirrorflag = topright.triangle != dummytri; if (mirrorflag) { topright.LnextSelf(); topright.Sym(ref toprcasing); MakeTriangle(ref newtopright); } else { // Splitting a boundary edge increases the number of boundary edges. hullsize++; } MakeTriangle(ref newbotright); // Set the vertices of changed and new triangles. rightvertex = horiz.Org(); leftvertex = horiz.Dest(); botvertex = horiz.Apex(); newbotright.SetOrg(botvertex); newbotright.SetDest(rightvertex); newbotright.SetApex(newvertex); horiz.SetOrg(newvertex); // Set the region of a new triangle. newbotright.triangle.region = botright.triangle.region; if (behavior.VarArea) { // Set the area constraint of a new triangle. newbotright.triangle.area = botright.triangle.area; } if (mirrorflag) { topvertex = topright.Dest(); newtopright.SetOrg(rightvertex); newtopright.SetDest(topvertex); newtopright.SetApex(newvertex); topright.SetOrg(newvertex); // Set the region of another new triangle. newtopright.triangle.region = topright.triangle.region; if (behavior.VarArea) { // Set the area constraint of another new triangle. newtopright.triangle.area = topright.triangle.area; } } // There may be subsegments that need to be bonded // to the new triangle(s). if (checksegments) { botright.SegPivot(ref botrsubseg); if (botrsubseg.seg != dummysub) { botright.SegDissolve(); newbotright.SegBond(ref botrsubseg); } if (mirrorflag) { topright.SegPivot(ref toprsubseg); if (toprsubseg.seg != dummysub) { topright.SegDissolve(); newtopright.SegBond(ref toprsubseg); } } } // Bond the new triangle(s) to the surrounding triangles. newbotright.Bond(ref botrcasing); newbotright.LprevSelf(); newbotright.Bond(ref botright); newbotright.LprevSelf(); if (mirrorflag) { newtopright.Bond(ref toprcasing); newtopright.LnextSelf(); newtopright.Bond(ref topright); newtopright.LnextSelf(); newtopright.Bond(ref newbotright); } if (splitseg.seg != null) { // Split the subsegment into two. splitseg.SetDest(newvertex); segmentorg = splitseg.SegOrg(); segmentdest = splitseg.SegDest(); splitseg.SymSelf(); splitseg.Pivot(ref rightsubseg); InsertSubseg(ref newbotright, splitseg.seg.boundary); newbotright.SegPivot(ref newsubseg); newsubseg.SetSegOrg(segmentorg); newsubseg.SetSegDest(segmentdest); splitseg.Bond(ref newsubseg); newsubseg.SymSelf(); newsubseg.Bond(ref rightsubseg); splitseg.SymSelf(); // Transfer the subsegment's boundary marker to the vertex if required. if (newvertex.mark == 0) { newvertex.mark = splitseg.seg.boundary; } } if (checkquality) { flipstack.Clear(); flipstack.Push(default(Otri)); // Dummy flip (see UndoVertex) flipstack.Push(horiz); } // Position 'horiz' on the first edge to check for // the Delaunay property. horiz.LnextSelf(); } else { // Insert the vertex in a triangle, splitting it into three. horiz.Lnext(ref botleft); horiz.Lprev(ref botright); botleft.Sym(ref botlcasing); botright.Sym(ref botrcasing); MakeTriangle(ref newbotleft); MakeTriangle(ref newbotright); // Set the vertices of changed and new triangles. rightvertex = horiz.Org(); leftvertex = horiz.Dest(); botvertex = horiz.Apex(); newbotleft.SetOrg(leftvertex); newbotleft.SetDest(botvertex); newbotleft.SetApex(newvertex); newbotright.SetOrg(botvertex); newbotright.SetDest(rightvertex); newbotright.SetApex(newvertex); horiz.SetApex(newvertex); // Set the region of the new triangles. newbotleft.triangle.region = horiz.triangle.region; newbotright.triangle.region = horiz.triangle.region; if (behavior.VarArea) { // Set the area constraint of the new triangles. area = horiz.triangle.area; newbotleft.triangle.area = area; newbotright.triangle.area = area; } // There may be subsegments that need to be bonded // to the new triangles. if (checksegments) { botleft.SegPivot(ref botlsubseg); if (botlsubseg.seg != dummysub) { botleft.SegDissolve(); newbotleft.SegBond(ref botlsubseg); } botright.SegPivot(ref botrsubseg); if (botrsubseg.seg != dummysub) { botright.SegDissolve(); newbotright.SegBond(ref botrsubseg); } } // Bond the new triangles to the surrounding triangles. newbotleft.Bond(ref botlcasing); newbotright.Bond(ref botrcasing); newbotleft.LnextSelf(); newbotright.LprevSelf(); newbotleft.Bond(ref newbotright); newbotleft.LnextSelf(); botleft.Bond(ref newbotleft); newbotright.LprevSelf(); botright.Bond(ref newbotright); if (checkquality) { flipstack.Clear(); flipstack.Push(horiz); } } // The insertion is successful by default, unless an encroached // subsegment is found. success = InsertVertexResult.Successful; // Circle around the newly inserted vertex, checking each edge opposite it // for the Delaunay property. Non-Delaunay edges are flipped. 'horiz' is // always the edge being checked. 'first' marks where to stop circling. first = horiz.Org(); rightvertex = first; leftvertex = horiz.Dest(); // Circle until finished. while (true) { // By default, the edge will be flipped. doflip = true; if (checksegments) { // Check for a subsegment, which cannot be flipped. horiz.SegPivot(ref checksubseg); if (checksubseg.seg != dummysub) { // The edge is a subsegment and cannot be flipped. doflip = false; if (segmentflaws) { // Does the new vertex encroach upon this subsegment? if (quality.CheckSeg4Encroach(ref checksubseg) > 0) { success = InsertVertexResult.Encroaching; } } } } if (doflip) { // Check if the edge is a boundary edge. horiz.Sym(ref top); if (top.triangle == dummytri) { // The edge is a boundary edge and cannot be flipped. doflip = false; } else { // Find the vertex on the other side of the edge. farvertex = top.Apex(); // In the incremental Delaunay triangulation algorithm, any of // 'leftvertex', 'rightvertex', and 'farvertex' could be vertices // of the triangular bounding box. These vertices must be // treated as if they are infinitely distant, even though their // "coordinates" are not. if ((leftvertex == infvertex1) || (leftvertex == infvertex2) || (leftvertex == infvertex3)) { // 'leftvertex' is infinitely distant. Check the convexity of // the boundary of the triangulation. 'farvertex' might be // infinite as well, but trust me, this same condition should // be applied. doflip = Primitives.CounterClockwise(newvertex, rightvertex, farvertex) > 0.0; } else if ((rightvertex == infvertex1) || (rightvertex == infvertex2) || (rightvertex == infvertex3)) { // 'rightvertex' is infinitely distant. Check the convexity of // the boundary of the triangulation. 'farvertex' might be // infinite as well, but trust me, this same condition should // be applied. doflip = Primitives.CounterClockwise(farvertex, leftvertex, newvertex) > 0.0; } else if ((farvertex == infvertex1) || (farvertex == infvertex2) || (farvertex == infvertex3)) { // 'farvertex' is infinitely distant and cannot be inside // the circumcircle of the triangle 'horiz'. doflip = false; } else { // Test whether the edge is locally Delaunay. doflip = Primitives.InCircle(leftvertex, newvertex, rightvertex, farvertex) > 0.0; } if (doflip) { // We made it! Flip the edge 'horiz' by rotating its containing // quadrilateral (the two triangles adjacent to 'horiz'). // Identify the casing of the quadrilateral. top.Lprev(ref topleft); topleft.Sym(ref toplcasing); top.Lnext(ref topright); topright.Sym(ref toprcasing); horiz.Lnext(ref botleft); botleft.Sym(ref botlcasing); horiz.Lprev(ref botright); botright.Sym(ref botrcasing); // Rotate the quadrilateral one-quarter turn counterclockwise. topleft.Bond(ref botlcasing); botleft.Bond(ref botrcasing); botright.Bond(ref toprcasing); topright.Bond(ref toplcasing); if (checksegments) { // Check for subsegments and rebond them to the quadrilateral. topleft.SegPivot(ref toplsubseg); botleft.SegPivot(ref botlsubseg); botright.SegPivot(ref botrsubseg); topright.SegPivot(ref toprsubseg); if (toplsubseg.seg == dummysub) { topright.SegDissolve(); } else { topright.SegBond(ref toplsubseg); } if (botlsubseg.seg == dummysub) { topleft.SegDissolve(); } else { topleft.SegBond(ref botlsubseg); } if (botrsubseg.seg == dummysub) { botleft.SegDissolve(); } else { botleft.SegBond(ref botrsubseg); } if (toprsubseg.seg == dummysub) { botright.SegDissolve(); } else { botright.SegBond(ref toprsubseg); } } // New vertex assignments for the rotated quadrilateral. horiz.SetOrg(farvertex); horiz.SetDest(newvertex); horiz.SetApex(rightvertex); top.SetOrg(newvertex); top.SetDest(farvertex); top.SetApex(leftvertex); // Assign region. // TODO: check region ok (no Math.Min necessary) region = Math.Min(top.triangle.region, horiz.triangle.region); top.triangle.region = region; horiz.triangle.region = region; if (behavior.VarArea) { if ((top.triangle.area <= 0.0) || (horiz.triangle.area <= 0.0)) { area = -1.0; } else { // Take the average of the two triangles' area constraints. // This prevents small area constraints from migrating a // long, long way from their original location due to flips. area = 0.5 * (top.triangle.area + horiz.triangle.area); } top.triangle.area = area; horiz.triangle.area = area; } if (checkquality) { flipstack.Push(horiz); } // On the next iterations, consider the two edges that were exposed (this // is, are now visible to the newly inserted vertex) by the edge flip. horiz.LprevSelf(); leftvertex = farvertex; } } } if (!doflip) { // The handle 'horiz' is accepted as locally Delaunay. if (triflaws) { // Check the triangle 'horiz' for quality. quality.TestTriangle(ref horiz); } // Look for the next edge around the newly inserted vertex. horiz.LnextSelf(); horiz.Sym(ref testtri); // Check for finishing a complete revolution about the new vertex, or // falling outside of the triangulation. The latter will happen when // a vertex is inserted at a boundary. if ((leftvertex == first) || (testtri.triangle == dummytri)) { // We're done. Return a triangle whose origin is the new vertex. horiz.Lnext(ref searchtri); Otri recenttri = default(Otri); horiz.Lnext(ref recenttri); locator.Update(ref recenttri); return success; } // Finish finding the next edge around the newly inserted vertex. testtri.Lnext(ref horiz); rightvertex = leftvertex; leftvertex = horiz.Dest(); } } }
public void Update(ref Otri otri) { otri.Copy(ref recenttri); }
/// <summary> /// Given the triangulation, and a vertex returns the minimum distance to the /// vertices of the triangle where the given vertex located. /// </summary> /// <param name="newlocX"></param> /// <param name="newlocY"></param> /// <param name="searchtri"></param> /// <returns></returns> private double MinDistanceToNeighbor(double newlocX, double newlocY, ref Otri searchtri) { Otri horiz = default(Otri); // for search operation LocateResult intersect = LocateResult.Outside; Vertex v1, v2, v3, torg, tdest; double d1, d2, d3, ahead; //triangle ptr; // Temporary variable used by sym(). Point newvertex = new Point(newlocX, newlocY); // printf("newvertex %f,%f\n", newvertex[0], newvertex[1]); // Find the location of the vertex to be inserted. Check if a good // starting triangle has already been provided by the caller. // Find a boundary triangle. //horiz.tri = m.dummytri; //horiz.orient = 0; //horiz.symself(); // Search for a triangle containing 'newvertex'. // Start searching from the triangle provided by the caller. // Where are we? torg = searchtri.Org(); tdest = searchtri.Dest(); // Check the starting triangle's vertices. if ((torg.x == newvertex.x) && (torg.y == newvertex.y)) { intersect = LocateResult.OnVertex; searchtri.Copy(ref horiz); } else if ((tdest.x == newvertex.x) && (tdest.y == newvertex.y)) { searchtri.LnextSelf(); intersect = LocateResult.OnVertex; searchtri.Copy(ref horiz); } else { // Orient 'searchtri' to fit the preconditions of calling preciselocate(). ahead = Primitives.CounterClockwise(torg, tdest, newvertex); if (ahead < 0.0) { // Turn around so that 'searchpoint' is to the left of the // edge specified by 'searchtri'. searchtri.SymSelf(); searchtri.Copy(ref horiz); intersect = mesh.locator.PreciseLocate(newvertex, ref horiz, false); } else if (ahead == 0.0) { // Check if 'searchpoint' is between 'torg' and 'tdest'. if (((torg.x < newvertex.x) == (newvertex.x < tdest.x)) && ((torg.y < newvertex.y) == (newvertex.y < tdest.y))) { intersect = LocateResult.OnEdge; searchtri.Copy(ref horiz); } } else { searchtri.Copy(ref horiz); intersect = mesh.locator.PreciseLocate(newvertex, ref horiz, false); } } if (intersect == LocateResult.OnVertex || intersect == LocateResult.Outside) { // set distance to 0 //m.VertexDealloc(newvertex); return 0.0; } else { // intersect == ONEDGE || intersect == INTRIANGLE // find the triangle vertices v1 = horiz.Org(); v2 = horiz.Dest(); v3 = horiz.Apex(); d1 = (v1.x - newvertex.x) * (v1.x - newvertex.x) + (v1.y - newvertex.y) * (v1.y - newvertex.y); d2 = (v2.x - newvertex.x) * (v2.x - newvertex.x) + (v2.y - newvertex.y) * (v2.y - newvertex.y); d3 = (v3.x - newvertex.x) * (v3.x - newvertex.x) + (v3.y - newvertex.y) * (v3.y - newvertex.y); //m.VertexDealloc(newvertex); // find minimum of the distance if (d1 <= d2 && d1 <= d3) { return d1; } else if (d2 <= d3) { return d2; } else { return d3; } } }
/// <summary> /// Test a triangle for quality and size. /// </summary> /// <param name="testtri">Triangle to check.</param> /// <remarks> /// 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. /// </remarks> 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) { // 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.triangle.area) && (testtri.triangle.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 && userTest != null) { if (userTest(torg, tdest, tapex, 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.SegPivot(ref testsub); if (testsub.seg == Mesh.dummysub) { // No common segment. Find a subsegment that contains 'torg'. tri1.Copy(ref tri2); do { tri1.OprevSelf(); tri1.SegPivot(ref testsub); } while (testsub.seg == Mesh.dummysub); // Find the endpoints of the containing segment. org1 = testsub.SegOrg(); dest1 = testsub.SegDest(); // Find a subsegment that contains 'tdest'. do { tri2.DnextSelf(); tri2.SegPivot(ref testsub); } while (testsub.seg == Mesh.dummysub); // 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); } }