Пример #1
0
        // Connect/cut edges at bounding box
        public void ClipEdges(BoundingRect bbox)
        {
            // connect all dangling edges to bounding box
            // or get rid of them if it can't be done

            // iterate backward so we can splice safely
            //while (iEdge--) {
            for (int iEdge = this.edges.Count - 1; iEdge >= 0; iEdge--)
            {
                Edge edge = this.edges[iEdge];
                // edge is removed if:
                //   it is wholly outside the bounding box
                //   it is actually a point rather than a line
                if (!this.ConnectEdge(edge, bbox) ||
                    !this.ClipEdge(edge, bbox) ||
                    (Mathf.Abs(edge.va.x - edge.vb.x) < EPSILON && Mathf.Abs(edge.va.y - edge.vb.y) < EPSILON))
                {
                    edge.va = edge.vb = null;
                    //array_splice(this.edges, iEdge,1);
                    //this.edges.RemoveAt(iEdge);
                    this.edges.Remove(edge);
                }
            }
        }
Пример #2
0
        // line-clipping code taken from:
        //   Liang-Barsky function by Daniel White
        //   http://www.skytopia.com/project/articles/compsci/clipping.html
        // Thanks!
        // A bit modified to minimize code paths
        public bool ClipEdge(Edge edge, BoundingRect bbox)
        {
            float ax = edge.va.x;
            float ay = edge.va.y;
            float bx = edge.vb != null ? edge.vb.x : float.NaN;
            float by = edge.vb != null ? edge.vb.y : float.NaN;
            float t0 = 0;
            float t1 = 1;
            float dx = bx - ax;
            float dy = by - ay;

            // left
            float q = ax - bbox.xmin;

            if (dx == 0 && q < 0)
            {
                return(false);
            }
            float r = -q / dx;

            if (dx < 0)
            {
                if (r < t0)
                {
                    return(false);
                }
                if (r < t1)
                {
                    t1 = r;
                }
            }
            else if (dx > 0)
            {
                if (r > t1)
                {
                    return(false);
                }
                if (r > t0)
                {
                    t0 = r;
                }
            }
            // right
            q = bbox.xmax - ax;
            if (dx == 0 && q < 0)
            {
                return(false);
            }
            r = q / dx;
            if (dx < 0)
            {
                if (r > t1)
                {
                    return(false);
                }
                if (r > t0)
                {
                    t0 = r;
                }
            }
            else if (dx > 0)
            {
                if (r < t0)
                {
                    return(false);
                }
                if (r < t1)
                {
                    t1 = r;
                }
            }
            // top
            q = ay - bbox.ymin;
            if (dy == 0 && q < 0)
            {
                return(false);
            }
            r = -q / dy;
            if (dy < 0)
            {
                if (r < t0)
                {
                    return(false);
                }
                if (r < t1)
                {
                    t1 = r;
                }
            }
            else if (dy > 0)
            {
                if (r > t1)
                {
                    return(false);
                }
                if (r > t0)
                {
                    t0 = r;
                }
            }
            // bottom
            q = bbox.ymax - ay;
            if (dy == 0 && q < 0)
            {
                return(false);
            }
            r = q / dy;
            if (dy < 0)
            {
                if (r > t1)
                {
                    return(false);
                }
                if (r > t0)
                {
                    t0 = r;
                }
            }
            else if (dy > 0)
            {
                if (r < t0)
                {
                    return(false);
                }
                if (r < t1)
                {
                    t1 = r;
                }
            }

            // if we reach this point, Voronoi edge is within bbox

            // if t0 > 0, va needs to change
            // rhill 2011-06-03: we need to create a new vertex rather
            // than modifying the existing one, since the existing
            // one is likely shared with at least another edge
            if (t0 > 0)
            {
                edge.va = new Point(ax + t0 * dx, ay + t0 * dy);
            }

            // if t1 < 1, vb needs to change
            // rhill 2011-06-03: we need to create a new vertex rather
            // than modifying the existing one, since the existing
            // one is likely shared with at least another edge
            if (t1 < 1)
            {
                edge.vb = new Point(ax + t1 * dx, ay + t1 * dy);
            }

            // va and/or vb were clipped, thus we will need to close
            // cells which use this edge.
            if (t0 > 0 || t1 < 1)
            {
                this.cells[edge.lSite.id].closeMe = true;
                this.cells[edge.rSite.id].closeMe = true;
            }

            return(true);
        }
