/// <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.mark == 0) { triorg.mark = subsegmark; } if (tridest.mark == 0) { tridest.mark = subsegmark; } // Check if there's already a subsegment here. tri.SegPivot(ref newsubseg); if (newsubseg.seg == dummysub) { // 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.SymSelf(); oppotri.SegBond(ref newsubseg); newsubseg.seg.boundary = subsegmark; } else { if (newsubseg.seg.boundary == 0) { newsubseg.seg.boundary = subsegmark; } } }
/// <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 == dummytri) //{ // logger.Error("Attempt to flip on boundary.", "Mesh.Flip()"); // flipedge.LnextSelf(); // return; //} //if (checksegments) //{ // flipedge.SegPivot(ref toplsubseg); // if (toplsubseg.ss != dummysub) // { // 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.SegPivot(ref toplsubseg); botleft.SegPivot(ref botlsubseg); botright.SegPivot(ref botrsubseg); topright.SegPivot(ref toprsubseg); if (toplsubseg.seg == Mesh.dummysub) { topright.SegDissolve(); } else { topright.SegBond(ref toplsubseg); } if (botlsubseg.seg == Mesh.dummysub) { topleft.SegDissolve(); } else { topleft.SegBond(ref botlsubseg); } if (botrsubseg.seg == Mesh.dummysub) { botleft.SegDissolve(); } else { botleft.SegBond(ref botrsubseg); } if (toprsubseg.seg == Mesh.dummysub) { botright.SegDissolve(); } 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); }
/// <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.triangle != Mesh.dummytri)) { // 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> /// 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.SegPivot(ref toplsubseg); botleft.SegPivot(ref botlsubseg); botright.SegPivot(ref botrsubseg); topright.SegPivot(ref toprsubseg); if (toplsubseg.seg == Mesh.dummysub) { botleft.SegDissolve(); } else { botleft.SegBond(ref toplsubseg); } if (botlsubseg.seg == Mesh.dummysub) { botright.SegDissolve(); } else { botright.SegBond(ref botlsubseg); } if (botrsubseg.seg == Mesh.dummysub) { topright.SegDissolve(); } else { topright.SegBond(ref botrsubseg); } if (toprsubseg.seg == Mesh.dummysub) { topleft.SegDissolve(); } 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); }
/// <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.LnextSelf(); farleft.SymSelf(); 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.LnextSelf(); innerright.SymSelf(); 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 (Primitives.CounterClockwise(innerleftdest, innerleftapex, innerrightorg) > 0.0) { innerleft.LprevSelf(); innerleft.SymSelf(); innerleftdest = innerleftapex; innerleftapex = innerleft.Apex(); changemade = true; } // Make innerrightorg the "bottommost" vertex of the right hull. if (Primitives.CounterClockwise(innerrightapex, innerrightorg, innerleftdest) > 0.0) { innerright.LnextSelf(); innerright.SymSelf(); 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.LnextSelf(); baseedge.Bond(ref innerright); baseedge.LnextSelf(); 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 = Primitives.CounterClockwise(upperleft, lowerleft, lowerright) <= 0.0; rightfinished = Primitives.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.LnextSelf(); nextedge.Bond(ref rightcand); nextedge.LnextSelf(); 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.LprevSelf(); farright.SymSelf(); 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.SymSelf(); 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 = Primitives.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.LnextSelf(); nextedge.Sym(ref topcasing); nextedge.LnextSelf(); nextedge.Sym(ref sidecasing); nextedge.Bond(ref topcasing); leftcand.Bond(ref sidecasing); leftcand.LnextSelf(); leftcand.Sym(ref outercasing); nextedge.LprevSelf(); 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 = Primitives.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.SymSelf(); 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 = Primitives.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.LprevSelf(); nextedge.Sym(ref topcasing); nextedge.LprevSelf(); nextedge.Sym(ref sidecasing); nextedge.Bond(ref topcasing); rightcand.Bond(ref sidecasing); rightcand.LprevSelf(); rightcand.Sym(ref outercasing); nextedge.LnextSelf(); 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 = Primitives.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0; } else { // Avoid eating right through the triangulation. badedge = false; } } } } if (leftfinished || (!rightfinished && (Primitives.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(); } } }