/// <summary> /// Add the beach to the end of the list. /// </summary> public void AddLast(Beach objBeach) { this.RunningIndex++; this.InternalTransitions.Add(this.RunningIndex, objBeach); this.InternalIndex.AddLast(this.RunningIndex); }
/// <summary> /// Detach a beach section. /// </summary> private void DetachBeach(Beach objBeach) { // Detach potentially attached circle event. this.DetachCircleEvent(objBeach); // Remove from RB-tree. this.BeachLines.RemoveNode(objBeach); // Mark for reuse. this.BeachJunkyard.Push(objBeach); }
/// <summary> /// Remove beach section. /// </summary> private void RemoveBeach(Beach objBeach) { Circle objCircle = objBeach.Circle; double x = objCircle.x; double y = objCircle.ycenter; Point objVertex = this.CreateEdgeVertex(x, y); Beach objPrevious = objBeach.Previous as Beach; Beach objNext = objBeach.Next as Beach; var lstDisappearingTransitions = new Transitions(); lstDisappearingTransitions.AddFirst(objBeach); // Remove collapsed beach section from beach line. this.DetachBeach(objBeach); // there could be more than one empty arc at the deletion point, this // happens when more than two edges are linked by the same vertex; // so we will collect all those edges by looking up both sides of // the deletion point. // by the way, there is *always* a predecessor/successor to any collapsed // beach section, it's just impossible to have a collapsing first/last // beach sections on the beachline, since they obviously are unconstrained // on their left/right side. // look left Beach objLeftArc = objPrevious; while (objLeftArc.Circle != null && Math.Abs(x - objLeftArc.Circle.x) < Polygons.Epsilon && Math.Abs(y - objLeftArc.Circle.ycenter) < Polygons.Epsilon) { objPrevious = objLeftArc.Previous as Beach; lstDisappearingTransitions.AddFirst(objLeftArc); this.DetachBeach(objLeftArc); objLeftArc = objPrevious; } // even though it is not disappearing, I will also add the beach section // immediately to the left of the left-most collapsed beach section, for // convenience, since we need to refer to it later as this beach section // is the 'left' site of an edge for which a start point is set. lstDisappearingTransitions.AddFirst(objLeftArc); this.DetachCircleEvent(objLeftArc); // look right Beach objRightArc = objNext; while (objRightArc.Circle != null && Math.Abs(x - objRightArc.Circle.x) < Polygons.Epsilon && Math.Abs(y - objRightArc.Circle.ycenter) < Polygons.Epsilon) { objNext = objRightArc.Next as Beach; lstDisappearingTransitions.AddLast(objRightArc); this.DetachBeach(objRightArc); objRightArc = objNext; } // we also have to add the beach section immediately to the right of the // right-most collapsed beach section, since there is also a disappearing // transition representing an edge's start point on its left. lstDisappearingTransitions.AddLast(objRightArc); this.DetachCircleEvent(objRightArc); // walk through all the disappearing transitions between beach sections and // set the start point of their (implied) edge. int intArcs = lstDisappearingTransitions.Count; for (int intArc = 1; intArc < intArcs; intArc++) { // Reset objects. Beach objRightDis = lstDisappearingTransitions.Item(intArc); Beach objLeftDis = lstDisappearingTransitions.Item(intArc - 1); if (objRightDis.Edge != null) { objRightDis.Edge.SetStartPoint(objLeftDis.Site, objRightDis.Site, objVertex); } } // create a new edge as we have now a new transition between // two beach sections which were previously not adjacent. // since this edge appears as a new vertex is defined, the vertex // actually define an end point of the edge (relative to the site // on the left) Beach objFirstDis = lstDisappearingTransitions.Item(0); Beach objLastDis = lstDisappearingTransitions.Item(intArcs - 1); objLastDis.Edge = this.CreateEdge(objFirstDis.Site, objLastDis.Site, null, objVertex); // create circle events if any for beach sections left in the beachline // adjacent to collapsed sections this.AttachCircle(objFirstDis); this.AttachCircle(objLastDis); }
/// <summary> /// Checks to see if a beach section is available in the junkyard and uses it, /// or makes a new one and returns it. /// </summary> private Beach GetBeach() { Beach objBeach = null; // Check to see whether the stack is empty. if (this.BeachJunkyard.Count > 0) { // Try to retrieve a circle vent from the junkyard. objBeach = this.BeachJunkyard.Pop(); } // If we don't find a circle event in the junkyard we'll make a new one. if (objBeach == null) { objBeach = new Beach(); } // And give it back! return objBeach; }
/// <summary> /// Attach a circle event. /// </summary> private void AttachCircle(Beach objArc) { // This is a node in the RBTree which points to a beachsection. Beach objArcLeft = objArc.Previous as Beach; Beach objArcRight = objArc.Next as Beach; if (objArcLeft == null || objArcRight == null) { return; } // Does that ever happen? Point objSiteLeft = objArcLeft.Site; Point objSite = objArc.Site; Point objSiteRight = objArcRight.Site; // If site of left beachsection is same as site of right beachsection, there can't be convergence. if (objSiteLeft == objSiteRight) { return; } // Find the circumscribed circle for the three sites associated // with the beachsection triplet. // rhill 2011-05-26: It is more efficient to calculate in-place // rather than getting the resulting circumscribed circle from an // object returned by calling Voronoi.circumcircle() // http://mathforum.org/library/drmath/view/55002.html // Except that I bring the origin at cSite to simplify calculations. // The bottom-most part of the circumcircle is our Fortune 'circle // event', and its center is a vertex potentially part of the final // Voronoi diagram. double bx = objSite.x; double by = objSite.y; double ax = objSiteLeft.x - bx; double ay = objSiteLeft.y - by; double cx = objSiteRight.x - bx; double cy = objSiteRight.y - by; // If points l->c->r are clockwise, then center beach section does not // collapse, hence it can't end up as a vertex (we reuse 'd' here, which // sign is reverse of the orientation, hence we reverse the test. // http://en.wikipedia.org/wiki/Curve_orientation#Orientation_of_a_simple_polygon // rhill 2011-05-21: Nasty finite precision error which caused circumcircle() to // return infinites: 1e-12 seems to fix the problem. double d = 2 * (ax * cy - ay * cx); if (d >= -2e-12) { return; } double ha = ax * ax + ay * ay; double hc = cx * cx + cy * cy; double x = (cy * ha - ay * hc) / d; double y = (ax * hc - cx * ha) / d; double ycenter = y + by; // Important: ybottom should always be under or at sweep, so no need // to waste CPU cycles by checking // Recycle circle event object if possible. Circle objCircle = this.GetCircle(); objCircle.Arc = objArc; objCircle.Site = objSite; objCircle.x = x + bx; objCircle.y = ycenter + Math.Sqrt(x * x + y * y); // y bottom objCircle.ycenter = ycenter; objArc.Circle = objCircle; // find insertion point in RB-tree: circle events are ordered from // smallest to largest Circle objNodePrevious = null; Circle objNode = this.Circles.Root as Circle; while (objNode != null) { if (objCircle.y < objNode.y || (objCircle.y == objNode.y && objCircle.x <= objNode.x)) { if (objNode.Left != null) { objNode = objNode.Left as Circle; } else { objNodePrevious = objNode.Previous as Circle; break; } } else { if (objNode.Right != null) { objNode = objNode.Right as Circle; } else { objNodePrevious = objNode; break; } } } this.Circles.InsertSuccessor(objNodePrevious, objCircle); if (objNodePrevious == null) { this.FirstCircle = objCircle; } }
/// <summary> /// Detach a circle event. /// </summary> private void DetachCircleEvent(Beach objArc) { Circle objCircle = objArc.Circle; if (objCircle == null) { return; } if (objCircle.Previous == null) { this.FirstCircle = objCircle.Next as Circle; } // Remove from RBTree. this.Circles.RemoveNode(objCircle); this.CircleJunkyard.Push(objCircle); objArc.Circle = null; }