Пример #3
0
        // ---------------------------------------------------------------------------
        // Diagram completion methods

        // connect dangling edges (not if a cursory test tells us
        // it is not going to be visible.
        // return value:
        //   false: the dangling va couldn't be connected
        //   true: the dangling va could be connected
        public bool ConnectEdge(Edge edge, BoundingRect bbox)
        {
            // skip if end point already connected
            Point vb = edge.vb;

            if (!!vb)
            {
                return(true);
            }

            // make local copy for performance purpose
            Point va    = edge.va;
            float xl    = bbox.xmin;
            float xr    = bbox.xmax;
            float yt    = bbox.ymin;
            float yb    = bbox.ymax;
            Point lSite = edge.lSite;
            Point rSite = edge.rSite;
            float lx    = lSite.x;
            float ly    = lSite.y;
            float rx    = rSite.x;
            float ry    = rSite.y;
            float fx    = (lx + rx) / 2;
            float fy    = (ly + ry) / 2;
            float fm    = float.NaN;
            float fb    = 0.0f;

            // if we reach here, this means cells which use this edge will need
            // to be closed, whether because the edge was removed, or because it
            // was connected to the bounding box.
            this.cells[lSite.id].closeMe = true;
            this.cells[rSite.id].closeMe = true;

            // get the line equation of the bisector if line is not vertical
            if (ry != ly)
            {
                fm = (lx - rx) / (ry - ly);
                fb = fy - fm * fx;
            }

            // remember, direction of line (relative to left site):
            // upward: left.x < right.x
            // downward: left.x > right.x
            // horizontal: left.x == right.x
            // upward: left.x < right.x
            // rightward: left.y < right.y
            // leftward: left.y > right.y
            // vertical: left.y == right.y

            // depending on the direction, find the best side of the
            // bounding box to use to determine a reasonable start point

            // rhill 2013-12-02:
            // While at it, since we have the values which define the line,
            // clip the end of va if it is outside the bbox.
            // https://github.com/gorhill/Javascript-Voronoi/issues/15
            // TODO: Do all the clipping here rather than rely on Liang-Barsky
            // which does not do well sometimes due to loss of arithmetic
            // precision. The code here doesn't degrade if one of the vertex is
            // at a huge distance.

            // special case: vertical line
            if (float.IsNaN(fm))
            {
                // doesn't intersect with viewport
                if (fx < xl || fx >= xr)
                {
                    return(false);
                }

                // downward
                if (lx > rx)
                {
                    if (!va || va.y < yt)
                    {
                        //Debug.Log(yt);
                        va = new Point(fx, yt);
                    }
                    else if (va.y >= yb)
                    {
                        //Debug.Log(yb);
                        return(false);
                    }

                    vb = new Point(fx, yb);
                }
                // upward
                else
                {
                    if (!va || va.y > yb)
                    {
                        //Debug.Log(yb);
                        va = new Point(fx, yb);
                    }
                    else if (va.y < yt)
                    {
                        //Debug.Log(yt);
                        return(false);
                    }

                    vb = new Point(fx, yt);
                }
            }
            // closer to vertical than horizontal, connect start point to the
            // top or bottom side of the bounding box
            else if (fm < -1 || fm > 1)
            {
                // downward
                if (lx > rx)
                {
                    if (!va || va.y < yt)
                    {
                        //Debug.Log(va.y + " yt: " + yt);
                        va = new Point((yt - fb) / fm, yt);
                    }
                    else if (va.y >= yb)
                    {
                        //Debug.Log(va.y + " yb: " + yb);
                        return(false);
                    }

                    vb = new Point((yb - fb) / fm, yb);
                }
                // upward
                else
                {
                    if (!va || va.y > yb)
                    {
                        //Debug.Log(va.y + " yb: " + yb);
                        va = new Point((yb - fb) / fm, yb);
                    }
                    else if (va.y < yt)
                    {
                        //Debug.Log(va.y + " yt: " + yt);
                        return(false);
                    }

                    vb = new Point((yt - fb) / fm, yt);
                }
            }
            // closer to horizontal than vertical, connect start point to the
            // left or right side of the bounding box
            else
            {
                // rightward
                if (ly < ry)
                {
                    if (!va || va.x < xl)
                    {
                        //Debug.Log(va.x + " xl: " + xl);
                        va = new Point(xl, fm * xl + fb);
                    }
                    else if (va.x >= xr)
                    {
                        //Debug.Log(va.x + " xr: " + xr);
                        return(false);
                    }

                    vb = new Point(xr, fm * xr + fb);
                }
                // leftward
                else
                {
                    if (!va || va.x > xr)
                    {
                        //Debug.Log(va.x + " xr: " + xr);
                        va = new Point(xr, fm * xr + fb);
                    }
                    else if (va.x < xl)
                    {
                        //Debug.Log(va.x + " xl: " + xl);
                        return(false);
                    }

                    vb = new Point(xl, fm * xl + fb);
                }
            }

            edge.va = va;
            edge.vb = vb;

            return(true);
        }
