public void DetachBeachSection(BeachSection beachSection) { this.DetachCircleEvent(beachSection); // detach potentially attached circle event this.beachLine.Remove(beachSection); // remove from RB-tree this.beachSectionJunkyard.Add(beachSection); // mark for reuse }
public void DetachCircleEvent(BeachSection arc) { CircleEvent circle = arc.circleEvent; if (circle) { if (!circle.Prev) { this.firstCircleEvent = circle.Next; } this.circleEvents.Remove(circle); // remove from RB-tree this.circleEventJunkyard.Add(circle); arc.circleEvent = null; } }
// rhill 2011-06-02: A lot of Beachsection instanciations // occur during the computation of the Voronoi diagram, // somewhere between the number of sites and twice the // number of sites, while the number of Beachsections on the // beachline at any given time is comparatively low. For this // reason, we reuse already created Beachsections, in order // to avoid new memory allocation. This resulted in a measurable // performance gain. public BeachSection CreateBeachSection(Point site) { BeachSection beachSection = this.beachSectionJunkyard.Count > 0 ? this.beachSectionJunkyard.Last() : null; if (beachSection) { this.beachSectionJunkyard.Remove(beachSection); beachSection.site = site; } else { beachSection = new BeachSection(site); } return beachSection; }
public void AttachCircleEvent(BeachSection arc) { BeachSection lArc = arc.Prev; BeachSection rArc = arc.Next; if (!lArc || !rArc) { return; } // does that ever happen? Point lSite = lArc.site; Point cSite = arc.site; Point rSite = rArc.site; // If site of Left beachsection is same as site of // Right beachsection, there can't be convergence if (lSite == rSite) { 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. float bx = cSite.x; float by = cSite.y; float ax = lSite.x - bx; float ay = lSite.y - by; float cx = rSite.x - bx; float cy = rSite.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/Curveorientation#Orientationofasimplepolygon // rhill 2011-05-21: Nasty finite precision error which caused circumcircle() to // return infinites: 1e-12 seems to fix the problem. float d = 2 * (ax * cy - ay * cx); if (d >= -2e-12) { return; } float ha = ax * ax + ay * ay; float hc = cx * cx + cy * cy; float x = (cy * ha - ay * hc) / d; float y = (ax * hc - cx * ha) / d; float 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 CircleEvent circleEvent = this.circleEventJunkyard.Count > 0 ? this.circleEventJunkyard.Last() : null; this.circleEventJunkyard.Remove(circleEvent); if (!circleEvent) { circleEvent = new CircleEvent(); } circleEvent.arc = arc; circleEvent.site = cSite; circleEvent.x = x + bx; circleEvent.y = ycenter + Mathf.Sqrt(x * x + y * y); // y bottom circleEvent.yCenter = ycenter; arc.circleEvent = circleEvent; // find insertion point in RB-tree: circle events are ordered from // smallest to largest CircleEvent predecessor = null; CircleEvent node = this.circleEvents.Root; while (node) { if (circleEvent.y < node.y || (circleEvent.y == node.y && circleEvent.x <= node.x)) { if (node.Left) { node = node.Left; } else { predecessor = node.Prev; break; } } else { if (node.Right) { node = node.Right; } else { predecessor = node; break; } } } this.circleEvents.Insert(predecessor, circleEvent); if (!predecessor) { this.firstCircleEvent = circleEvent; } }
// calculate the right break point of a particular beach section, // given a particular directrix public float RightBreakPoint(BeachSection arc, float directrix) { BeachSection rArc = arc.Next; if (rArc) { return this.LeftBreakPoint(rArc, directrix); } Point site = arc.site; return site.y == directrix ? site.x : Mathf.Infinity; }
public void RemoveBeachSection(BeachSection beachSection) { CircleEvent circle = beachSection.circleEvent; float x = circle.x; float y = circle.yCenter; Point vertex = new Point(x, y); BeachSection previous = beachSection.Prev; BeachSection next = beachSection.Next; LinkedList<BeachSection> disappearingTransitions = new LinkedList<BeachSection>(); disappearingTransitions.AddLast(beachSection); // remove collapsed beachsection from beachline this.DetachBeachSection(beachSection); // 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 BeachSection lArc = previous; while (lArc.circleEvent && Mathf.Abs(x - lArc.circleEvent.x) < EPSILON && Mathf.Abs(y - lArc.circleEvent.yCenter) < EPSILON) { previous = lArc.Prev; disappearingTransitions.AddFirst(lArc); this.DetachBeachSection(lArc); // mark for reuse lArc = previous; } // 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. disappearingTransitions.AddFirst(lArc); this.DetachCircleEvent(lArc); // look right BeachSection rArc = next; while (rArc.circleEvent && Mathf.Abs(x - rArc.circleEvent.x) < EPSILON && Mathf.Abs(y - rArc.circleEvent.yCenter) < EPSILON) { next = rArc.Next; disappearingTransitions.AddLast(rArc); this.DetachBeachSection(rArc); rArc = next; } // 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. disappearingTransitions.AddLast(rArc); this.DetachCircleEvent(rArc); // walk through all the disappearing transitions between beach sections and // set the start point of their (implied) edge. int nArcs = disappearingTransitions.Count; for (int iArc = 1; iArc < nArcs; iArc++) { rArc = disappearingTransitions.ElementAt(iArc); lArc = disappearingTransitions.ElementAt(iArc - 1); rArc.edge.SetStartPoint(lArc.site, rArc.site, vertex); } // 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) lArc = disappearingTransitions.ElementAt(0); rArc = disappearingTransitions.ElementAt(nArcs - 1); rArc.edge = this.CreateEdge(lArc.site, rArc.site, null, vertex); // create circle events if any for beach sections left in the beachline // adjacent to collapsed sections this.AttachCircleEvent(lArc); this.AttachCircleEvent(rArc); }
// calculate the left break point of a particular beach section, // given a particular sweep line public float LeftBreakPoint(BeachSection arc, float directrix) { // http://en.wikipedia.org/wiki/Parabola // http://en.wikipedia.org/wiki/Quadratic_equation // h1 = x1, // k1 = (y1+directrix)/2, // h2 = x2, // k2 = (y2+directrix)/2, // p1 = k1-directrix, // a1 = 1/(4*p1), // b1 = -h1/(2*p1), // c1 = h1*h1/(4*p1)+k1, // p2 = k2-directrix, // a2 = 1/(4*p2), // b2 = -h2/(2*p2), // c2 = h2*h2/(4*p2)+k2, // x = (-(b2-b1) + Math.sqrt((b2-b1)*(b2-b1) - 4*(a2-a1)*(c2-c1))) / (2*(a2-a1)) // When x1 become the x-origin: // h1 = 0, // k1 = (y1+directrix)/2, // h2 = x2-x1, // k2 = (y2+directrix)/2, // p1 = k1-directrix, // a1 = 1/(4*p1), // b1 = 0, // c1 = k1, // p2 = k2-directrix, // a2 = 1/(4*p2), // b2 = -h2/(2*p2), // c2 = h2*h2/(4*p2)+k2, // x = (-b2 + Math.sqrt(b2*b2 - 4*(a2-a1)*(c2-k1))) / (2*(a2-a1)) + x1 // change code below at your own risk: care has been taken to // reduce errors due to computers' finite arithmetic precision. // Maybe can still be improved, will see if any more of this // kind of errors pop up again. Point site = arc.site; float rfocx = site.x; float rfocy = site.y; float pby2 = rfocy - directrix; // parabola in degenerate case where focus is on directrix if (pby2 == 0) { return rfocx; } BeachSection lArc = arc.Prev; if (!lArc) { return -Mathf.Infinity; } site = lArc.site; float lfocx = site.x; float lfocy = site.y; float plby2 = lfocy - directrix; // parabola in degenerate case where focus is on directrix if (plby2 == 0) { return lfocx; } float hl = lfocx - rfocx; float aby2 = 1 / pby2 - 1 / plby2; float b = hl / plby2; if (aby2 != 0) { return (-b + Mathf.Sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx; } // both parabolas have same distance to directrix, thus break point is midway return (rfocx + lfocx) / 2; }