//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Insert a new parabola into the beachline when the beachline spans the X axis. </summary> /// <remarks> /// This is the normal case. We insert our new parabola and split the parabola above our site in /// two. This means one new leaf node is created for leftmost of the two nodes in the split (the /// old lfn is recycled to become the right node of the split). Also a new internal node to /// parent all this. /// </remarks> /// <param name="lfnOld"> Parabola above the new site. </param> /// <param name="lfnNewParabola"> parabola for the new site. </param> /// <param name="innSubRoot"> /// Parent node of both lfnOld and lfnNewParabola represneting /// the breakpoint between them. /// </param> //////////////////////////////////////////////////////////////////////////////////////////////////// private static void InsertAtDifferentY(LeafNode lfnOld, LeafNode lfnNewParabola, InternalNode innSubRoot) { // The old lfn will become the new right half of the split but we need a new leaf node // for the left half of the split... var lfnLeftHalf = new LeafNode(lfnOld.Poly); var innSubRootLeftChild = new InternalNode(lfnOld.Poly, lfnNewParabola.Poly); var edge = new FortuneEdge(); // This is all fairly straightforward (albeit dense) insertion of a node into a binary tree. innSubRoot.RightChild = lfnOld; innSubRoot.LeftChild = innSubRootLeftChild; innSubRoot.SetEdge(edge); innSubRoot.AddEdgeToPolygons(edge); innSubRootLeftChild.LeftChild = lfnLeftHalf; innSubRootLeftChild.RightChild = lfnNewParabola; innSubRootLeftChild.SetEdge(edge); lfnNewParabola.LeftAdjacentLeaf = lfnLeftHalf; lfnNewParabola.RightAdjacentLeaf = lfnOld; lfnLeftHalf.LeftAdjacentLeaf = lfnOld.LeftAdjacentLeaf; lfnLeftHalf.RightAdjacentLeaf = lfnNewParabola; if (lfnOld.LeftAdjacentLeaf != null) { lfnOld.LeftAdjacentLeaf.RightAdjacentLeaf = lfnLeftHalf; } lfnOld.LeftAdjacentLeaf = lfnNewParabola; edge.SetPolys(innSubRoot.PolyRight, innSubRoot.PolyLeft); }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Handle the top N nodes located on a single horizontal line. </summary> /// <remarks> /// This only handles the corner case where the top N nodes are on the same horizontal /// line. In that case the parabolas from previous points are vertically straight up and only /// project to a single point on the x axis so that the beachline is a series of points rather /// than a series of parabolas. When that is the case we can't "intersect" new points with /// parabolas that span the x axis. After the scanline passes that initial set of topmost points, /// there will always be a parabola which projects to the entire x axis so no need for this /// special handling. Normally, we produce two new parabolas at a site event like this - the new /// parabola for the site itself and the new parabola produced when we split the parabola above /// us. In this case there is no parabola above us so we only produce one new parabola - the one /// inserted by the site. /// </remarks> /// <param name="lfn"> LeafNode of the (degenerate) parabola nearest us. </param> /// <param name="lfnNewParabola"> LeafNode we're inserting. </param> /// <param name="innParent"> Parent of lfnOld. </param> /// <param name="innSubRoot"> Root of the tree. </param> /// <param name="fLeftChild"> Left child of innParent. </param> //////////////////////////////////////////////////////////////////////////////////////////////////// private static void NdInsertAtSameY( LeafNode lfn, LeafNode lfnNewParabola, InternalNode innParent, InternalNode innSubRoot, bool fLeftChild) { // Locals LeafNode lfnLeft, lfnRight; var lfnAdjacentParabolaLeft = lfn.LeftAdjacentLeaf; var lfnAdjacentParabolaRight = lfn.RightAdjacentLeaf; if (lfnNewParabola.Poly.VoronoiPoint.X < lfn.Poly.VoronoiPoint.X) { lfnLeft = lfnNewParabola; lfnRight = lfn; } else { //! Note: I don't think this case ever occurs in practice since we pull events off with higher //! x coordinates before events with lower x coordinates lfnLeft = lfn; lfnRight = lfnNewParabola; } innSubRoot.PolyLeft = lfnLeft.Poly; innSubRoot.PolyRight = lfnRight.Poly; innSubRoot.LeftChild = lfnLeft; innSubRoot.RightChild = lfnRight; var edge = new FortuneEdge(); innSubRoot.SetEdge(edge); innSubRoot.AddEdgeToPolygons(edge); lfnLeft.LeftAdjacentLeaf = lfnAdjacentParabolaLeft; lfnLeft.RightAdjacentLeaf = lfnRight; lfnRight.LeftAdjacentLeaf = lfnLeft; lfnRight.RightAdjacentLeaf = lfnAdjacentParabolaRight; if (innParent != null) { if (fLeftChild) { innParent.PolyLeft = lfnRight.Poly; } else { innParent.PolyRight = lfnLeft.Poly; } } edge.SetPolys(innSubRoot.PolyRight, innSubRoot.PolyLeft); }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Splits a doubly infinite edge. </summary> /// <remarks> Darrellp, 2/18/2011. </remarks> /// <param name="edge"> Edge we need to split. </param> //////////////////////////////////////////////////////////////////////////////////////////////////// private static void SplitDoublyInfiniteEdge(FortuneEdge edge) { // Initialize var pt1 = edge.Poly1.VoronoiPoint; var pt2 = edge.Poly2.VoronoiPoint; var dx = pt2.X - pt1.X; var dy = pt2.Y - pt1.Y; var ptMid = new Vector((pt1.X + pt2.X) / 2, (pt1.Y + pt2.Y) / 2); // Infinite vertices have directions in them rather than locations var vtx1 = FortuneVertex.InfiniteVertex(new Vector(-dy, dx)); var vtx2 = FortuneVertex.InfiniteVertex(new Vector(dy, -dx)); // Create the new edge an link it in edge.VtxStart = new FortuneVertex(ptMid); edge.VtxEnd = vtx1; var edgeNew = new FortuneEdge { VtxStart = edge.VtxStart, VtxEnd = vtx2 }; edgeNew.SetPolys(edge.Poly1, edge.Poly2); edge.Poly1.AddEdge(edgeNew); edge.Poly2.AddEdge(edgeNew); ((FortuneVertex)edge.VtxStart).Add(edge); ((FortuneVertex)edge.VtxStart).Add(edgeNew); vtx1.Add(edge); vtx2.Add(edgeNew); edge.FSplit = edgeNew.FSplit = true; // If the edge "leans right" // // We have to be very picky about how we set up the left and right // polygons for our new rays. // ReSharper disable once CompareOfFloatsByEqualityOperator if (dx == 0 || dx * dy > 0) // dy == 0 case needs to fall through... { // Set up left and right polygons one way edge.PolyRight = edgeNew.PolyLeft = edge.Poly1; edge.PolyLeft = edgeNew.PolyRight = edge.Poly2; } else { // Set left and right polygons the other way edge.PolyLeft = edgeNew.PolyRight = edge.Poly1; edge.PolyRight = edgeNew.PolyLeft = edge.Poly2; } }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Remove a parabola node from the beachline since it's being squeezed out and insert a vertex /// into the voronoi diagram. /// </summary> /// <remarks> /// This happens when a circle event occurs. It's a rather delicate operation. From the point of /// view of the voronoi diagram, we have two edges from above coming together into the newly /// created vertex and a new edge created which descends below it. This is really where the meat /// of actually creating the voronoi diagram occurs. One of the important details which seems to /// be left totally out of the book is the importance of keeping accurate left and right sibling /// pointers on the leaf nodes. Since each leaf node represents a parabola in the beachline, /// these pointers represent the set of parabolas from the left to the right of the beachline. /// In a case like this where a parabola is being squeezed out, it's left and right siblings will /// not butt up against each other forming a new edge and we need to be able to locate both these /// nodes in order to make everything come out right. /// This is very persnickety code. /// </remarks> /// <param name="cevt"> Circle event which caused this. </param> /// <param name="lfnEliminated"> Leaf node for the parabola being eliminated. </param> /// <param name="voronoiVertex"> The new vertex to be inserted into the voronoi diagram. </param> /// <param name="evq"> Event queue. </param> //////////////////////////////////////////////////////////////////////////////////////////////////// internal void RemoveNodeAndInsertVertex(CircleEvent cevt, LeafNode lfnEliminated, Vector voronoiVertex, EventQueue evq) { // Initialize var yScanLine = cevt.Pt.Y; // Determine whether we're the left or right child of our parent var fLeftChildEliminated = lfnEliminated.IsLeftChild; var innParent = lfnEliminated.NdParent; // Retrieve sibling nodes var lfnLeft = lfnEliminated.LeftAdjacentLeaf; var lfnRight = lfnEliminated.RightAdjacentLeaf; var lfnNearSibling = fLeftChildEliminated ? lfnRight : lfnLeft; // Remove from the queue any circle events which involve the eliminated node or its siblings RemoveAssociatedCircleEvents(lfnEliminated, evq); // remove the leaf from the tree and rearrange the nodes around it RemoveLeaf(lfnEliminated); // Locate the internal node which represents the breakpoint between our near and far sibling var innFarSiblingEdge = lfnNearSibling.InnFindSiblingEdge(fLeftChildEliminated); // Get the edges being developed on each side of us // // The meeting of these edges is what causes the creation of our vertex in the voronoi diagram var edgeNearSibling = innParent.Edge; var edgeFarSibling = innFarSiblingEdge.Edge; // Create a new fortune vertex to insert into the diagram var vertex = new FortuneVertex { Pt = voronoiVertex }; // Give both edges from above their brand new vertex - hooray! edgeFarSibling.AddVertex(vertex); edgeNearSibling.AddVertex(vertex); // Is this a zero length edge? // // Some of our incoming edges are zero length due to cocircular points, // so keep track of it in the polys which border them. This will be used // later in Fortune.BuildWingedEdge() to determine when to try and remove // zero length edges. if (cevt.FZeroLength) { // Mark the poly as having a zero length edge. // // We can't eliminate it here because most of our winged edge machinery // needs to assume three edges entering every vertex. We'll take care of // it later in post-processing. This flag is the signal to do that. SetZeroLengthFlagOnPolys(edgeNearSibling, edgeFarSibling); } // RQS- Add edges to the vertex in proper clockwise direction if (fLeftChildEliminated) { vertex.Add(edgeFarSibling); vertex.Add(edgeNearSibling); } else { vertex.Add(edgeNearSibling); vertex.Add(edgeFarSibling); } // -RQS // Create the new edge which emerges below this vertex var edge = new FortuneEdge(); edge.AddVertex(vertex); vertex.Add(edge); // Add the edge to our siblings // // Since lfnEliminated is being removed, it's siblings now butt against each other forming // the new edge so save that edge on the internal node and add it to the poly for the // generator represented by the near sibling. This means that polygon edges get added in // a fairly random order. They'll be sorted in postprocessing. // Add it to our winged edge polygon lfnNearSibling.Poly.AddEdge(edge); // Also add it to our beachline sibling innFarSiblingEdge.Edge = edge; // Fix up the siblings and the edges/polygons they border // // The inner node which used to represent one of the incoming edges now takes on responsibility // for the newly created edge so it no longer borders the polygon represented by the eliminated // leaf node, but rather borders the polygon represented by its sibling on the other side. // Also, that polygon receives a new edge. // If we eliminated the left child from a parent if (fLeftChildEliminated) { // Reset the data on the inner node representing our far sibling innFarSiblingEdge.PolyRight = lfnNearSibling.Poly; innFarSiblingEdge.PolyLeft.AddEdge(edge); // If this event represented a zero length edge if (cevt.FZeroLength) { // Keep track of it in the fortune polygon innFarSiblingEdge.PolyLeft.FZeroLengthEdge = true; } } else { // Reset the data on the inner node representing our far sibling innFarSiblingEdge.PolyLeft = lfnNearSibling.Poly; innFarSiblingEdge.PolyRight.AddEdge(edge); // If this event represented a zero length edge if (cevt.FZeroLength) { // Keep track of it in the fortune polygon innFarSiblingEdge.PolyRight.FZeroLengthEdge = true; } } // Set the polygons which border the new edge edge.SetPolys(innFarSiblingEdge.PolyRight, innFarSiblingEdge.PolyLeft); // Create new circle events for our siblings // // Now that we're squeezed out, our former left and right siblings become immediate siblings // so we need to set new circle events to represent when they get squeezed out by their // newly acquired siblings CreateCircleEventFromTriple(lfnLeft.LeftAdjacentLeaf, lfnLeft, lfnRight, yScanLine, evq); CreateCircleEventFromTriple(lfnLeft, lfnRight, lfnRight.RightAdjacentLeaf, yScanLine, evq); }