/// <summary> /// Form a Delaunay triangulation by incrementally inserting vertices. /// </summary> /// <returns>Returns the number of edges on the convex hull of the /// triangulation.</returns> public IMesh Triangulate(IList<Vertex> points, Configuration config) { this.mesh = new Mesh(config); this.mesh.TransferNodes(points); Otri starttri = new Otri(); // Create a triangular bounding box. GetBoundingBox(); foreach (var v in mesh.vertices.Values) { starttri.tri = mesh.dummytri; Osub tmp = default(Osub); if (mesh.InsertVertex(v, ref starttri, ref tmp, false, false) == InsertVertexResult.Duplicate) { if (Log.Verbose) { Log.Instance.Warning("A duplicate vertex appeared and was ignored.", "Incremental.Triangulate()"); } v.type = VertexType.UndeadVertex; mesh.undeads++; } } // Remove the bounding box. this.mesh.hullsize = RemoveBox(); return this.mesh; }
private void FormTopology_Load(object sender, EventArgs e) { mesh = (Mesh)GenericMesher.StructuredMesh(new Rectangle(0.0, 0.0, 4.0, 4.0), 4, 4); renderControl.Initialize(mesh); topoControlView.PrimitiveCommandInvoked += PrimitiveCommandHandler; current = default(Otri); }
/// <summary> /// Find a new location for a Steiner point. /// </summary> /// <param name="org"></param> /// <param name="dest"></param> /// <param name="apex"></param> /// <param name="xi"></param> /// <param name="eta"></param> /// <param name="offcenter"></param> /// <param name="badotri"></param> /// <returns></returns> public Point FindLocation(Vertex org, Vertex dest, Vertex apex, ref double xi, ref double eta, bool offcenter, Otri badotri) { // Based on using -U switch, call the corresponding function if (behavior.MaxAngle == 0.0) { return FindNewLocationWithoutMaxAngle(org, dest, apex, ref xi, ref eta, true, badotri); } // With max angle return FindNewLocation(org, dest, apex, ref xi, ref eta, true, badotri); }
/// <summary> /// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*] /// </summary> public void Dprev(ref Otri ot) { //Lnext(ref ot); ot.tri = tri; ot.orient = plus1Mod3[orient]; //ot.SymSelf(); int tmp = ot.orient; ot.orient = ot.tri.neighbors[tmp].orient; ot.tri = ot.tri.neighbors[tmp].tri; }
public void Update(Otri otri) { if (otri.Triangle == null || otri.Triangle.ID < 0) { renderer.SelectTriangle(null, null, null); } else { renderer.SelectTriangle(otri.Triangle, otri.Org(), otri.Dest()); } this.Render(); }
/// <summary> /// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**] /// </summary> public void Rprev(ref Otri ot) { //Sym(ref ot); ot.tri = tri.neighbors[orient].tri; ot.orient = tri.neighbors[orient].orient; //ot.LprevSelf(); ot.orient = minus1Mod3[ot.orient]; //ot.SymSelf(); int tmp = ot.orient; ot.orient = ot.tri.neighbors[tmp].orient; ot.tri = ot.tri.neighbors[tmp].tri; }
/// <summary> /// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca] /// </summary> public void Lnext(ref Otri ot) { ot.tri = tri; ot.orient = plus1Mod3[orient]; }
/// <summary> /// Finds a triangle abutting a subsegment. /// </summary> internal void Pivot(ref Otri ot) { ot = seg.triangles[orient]; }
/// <summary> /// Test for equality of oriented triangles. /// </summary> public bool Equals(Otri ot) { return((tri == ot.tri) && (orient == ot.orient)); }
/// <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; double searchdist, dist; double 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.tri != null) { if (!Otri.IsDead(recenttri.tri)) { 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(); foreach (var t in sampler) { sampletri.tri = t; if (!Otri.IsDead(sampletri.tri)) { 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.Lnext(); return LocateResult.OnVertex; } // Orient 'searchtri' to fit the preconditions of calling preciselocate(). ahead = predicates.CounterClockwise(torg, tdest, searchpoint); if (ahead < 0.0) { // Turn around so that 'searchpoint' is to the left of the // edge specified by 'searchtri'. searchtri.Sym(); } 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); }
/// <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.tri.id == DUMMY) { // Find a boundary triangle. horiz.tri = dummytri; horiz.orient = 0; horiz.Sym(); // 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.Pivot(ref brokensubseg); if (brokensubseg.seg.hash != DUMMY) { // 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.tri.id != DUMMY; } if (enq) { // Add the subsegment to the list of encroached subsegments. encroached = new BadSubseg(); encroached.subseg = brokensubseg; encroached.org = brokensubseg.Org(); encroached.dest = brokensubseg.Dest(); qualityMesher.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.tri.id != DUMMY; if (mirrorflag) { topright.Lnext(); 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.tri.label = botright.tri.label; if (behavior.VarArea) { // Set the area constraint of a new triangle. newbotright.tri.area = botright.tri.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.tri.label = topright.tri.label; if (behavior.VarArea) { // Set the area constraint of another new triangle. newtopright.tri.area = topright.tri.area; } } // There may be subsegments that need to be bonded // to the new triangle(s). if (checksegments) { botright.Pivot(ref botrsubseg); if (botrsubseg.seg.hash != DUMMY) { botright.SegDissolve(dummysub); newbotright.SegBond(ref botrsubseg); } if (mirrorflag) { topright.Pivot(ref toprsubseg); if (toprsubseg.seg.hash != DUMMY) { topright.SegDissolve(dummysub); newtopright.SegBond(ref toprsubseg); } } } // Bond the new triangle(s) to the surrounding triangles. newbotright.Bond(ref botrcasing); newbotright.Lprev(); newbotright.Bond(ref botright); newbotright.Lprev(); if (mirrorflag) { newtopright.Bond(ref toprcasing); newtopright.Lnext(); newtopright.Bond(ref topright); newtopright.Lnext(); newtopright.Bond(ref newbotright); } if (splitseg.seg != null) { // Split the subsegment into two. splitseg.SetDest(newvertex); segmentorg = splitseg.SegOrg(); segmentdest = splitseg.SegDest(); splitseg.Sym(); splitseg.Pivot(ref rightsubseg); InsertSubseg(ref newbotright, splitseg.seg.boundary); newbotright.Pivot(ref newsubseg); newsubseg.SetSegOrg(segmentorg); newsubseg.SetSegDest(segmentdest); splitseg.Bond(ref newsubseg); newsubseg.Sym(); newsubseg.Bond(ref rightsubseg); splitseg.Sym(); // Transfer the subsegment's boundary marker to the vertex if required. if (newvertex.label == 0) { newvertex.label = 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.Lnext(); } 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.tri.label = horiz.tri.label; newbotright.tri.label = horiz.tri.label; if (behavior.VarArea) { // Set the area constraint of the new triangles. area = horiz.tri.area; newbotleft.tri.area = area; newbotright.tri.area = area; } // There may be subsegments that need to be bonded // to the new triangles. if (checksegments) { botleft.Pivot(ref botlsubseg); if (botlsubseg.seg.hash != DUMMY) { botleft.SegDissolve(dummysub); newbotleft.SegBond(ref botlsubseg); } botright.Pivot(ref botrsubseg); if (botrsubseg.seg.hash != DUMMY) { botright.SegDissolve(dummysub); newbotright.SegBond(ref botrsubseg); } } // Bond the new triangles to the surrounding triangles. newbotleft.Bond(ref botlcasing); newbotright.Bond(ref botrcasing); newbotleft.Lnext(); newbotright.Lprev(); newbotleft.Bond(ref newbotright); newbotleft.Lnext(); botleft.Bond(ref newbotleft); newbotright.Lprev(); 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; if (newvertex.tri.tri != null) { // Store the coordinates of the triangle that contains newvertex. newvertex.tri.SetOrg(rightvertex); newvertex.tri.SetDest(leftvertex); newvertex.tri.SetApex(botvertex); } // 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.Pivot(ref checksubseg); if (checksubseg.seg.hash != DUMMY) { // The edge is a subsegment and cannot be flipped. doflip = false; if (segmentflaws) { // Does the new vertex encroach upon this subsegment? if (qualityMesher.CheckSeg4Encroach(ref checksubseg) > 0) { success = InsertVertexResult.Encroaching; } } } } if (doflip) { // Check if the edge is a boundary edge. horiz.Sym(ref top); if (top.tri.id == DUMMY) { // 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 = predicates.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 = predicates.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 = predicates.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.Pivot(ref toplsubseg); botleft.Pivot(ref botlsubseg); botright.Pivot(ref botrsubseg); topright.Pivot(ref toprsubseg); if (toplsubseg.seg.hash == DUMMY) { topright.SegDissolve(dummysub); } else { topright.SegBond(ref toplsubseg); } if (botlsubseg.seg.hash == DUMMY) { topleft.SegDissolve(dummysub); } else { topleft.SegBond(ref botlsubseg); } if (botrsubseg.seg.hash == DUMMY) { botleft.SegDissolve(dummysub); } else { botleft.SegBond(ref botrsubseg); } if (toprsubseg.seg.hash == DUMMY) { botright.SegDissolve(dummysub); } 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.tri.label, horiz.tri.label); top.tri.label = region; horiz.tri.label = region; if (behavior.VarArea) { if ((top.tri.area <= 0.0) || (horiz.tri.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.tri.area + horiz.tri.area); } top.tri.area = area; horiz.tri.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.Lprev(); leftvertex = farvertex; } } } if (!doflip) { // The handle 'horiz' is accepted as locally Delaunay. if (triflaws) { // Check the triangle 'horiz' for quality. qualityMesher.TestTriangle(ref horiz); } // Look for the next edge around the newly inserted vertex. horiz.Lnext(); 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.tri.id == DUMMY)) { // 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(); } } }
/// <summary> /// Delete a vertex from a Delaunay triangulation, ensuring that the /// triangulation remains Delaunay. /// </summary> /// <param name="deltri"></param> /// <remarks>The origin of 'deltri' is deleted. The union of the triangles /// adjacent to this vertex is a polygon, for which the Delaunay triangulation /// is found. Two triangles are removed from the mesh. /// /// Only interior vertices that do not lie on segments or boundaries /// may be deleted. /// </remarks> internal void DeleteVertex(ref Otri deltri) { Otri countingtri = default(Otri); Otri firstedge = default(Otri), lastedge = default(Otri); Otri deltriright = default(Otri); Otri lefttri = default(Otri), righttri = default(Otri); Otri leftcasing = default(Otri), rightcasing = default(Otri); Osub leftsubseg = default(Osub), rightsubseg = default(Osub); Vertex delvertex; Vertex neworg; int edgecount; delvertex = deltri.Org(); VertexDealloc(delvertex); // Count the degree of the vertex being deleted. deltri.Onext(ref countingtri); edgecount = 1; while (!deltri.Equals(countingtri)) { edgecount++; countingtri.Onext(); } if (edgecount > 3) { // Triangulate the polygon defined by the union of all triangles // adjacent to the vertex being deleted. Check the quality of // the resulting triangles. deltri.Onext(ref firstedge); deltri.Oprev(ref lastedge); TriangulatePolygon(firstedge, lastedge, edgecount, false, behavior.NoBisect == 0); } // Splice out two triangles. deltri.Lprev(ref deltriright); deltri.Dnext(ref lefttri); lefttri.Sym(ref leftcasing); deltriright.Oprev(ref righttri); righttri.Sym(ref rightcasing); deltri.Bond(ref leftcasing); deltriright.Bond(ref rightcasing); lefttri.Pivot(ref leftsubseg); if (leftsubseg.seg.hash != DUMMY) { deltri.SegBond(ref leftsubseg); } righttri.Pivot(ref rightsubseg); if (rightsubseg.seg.hash != DUMMY) { deltriright.SegBond(ref rightsubseg); } // Set the new origin of 'deltri' and check its quality. neworg = lefttri.Org(); deltri.SetOrg(neworg); if (behavior.NoBisect == 0) { qualityMesher.TestTriangle(ref deltri); } // Delete the two spliced-out triangles. TriangleDealloc(lefttri.tri); TriangleDealloc(righttri.tri); }
SplayNode CircleTopInsert(SplayNode splayroot, Otri newkey, Vertex pa, Vertex pb, Vertex pc, double topy) { double ccwabc; double xac, yac, xbc, ybc; double aclen2, bclen2; Point searchpoint = new Point(); // TODO: mesh.nextras Otri dummytri = default(Otri); ccwabc = predicates.CounterClockwise(pa, pb, pc); xac = pa.x - pc.x; yac = pa.y - pc.y; xbc = pb.x - pc.x; ybc = pb.y - pc.y; aclen2 = xac * xac + yac * yac; bclen2 = xbc * xbc + ybc * ybc; searchpoint.x = pc.x - (yac * bclen2 - ybc * aclen2) / (2.0 * ccwabc); searchpoint.y = topy; return SplayInsert(Splay(splayroot, searchpoint, ref dummytri), newkey, searchpoint); }
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.Onext(); farrightflag = searchtri.Equals(bottommost); } farright = farrightflag; return splayroot; }
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; }
SplayNode Splay(SplayNode splaytree, Point searchpoint, ref Otri searchtri) { SplayNode child, grandchild; SplayNode lefttree, righttree; SplayNode leftright; Vertex checkvertex; bool rightofroot, rightofchild; if (splaytree == null) { return null; } checkvertex = splaytree.keyedge.Dest(); if (checkvertex == splaytree.keydest) { rightofroot = RightOfHyperbola(ref splaytree.keyedge, searchpoint); if (rightofroot) { splaytree.keyedge.Copy(ref searchtri); child = splaytree.rchild; } else { child = splaytree.lchild; } if (child == null) { return splaytree; } checkvertex = child.keyedge.Dest(); if (checkvertex != child.keydest) { child = Splay(child, searchpoint, ref searchtri); if (child == null) { if (rightofroot) { splaytree.rchild = null; } else { splaytree.lchild = null; } return splaytree; } } rightofchild = RightOfHyperbola(ref child.keyedge, searchpoint); if (rightofchild) { child.keyedge.Copy(ref searchtri); grandchild = Splay(child.rchild, searchpoint, ref searchtri); child.rchild = grandchild; } else { grandchild = Splay(child.lchild, searchpoint, ref searchtri); child.lchild = grandchild; } if (grandchild == null) { if (rightofroot) { splaytree.rchild = child.lchild; child.lchild = splaytree; } else { splaytree.lchild = child.rchild; child.rchild = splaytree; } return child; } if (rightofchild) { if (rightofroot) { splaytree.rchild = child.lchild; child.lchild = splaytree; } else { splaytree.lchild = grandchild.rchild; grandchild.rchild = splaytree; } child.rchild = grandchild.lchild; grandchild.lchild = child; } else { if (rightofroot) { splaytree.rchild = grandchild.lchild; grandchild.lchild = splaytree; } else { splaytree.lchild = child.rchild; child.rchild = splaytree; } child.lchild = grandchild.rchild; grandchild.rchild = child; } return grandchild; } else { lefttree = Splay(splaytree.lchild, searchpoint, ref searchtri); righttree = Splay(splaytree.rchild, searchpoint, ref searchtri); splaynodes.Remove(splaytree); if (lefttree == null) { return righttree; } else if (righttree == null) { return lefttree; } else if (lefttree.rchild == null) { lefttree.rchild = righttree.lchild; righttree.lchild = lefttree; return righttree; } else if (righttree.lchild == null) { righttree.lchild = lefttree.rchild; lefttree.rchild = righttree; return lefttree; } else { // printf("Holy Toledo!!!\n"); leftright = lefttree.rchild; while (leftright.rchild != null) { leftright = leftright.rchild; } leftright.rchild = righttree; return lefttree; } } }
// The following primitives are all described by Guibas and Stolfi. // However, Guibas and Stolfi use an edge-based data structure, // whereas I use a triangle-based data structure. // // lnext: finds the next edge (counterclockwise) of a triangle. // // onext: spins counterclockwise around a vertex; that is, it finds // the next edge with the same origin in the counterclockwise direction. This // edge is part of a different triangle. // // oprev: spins clockwise around a vertex; that is, it finds the // next edge with the same origin in the clockwise direction. This edge is // part of a different triangle. // // dnext: spins counterclockwise around a vertex; that is, it finds // the next edge with the same destination in the counterclockwise direction. // This edge is part of a different triangle. // // dprev: spins clockwise around a vertex; that is, it finds the // next edge with the same destination in the clockwise direction. This edge // is part of a different triangle. // // rnext: moves one edge counterclockwise about the adjacent // triangle. (It's best understood by reading Guibas and Stolfi. It // involves changing triangles twice.) // // rprev: moves one edge clockwise about the adjacent triangle. // (It's best understood by reading Guibas and Stolfi. It involves // changing triangles twice.) /// <summary> /// Find the abutting triangle; same edge. [sym(abc) -> ba*] /// </summary> /// Note that the edge direction is necessarily reversed, because the handle specified /// by an oriented triangle is directed counterclockwise around the triangle. /// </remarks> public void Sym(ref Otri ot) { ot.tri = tri.neighbors[orient].tri; ot.orient = tri.neighbors[orient].orient; }
/// <summary> /// Transform two triangles to two different triangles by flipping an edge /// clockwise within a quadrilateral. Reverses the flip() operation so that /// the data structures representing the triangles are back where they were /// before the flip(). /// </summary> /// <param name="flipedge"></param> /// <remarks> /// See above Flip() remarks for more information. /// /// Upon completion of this routine, the 'flipedge' handle holds the edge /// cd of triangle cdb, and is directed up, from vertex c to vertex d. /// (Hence, the two triangles have rotated clockwise.) /// </remarks> internal void Unflip(ref Otri flipedge) { Otri botleft = default(Otri), botright = default(Otri); Otri topleft = default(Otri), topright = default(Otri); Otri top = default(Otri); Otri botlcasing = default(Otri), botrcasing = default(Otri); Otri toplcasing = default(Otri), toprcasing = default(Otri); Osub botlsubseg = default(Osub), botrsubseg = default(Osub); Osub toplsubseg = default(Osub), toprsubseg = default(Osub); Vertex leftvertex, rightvertex, botvertex; Vertex farvertex; // Identify the vertices of the quadrilateral. rightvertex = flipedge.Org(); leftvertex = flipedge.Dest(); botvertex = flipedge.Apex(); flipedge.Sym(ref top); farvertex = top.Apex(); // Identify the casing of the quadrilateral. top.Lprev(ref topleft); topleft.Sym(ref toplcasing); top.Lnext(ref topright); topright.Sym(ref toprcasing); flipedge.Lnext(ref botleft); botleft.Sym(ref botlcasing); flipedge.Lprev(ref botright); botright.Sym(ref botrcasing); // Rotate the quadrilateral one-quarter turn clockwise. topleft.Bond(ref toprcasing); botleft.Bond(ref toplcasing); botright.Bond(ref botlcasing); topright.Bond(ref botrcasing); if (checksegments) { // Check for subsegments and rebond them to the quadrilateral. topleft.Pivot(ref toplsubseg); botleft.Pivot(ref botlsubseg); botright.Pivot(ref botrsubseg); topright.Pivot(ref toprsubseg); if (toplsubseg.seg.hash == DUMMY) { botleft.SegDissolve(dummysub); } else { botleft.SegBond(ref toplsubseg); } if (botlsubseg.seg.hash == DUMMY) { botright.SegDissolve(dummysub); } else { botright.SegBond(ref botlsubseg); } if (botrsubseg.seg.hash == DUMMY) { topright.SegDissolve(dummysub); } else { topright.SegBond(ref botrsubseg); } if (toprsubseg.seg.hash == DUMMY) { topleft.SegDissolve(dummysub); } else { topleft.SegBond(ref toprsubseg); } } // New vertex assignments for the rotated quadrilateral. flipedge.SetOrg(botvertex); flipedge.SetDest(farvertex); flipedge.SetApex(leftvertex); top.SetOrg(farvertex); top.SetDest(botvertex); top.SetApex(rightvertex); }
bool RightOfHyperbola(ref Otri fronttri, Point newsite) { Vertex leftvertex, rightvertex; double dxa, dya, dxb, dyb; Statistic.HyperbolaCount++; leftvertex = fronttri.Dest(); rightvertex = fronttri.Apex(); if ((leftvertex.y < rightvertex.y) || ((leftvertex.y == rightvertex.y) && (leftvertex.x < rightvertex.x))) { if (newsite.x >= rightvertex.x) { return true; } } else { if (newsite.x <= leftvertex.x) { return false; } } dxa = leftvertex.x - newsite.x; dya = leftvertex.y - newsite.y; dxb = rightvertex.x - newsite.x; dyb = rightvertex.y - newsite.y; return dya * (dxb * dxb + dyb * dyb) > dyb * (dxa * dxa + dya * dya); }
/// <summary> /// Find the Delaunay triangulation of a polygon that has a certain "nice" shape. /// This includes the polygons that result from deletion of a vertex or insertion /// of a segment. /// </summary> /// <param name="firstedge">The primary edge of the first triangle.</param> /// <param name="lastedge">The primary edge of the last triangle.</param> /// <param name="edgecount">The number of sides of the polygon, including its /// base.</param> /// <param name="doflip">A flag, wether to perform the last flip.</param> /// <param name="triflaws">A flag that determines whether the new triangles should /// be tested for quality, and enqueued if they are bad.</param> /// <remarks> // This is a conceptually difficult routine. The starting assumption is // that we have a polygon with n sides. n - 1 of these sides are currently // represented as edges in the mesh. One side, called the "base", need not // be. // // Inside the polygon is a structure I call a "fan", consisting of n - 1 // triangles that share a common origin. For each of these triangles, the // edge opposite the origin is one of the sides of the polygon. The // primary edge of each triangle is the edge directed from the origin to // the destination; note that this is not the same edge that is a side of // the polygon. 'firstedge' is the primary edge of the first triangle. // From there, the triangles follow in counterclockwise order about the // polygon, until 'lastedge', the primary edge of the last triangle. // 'firstedge' and 'lastedge' are probably connected to other triangles // beyond the extremes of the fan, but their identity is not important, as // long as the fan remains connected to them. // // Imagine the polygon oriented so that its base is at the bottom. This // puts 'firstedge' on the far right, and 'lastedge' on the far left. // The right vertex of the base is the destination of 'firstedge', and the // left vertex of the base is the apex of 'lastedge'. // // The challenge now is to find the right sequence of edge flips to // transform the fan into a Delaunay triangulation of the polygon. Each // edge flip effectively removes one triangle from the fan, committing it // to the polygon. The resulting polygon has one fewer edge. If 'doflip' // is set, the final flip will be performed, resulting in a fan of one // (useless?) triangle. If 'doflip' is not set, the final flip is not // performed, resulting in a fan of two triangles, and an unfinished // triangular polygon that is not yet filled out with a single triangle. // On completion of the routine, 'lastedge' is the last remaining triangle, // or the leftmost of the last two. // // Although the flips are performed in the order described above, the // decisions about what flips to perform are made in precisely the reverse // order. The recursive triangulatepolygon() procedure makes a decision, // uses up to two recursive calls to triangulate the "subproblems" // (polygons with fewer edges), and then performs an edge flip. // // The "decision" it makes is which vertex of the polygon should be // connected to the base. This decision is made by testing every possible // vertex. Once the best vertex is found, the two edges that connect this // vertex to the base become the bases for two smaller polygons. These // are triangulated recursively. Unfortunately, this approach can take // O(n^2) time not only in the worst case, but in many common cases. It's // rarely a big deal for vertex deletion, where n is rarely larger than // ten, but it could be a big deal for segment insertion, especially if // there's a lot of long segments that each cut many triangles. I ought to // code a faster algorithm some day. /// </remarks> private void TriangulatePolygon(Otri firstedge, Otri lastedge, int edgecount, bool doflip, bool triflaws) { Otri testtri = default(Otri); Otri besttri = default(Otri); Otri tempedge = default(Otri); Vertex leftbasevertex, rightbasevertex; Vertex testvertex; Vertex bestvertex; int bestnumber = 1; // Identify the base vertices. leftbasevertex = lastedge.Apex(); rightbasevertex = firstedge.Dest(); // Find the best vertex to connect the base to. firstedge.Onext(ref besttri); bestvertex = besttri.Dest(); besttri.Copy(ref testtri); for (int i = 2; i <= edgecount - 2; i++) { testtri.Onext(); testvertex = testtri.Dest(); // Is this a better vertex? if (predicates.InCircle(leftbasevertex, rightbasevertex, bestvertex, testvertex) > 0.0) { testtri.Copy(ref besttri); bestvertex = testvertex; bestnumber = i; } } if (bestnumber > 1) { // Recursively triangulate the smaller polygon on the right. besttri.Oprev(ref tempedge); TriangulatePolygon(firstedge, tempedge, bestnumber + 1, true, triflaws); } if (bestnumber < edgecount - 2) { // Recursively triangulate the smaller polygon on the left. besttri.Sym(ref tempedge); TriangulatePolygon(besttri, lastedge, edgecount - bestnumber, true, triflaws); // Find 'besttri' again; it may have been lost to edge flips. tempedge.Sym(ref besttri); } if (doflip) { // Do one final edge flip. Flip(ref besttri); if (triflaws) { // Check the quality of the newly committed triangle. besttri.Sym(ref testtri); qualityMesher.TestTriangle(ref testtri); } } // Return the base triangle. besttri.Copy(ref lastedge); }
void Check4DeadEvent(ref Otri checktri, SweepEvent[] eventheap, ref int heapsize) { SweepEvent deadevent; SweepEventVertex eventvertex; int eventnum = -1; eventvertex = checktri.Org() as SweepEventVertex; if (eventvertex != null) { deadevent = eventvertex.evt; eventnum = deadevent.heapposition; HeapDelete(eventheap, heapsize, eventnum); heapsize--; checktri.SetOrg(null); } }
/// <summary> /// Create a new triangle with orientation zero. /// </summary> /// <param name="newotri">Reference to the new triangle.</param> internal void MakeTriangle(ref Otri newotri) { Triangle tri = triangles.Get(); //tri.id = tri.hash; tri.subsegs[0].seg = dummysub; tri.subsegs[1].seg = dummysub; tri.subsegs[2].seg = dummysub; tri.neighbors[0].tri = dummytri; tri.neighbors[1].tri = dummytri; tri.neighbors[2].tri = dummytri; newotri.tri = tri; newotri.orient = 0; }
/// <summary> /// Checks if smothing is possible for a given bad triangle. /// </summary> /// <param name="badotri"></param> /// <param name="torg"></param> /// <param name="tdest"></param> /// <param name="tapex"></param> /// <param name="newloc">The new location for the point, if somothing is possible.</param> /// <returns>Returns 1, 2 or 3 if smoothing will work, 0 otherwise.</returns> private int DoSmoothing(Otri badotri, Vertex torg, Vertex tdest, Vertex tapex, ref double[] newloc) { int numpoints_p = 0;// keeps the number of points in a star of point p, q, r int numpoints_q = 0; int numpoints_r = 0; //int i; double[] possibilities = new double[6];//there can be more than one possibilities int num_pos = 0; // number of possibilities int flag1 = 0, flag2 = 0, flag3 = 0; bool newLocFound = false; //vertex v1, v2, v3; // for ccw test //double p1[2], p2[2], p3[2]; //double temp[2]; //********************* TRY TO RELOCATE POINT "p" *************** // get the surrounding points of p, so this gives us the triangles numpoints_p = GetStarPoints(badotri, torg, tdest, tapex, 1, ref points_p); // check if the points in counterclockwise order // p1[0] = points_p[0]; p1[1] = points_p[1]; // p2[0] = points_p[2]; p2[1] = points_p[3]; // p3[0] = points_p[4]; p3[1] = points_p[5]; // v1 = (vertex)p1; v2 = (vertex)p2; v3 = (vertex)p3; // if(counterclockwise(m,b,v1,v2,v3) < 0){ // // reverse the order to ccw // for(i = 0; i < numpoints_p/2; i++){ // temp[0] = points_p[2*i]; // temp[1] = points_p[2*i+1]; // points_p[2*i] = points_p[2*(numpoints_p-1)-2*i]; // points_p[2*i+1] = points_p[2*(numpoints_p-1)+1-2*i]; // points_p[2*(numpoints_p-1)-2*i] = temp[0]; // points_p[2*(numpoints_p-1)+1-2*i] = temp[1]; // } // } // m.counterclockcount--; // INTERSECTION OF PETALS // first check whether the star angles are appropriate for relocation if (torg.type == VertexType.FreeVertex && numpoints_p != 0 && ValidPolygonAngles(numpoints_p, points_p)) { //newLocFound = getPetalIntersection(m, b, numpoints_p, points_p, newloc); //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_p, points_p, newloc,torg[0],torg[1]); if (behavior.MaxAngle == 0.0) { newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_p, points_p, ref newloc); } else { newLocFound = GetWedgeIntersection(numpoints_p, points_p, ref newloc); } //printf("call petal intersection for p\n"); // make sure the relocated point is a free vertex if (newLocFound) { possibilities[0] = newloc[0];// something found possibilities[1] = newloc[1]; num_pos++;// increase the number of possibilities flag1 = 1; } } //********************* TRY TO RELOCATE POINT "q" *************** // get the surrounding points of q, so this gives us the triangles numpoints_q = GetStarPoints(badotri, torg, tdest, tapex, 2, ref points_q); // // check if the points in counterclockwise order // v1[0] = points_q[0]; v1[1] = points_q[1]; // v2[0] = points_q[2]; v2[1] = points_q[3]; // v3[0] = points_q[4]; v3[1] = points_q[5]; // if(counterclockwise(m,b,v1,v2,v3) < 0){ // // reverse the order to ccw // for(i = 0; i < numpoints_q/2; i++){ // temp[0] = points_q[2*i]; // temp[1] = points_q[2*i+1]; // points_q[2*i] = points_q[2*(numpoints_q-1)-2*i]; // points_q[2*i+1] = points_q[2*(numpoints_q-1)+1-2*i]; // points_q[2*(numpoints_q-1)-2*i] = temp[0]; // points_q[2*(numpoints_q-1)+1-2*i] = temp[1]; // } // } // m.counterclockcount--; // INTERSECTION OF PETALS // first check whether the star angles are appropriate for relocation if (tdest.type == VertexType.FreeVertex && numpoints_q != 0 && ValidPolygonAngles(numpoints_q, points_q)) { //newLocFound = getPetalIntersection(m, b,numpoints_q, points_q, newloc); //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_q, points_q, newloc,tapex[0],tapex[1]); if (behavior.MaxAngle == 0.0) { newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_q, points_q, ref newloc); } else { newLocFound = GetWedgeIntersection(numpoints_q, points_q, ref newloc); } //printf("call petal intersection for q\n"); // make sure the relocated point is a free vertex if (newLocFound) { possibilities[2] = newloc[0];// something found possibilities[3] = newloc[1]; num_pos++;// increase the number of possibilities flag2 = 2; } } //********************* TRY TO RELOCATE POINT "q" *************** // get the surrounding points of r, so this gives us the triangles numpoints_r = GetStarPoints(badotri, torg, tdest, tapex, 3, ref points_r); // check if the points in counterclockwise order // v1[0] = points_r[0]; v1[1] = points_r[1]; // v2[0] = points_r[2]; v2[1] = points_r[3]; // v3[0] = points_r[4]; v3[1] = points_r[5]; // if(counterclockwise(m,b,v1,v2,v3) < 0){ // // reverse the order to ccw // for(i = 0; i < numpoints_r/2; i++){ // temp[0] = points_r[2*i]; // temp[1] = points_r[2*i+1]; // points_r[2*i] = points_r[2*(numpoints_r-1)-2*i]; // points_r[2*i+1] = points_r[2*(numpoints_r-1)+1-2*i]; // points_r[2*(numpoints_r-1)-2*i] = temp[0]; // points_r[2*(numpoints_r-1)+1-2*i] = temp[1]; // } // } // m.counterclockcount--; // INTERSECTION OF PETALS // first check whether the star angles are appropriate for relocation if (tapex.type == VertexType.FreeVertex && numpoints_r != 0 && ValidPolygonAngles(numpoints_r, points_r)) { //newLocFound = getPetalIntersection(m, b,numpoints_r, points_r, newloc); //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_r, points_r, newloc,tdest[0],tdest[1]); if (behavior.MaxAngle == 0.0) { newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_r, points_r, ref newloc); } else { newLocFound = GetWedgeIntersection(numpoints_r, points_r, ref newloc); } //printf("call petal intersection for r\n"); // make sure the relocated point is a free vertex if (newLocFound) { possibilities[4] = newloc[0];// something found possibilities[5] = newloc[1]; num_pos++;// increase the number of possibilities flag3 = 3; } } //printf("numpossibilities %d\n",num_pos); //////////// AFTER FINISH CHECKING EVERY POSSIBILITY, CHOOSE ANY OF THE AVAILABLE ONE ////////////////////// if (num_pos > 0) { if (flag1 > 0) { // suggest to relocate origin newloc[0] = possibilities[0]; newloc[1] = possibilities[1]; return flag1; } else { if (flag2 > 0) { // suggest to relocate apex newloc[0] = possibilities[2]; newloc[1] = possibilities[3]; return flag2; } else {// suggest to relocate destination if (flag3 > 0) { newloc[0] = possibilities[4]; newloc[1] = possibilities[5]; return flag3; } } } } return 0;// could not find any good relocation }
/// <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; double 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.Lprev(); 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 = predicates.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 = predicates.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.Lprev(); return LocateResult.OnEdge; } if (orgorient == 0.0) { searchtri.Lnext(); 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.Pivot(ref checkedge); if (checkedge.seg.hash != Mesh.DUMMY) { // Go back to the last triangle. backtracktri.Copy(ref searchtri); return LocateResult.Outside; } } // Check for walking right out of the triangulation. if (searchtri.tri.id == Mesh.DUMMY) { // Go back to the last triangle. backtracktri.Copy(ref searchtri); return LocateResult.Outside; } fapex = searchtri.Apex(); } }
/// <summary> /// Gets a neighbours vertex. /// </summary> /// <param name="badotri"></param> /// <param name="first_x"></param> /// <param name="first_y"></param> /// <param name="second_x"></param> /// <param name="second_y"></param> /// <param name="thirdpoint">Neighbor's third vertex incident to given edge.</param> /// <param name="neighotri">Pointer for the neighbor triangle.</param> /// <returns>Returns true if vertex was found.</returns> private bool GetNeighborsVertex(Otri badotri, double first_x, double first_y, double second_x, double second_y, ref double[] thirdpoint, ref Otri neighotri) { Otri neighbor = default(Otri); // keeps the neighbor triangles bool notFound = false; // boolean variable if we can find that neighbor or not // for keeping the vertices of the neighbor triangle Vertex neighborvertex_1 = null; Vertex neighborvertex_2 = null; Vertex neighborvertex_3 = null; // used for finding neighbor triangle int firstVertexMatched = 0, secondVertexMatched = 0; // to find the correct neighbor //triangle ptr; // Temporary variable used by sym() //int i; // index variable // find neighbors // Check each of the triangle's three neighbors to find the correct one for (badotri.orient = 0; badotri.orient < 3; badotri.orient++) { // Find the neighbor. badotri.Sym(ref neighbor); // check if it is the one we are looking for by checking the corners // first check if the neighbor is nonexistent, since it can be on the border if (neighbor.tri.id != Mesh.DUMMY) { // then check if two wanted corners are also in this triangle // take the vertices of the candidate neighbor neighborvertex_1 = neighbor.Org(); neighborvertex_2 = neighbor.Dest(); neighborvertex_3 = neighbor.Apex(); // check if it is really a triangle if ((neighborvertex_1.x == neighborvertex_2.x && neighborvertex_1.y == neighborvertex_2.y) || (neighborvertex_2.x == neighborvertex_3.x && neighborvertex_2.y == neighborvertex_3.y) || (neighborvertex_1.x == neighborvertex_3.x && neighborvertex_1.y == neighborvertex_3.y)) { //printf("Two vertices are the same!!!!!!!\n"); } else { // begin searching for the correct neighbor triangle firstVertexMatched = 0; if ((Math.Abs(first_x - neighborvertex_1.x) < EPS) && (Math.Abs(first_y - neighborvertex_1.y) < EPS)) { firstVertexMatched = 11; // neighbor's 1st vertex is matched to first vertex } else if ((Math.Abs(first_x - neighborvertex_2.x) < EPS) && (Math.Abs(first_y - neighborvertex_2.y) < EPS)) { firstVertexMatched = 12; // neighbor's 2nd vertex is matched to first vertex } else if ((Math.Abs(first_x - neighborvertex_3.x) < EPS) && (Math.Abs(first_y - neighborvertex_3.y) < EPS)) { firstVertexMatched = 13; // neighbor's 3rd vertex is matched to first vertex }/*else{ // none of them matched } // end of first vertex matching */ secondVertexMatched = 0; if ((Math.Abs(second_x - neighborvertex_1.x) < EPS) && (Math.Abs(second_y - neighborvertex_1.y) < EPS)) { secondVertexMatched = 21; // neighbor's 1st vertex is matched to second vertex } else if ((Math.Abs(second_x - neighborvertex_2.x) < EPS) && (Math.Abs(second_y - neighborvertex_2.y) < EPS)) { secondVertexMatched = 22; // neighbor's 2nd vertex is matched to second vertex } else if ((Math.Abs(second_x - neighborvertex_3.x) < EPS) && (Math.Abs(second_y - neighborvertex_3.y) < EPS)) { secondVertexMatched = 23; // neighbor's 3rd vertex is matched to second vertex }/*else{ // none of them matched } // end of second vertex matching*/ } }// if neighbor exists or not if (((firstVertexMatched == 11) && (secondVertexMatched == 22 || secondVertexMatched == 23)) || ((firstVertexMatched == 12) && (secondVertexMatched == 21 || secondVertexMatched == 23)) || ((firstVertexMatched == 13) && (secondVertexMatched == 21 || secondVertexMatched == 22))) break; }// end of for loop over all orientations switch (firstVertexMatched) { case 0: notFound = true; break; case 11: if (secondVertexMatched == 22) { thirdpoint[0] = neighborvertex_3.x; thirdpoint[1] = neighborvertex_3.y; } else if (secondVertexMatched == 23) { thirdpoint[0] = neighborvertex_2.x; thirdpoint[1] = neighborvertex_2.y; } else { notFound = true; } break; case 12: if (secondVertexMatched == 21) { thirdpoint[0] = neighborvertex_3.x; thirdpoint[1] = neighborvertex_3.y; } else if (secondVertexMatched == 23) { thirdpoint[0] = neighborvertex_1.x; thirdpoint[1] = neighborvertex_1.y; } else { notFound = true; } break; case 13: if (secondVertexMatched == 21) { thirdpoint[0] = neighborvertex_2.x; thirdpoint[1] = neighborvertex_2.y; } else if (secondVertexMatched == 22) { thirdpoint[0] = neighborvertex_1.x; thirdpoint[1] = neighborvertex_1.y; } else { notFound = true; } break; default: if (secondVertexMatched == 0) { notFound = true; } break; } // pointer of the neighbor triangle neighotri = neighbor; return notFound; }
/// <summary> /// Suggest the given triangle as a starting triangle for point location. /// </summary> /// <param name="otri"></param> public void Update(ref Otri otri) { otri.Copy(ref recenttri); }
/// <summary> /// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab] /// </summary> public void Lprev(ref Otri ot) { ot.tri = tri; ot.orient = minus1Mod3[orient]; }
/// <summary> /// Merge two adjacent Delaunay triangulations into a single Delaunay triangulation. /// </summary> /// <param name="farleft">Bounding triangles of the left triangulation.</param> /// <param name="innerleft">Bounding triangles of the left triangulation.</param> /// <param name="innerright">Bounding triangles of the right triangulation.</param> /// <param name="farright">Bounding triangles of the right triangulation.</param> /// <param name="axis"></param> /// <remarks> /// This is similar to the algorithm given by Guibas and Stolfi, but uses /// a triangle-based, rather than edge-based, data structure. /// /// The algorithm walks up the gap between the two triangulations, knitting /// them together. As they are merged, some of their bounding triangles /// are converted into real triangles of the triangulation. The procedure /// pulls each hull's bounding triangles apart, then knits them together /// like the teeth of two gears. The Delaunay property determines, at each /// step, whether the next "tooth" is a bounding triangle of the left hull /// or the right. When a bounding triangle becomes real, its apex is /// changed from NULL to a real vertex. /// /// Only two new triangles need to be allocated. These become new bounding /// triangles at the top and bottom of the seam. They are used to connect /// the remaining bounding triangles (those that have not been converted /// into real triangles) into a single fan. /// /// On entry, 'farleft' and 'innerleft' are bounding triangles of the left /// triangulation. The origin of 'farleft' is the leftmost vertex, and /// the destination of 'innerleft' is the rightmost vertex of the /// triangulation. Similarly, 'innerright' and 'farright' are bounding /// triangles of the right triangulation. The origin of 'innerright' and /// destination of 'farright' are the leftmost and rightmost vertices. /// /// On completion, the origin of 'farleft' is the leftmost vertex of the /// merged triangulation, and the destination of 'farright' is the rightmost /// vertex. /// </remarks> void MergeHulls(ref Otri farleft, ref Otri innerleft, ref Otri innerright, ref Otri farright, int axis) { Otri leftcand = default(Otri), rightcand = default(Otri); Otri nextedge = default(Otri); Otri sidecasing = default(Otri), topcasing = default(Otri), outercasing = default(Otri); Otri checkedge = default(Otri); Otri baseedge = default(Otri); Vertex innerleftdest; Vertex innerrightorg; Vertex innerleftapex, innerrightapex; Vertex farleftpt, farrightpt; Vertex farleftapex, farrightapex; Vertex lowerleft, lowerright; Vertex upperleft, upperright; Vertex nextapex; Vertex checkvertex; bool changemade; bool badedge; bool leftfinished, rightfinished; innerleftdest = innerleft.Dest(); innerleftapex = innerleft.Apex(); innerrightorg = innerright.Org(); innerrightapex = innerright.Apex(); // Special treatment for horizontal cuts. if (UseDwyer && (axis == 1)) { farleftpt = farleft.Org(); farleftapex = farleft.Apex(); farrightpt = farright.Dest(); farrightapex = farright.Apex(); // The pointers to the extremal vertices are shifted to point to the // topmost and bottommost vertex of each hull, rather than the // leftmost and rightmost vertices. while (farleftapex.y < farleftpt.y) { farleft.Lnext(); farleft.Sym(); farleftpt = farleftapex; farleftapex = farleft.Apex(); } innerleft.Sym(ref checkedge); checkvertex = checkedge.Apex(); while (checkvertex.y > innerleftdest.y) { checkedge.Lnext(ref innerleft); innerleftapex = innerleftdest; innerleftdest = checkvertex; innerleft.Sym(ref checkedge); checkvertex = checkedge.Apex(); } while (innerrightapex.y < innerrightorg.y) { innerright.Lnext(); innerright.Sym(); innerrightorg = innerrightapex; innerrightapex = innerright.Apex(); } farright.Sym(ref checkedge); checkvertex = checkedge.Apex(); while (checkvertex.y > farrightpt.y) { checkedge.Lnext(ref farright); farrightapex = farrightpt; farrightpt = checkvertex; farright.Sym(ref checkedge); checkvertex = checkedge.Apex(); } } // Find a line tangent to and below both hulls. do { changemade = false; // Make innerleftdest the "bottommost" vertex of the left hull. if (predicates.CounterClockwise(innerleftdest, innerleftapex, innerrightorg) > 0.0) { innerleft.Lprev(); innerleft.Sym(); innerleftdest = innerleftapex; innerleftapex = innerleft.Apex(); changemade = true; } // Make innerrightorg the "bottommost" vertex of the right hull. if (predicates.CounterClockwise(innerrightapex, innerrightorg, innerleftdest) > 0.0) { innerright.Lnext(); innerright.Sym(); innerrightorg = innerrightapex; innerrightapex = innerright.Apex(); changemade = true; } } while (changemade); // Find the two candidates to be the next "gear tooth." innerleft.Sym(ref leftcand); innerright.Sym(ref rightcand); // Create the bottom new bounding triangle. mesh.MakeTriangle(ref baseedge); // Connect it to the bounding boxes of the left and right triangulations. baseedge.Bond(ref innerleft); baseedge.Lnext(); baseedge.Bond(ref innerright); baseedge.Lnext(); baseedge.SetOrg(innerrightorg); baseedge.SetDest(innerleftdest); // Apex is intentionally left NULL. // Fix the extreme triangles if necessary. farleftpt = farleft.Org(); if (innerleftdest == farleftpt) { baseedge.Lnext(ref farleft); } farrightpt = farright.Dest(); if (innerrightorg == farrightpt) { baseedge.Lprev(ref farright); } // The vertices of the current knitting edge. lowerleft = innerleftdest; lowerright = innerrightorg; // The candidate vertices for knitting. upperleft = leftcand.Apex(); upperright = rightcand.Apex(); // Walk up the gap between the two triangulations, knitting them together. while (true) { // Have we reached the top? (This isn't quite the right question, // because even though the left triangulation might seem finished now, // moving up on the right triangulation might reveal a new vertex of // the left triangulation. And vice-versa.) leftfinished = predicates.CounterClockwise(upperleft, lowerleft, lowerright) <= 0.0; rightfinished = predicates.CounterClockwise(upperright, lowerleft, lowerright) <= 0.0; if (leftfinished && rightfinished) { // Create the top new bounding triangle. mesh.MakeTriangle(ref nextedge); nextedge.SetOrg(lowerleft); nextedge.SetDest(lowerright); // Apex is intentionally left NULL. // Connect it to the bounding boxes of the two triangulations. nextedge.Bond(ref baseedge); nextedge.Lnext(); nextedge.Bond(ref rightcand); nextedge.Lnext(); nextedge.Bond(ref leftcand); // Special treatment for horizontal cuts. if (UseDwyer && (axis == 1)) { farleftpt = farleft.Org(); farleftapex = farleft.Apex(); farrightpt = farright.Dest(); farrightapex = farright.Apex(); farleft.Sym(ref checkedge); checkvertex = checkedge.Apex(); // The pointers to the extremal vertices are restored to the // leftmost and rightmost vertices (rather than topmost and // bottommost). while (checkvertex.x < farleftpt.x) { checkedge.Lprev(ref farleft); farleftapex = farleftpt; farleftpt = checkvertex; farleft.Sym(ref checkedge); checkvertex = checkedge.Apex(); } while (farrightapex.x > farrightpt.x) { farright.Lprev(); farright.Sym(); farrightpt = farrightapex; farrightapex = farright.Apex(); } } return; } // Consider eliminating edges from the left triangulation. if (!leftfinished) { // What vertex would be exposed if an edge were deleted? leftcand.Lprev(ref nextedge); nextedge.Sym(); nextapex = nextedge.Apex(); // If nextapex is NULL, then no vertex would be exposed; the // triangulation would have been eaten right through. if (nextapex != null) { // Check whether the edge is Delaunay. badedge = predicates.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0; while (badedge) { // Eliminate the edge with an edge flip. As a result, the // left triangulation will have one more boundary triangle. nextedge.Lnext(); nextedge.Sym(ref topcasing); nextedge.Lnext(); nextedge.Sym(ref sidecasing); nextedge.Bond(ref topcasing); leftcand.Bond(ref sidecasing); leftcand.Lnext(); leftcand.Sym(ref outercasing); nextedge.Lprev(); nextedge.Bond(ref outercasing); // Correct the vertices to reflect the edge flip. leftcand.SetOrg(lowerleft); leftcand.SetDest(null); leftcand.SetApex(nextapex); nextedge.SetOrg(null); nextedge.SetDest(upperleft); nextedge.SetApex(nextapex); // Consider the newly exposed vertex. upperleft = nextapex; // What vertex would be exposed if another edge were deleted? sidecasing.Copy(ref nextedge); nextapex = nextedge.Apex(); if (nextapex != null) { // Check whether the edge is Delaunay. badedge = predicates.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0; } else { // Avoid eating right through the triangulation. badedge = false; } } } } // Consider eliminating edges from the right triangulation. if (!rightfinished) { // What vertex would be exposed if an edge were deleted? rightcand.Lnext(ref nextedge); nextedge.Sym(); nextapex = nextedge.Apex(); // If nextapex is NULL, then no vertex would be exposed; the // triangulation would have been eaten right through. if (nextapex != null) { // Check whether the edge is Delaunay. badedge = predicates.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0; while (badedge) { // Eliminate the edge with an edge flip. As a result, the // right triangulation will have one more boundary triangle. nextedge.Lprev(); nextedge.Sym(ref topcasing); nextedge.Lprev(); nextedge.Sym(ref sidecasing); nextedge.Bond(ref topcasing); rightcand.Bond(ref sidecasing); rightcand.Lprev(); rightcand.Sym(ref outercasing); nextedge.Lnext(); nextedge.Bond(ref outercasing); // Correct the vertices to reflect the edge flip. rightcand.SetOrg(null); rightcand.SetDest(lowerright); rightcand.SetApex(nextapex); nextedge.SetOrg(upperright); nextedge.SetDest(null); nextedge.SetApex(nextapex); // Consider the newly exposed vertex. upperright = nextapex; // What vertex would be exposed if another edge were deleted? sidecasing.Copy(ref nextedge); nextapex = nextedge.Apex(); if (nextapex != null) { // Check whether the edge is Delaunay. badedge = predicates.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0; } else { // Avoid eating right through the triangulation. badedge = false; } } } } if (leftfinished || (!rightfinished && (predicates.InCircle(upperleft, lowerleft, lowerright, upperright) > 0.0))) { // Knit the triangulations, adding an edge from 'lowerleft' // to 'upperright'. baseedge.Bond(ref rightcand); rightcand.Lprev(ref baseedge); baseedge.SetDest(lowerleft); lowerright = upperright; baseedge.Sym(ref rightcand); upperright = rightcand.Apex(); } else { // Knit the triangulations, adding an edge from 'upperleft' // to 'lowerright'. baseedge.Bond(ref leftcand); leftcand.Lnext(ref baseedge); baseedge.SetOrg(lowerright); lowerleft = upperleft; baseedge.Sym(ref leftcand); upperleft = leftcand.Apex(); } } }
/// <summary> /// Copy an oriented triangle. /// </summary> public void Copy(ref Otri ot) { ot.tri = tri; ot.orient = orient; }
/// <summary> /// Finds the star of a given point. /// </summary> /// <param name="badotri"></param> /// <param name="p"></param> /// <param name="q"></param> /// <param name="r"></param> /// <param name="whichPoint"></param> /// <param name="points">List of points on the star of the given point.</param> /// <returns>Number of points on the star of the given point.</returns> private int GetStarPoints(Otri badotri, Vertex p, Vertex q, Vertex r, int whichPoint, ref double[] points) { Otri neighotri = default(Otri); // for return value of the function Otri tempotri; // for temporary usage double first_x = 0, first_y = 0; // keeps the first point to be considered double second_x = 0, second_y = 0; // for determining the edge we will begin double third_x = 0, third_y = 0; // termination double[] returnPoint = new double[2]; // for keeping the returned point int numvertices = 0; // for keeping number of surrounding vertices // first determine which point to be used to find its neighbor triangles switch (whichPoint) { case 1: first_x = p.x; // point at the center first_y = p.y; second_x = r.x; // second vertex of first edge to consider second_y = r.y; third_x = q.x; // for terminating the search third_y = q.y; break; case 2: first_x = q.x; // point at the center first_y = q.y; second_x = p.x; // second vertex of first edge to consider second_y = p.y; third_x = r.x; // for terminating the search third_y = r.y; break; case 3: first_x = r.x; // point at the center first_y = r.y; second_x = q.x; // second vertex of first edge to consider second_y = q.y; third_x = p.x; // for terminating the search third_y = p.y; break; } tempotri = badotri; // add first point as the end of first edge points[numvertices] = second_x; numvertices++; points[numvertices] = second_y; numvertices++; // assign as dummy value returnPoint[0] = second_x; returnPoint[1] = second_y; // until we reach the third point of the beginning triangle do { // find the neighbor's third point where it is incident to given edge if (!GetNeighborsVertex(tempotri, first_x, first_y, second_x, second_y, ref returnPoint, ref neighotri)) { // go to next triangle tempotri = neighotri; // now the second point is the neighbor's third vertex second_x = returnPoint[0]; second_y = returnPoint[1]; // add a new point to the list of surrounding points points[numvertices] = returnPoint[0]; numvertices++; points[numvertices] = returnPoint[1]; numvertices++; } else { numvertices = 0; break; } } while (!((Math.Abs(returnPoint[0] - third_x) <= EPS) && (Math.Abs(returnPoint[1] - third_y) <= EPS))); return numvertices / 2; }
/// <summary> /// Recursively form a Delaunay triangulation by the divide-and-conquer method. /// </summary> /// <param name="left"></param> /// <param name="right"></param> /// <param name="axis"></param> /// <param name="farleft"></param> /// <param name="farright"></param> /// <remarks> /// Recursively breaks down the problem into smaller pieces, which are /// knitted together by mergehulls(). The base cases (problems of two or /// three vertices) are handled specially here. /// /// On completion, 'farleft' and 'farright' are bounding triangles such that /// the origin of 'farleft' is the leftmost vertex (breaking ties by /// choosing the highest leftmost vertex), and the destination of /// 'farright' is the rightmost vertex (breaking ties by choosing the /// lowest rightmost vertex). /// </remarks> void DivconqRecurse(int left, int right, int axis, ref Otri farleft, ref Otri farright) { Otri midtri = default(Otri); Otri tri1 = default(Otri); Otri tri2 = default(Otri); Otri tri3 = default(Otri); Otri innerleft = default(Otri), innerright = default(Otri); double area; int vertices = right - left + 1; int divider; if (vertices == 2) { // The triangulation of two vertices is an edge. An edge is // represented by two bounding triangles. mesh.MakeTriangle(ref farleft); farleft.SetOrg(sortarray[left]); farleft.SetDest(sortarray[left + 1]); // The apex is intentionally left NULL. mesh.MakeTriangle(ref farright); farright.SetOrg(sortarray[left + 1]); farright.SetDest(sortarray[left]); // The apex is intentionally left NULL. farleft.Bond(ref farright); farleft.Lprev(); farright.Lnext(); farleft.Bond(ref farright); farleft.Lprev(); farright.Lnext(); farleft.Bond(ref farright); // Ensure that the origin of 'farleft' is sortarray[0]. farright.Lprev(ref farleft); return; } else if (vertices == 3) { // The triangulation of three vertices is either a triangle (with // three bounding triangles) or two edges (with four bounding // triangles). In either case, four triangles are created. mesh.MakeTriangle(ref midtri); mesh.MakeTriangle(ref tri1); mesh.MakeTriangle(ref tri2); mesh.MakeTriangle(ref tri3); area = predicates.CounterClockwise(sortarray[left], sortarray[left + 1], sortarray[left + 2]); if (area == 0.0) { // Three collinear vertices; the triangulation is two edges. midtri.SetOrg(sortarray[left]); midtri.SetDest(sortarray[left + 1]); tri1.SetOrg(sortarray[left + 1]); tri1.SetDest(sortarray[left]); tri2.SetOrg(sortarray[left + 2]); tri2.SetDest(sortarray[left + 1]); tri3.SetOrg(sortarray[left + 1]); tri3.SetDest(sortarray[left + 2]); // All apices are intentionally left NULL. midtri.Bond(ref tri1); tri2.Bond(ref tri3); midtri.Lnext(); tri1.Lprev(); tri2.Lnext(); tri3.Lprev(); midtri.Bond(ref tri3); tri1.Bond(ref tri2); midtri.Lnext(); tri1.Lprev(); tri2.Lnext(); tri3.Lprev(); midtri.Bond(ref tri1); tri2.Bond(ref tri3); // Ensure that the origin of 'farleft' is sortarray[0]. tri1.Copy(ref farleft); // Ensure that the destination of 'farright' is sortarray[2]. tri2.Copy(ref farright); } else { // The three vertices are not collinear; the triangulation is one // triangle, namely 'midtri'. midtri.SetOrg(sortarray[left]); tri1.SetDest(sortarray[left]); tri3.SetOrg(sortarray[left]); // Apices of tri1, tri2, and tri3 are left NULL. if (area > 0.0) { // The vertices are in counterclockwise order. midtri.SetDest(sortarray[left + 1]); tri1.SetOrg(sortarray[left + 1]); tri2.SetDest(sortarray[left + 1]); midtri.SetApex(sortarray[left + 2]); tri2.SetOrg(sortarray[left + 2]); tri3.SetDest(sortarray[left + 2]); } else { // The vertices are in clockwise order. midtri.SetDest(sortarray[left + 2]); tri1.SetOrg(sortarray[left + 2]); tri2.SetDest(sortarray[left + 2]); midtri.SetApex(sortarray[left + 1]); tri2.SetOrg(sortarray[left + 1]); tri3.SetDest(sortarray[left + 1]); } // The topology does not depend on how the vertices are ordered. midtri.Bond(ref tri1); midtri.Lnext(); midtri.Bond(ref tri2); midtri.Lnext(); midtri.Bond(ref tri3); tri1.Lprev(); tri2.Lnext(); tri1.Bond(ref tri2); tri1.Lprev(); tri3.Lprev(); tri1.Bond(ref tri3); tri2.Lnext(); tri3.Lprev(); tri2.Bond(ref tri3); // Ensure that the origin of 'farleft' is sortarray[0]. tri1.Copy(ref farleft); // Ensure that the destination of 'farright' is sortarray[2]. if (area > 0.0) { tri2.Copy(ref farright); } else { farleft.Lnext(ref farright); } } return; } else { // Split the vertices in half. divider = vertices >> 1; // Recursively triangulate each half. DivconqRecurse(left, left + divider - 1, 1 - axis, ref farleft, ref innerleft); //DebugWriter.Session.Write(mesh, true); DivconqRecurse(left + divider, right, 1 - axis, ref innerright, ref farright); //DebugWriter.Session.Write(mesh, true); // Merge the two triangulations into one. MergeHulls(ref farleft, ref innerleft, ref innerright, ref farright, axis); //DebugWriter.Session.Write(mesh, true); } }
/// <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.Lnext(); intersect = LocateResult.OnVertex; searchtri.Copy(ref horiz); } else { // Orient 'searchtri' to fit the preconditions of calling preciselocate(). ahead = predicates.CounterClockwise(torg, tdest, newvertex); if (ahead < 0.0) { // Turn around so that 'searchpoint' is to the left of the // edge specified by 'searchtri'. searchtri.Sym(); 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> /// 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.Sym(); 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.Lprev(); dissolveedge.Sym(); // 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.tri.id != Mesh.DUMMY) { markorg = dissolveedge.Org(); if (markorg.label == 0) { markorg.label = 1; } } } // Remove a bounding triangle from a convex hull triangle. dissolveedge.Dissolve(mesh.dummytri); // Find the next bounding triangle. deadtriangle.Sym(ref dissolveedge); // Delete the bounding triangle. mesh.TriangleDealloc(deadtriangle.tri); } while (!dissolveedge.Equals(startghost)); return hullsize; }
/// <summary> /// Find a new location for a Steiner point. /// </summary> /// <param name="torg"></param> /// <param name="tdest"></param> /// <param name="tapex"></param> /// <param name="circumcenter"></param> /// <param name="xi"></param> /// <param name="eta"></param> /// <param name="offcenter"></param> /// <param name="badotri"></param> private Point FindNewLocationWithoutMaxAngle(Vertex torg, Vertex tdest, Vertex tapex, ref double xi, ref double eta, bool offcenter, Otri badotri) { double offconstant = behavior.offconstant; // for calculating the distances of the edges double xdo, ydo, xao, yao, xda, yda; double dodist, aodist, dadist; // for exact calculation double denominator; double dx, dy, dxoff, dyoff; ////////////////////////////// HALE'S VARIABLES ////////////////////////////// // keeps the difference of coordinates edge double xShortestEdge = 0, yShortestEdge = 0, xMiddleEdge, yMiddleEdge, xLongestEdge, yLongestEdge; // keeps the square of edge lengths double shortestEdgeDist = 0, middleEdgeDist = 0, longestEdgeDist = 0; // keeps the vertices according to the angle incident to that vertex in a triangle Point smallestAngleCorner, middleAngleCorner, largestAngleCorner; // keeps the type of orientation if the triangle int orientation = 0; // keeps the coordinates of circumcenter of itself and neighbor triangle circumcenter Point myCircumcenter, neighborCircumcenter; // keeps if bad triangle is almost good or not int almostGood = 0; // keeps the cosine of the largest angle double cosMaxAngle; bool isObtuse; // 1: obtuse 0: nonobtuse // keeps the radius of petal double petalRadius; // for calculating petal center double xPetalCtr_1, yPetalCtr_1, xPetalCtr_2, yPetalCtr_2, xPetalCtr, yPetalCtr, xMidOfShortestEdge, yMidOfShortestEdge; double dxcenter1, dycenter1, dxcenter2, dycenter2; // for finding neighbor Otri neighborotri = default(Otri); double[] thirdPoint = new double[2]; //int neighborNotFound = -1; bool neighborNotFound; // for keeping the vertices of the neighbor triangle Vertex neighborvertex_1; Vertex neighborvertex_2; Vertex neighborvertex_3; // dummy variables double xi_tmp = 0, eta_tmp = 0; //vertex thirdVertex; // for petal intersection double vector_x, vector_y, xMidOfLongestEdge, yMidOfLongestEdge, inter_x, inter_y; double[] p = new double[5], voronoiOrInter = new double[4]; bool isCorrect; // for vector calculations in perturbation double ax, ay, d; double pertConst = 0.06; // perturbation constant double lengthConst = 1; // used at comparing circumcenter's distance to proposed point's distance double justAcute = 1; // used for making the program working for one direction only // for smoothing int relocated = 0;// used to differentiate between calling the deletevertex and just proposing a steiner point double[] newloc = new double[2]; // new location suggested by smoothing double origin_x = 0, origin_y = 0; // for keeping torg safe Otri delotri; // keeping the original orientation for relocation process // keeps the first and second direction suggested points double dxFirstSuggestion, dyFirstSuggestion, dxSecondSuggestion, dySecondSuggestion; // second direction variables double xMidOfMiddleEdge, yMidOfMiddleEdge; ////////////////////////////// END OF HALE'S VARIABLES ////////////////////////////// Statistic.CircumcenterCount++; // Compute the circumcenter of the triangle. xdo = tdest.x - torg.x; ydo = tdest.y - torg.y; xao = tapex.x - torg.x; yao = tapex.y - torg.y; xda = tapex.x - tdest.x; yda = tapex.y - tdest.y; // keeps the square of the distances dodist = xdo * xdo + ydo * ydo; aodist = xao * xao + yao * yao; dadist = (tdest.x - tapex.x) * (tdest.x - tapex.x) + (tdest.y - tapex.y) * (tdest.y - tapex.y); // checking if the user wanted exact arithmetic or not if (Behavior.NoExact) { denominator = 0.5 / (xdo * yao - xao * ydo); } else { // Use the counterclockwise() routine to ensure a positive (and // reasonably accurate) result, avoiding any possibility of // division by zero. denominator = 0.5 / predicates.CounterClockwise(tdest, tapex, torg); // Don't count the above as an orientation test. Statistic.CounterClockwiseCount--; } // calculate the circumcenter in terms of distance to origin point dx = (yao * dodist - ydo * aodist) * denominator; dy = (xdo * aodist - xao * dodist) * denominator; // for debugging and for keeping circumcenter to use later // coordinate value of the circumcenter myCircumcenter = new Point(torg.x + dx, torg.y + dy); delotri = badotri; // save for later ///////////////// FINDING THE ORIENTATION OF TRIANGLE ////////////////// // Find the (squared) length of the triangle's shortest edge. This // serves as a conservative estimate of the insertion radius of the // circumcenter's parent. The estimate is used to ensure that // the algorithm terminates even if very small angles appear in // the input PSLG. // find the orientation of the triangle, basically shortest and longest edges orientation = LongestShortestEdge(aodist, dadist, dodist); //printf("org: (%f,%f), dest: (%f,%f), apex: (%f,%f)\n",torg[0],torg[1],tdest[0],tdest[1],tapex[0],tapex[1]); ///////////////////////////////////////////////////////////////////////////////////////////// // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist // // middle: dadist // middle: aodist // middle: aodist // // longest: dodist // longest: dodist // longest: dadist // // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist // // middle: dodist // middle: dodist // middle: dadist // // longest: dadist // longest: aodist // longest: aodist // ///////////////////////////////////////////////////////////////////////////////////////////// switch (orientation) { case 123: // assign necessary information /// smallest angle corner: dest /// largest angle corner: apex xShortestEdge = xao; yShortestEdge = yao; xMiddleEdge = xda; yMiddleEdge = yda; xLongestEdge = xdo; yLongestEdge = ydo; shortestEdgeDist = aodist; middleEdgeDist = dadist; longestEdgeDist = dodist; smallestAngleCorner = tdest; middleAngleCorner = torg; largestAngleCorner = tapex; break; case 132: // assign necessary information /// smallest angle corner: dest /// largest angle corner: org xShortestEdge = xao; yShortestEdge = yao; xMiddleEdge = xdo; yMiddleEdge = ydo; xLongestEdge = xda; yLongestEdge = yda; shortestEdgeDist = aodist; middleEdgeDist = dodist; longestEdgeDist = dadist; smallestAngleCorner = tdest; middleAngleCorner = tapex; largestAngleCorner = torg; break; case 213: // assign necessary information /// smallest angle corner: org /// largest angle corner: apex xShortestEdge = xda; yShortestEdge = yda; xMiddleEdge = xao; yMiddleEdge = yao; xLongestEdge = xdo; yLongestEdge = ydo; shortestEdgeDist = dadist; middleEdgeDist = aodist; longestEdgeDist = dodist; smallestAngleCorner = torg; middleAngleCorner = tdest; largestAngleCorner = tapex; break; case 231: // assign necessary information /// smallest angle corner: org /// largest angle corner: dest xShortestEdge = xda; yShortestEdge = yda; xMiddleEdge = xdo; yMiddleEdge = ydo; xLongestEdge = xao; yLongestEdge = yao; shortestEdgeDist = dadist; middleEdgeDist = dodist; longestEdgeDist = aodist; smallestAngleCorner = torg; middleAngleCorner = tapex; largestAngleCorner = tdest; break; case 312: // assign necessary information /// smallest angle corner: apex /// largest angle corner: org xShortestEdge = xdo; yShortestEdge = ydo; xMiddleEdge = xao; yMiddleEdge = yao; xLongestEdge = xda; yLongestEdge = yda; shortestEdgeDist = dodist; middleEdgeDist = aodist; longestEdgeDist = dadist; smallestAngleCorner = tapex; middleAngleCorner = tdest; largestAngleCorner = torg; break; case 321: // assign necessary information default: // TODO: is this safe? /// smallest angle corner: apex /// largest angle corner: dest xShortestEdge = xdo; yShortestEdge = ydo; xMiddleEdge = xda; yMiddleEdge = yda; xLongestEdge = xao; yLongestEdge = yao; shortestEdgeDist = dodist; middleEdgeDist = dadist; longestEdgeDist = aodist; smallestAngleCorner = tapex; middleAngleCorner = torg; largestAngleCorner = tdest; break; }// end of switch // check for offcenter condition if (offcenter && (offconstant > 0.0)) { // origin has the smallest angle if (orientation == 213 || orientation == 231) { // Find the position of the off-center, as described by Alper Ungor. dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; // If the off-center is closer to destination than the // circumcenter, use the off-center instead. /// doubleLY BAD CASE /// if (dxoff * dxoff + dyoff * dyoff < (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) { dx = xdo + dxoff; dy = ydo + dyoff; } /// ALMOST GOOD CASE /// else { almostGood = 1; } // destination has the smallest angle } else if (orientation == 123 || orientation == 132) { // Find the position of the off-center, as described by Alper Ungor. dxoff = 0.5 * xShortestEdge + offconstant * yShortestEdge; dyoff = 0.5 * yShortestEdge - offconstant * xShortestEdge; // If the off-center is closer to the origin than the // circumcenter, use the off-center instead. /// doubleLY BAD CASE /// if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) { dx = dxoff; dy = dyoff; } /// ALMOST GOOD CASE /// else { almostGood = 1; } // apex has the smallest angle } else {//orientation == 312 || orientation == 321 // Find the position of the off-center, as described by Alper Ungor. dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; // If the off-center is closer to the origin than the // circumcenter, use the off-center instead. /// doubleLY BAD CASE /// if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) { dx = dxoff; dy = dyoff; } /// ALMOST GOOD CASE /// else { almostGood = 1; } } } // if the bad triangle is almost good, apply our approach if (almostGood == 1) { /// calculate cosine of largest angle /// cosMaxAngle = (middleEdgeDist + shortestEdgeDist - longestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(shortestEdgeDist)); if (cosMaxAngle < 0.0) { // obtuse isObtuse = true; } else if (Math.Abs(cosMaxAngle - 0.0) <= EPS) { // right triangle (largest angle is 90 degrees) isObtuse = true; } else { // nonobtuse isObtuse = false; } /// RELOCATION (LOCAL SMOOTHING) /// /// check for possible relocation of one of triangle's points /// relocated = DoSmoothing(delotri, torg, tdest, tapex, ref newloc); /// if relocation is possible, delete that vertex and insert a vertex at the new location /// if (relocated > 0) { Statistic.RelocationCount++; dx = newloc[0] - torg.x; dy = newloc[1] - torg.y; origin_x = torg.x; // keep for later use origin_y = torg.y; switch (relocated) { case 1: //printf("Relocate: (%f,%f)\n", torg[0],torg[1]); mesh.DeleteVertex(ref delotri); break; case 2: //printf("Relocate: (%f,%f)\n", tdest[0],tdest[1]); delotri.Lnext(); mesh.DeleteVertex(ref delotri); break; case 3: //printf("Relocate: (%f,%f)\n", tapex[0],tapex[1]); delotri.Lprev(); mesh.DeleteVertex(ref delotri); break; } } else { // calculate radius of the petal according to angle constraint // first find the visible region, PETAL // find the center of the circle and radius petalRadius = Math.Sqrt(shortestEdgeDist) / (2 * Math.Sin(behavior.MinAngle * Math.PI / 180.0)); /// compute two possible centers of the petal /// // finding the center // first find the middle point of smallest edge xMidOfShortestEdge = (middleAngleCorner.x + largestAngleCorner.x) / 2.0; yMidOfShortestEdge = (middleAngleCorner.y + largestAngleCorner.y) / 2.0; // two possible centers xPetalCtr_1 = xMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); yPetalCtr_1 = yMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); xPetalCtr_2 = xMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); yPetalCtr_2 = yMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); // find the correct circle since there will be two possible circles // calculate the distance to smallest angle corner dxcenter1 = (xPetalCtr_1 - smallestAngleCorner.x) * (xPetalCtr_1 - smallestAngleCorner.x); dycenter1 = (yPetalCtr_1 - smallestAngleCorner.y) * (yPetalCtr_1 - smallestAngleCorner.y); dxcenter2 = (xPetalCtr_2 - smallestAngleCorner.x) * (xPetalCtr_2 - smallestAngleCorner.x); dycenter2 = (yPetalCtr_2 - smallestAngleCorner.y) * (yPetalCtr_2 - smallestAngleCorner.y); // whichever is closer to smallest angle corner, it must be the center if (dxcenter1 + dycenter1 <= dxcenter2 + dycenter2) { xPetalCtr = xPetalCtr_1; yPetalCtr = yPetalCtr_1; } else { xPetalCtr = xPetalCtr_2; yPetalCtr = yPetalCtr_2; } /// find the third point of the neighbor triangle /// neighborNotFound = GetNeighborsVertex(badotri, middleAngleCorner.x, middleAngleCorner.y, smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); /// find the circumcenter of the neighbor triangle /// dxFirstSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter dyFirstSuggestion = dy; // if there is a neighbor triangle if (!neighborNotFound) { neighborvertex_1 = neighborotri.Org(); neighborvertex_2 = neighborotri.Dest(); neighborvertex_3 = neighborotri.Apex(); // now calculate neighbor's circumcenter which is the voronoi site neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, ref xi_tmp, ref eta_tmp); /// compute petal and Voronoi edge intersection /// // in order to avoid degenerate cases, we need to do a vector based calculation for line vector_x = (middleAngleCorner.y - smallestAngleCorner.y);//(-y, x) vector_y = smallestAngleCorner.x - middleAngleCorner.x; vector_x = myCircumcenter.x + vector_x; vector_y = myCircumcenter.y + vector_y; // by intersecting bisectors you will end up with the one you want to walk on // then this line and circle should be intersected CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, xPetalCtr, yPetalCtr, petalRadius, ref p); /// choose the correct intersection point /// // calculate middle point of the longest edge(bisector) xMidOfLongestEdge = (middleAngleCorner.x + smallestAngleCorner.x) / 2.0; yMidOfLongestEdge = (middleAngleCorner.y + smallestAngleCorner.y) / 2.0; // we need to find correct intersection point, since line intersects circle twice isCorrect = ChooseCorrectPoint(xMidOfLongestEdge, yMidOfLongestEdge, p[3], p[4], myCircumcenter.x, myCircumcenter.y, isObtuse); // make sure which point is the correct one to be considered if (isCorrect) { inter_x = p[3]; inter_y = p[4]; } else { inter_x = p[1]; inter_y = p[2]; } /// check if there is a Voronoi vertex between before intersection /// // check if the voronoi vertex is between the intersection and circumcenter PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); /// determine the point to be suggested /// if (p[0] > 0.0) { // there is at least one intersection point // if it is between circumcenter and intersection // if it returns 1.0 this means we have a voronoi vertex within feasible region if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) { if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) { // go back to circumcenter dxFirstSuggestion = dx; dyFirstSuggestion = dy; } else { // we are not creating a bad triangle // neighbor's circumcenter is suggested dxFirstSuggestion = voronoiOrInter[2] - torg.x; dyFirstSuggestion = voronoiOrInter[3] - torg.y; } } else { // there is no voronoi vertex between intersection point and circumcenter if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, inter_x, inter_y)) { // if it is inside feasible region, then insert v2 // apply perturbation // find the distance between circumcenter and intersection point d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); // then find the vector going from intersection point to circumcenter ax = myCircumcenter.x - inter_x; ay = myCircumcenter.y - inter_y; ax = ax / d; ay = ay / d; // now calculate the new intersection point which is perturbated towards the circumcenter inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) { // go back to circumcenter dxFirstSuggestion = dx; dyFirstSuggestion = dy; } else { // intersection point is suggested dxFirstSuggestion = inter_x - torg.x; dyFirstSuggestion = inter_y - torg.y; } } else { // intersection point is suggested dxFirstSuggestion = inter_x - torg.x; dyFirstSuggestion = inter_y - torg.y; } } /// if it is an acute triangle, check if it is a good enough location /// // for acute triangle case, we need to check if it is ok to use either of them if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > lengthConst * ((smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)))) { // use circumcenter dxFirstSuggestion = dx; dyFirstSuggestion = dy; }// else we stick to what we have found }// intersection point }// if it is on the boundary, meaning no neighbor triangle in this direction, try other direction /// DO THE SAME THING FOR THE OTHER DIRECTION /// /// find the third point of the neighbor triangle /// neighborNotFound = GetNeighborsVertex(badotri, largestAngleCorner.x, largestAngleCorner.y, smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); /// find the circumcenter of the neighbor triangle /// dxSecondSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter dySecondSuggestion = dy; // if there is a neighbor triangle if (!neighborNotFound) { neighborvertex_1 = neighborotri.Org(); neighborvertex_2 = neighborotri.Dest(); neighborvertex_3 = neighborotri.Apex(); // now calculate neighbor's circumcenter which is the voronoi site neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, ref xi_tmp, ref eta_tmp); /// compute petal and Voronoi edge intersection /// // in order to avoid degenerate cases, we need to do a vector based calculation for line vector_x = (largestAngleCorner.y - smallestAngleCorner.y);//(-y, x) vector_y = smallestAngleCorner.x - largestAngleCorner.x; vector_x = myCircumcenter.x + vector_x; vector_y = myCircumcenter.y + vector_y; // by intersecting bisectors you will end up with the one you want to walk on // then this line and circle should be intersected CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, xPetalCtr, yPetalCtr, petalRadius, ref p); /// choose the correct intersection point /// // calcuwedgeslate middle point of the longest edge(bisector) xMidOfMiddleEdge = (largestAngleCorner.x + smallestAngleCorner.x) / 2.0; yMidOfMiddleEdge = (largestAngleCorner.y + smallestAngleCorner.y) / 2.0; // we need to find correct intersection point, since line intersects circle twice // this direction is always ACUTE isCorrect = ChooseCorrectPoint(xMidOfMiddleEdge, yMidOfMiddleEdge, p[3], p[4], myCircumcenter.x, myCircumcenter.y, false/*(isObtuse+1)%2*/); // make sure which point is the correct one to be considered if (isCorrect) { inter_x = p[3]; inter_y = p[4]; } else { inter_x = p[1]; inter_y = p[2]; } /// check if there is a Voronoi vertex between before intersection /// // check if the voronoi vertex is between the intersection and circumcenter PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); /// determine the point to be suggested /// if (p[0] > 0.0) { // there is at least one intersection point // if it is between circumcenter and intersection // if it returns 1.0 this means we have a voronoi vertex within feasible region if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) { if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) { // go back to circumcenter dxSecondSuggestion = dx; dySecondSuggestion = dy; } else { // we are not creating a bad triangle // neighbor's circumcenter is suggested dxSecondSuggestion = voronoiOrInter[2] - torg.x; dySecondSuggestion = voronoiOrInter[3] - torg.y; } } else { // there is no voronoi vertex between intersection point and circumcenter if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) { // if it is inside feasible region, then insert v2 // apply perturbation // find the distance between circumcenter and intersection point d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); // then find the vector going from intersection point to circumcenter ax = myCircumcenter.x - inter_x; ay = myCircumcenter.y - inter_y; ax = ax / d; ay = ay / d; // now calculate the new intersection point which is perturbated towards the circumcenter inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) { // go back to circumcenter dxSecondSuggestion = dx; dySecondSuggestion = dy; } else { // intersection point is suggested dxSecondSuggestion = inter_x - torg.x; dySecondSuggestion = inter_y - torg.y; } } else { // intersection point is suggested dxSecondSuggestion = inter_x - torg.x; dySecondSuggestion = inter_y - torg.y; } } /// if it is an acute triangle, check if it is a good enough location /// // for acute triangle case, we need to check if it is ok to use either of them if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > lengthConst * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * (smallestAngleCorner.y - (dySecondSuggestion + torg.y)))) { // use circumcenter dxSecondSuggestion = dx; dySecondSuggestion = dy; }// else we stick on what we have found } }// if it is on the boundary, meaning no neighbor triangle in this direction, the other direction might be ok if (isObtuse) { //obtuse: do nothing dx = dxFirstSuggestion; dy = dyFirstSuggestion; } else { // acute : consider other direction if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) { dx = dxSecondSuggestion; dy = dySecondSuggestion; } else { dx = dxFirstSuggestion; dy = dyFirstSuggestion; } }// end if obtuse }// end of relocation }// end of almostGood Point circumcenter = new Point(); if (relocated <= 0) { circumcenter.x = torg.x + dx; circumcenter.y = torg.y + dy; } else { circumcenter.x = origin_x + dx; circumcenter.y = origin_y + dy; } xi = (yao * dx - xao * dy) * (2.0 * denominator); eta = (xdo * dy - ydo * dx) * (2.0 * denominator); return circumcenter; }
/// <summary> /// Create a new subsegment and inserts it between two triangles. Its /// vertices are properly initialized. /// </summary> /// <param name="tri">The new subsegment is inserted at the edge /// described by this handle.</param> /// <param name="subsegmark">The marker 'subsegmark' is applied to the /// subsegment and, if appropriate, its vertices.</param> internal void InsertSubseg(ref Otri tri, int subsegmark) { Otri oppotri = default(Otri); Osub newsubseg = default(Osub); Vertex triorg, tridest; triorg = tri.Org(); tridest = tri.Dest(); // Mark vertices if possible. if (triorg.label == 0) { triorg.label = subsegmark; } if (tridest.label == 0) { tridest.label = subsegmark; } // Check if there's already a subsegment here. tri.Pivot(ref newsubseg); if (newsubseg.seg.hash == DUMMY) { // Make new subsegment and initialize its vertices. MakeSegment(ref newsubseg); newsubseg.SetOrg(tridest); newsubseg.SetDest(triorg); newsubseg.SetSegOrg(tridest); newsubseg.SetSegDest(triorg); // Bond new subsegment to the two triangles it is sandwiched between. // Note that the facing triangle 'oppotri' might be equal to 'dummytri' // (outer space), but the new subsegment is bonded to it all the same. tri.SegBond(ref newsubseg); tri.Sym(ref oppotri); newsubseg.Sym(); oppotri.SegBond(ref newsubseg); newsubseg.seg.boundary = subsegmark; } else if (newsubseg.seg.boundary == 0) { newsubseg.seg.boundary = subsegmark; } }
/// <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 != 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); } }
/// <summary> /// Transform two triangles to two different triangles by flipping an edge /// counterclockwise within a quadrilateral. /// </summary> /// <param name="flipedge">Handle to the edge that will be flipped.</param> /// <remarks>Imagine the original triangles, abc and bad, oriented so that the /// shared edge ab lies in a horizontal plane, with the vertex b on the left /// and the vertex a on the right. The vertex c lies below the edge, and /// the vertex d lies above the edge. The 'flipedge' handle holds the edge /// ab of triangle abc, and is directed left, from vertex a to vertex b. /// /// The triangles abc and bad are deleted and replaced by the triangles cdb /// and dca. The triangles that represent abc and bad are NOT deallocated; /// they are reused for dca and cdb, respectively. Hence, any handles that /// may have held the original triangles are still valid, although not /// directed as they were before. /// /// Upon completion of this routine, the 'flipedge' handle holds the edge /// dc of triangle dca, and is directed down, from vertex d to vertex c. /// (Hence, the two triangles have rotated counterclockwise.) /// /// WARNING: This transformation is geometrically valid only if the /// quadrilateral adbc is convex. Furthermore, this transformation is /// valid only if there is not a subsegment between the triangles abc and /// bad. This routine does not check either of these preconditions, and /// it is the responsibility of the calling routine to ensure that they are /// met. If they are not, the streets shall be filled with wailing and /// gnashing of teeth. /// /// Terminology /// /// A "local transformation" replaces a small set of triangles with another /// set of triangles. This may or may not involve inserting or deleting a /// vertex. /// /// The term "casing" is used to describe the set of triangles that are /// attached to the triangles being transformed, but are not transformed /// themselves. Think of the casing as a fixed hollow structure inside /// which all the action happens. A "casing" is only defined relative to /// a single transformation; each occurrence of a transformation will /// involve a different casing. /// </remarks> internal void Flip(ref Otri flipedge) { Otri botleft = default(Otri), botright = default(Otri); Otri topleft = default(Otri), topright = default(Otri); Otri top = default(Otri); Otri botlcasing = default(Otri), botrcasing = default(Otri); Otri toplcasing = default(Otri), toprcasing = default(Otri); Osub botlsubseg = default(Osub), botrsubseg = default(Osub); Osub toplsubseg = default(Osub), toprsubseg = default(Osub); Vertex leftvertex, rightvertex, botvertex; Vertex farvertex; // Identify the vertices of the quadrilateral. rightvertex = flipedge.Org(); leftvertex = flipedge.Dest(); botvertex = flipedge.Apex(); flipedge.Sym(ref top); // SELF CHECK //if (top.triangle.id == DUMMY) //{ // logger.Error("Attempt to flip on boundary.", "Mesh.Flip()"); // flipedge.LnextSelf(); // return; //} //if (checksegments) //{ // flipedge.SegPivot(ref toplsubseg); // if (toplsubseg.ss != Segment.Empty) // { // logger.Error("Attempt to flip a segment.", "Mesh.Flip()"); // flipedge.LnextSelf(); // return; // } //} farvertex = top.Apex(); // Identify the casing of the quadrilateral. top.Lprev(ref topleft); topleft.Sym(ref toplcasing); top.Lnext(ref topright); topright.Sym(ref toprcasing); flipedge.Lnext(ref botleft); botleft.Sym(ref botlcasing); flipedge.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.Pivot(ref toplsubseg); botleft.Pivot(ref botlsubseg); botright.Pivot(ref botrsubseg); topright.Pivot(ref toprsubseg); if (toplsubseg.seg.hash == DUMMY) { topright.SegDissolve(dummysub); } else { topright.SegBond(ref toplsubseg); } if (botlsubseg.seg.hash == DUMMY) { topleft.SegDissolve(dummysub); } else { topleft.SegBond(ref botlsubseg); } if (botrsubseg.seg.hash == DUMMY) { botleft.SegDissolve(dummysub); } else { botleft.SegBond(ref botrsubseg); } if (toprsubseg.seg.hash == DUMMY) { botright.SegDissolve(dummysub); } else { botright.SegBond(ref toprsubseg); } } // New vertex assignments for the rotated quadrilateral. flipedge.SetOrg(farvertex); flipedge.SetDest(botvertex); flipedge.SetApex(rightvertex); top.SetOrg(botvertex); top.SetDest(farvertex); top.SetApex(leftvertex); }