/// <summary> /// Enforce the Delaunay condition at an edge, fanning out recursively from /// an existing vertex. Pay special attention to stacking inverted triangles. /// </summary> /// <param name="fixuptri"></param> /// <param name="leftside"> /// Indicates whether or not fixuptri is to the left of /// the segment being inserted. (Imagine that the segment is pointing up from /// endpoint1 to endpoint2.) /// </param> /// <remarks> /// This is a support routine for inserting segments into a constrained /// Delaunay triangulation. /// The origin of fixuptri is treated as if it has just been inserted, and /// the local Delaunay condition needs to be enforced. It is only enforced /// in one sector, however, that being the angular range defined by /// fixuptri. /// This routine also needs to make decisions regarding the "stacking" of /// triangles. (Read the description of ConstrainedEdge() below before /// reading on here, so you understand the algorithm.) If the position of /// the new vertex (the origin of fixuptri) indicates that the vertex before /// it on the polygon is a reflex vertex, then "stack" the triangle by /// doing nothing. (fixuptri is an inverted triangle, which is how stacked /// triangles are identified.) /// Otherwise, check whether the vertex before that was a reflex vertex. /// If so, perform an edge flip, thereby eliminating an inverted triangle /// (popping it off the stack). The edge flip may result in the creation /// of a new inverted triangle, depending on whether or not the new vertex /// is visible to the vertex three edges behind on the polygon. /// If neither of the two vertices behind the new vertex are reflex /// vertices, fixuptri and fartri, the triangle opposite it, are not /// inverted; hence, ensure that the edge between them is locally Delaunay. /// </remarks> private void DelaunayFixup(ref Otri fixuptri, bool leftside) { Otri neartri = default(Otri); Otri fartri = default(Otri); Osub faredge = default(Osub); Vertex nearvertex, leftvertex, rightvertex, farvertex; fixuptri.Lnext(ref neartri); neartri.Sym(ref fartri); // Check if the edge opposite the origin of fixuptri can be flipped. if (fartri.tri.Id == Mesh.DUMMY) { return; } neartri.Pivot(ref faredge); if (faredge.seg.hash != Mesh.DUMMY) { return; } // Find all the relevant vertices. nearvertex = neartri.Apex(); leftvertex = neartri.Org(); rightvertex = neartri.Dest(); farvertex = fartri.Apex(); // Check whether the previous polygon vertex is a reflex vertex. if (leftside) { if (RobustPredicates.CounterClockwise(nearvertex, leftvertex, farvertex) <= 0.0) { // leftvertex is a reflex vertex too. Nothing can // be done until a convex section is found. return; } } else { if (RobustPredicates.CounterClockwise(farvertex, rightvertex, nearvertex) <= 0.0) { // rightvertex is a reflex vertex too. Nothing can // be done until a convex section is found. return; } } if (RobustPredicates.CounterClockwise(rightvertex, leftvertex, farvertex) > 0.0) { // fartri is not an inverted triangle, and farvertex is not a reflex // vertex. As there are no reflex vertices, fixuptri isn't an // inverted triangle, either. Hence, test the edge between the // triangles to ensure it is locally Delaunay. if (RobustPredicates.InCircle(leftvertex, farvertex, rightvertex, nearvertex) <= 0.0) { return; } // Not locally Delaunay; go on to an edge flip. } // else fartri is inverted; remove it from the stack by flipping. mesh.Flip(ref neartri); fixuptri.Lprev(); // Restore the origin of fixuptri after the flip. // Recursively process the two triangles that result from the flip. DelaunayFixup(ref fixuptri, leftside); DelaunayFixup(ref fartri, leftside); }
/// <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 (RobustPredicates.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 (RobustPredicates.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 = RobustPredicates.CounterClockwise(upperleft, lowerleft, lowerright) <= 0.0; rightfinished = RobustPredicates.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 = RobustPredicates.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 = RobustPredicates.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 = RobustPredicates.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 = RobustPredicates.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0; } else { // Avoid eating right through the triangulation. badedge = false; } } } } if (leftfinished || (!rightfinished && (RobustPredicates.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(); } } }