//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Adds a circle event. </summary> /// <remarks> Darrellp, 2/21/2011. </remarks> /// <param name="cevt"> The event to add. </param> //////////////////////////////////////////////////////////////////////////////////////////////////// internal void AddCircleEvent(CircleEvent cevt) { // Add to the priority queue Add(cevt); // We also have to add them to our linked list of circle events CircleEvents.AddFirst(cevt); // Keeping track of their LinkedListNode allows us to delete them efficiently later cevt.LinkedListNode = CircleEvents.First; }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Remove the circle event which snuffs this node's parabola from the event queue. </summary> /// <remarks> Darrellp, 2/18/2011. </remarks> /// <param name="evq"> Event queue. </param> //////////////////////////////////////////////////////////////////////////////////////////////////// internal void DeleteAssociatedCircleEvent(EventQueue evq) { // If we've got a circle event if (_cevt != null) { // Delete it evq.Delete(_cevt); if (_cevt.LinkedListNode != null) { evq.CircleEvents.Remove(_cevt.LinkedListNode); } _cevt = null; } }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Creates a circle event. </summary> /// <remarks>Circle events are created at the circumcenters of three sites - the sites for poly1/2/3.</remarks> /// <param name="poly1"> The first polygon. </param> /// <param name="poly2"> The second polygon. </param> /// <param name="poly3"> The third polygon. </param> /// <param name="yScanLine"> The y coordinate scan line. </param> /// <returns> A new circle event. </returns> //////////////////////////////////////////////////////////////////////////////////////////////////// internal static CircleEvent CreateCircleEvent(FortunePoly poly1, FortunePoly poly2, FortunePoly poly3, double yScanLine) { // Locals CircleEvent cevtRet = null; // Determine a circumcenter for the sites of poly1/2/3. if (Geometry2D.FFindCircumcenter(poly1.VoronoiPoint, poly2.VoronoiPoint, poly3.VoronoiPoint, out var ptCenter)) { // Determine y coordinate for the side of the circle // The event will fire when the scan line hits that y position var radius = Geometry2D.Distance(poly1.VoronoiPoint, ptCenter); ptCenter.Y -= radius; // If the circumcenter is above the scan line we've already passed it by, so don't put it in the queue if (ptCenter.Y <= yScanLine) { cevtRet = new CircleEvent(ptCenter, radius); } } return(cevtRet); }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <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); }
/// <summary> /// Remove events from the queue and handle them one at a time. /// </summary> private void ProcessEvents() { // Create an impossible fictional "previous event" FortuneEvent evtPrev = new CircleEvent(new Vector(Single.MaxValue, Single.MaxValue), 0); // While there are events in the queue while (QevEvents.Count > 0) { // Get the next event // // Events are pulled off in top to bottom, left to right, site event before // circle event order. This ensures that events/sites at identical locations // will be pulled off one after the other which allows us to cull them. var evt = QevEvents.Pop(); // If we've got a pair of identically placed events if (FCloseEnough(evt.Pt, evtPrev.Pt)) { // Locals var tpPrev = evtPrev.GetType(); var tpCur = evt.GetType(); // If the previous event was a site event if (tpPrev == typeof(SiteEvent)) { // And the current one is also if (tpCur == typeof(SiteEvent)) { // Skip identical site events. // // This handles cases where the same point is in the input data two or more times. continue; } } // Else if it's a circle event else if (tpCur == typeof(CircleEvent)) { // Locals var cevt = evt as CircleEvent; var cevtPrev = evtPrev as CircleEvent; // If we have identically placed circle events // // Identically placed circle events still have to be processed but we handle the // case specially. The implication of identically placed circle events is that // we had four or more cocircular points which implies that the polygons // for those points come together to a point. Since we only allow for vertices // of order three during voronoi processing, we create "zero length" edges for the // polygons which meet at that common point. These will be removed later in postprocessing. // // ReSharper disable PossibleNullReferenceException if (FCloseEnough(cevt.VoronoiVertex, cevtPrev.VoronoiVertex)) { // We're going to create a zero length edge. cevt.FZeroLength = true; } // ReSharper restore PossibleNullReferenceException } } // Handle the event evt.Handle(this); evtPrev = evt; } }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Set the circle event which snuff's this node's parabola. </summary> /// <remarks> Darrellp, 2/18/2011. </remarks> /// <param name="cevt"> The cevt. </param> //////////////////////////////////////////////////////////////////////////////////////////////////// internal void SetCircleEvent(CircleEvent cevt) { _cevt = cevt; }