// 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); } } }
// 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); }
// --------------------------------------------------------------------------- // 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); }
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); }
// 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; } }