예제 #1
0
        // 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);
        }
예제 #2
0
        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;
            }
        }
예제 #3
0
        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);
        }
예제 #4
0
        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;
            }
        }
예제 #5
0
 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
 }