Пример #4
0
        public VoronoiGraph Compute(List <Point> sites, BoundingRect bbox)
        {
            this.Reset();

            // Initialize site event queue
            var siteEvents = sites.Take(sites.Count).ToList();

            /*siteEvents.Sort((a, b) =>
             *  {
             *      float r = b.y - a.y;
             *      if (r != 0) { return Mathf.CeilToInt(r); }
             *      return Mathf.CeilToInt(b.x - a.x);
             *  });*/
            siteEvents = siteEvents.OrderByDescending(s => s.y).ToList();

            // process queue
            Point site = siteEvents.Last();

            siteEvents.Remove(site);
            int   siteid = 0;
            float xsitex = -Mathf.Infinity;
            float xsitey = -Mathf.Infinity;

            // main loop
            for (; ;)
            {
                // we need to figure whether we handle a site or circle event
                // for this we find out if there is a site event and it is
                // 'earlier' than the circle event
                CircleEvent circle = this.firstCircleEvent;

                // add beach section
                if (site && (!circle || site.y < circle.y || (site.y == circle.y && site.x < circle.x)))
                {
                    // only if site is not a duplicate
                    if (site.x != xsitex || site.y != xsitey)
                    {
                        // first create cell for new site
                        //this.cells[siteid] = new Cell(site);
                        this.cells.Insert(siteid, new Cell(site));
                        site.id = siteid++;

                        // then create a beachsection for that site
                        this.AddBeachSection(site);

                        // remember last site coords to detect duplicate
                        xsitey = site.y;
                        xsitex = site.x;
                    }

                    site = siteEvents.Count > 0 ? siteEvents.Last() : null;
                    if (siteEvents.Count > 0)
                    {
                        siteEvents.Remove(site);
                    }
                }
                // remove beach section
                else if (circle)
                {
                    this.RemoveBeachSection(circle.arc);
                }
                // all done, quit
                else
                {
                    break;
                }
            }

            // wrapping-up
            this.ClipEdges(bbox);

            this.CloseCells(bbox);

            VoronoiGraph graph = new VoronoiGraph();

            graph.sites = sites;
            graph.cells = this.cells;
            graph.edges = this.edges;

            this.Reset();

            return(graph);
        }
