/// <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> /// 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> /// 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.LprevSelf(); farright.LnextSelf(); farleft.Bond(ref farright); farleft.LprevSelf(); farright.LnextSelf(); 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 = Primitives.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.LnextSelf(); tri1.LprevSelf(); tri2.LnextSelf(); tri3.LprevSelf(); midtri.Bond(ref tri3); tri1.Bond(ref tri2); midtri.LnextSelf(); tri1.LprevSelf(); tri2.LnextSelf(); tri3.LprevSelf(); 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.LnextSelf(); midtri.Bond(ref tri2); midtri.LnextSelf(); midtri.Bond(ref tri3); tri1.LprevSelf(); tri2.LnextSelf(); tri1.Bond(ref tri2); tri1.LprevSelf(); tri3.LprevSelf(); tri1.Bond(ref tri3); tri2.LnextSelf(); tri3.LprevSelf(); 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); } }