//////////////////////////////////////////////////////////////////////////////////////////////////// /// <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> Remove a leaf node. </summary> /// <remarks> Darrellp, 2/18/2011. </remarks> /// <param name="lfn"> node to remove. </param> //////////////////////////////////////////////////////////////////////////////////////////////////// private void RemoveLeaf(LeafNode lfn) { // If we're the root, then tree go bye bye... if (lfn == NdRoot) { NdRoot = null; return; } // If we're a child of the root then all we do is change the root to our immediate sibling if (lfn.NdParent == NdRoot) { NdRoot = lfn.ImmediateSibling; return; } // Remove the leaf node and its parent // // We remove both the leafnode and it's parent (it's parent represents the edge // that just terminated in our new fortune vertex, hence it's need to be removed // also). Our immediate sibling // is moved up to be a child of the grandparent. This changes the height // balance on the grandparent since it loses a level. var innParent = lfn.NdParent; var innGrandparent = innParent.NdParent; var fIsParentLeftChild = innParent.IsLeftChild; // Remove our parent innParent.SnipFromParent(); // Insert our sibling in place of our parent // Was our parent the left child of our grandparent? if (fIsParentLeftChild) { // Move sibling to be the left child of our grandparent innGrandparent.LeftChild = lfn.ImmediateSibling; innGrandparent.DecDht(); } else { // Move sibling to be the right child of our grandparent innGrandparent.RightChild = lfn.ImmediateSibling; innGrandparent.IncDht(); } // Link our former siblings together // // Now that we've been removed, our former siblings become direct siblings so link them together // in the adjacent leaf chain lfn.LinkSiblingsTogether(); }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Create a circle event from a triple of leaf nodes. </summary> /// <remarks> Darrellp, 2/18/2011. </remarks> /// <param name="lfnLeft"> Leaf node representing the leftmost parabola. </param> /// <param name="lfnCenter"> Leaf node representing the center parabola. </param> /// <param name="lfnRight"> Leaf node representing the rightmost parabola. </param> /// <param name="yScanLine"> Where the scan line is located. </param> /// <param name="evq"> Event queue. </param> //////////////////////////////////////////////////////////////////////////////////////////////////// private static void CreateCircleEventFromTriple( LeafNode lfnLeft, LeafNode lfnCenter, LeafNode lfnRight, double yScanLine, EventQueue evq) { // This happens if we're the farthest right or left parabola... if (lfnLeft == null || lfnRight == null || lfnCenter == null) { // No circle events associated with non-existent parabolas return; } // We need at least three points if (lfnRight == lfnCenter || lfnRight == lfnLeft || lfnCenter == lfnLeft) { // If two of the points are identical then we don't have three points return; } // Make sure we don't insert the same circle eventin twice if (ICcwVoronoi(lfnLeft.Poly.VoronoiPoint, lfnCenter.Poly.VoronoiPoint, lfnRight.Poly.VoronoiPoint) > 0) { // Don't create an event if we've already put it in before return; } // Create the circle event var cevt = FortuneEvent.CreateCircleEvent(lfnLeft.Poly, lfnCenter.Poly, lfnRight.Poly, yScanLine); // If we got a valid circle event if (cevt != null) { // Indicate which leaf node gets snuffed when this event is handled cevt.LfnEliminated = lfnCenter; // Add it to the event queue evq.AddCircleEvent(cevt); // Let the center node know this event will bring about its ominous demise lfnCenter.SetCircleEvent(cevt); } }
/// <summary> /// Create the new circle events that arise from a site event /// </summary> /// <param name="lfnLeft">Node to the left</param> /// <param name="lfnRight">Node to the right</param> /// <param name="yScanLine">Scan line position</param> /// <param name="evq">Event queue</param> private static void CreateCircleEventsFromSiteEvent( LeafNode lfnLeft, LeafNode lfnRight, double yScanLine, EventQueue evq) { if (lfnLeft?.RightAdjacentLeaf != null) { CreateCircleEventFromTriple( lfnLeft, lfnLeft.RightAdjacentLeaf, lfnLeft.RightAdjacentLeaf.RightAdjacentLeaf, yScanLine, evq); } if (lfnRight?.LeftAdjacentLeaf != null) { CreateCircleEventFromTriple( lfnRight.LeftAdjacentLeaf.LeftAdjacentLeaf, lfnRight.LeftAdjacentLeaf, lfnRight, yScanLine, evq); } }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Insert a new LeafNode into the tree. </summary> /// <remarks> Darrellp, 2/19/2011. </remarks> /// <param name="lfn"> Place to put the new leaf node. </param> /// <param name="evt"> The event to insert. </param> /// <returns> . </returns> //////////////////////////////////////////////////////////////////////////////////////////////////// private static InternalNode NdCreateInsertionSubtree(LeafNode lfn, SiteEvent evt) { // Initialize locals var innParent = lfn.NdParent; var lfnNewParabola = new LeafNode(evt.Poly); var innSubRoot = new InternalNode(evt.Poly, lfn.Poly); var fLeftChild = true; // If this isn't on the root node, shuffle things around a bit if (innParent != null) { fLeftChild = lfn.IsLeftChild; lfn.SnipFromParent(); if (fLeftChild) { innParent.LeftChild = innSubRoot; } else { innParent.RightChild = innSubRoot; } } // Watch for the odd corner case of the top n generators having the same y coordinate. See comments // on NdInsertAtSameY(). if (Geometry2D.FCloseEnough(evt.Pt.Y, lfn.Poly.VoronoiPoint.Y)) { NdInsertAtSameY(lfn, lfnNewParabola, innParent, innSubRoot, fLeftChild); } else { InsertAtDifferentY(lfn, lfnNewParabola, innSubRoot); } return(innSubRoot); }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <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); }