Пример #5
0
        // Close the cells.
        // The cells are bound by the supplied bounding box.
        // Each cell refers to its associated site, and a list
        // of halfedges ordered counterclockwise.
        public void CloseCells(BoundingRect bbox)
        {
            // prune, order halfedges, then add missing ones
            // required to close cells
            float xl = bbox.xmin;
            float xr = bbox.xmax;
            float yt = bbox.ymin;
            float yb = bbox.ymax;

            int badIterations = 0;

            for (int iCell = this.cells.Count - 1; iCell >= 0; iCell--)
            {
                Cell cell = this.cells[iCell];

                // prune, order halfedges counterclockwise, then add missing ones
                // required to close cells
                if (cell.Prepare() <= 0)
                {
                    continue;
                }
                if (!cell.closeMe)
                {
                    continue;
                }

                // close open cells
                // step 1: find first 'unclosed' point, if any.
                // an 'unclosed' point will be the end point of a halfedge which
                // does not match the start point of the following halfedge
                int nHalfedges = cell.halfEdges.Count;

                // special case: only one site, in which case, the viewport is the cell
                // ...
                // all other cases
                int iLeft = 0;
                int iter  = 0;

                while (iLeft < nHalfedges)// && iter < 10)
                {
                    iter++;

                    /*int iRight = (iLeft+1) % nHalfedges;
                     * Point va = cell.halfEdges[iLeft].Getva();
                     * Point vz = cell.halfEdges[iRight].Getvz();*/

                    Point va = cell.halfEdges[iLeft].GetEndPoint();
                    Point vz = cell.halfEdges[(iLeft + 1) % nHalfedges].GetStartPoint();

                    // if end point is not equal to start point, we need to add the missing
                    // halfedge(s) to close the cell
                    if ((Mathf.Abs(va.x - vz.x) >= EPSILON || Mathf.Abs(va.y - vz.y) >= EPSILON))
                    {
                        // rhill 2013-12-02:
                        // "Holes" in the halfedges are not necessarily always adjacent.
                        // https://github.com/gorhill/Javascript-Voronoi/issues/16

                        bool  lastBorderSegment = false;
                        Point vb;
                        Edge  edge;

                        // walk downward along left side
                        if (equalWithEpsilon(va.x, xl) && lessThanWithEpsilon(va.y, yb))
                        {
                            lastBorderSegment = this.equalWithEpsilon(vz.x, xl);
                            vb   = new Point(xl, lastBorderSegment ? vz.y : yb);
                            edge = this.CreateBorderEdge(cell.site, va, vb);
                            iLeft++;
                            //halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
                            cell.halfEdges.Insert(iLeft, new HalfEdge(edge, cell.site, null));
                            nHalfedges++;
                            if (!lastBorderSegment)
                            {
                                va = vb;
                            }
                        }
                        // walk rightward along bottom side
                        if (!lastBorderSegment && this.equalWithEpsilon(va.y, yb) && this.lessThanWithEpsilon(va.x, xr))
                        {
                            lastBorderSegment = this.equalWithEpsilon(vz.y, yb);
                            vb   = new Point(lastBorderSegment ? vz.x : xr, yb);
                            edge = this.CreateBorderEdge(cell.site, va, vb);
                            iLeft++;
                            //halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
                            cell.halfEdges.Insert(iLeft, new HalfEdge(edge, cell.site, null));
                            nHalfedges++;
                            if (!lastBorderSegment)
                            {
                                va = vb;
                            }
                        }
                        // walk upward along right side
                        if (!lastBorderSegment && this.equalWithEpsilon(va.x, xr) && this.greaterThanWithEpsilon(va.y, yt))
                        {
                            lastBorderSegment = this.equalWithEpsilon(vz.x, xr);
                            vb   = new Point(xr, lastBorderSegment ? vz.y : yt);
                            edge = this.CreateBorderEdge(cell.site, va, vb);
                            iLeft++;
                            //halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
                            cell.halfEdges.Insert(iLeft, new HalfEdge(edge, cell.site, null));
                            nHalfedges++;
                            if (!lastBorderSegment)
                            {
                                va = vb;
                            }
                        }
                        // walk leftward along top side
                        if (!lastBorderSegment && this.equalWithEpsilon(va.y, yt) && this.greaterThanWithEpsilon(va.x, xl))
                        {
                            lastBorderSegment = this.equalWithEpsilon(vz.y, yt);
                            vb   = new Point(lastBorderSegment ? vz.x : xl, yt);
                            edge = this.CreateBorderEdge(cell.site, va, vb);
                            iLeft++;
                            //halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
                            cell.halfEdges.Insert(iLeft, new HalfEdge(edge, cell.site, null));
                            nHalfedges++;
                            if (!lastBorderSegment)
                            {
                                va = vb;
                            }
                        }

                        // walk downward along left side
                        if (!lastBorderSegment)
                        {
                            lastBorderSegment = this.equalWithEpsilon(vz.x, xl);
                            vb   = new Point(xl, lastBorderSegment ? vz.y : yb);
                            edge = this.CreateBorderEdge(cell.site, va, vb);
                            iLeft++;
                            //halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
                            cell.halfEdges.Insert(iLeft, new HalfEdge(edge, cell.site, null));
                            nHalfedges++;
                            if (!lastBorderSegment)
                            {
                                va = vb;
                            }
                        }

                        // walk rightward along bottom side
                        if (!lastBorderSegment)
                        {
                            lastBorderSegment = this.equalWithEpsilon(vz.y, yb);
                            vb   = new Point(lastBorderSegment ? vz.x : xr, yb);
                            edge = this.CreateBorderEdge(cell.site, va, vb);
                            iLeft++;
                            //halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
                            cell.halfEdges.Insert(iLeft, new HalfEdge(edge, cell.site, null));
                            nHalfedges++;
                            if (!lastBorderSegment)
                            {
                                va = vb;
                            }
                        }

                        // walk upward along right side
                        if (!lastBorderSegment)
                        {
                            lastBorderSegment = this.equalWithEpsilon(vz.x, xr);
                            vb   = new Point(xr, lastBorderSegment ? vz.y : yt);
                            edge = this.CreateBorderEdge(cell.site, va, vb);
                            iLeft++;
                            //halfedges.splice(iLeft, 0, this.createHalfedge(edge, cell.site, null));
                            cell.halfEdges.Insert(iLeft, new HalfEdge(edge, cell.site, null));
                            nHalfedges++;
                        }

                        if (!lastBorderSegment)
                        {
                            Debug.LogError("This makes no sense");
                            badIterations++;
                        }
                    }
                    iLeft++;
                }
                cell.closeMe = false;
            }
            if (badIterations > 0)
            {
                this.valid = false;
            }
            else
            {
                this.valid = true;
            }
        }