//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Insert the edge at infinity into the edge list for the vertices at infinity. </summary> /// <remarks> Darrellp, 2/19/2011. </remarks> /// <param name="edge"> Edge at infinity being added. </param> /// <param name="leadingVtxCw"> Vertex on the left of infinite poly as we look out. </param> /// <param name="trailingVtxCw"> Vertex on the right. </param> //////////////////////////////////////////////////////////////////////////////////////////////////// private static void HookEdgeAtInfinityToVerticesAtInfinity(FortuneEdge edge, FortuneVertex leadingVtxCw, FortuneVertex trailingVtxCw) { // if we've only got one edge at infinity if (leadingVtxCw.FortuneEdges.Count == 1) { // Add this one as a placeholder // // This will be overwritten later but we have to insert here so that we can insert ourselves at // index 2. This is just a placeholder. leadingVtxCw.FortuneEdges.Add(edge); } // Add this edge into it's proper position leadingVtxCw.FortuneEdges.Add(edge); // If we have three edges if (trailingVtxCw.FortuneEdges.Count == 3) { // Overwrite the placeholder we placed in another call // // Here is where the overwriting referred to above occurs // This will happen on a later call - not on the current one trailingVtxCw.FortuneEdges[1] = edge; } else { // Otherwise just add us in to the trailing vertex's list of edges trailingVtxCw.FortuneEdges.Add(edge); } }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Produce a vertex at infinity. </summary> /// <remarks> Darrellp, 2/19/2011. </remarks> /// <param name="ptDirection"> Direction for the vertex. </param> /// <param name="fNormalize"> If true we normalize, else not. </param> /// <returns> The vertex at infinity. </returns> //////////////////////////////////////////////////////////////////////////////////////////////////// internal static FortuneVertex InfiniteVertex(Vector ptDirection, bool fNormalize = true) { var vtx = new FortuneVertex(ptDirection); vtx.SetInfinite(fNormalize); return(vtx); }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Add a vertex in the proper place according to _fStartVertexSet. </summary> /// <remarks> Darrellp, 2/19/2011. </remarks> /// <param name="vtx"> Vertex to add. </param> //////////////////////////////////////////////////////////////////////////////////////////////////// internal void AddVertex(FortuneVertex vtx) { // If we've already set the start vertex if (_fStartVertexSet) { // This is the end vertex VtxEnd = vtx; } else { // Make this the start vertex and set the start vertex flag _fStartVertexSet = true; VtxStart = vtx; } }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <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> Turn a null endpoint of the ray into an infinite vertex. </summary> /// <remarks> Darrellp, 2/18/2011. </remarks> /// <param name="edge"> Edge with the null vertex. </param> //////////////////////////////////////////////////////////////////////////////////////////////////// private static void ProcessRay(FortuneEdge edge) { // If it's the start vertex that's null if (edge.VtxStart == null) { // Swap the vertices // // This is what justifies the assertion in WeEdge.FLeftOf() (see the code). edge.VtxStart = edge.VtxEnd; edge.VtxEnd = null; } // Replace the null vertex with an infinite vertex var pt1 = edge.Poly1.VoronoiPoint; var pt2 = edge.Poly2.VoronoiPoint; var ptMid = new Vector((pt1.X + pt2.X) / 2, (pt1.Y + pt2.Y) / 2); // Point the ray in the proper direction // // We have to be careful to get this ray pointed in the proper orientation. At // this point we just have a vertex and points which are the original voronoi // points which created these infinite polys. That's enough to figure out the // absolute direction the voronoi points but not enough to determine which "orientation" // it points along. To do this, we find the third polygon at the "base" of this ray // and point the ray "away" from it. var polyThird = ((FortuneVertex)edge.VtxStart).PolyThird(edge); var fThirdOnLeft = FLeft(pt1, pt2, polyThird.VoronoiPoint); var dx = pt2.X - pt1.X; var dy = pt2.Y - pt1.Y; var ptProposedDirection = new Vector(dy, -dx); var ptInProposedDirection = new Vector(ptMid.X + dy, ptMid.Y - dx); var fProposedOnLeft = FLeft(pt1, pt2, ptInProposedDirection); // Do we need to reverse orientation? if (fProposedOnLeft == fThirdOnLeft) { // Do it ptProposedDirection.X = -ptProposedDirection.X; ptProposedDirection.Y = -ptProposedDirection.Y; } // Create the new infinite vertex and add it to our edge edge.VtxEnd = FortuneVertex.InfiniteVertex(ptProposedDirection); ((FortuneVertex)edge.VtxEnd).FortuneEdges.Add(edge); }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Get the next edge in both the cw and ccw directions from this edge at the given 3 valent /// vertex. /// </summary> /// <remarks> /// This is the simplest and most common case. It's called before the winged edge stuff is set up /// so we have to search manually. The edges have been sorted in clockwise order however. /// Darrellp, 2/19/2011. /// </remarks> /// <param name="vtx"> vertex to use. </param> /// <param name="edgeCW"> [out] returned cw edge. </param> /// <param name="edgeCCW"> [out] returned ccw edge. </param> //////////////////////////////////////////////////////////////////////////////////////////////////// private void GetSuccessorEdgesFrom3ValentVertex( FortuneVertex vtx, out FortuneEdge edgeCW, out FortuneEdge edgeCCW) { // Locals int iEdge; // Figure out which of the edges is ours for (iEdge = 0; iEdge < 3; iEdge++) { // If the current edge is us if (ReferenceEquals(vtx.FortuneEdges[iEdge], this)) { break; } } // The next two edges, in order, are the onew we're looking for edgeCW = vtx.FortuneEdges[(iEdge + 1) % 3]; edgeCCW = vtx.FortuneEdges[(iEdge + 2) % 3]; }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Get the next edge in both the cw and ccw directions from this edge at the given vertex. /// </summary> /// <remarks> /// This routine is called before they've been set up as winged edges so we have to search them /// out ourselves. The edges have been ordered in CW order, however. /// Darrellp, 2/19/2011. /// </remarks> /// <param name="vtx"> vertex to use. </param> /// <param name="edgeCW"> [out] returned cw edge. </param> /// <param name="edgeCCW"> [out] returned ccw edge. </param> //////////////////////////////////////////////////////////////////////////////////////////////////// private void GetSuccessorEdgesFromVertex( FortuneVertex vtx, out FortuneEdge edgeCW, out FortuneEdge edgeCCW) { // Are we free of zero length edges? if (vtx.FortuneEdges.Count == 3) { // Do the extremely simple, extremely common 3 valent case GetSuccessorEdgesFrom3ValentVertex(vtx, out edgeCW, out edgeCCW); } else { // Locals int iEdge; int cEdges; // If we're looking at our start vertex if (vtx == VtxStart) { // Find our place in the list of edges for start vertex iEdge = EdgeIndex(true); cEdges = ((FortuneVertex)VtxStart).CtEdges; } else { // Find our place in the list of edges for end vertex iEdge = EdgeIndex(false); cEdges = ((FortuneVertex)VtxEnd).CtEdges; } // Get our immediate neighbors on the edge list edgeCW = vtx.FortuneEdges[(iEdge + 1) % cEdges]; edgeCCW = vtx.FortuneEdges[(iEdge + cEdges - 1) % cEdges]; } }
//////////////////////////////////////////////////////////////////////////////////////////////////// /// <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); }