// 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); }
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; } }
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); }
public void AddBeachSection(Point site) { float x = site.x; float directrix = site.y; // find the left and right beach sections which will surround the newly // created beach section. // rhill 2011-06-01: This loop is one of the most often executed, // hence we expand in-place the comparison-against-epsilon calls. BeachSection node = this.beachLine.Root; BeachSection lArc = null; BeachSection rArc = null; float dxl, dxr; while (node) { dxl = this.LeftBreakPoint(node, directrix) - x; // x LessThanWithEpsilon xl => falls somewhere before the Left edge of the beachsection if (dxl > EPSILON) { // this case should never happen // if (!node.rbLeft) { // rArc = node.rbLeft; // break; // } /*if (!node.Left) { * rArc = node.Left; * break; * } else { * node = node.Left; * }*/ node = node.Left; } else { dxr = x - this.RightBreakPoint(node, directrix); // x GreaterThanWithEpsilon xr => falls somewhere after the Right edge of the beachsection if (dxr > EPSILON) { if (!node.Right) { lArc = node; break; } node = node.Right; } else { // x EqualWithEpsilon xl => falls exactly on the Left edge of the beachsection if (dxl > -EPSILON) { lArc = node.Prev; rArc = node; } // x EqualWithEpsilon xr => falls exactly on the Right edge of the beachsection else if (dxr > -EPSILON) { lArc = node; rArc = node.Next; } // falls exactly somewhere in the middle of the beachsection else { lArc = rArc = node; } break; } } } // at this point, keep in mind that lArc and/or rArc could be // undefined or null. // create a new beach section object for the site and add it to RB-tree BeachSection newArc = this.CreateBeachSection(site); this.beachLine.Insert(lArc, newArc); // cases: // // [null,null] // least likely case: new beach section is the first beach section on the // beachLine. // This case means: // no new transition appears // no collapsing beach section // new beachsection become root of the RB-tree if (!lArc && !rArc) { return; } // [lArc,rArc] where lArc == rArc // most likely case: new beach section split an existing beach // section. // This case means: // one new transition appears // the Left and Right beach section might be collapsing as a result // two new nodes added to the RB-tree if (lArc == rArc) { // invalidate circle event of split beach section this.DetachCircleEvent(lArc); // split the beach section into two separate beach sections rArc = this.CreateBeachSection(lArc.site); this.beachLine.Insert(newArc, rArc); // since we have a new transition between two beach sections; // a new edge is born newArc.edge = rArc.edge = this.CreateEdge(lArc.site, newArc.site, null, null); // check whether the Left and Right beach sections are collapsing // and if so create circle events, to be notified when the point of // collapse is reached. this.AttachCircleEvent(lArc); this.AttachCircleEvent(rArc); return; } // [lArc,null] // even less likely case: new beach section is the *last* beach section // on the beachLine -- this can happen *only* if *all* the Prev beach // sections currently on the beachLine share the same y value as // the new beach section. // This case means: // one new transition appears // no collapsing beach section as a result // new beach section become Right-most node of the RB-tree if (lArc && !rArc) { newArc.edge = this.CreateEdge(lArc.site, newArc.site, null, null); return; } // [null,rArc] // impossible case: because sites are strictly processed from top to bottom; // and Left to Right, which guarantees that there will always be a beach section // on the Left -- except of course when there are no beach section at all on // the beach line, which case was handled above. // rhill 2011-06-02: No point testing in non-debug version //if (!lArc && rArc) { // throw "Voronoi.addBeachsection(): What is this I don't even"; // } if (!lArc && rArc) { Debug.LogError("Shouldn't appear"); } // [lArc,rArc] where lArc != rArc // somewhat less likely case: new beach section falls *exactly* in between two // existing beach sections // This case means: // one transition disappears // two new transitions appear // the Left and Right beach section might be collapsing as a result // only one new node added to the RB-tree if (lArc != rArc) { // invalidate circle events of Left and Right sites this.DetachCircleEvent(lArc); this.DetachCircleEvent(rArc); // an existing transition disappears, meaning a vertex is defined at // the disappearance point. // since the disappearance is caused by the new beachsection, the // vertex is at the center of the circumscribed circle of the Left; // new and Right beachsections. // http://mathforum.org/library/drmath/view/55002.html // Except that I bring the origin at A to simplify // calculation Point lSite = lArc.site; float ax = lSite.x; float ay = lSite.y; float bx = site.x - ax; float by = site.y - ay; Point rSite = rArc.site; float cx = rSite.x - ax; float cy = rSite.y - ay; float d = 2 * (bx * cy - by * cx); float hb = bx * bx + by * by; float hc = cx * cx + cy * cy; Point vertex = new Point((cy * hb - by * hc) / d + ax, (bx * hc - cx * hb) / d + ay); // one transition disappear rArc.edge.SetStartPoint(lSite, rSite, vertex); // two new transitions appear at the new vertex location newArc.edge = this.CreateEdge(lSite, site, null, vertex); rArc.edge = this.CreateEdge(site, rSite, null, vertex); // check whether the Left and Right beach sections are collapsing // and if so create circle events, to handle the point of collapse. this.AttachCircleEvent(lArc); this.AttachCircleEvent(rArc); return; } }
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 }