package mindustry.graphics; import arc.math.geom.*; import arc.struct.*; import java.util.*; //TODO in dire need of cleanup public class Voronoi{ private final static int LE = 0; private final static int RE = 1; //TODO make local int siteidx; Site[] sites; int nsites; float borderMinX, borderMaxX, borderMinY, borderMaxY; float ymin; float deltay; int nvertices = 0; int nedges; Site bottomsite; int PQcount; int PQmin; int PQhashsize; Halfedge[] PQhash; int ELhashsize; Halfedge[] ELhash; Seq allEdges; float minDistanceBetweenSites = 1f; public static Seq generate(Vec2[] values, float minX, float maxX, float minY, float maxY){ return new Voronoi().generateVoronoi(values, minX, maxX, minY, maxY); } Seq generateVoronoi(Vec2[] values, float minX, float maxX, float minY, float maxY){ allEdges = new Seq<>(); nsites = values.length; float sn = (float)nsites + 4; int rtsites = (int)Math.sqrt(sn); sites = new Site[nsites]; Vec2 first = values[0]; float xmin = first.x; ymin = first.y; float xmax = first.x; float ymax = first.y; for(int i = 0; i < nsites; i++){ sites[i] = new Site(); sites[i].coord.set(values[i]); sites[i].sitenbr = i; if(values[i].x < xmin){ xmin = values[i].x; }else if(values[i].x > xmax){ xmax = values[i].x; } if(values[i].y < ymin){ ymin = values[i].y; }else if(values[i].y > ymax){ ymax = values[i].y; } } Arrays.sort(sites, (p1, p2) -> { Vec2 s1 = p1.coord, s2 = p2.coord; if(s1.y < s2.y){ return (-1); } if(s1.y > s2.y){ return (1); } return Float.compare(s1.x, s2.x); }); deltay = ymax - ymin; float deltax = xmax - xmin; // Check bounding box inputs - if mins are bigger than maxes, swap them float temp; if(minX > maxX){ temp = minX; minX = maxX; maxX = temp; } if(minY > maxY){ temp = minY; minY = maxY; maxY = temp; } borderMinX = minX; borderMinY = minY; borderMaxX = maxX; borderMaxY = maxY; siteidx = 0; PQcount = 0; PQmin = 0; PQhashsize = 4 * rtsites; PQhash = new Halfedge[PQhashsize]; for(int i2 = 0; i2 < PQhashsize; i2 += 1){ PQhash[i2] = new Halfedge(); } int i1; ELhashsize = 2 * rtsites; ELhash = new Halfedge[ELhashsize]; for(i1 = 0; i1 < ELhashsize; i1 += 1){ ELhash[i1] = null; } Halfedge ELleftend = newHe(null, 0); Halfedge ELrightend = newHe(null, 0); ELleftend.ELleft = null; ELleftend.ELright = ELrightend; ELrightend.ELleft = ELleftend; ELrightend.ELright = null; ELhash[0] = ELleftend; ELhash[ELhashsize - 1] = ELrightend; bottomsite = next(); Site newsite = next(); Halfedge lbnd; Vec2 newintstar = null; Edge e; while(true){ if(PQcount != 0){ Vec2 answer = new Vec2(); while(PQhash[PQmin].PQnext == null){ PQmin += 1; } answer.x = PQhash[PQmin].PQnext.vertex.coord.x; answer.y = PQhash[PQmin].PQnext.ystar; newintstar = (answer); } Halfedge rbnd; Halfedge bisector; Site p; Site bot; if(newsite != null && (PQcount == 0 || newsite.coord.y < newintstar.y || (newsite.coord.y == newintstar.y && newsite.coord.x < newintstar.x))){ int bucket = (int)(((newsite.coord).x - xmin) / deltax * ELhashsize); if(bucket < 0){ bucket = 0; } if(bucket >= ELhashsize){ bucket = ELhashsize - 1; } Halfedge he = getHash(bucket); if(he == null){ for(int i = 1; i < ELhashsize; i += 1){ if((he = getHash(bucket - i)) != null){ break; } if((he = getHash(bucket + i)) != null){ break; } } } if(he == ELleftend || (he != ELrightend && right(he, (newsite.coord)))){ do{ he = he.ELright; }while(he != ELrightend && right(he, (newsite.coord))); he = he.ELleft; }else{ do{ he = he.ELleft; }while(he != ELleftend && !right(he, (newsite.coord))); } if(bucket > 0 && bucket < ELhashsize - 1){ ELhash[bucket] = he; } lbnd = he; rbnd = lbnd.ELright; bot = rightreg(lbnd); e = bisect(bot, newsite); bisector = newHe(e, LE); insert(lbnd, bisector); if((p = intersect(lbnd, bisector)) != null){ pqdelete(lbnd); pqinsert(lbnd, p, p.coord.dst(newsite.coord)); } lbnd = bisector; bisector = newHe(e, RE); insert(lbnd, bisector); if((p = intersect(bisector, rbnd)) != null){ pqinsert(bisector, p, p.coord.dst(newsite.coord)); } newsite = next(); }else if(!(PQcount == 0)){ Halfedge curr; curr = PQhash[PQmin].PQnext; PQhash[PQmin].PQnext = curr.PQnext; PQcount -= 1; lbnd = (curr); Halfedge llbnd = lbnd.ELleft; rbnd = lbnd.ELright; Halfedge rrbnd = (rbnd.ELright); bot = leftReg(lbnd); Site top = rightreg(rbnd); Site v = lbnd.vertex; v.sitenbr = nvertices; nvertices += 1; endpoint(lbnd.ELedge, lbnd.ELpm, v); endpoint(rbnd.ELedge, rbnd.ELpm, v); delete(lbnd); pqdelete(rbnd); delete(rbnd); int pm = LE; if(bot.coord.y > top.coord.y){ Site temp1 = bot; bot = top; top = temp1; pm = RE; } e = bisect(bot, top); bisector = newHe(e, pm); insert(llbnd, bisector); endpoint(e, RE - pm, v); if((p = intersect(llbnd, bisector)) != null){ pqdelete(llbnd); pqinsert(llbnd, p, p.coord.dst(bot.coord)); } if((p = intersect(bisector, rrbnd)) != null){ pqinsert(bisector, p, p.coord.dst(bot.coord)); } }else{ break; } } for(lbnd = (ELleftend.ELright); lbnd != ELrightend; lbnd = (lbnd.ELright)){ e = lbnd.ELedge; clipLine(e); } return allEdges; } private Site next(){ return siteidx < nsites ? sites[siteidx ++] : null; } private Edge bisect(Site s1, Site s2){ Edge newedge = new Edge(); // store the sites that this edge is bisecting newedge.reg[0] = s1; newedge.reg[1] = s2; // to begin with, there are no endpoints on the bisector - it goes to // infinity newedge.ep[0] = null; newedge.ep[1] = null; // get the difference in x dist between the sites float dx = s2.coord.x - s1.coord.x; float dy = s2.coord.y - s1.coord.y; // make sure that the difference in positive float adx = dx > 0 ? dx : -dx; float ady = dy > 0 ? dy : -dy; newedge.c = s1.coord.x * dx + s1.coord.y * dy + (dx * dx + dy * dy) * 0.5f;// get the slope of the line if(adx > ady){ newedge.a = 1.0f; newedge.b = dy / dx; newedge.c /= dx;// set formula of line, with x fixed to 1 }else{ newedge.b = 1.0f; newedge.a = dx / dy; newedge.c /= dy;// set formula of line, with y fixed to 1 } newedge.edgenbr = nedges; nedges += 1; return newedge; } private int pqbucket(Halfedge he){ int bucket; bucket = (int)((he.ystar - ymin) / deltay * PQhashsize); if(bucket < 0){ bucket = 0; } if(bucket >= PQhashsize){ bucket = PQhashsize - 1; } if(bucket < PQmin){ PQmin = bucket; } return bucket; } // push the HalfEdge into the ordered linked list of vertices private void pqinsert(Halfedge he, Site v, float offset){ Halfedge last, next; he.vertex = v; he.ystar = v.coord.y + offset; last = PQhash[pqbucket(he)]; while((next = last.PQnext) != null && (he.ystar > next.ystar || (he.ystar == next.ystar && v.coord.x > next.vertex.coord.x))){ last = next; } he.PQnext = last.PQnext; last.PQnext = he; PQcount += 1; } // remove the HalfEdge from the list of vertices private void pqdelete(Halfedge he){ Halfedge last; if(he.vertex != null){ last = PQhash[pqbucket(he)]; while(last.PQnext != he){ last = last.PQnext; } last.PQnext = he.PQnext; PQcount -= 1; he.vertex = null; } } private Halfedge newHe(Edge e, int pm){ Halfedge answer = new Halfedge(); answer.ELedge = e; answer.ELpm = pm; answer.PQnext = null; answer.vertex = null; return answer; } private Site leftReg(Halfedge he){ if(he.ELedge == null){ return bottomsite; } return he.ELpm == LE ? he.ELedge.reg[LE] : he.ELedge.reg[RE]; } private void insert(Halfedge lb, Halfedge newHe){ newHe.ELleft = lb; newHe.ELright = lb.ELright; lb.ELright.ELleft = newHe; lb.ELright = newHe; } /* * This delete routine can't reclaim node, since pointers from hash table * may be present. */ private void delete(Halfedge he){ he.ELleft.ELright = he.ELright; he.ELright.ELleft = he.ELleft; he.deleted = true; } /* Get entry from hash table, pruning any deleted nodes */ private Halfedge getHash(int b){ Halfedge he; if(b < 0 || b >= ELhashsize){ return (null); } he = ELhash[b]; if(he == null || !he.deleted){ return (he); } /* Hash table points to deleted half edge. Patch as necessary. */ ELhash[b] = null; return (null); } private void clipLine(Edge e){ float pxmin, pxmax, pymin, pymax; Site s1, s2; float x1, x2, y1, y2; x1 = e.reg[0].coord.x; x2 = e.reg[1].coord.x; y1 = e.reg[0].coord.y; y2 = e.reg[1].coord.y; // if the distance between the two points this line was created from is // less than the square root of 2, then ignore it if(Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1))) < minDistanceBetweenSites){ return; } pxmin = borderMinX; pxmax = borderMaxX; pymin = borderMinY; pymax = borderMaxY; if(e.a == 1.0 && e.b >= 0.0){ s1 = e.ep[1]; s2 = e.ep[0]; }else{ s1 = e.ep[0]; s2 = e.ep[1]; } if(e.a == 1.0){ y1 = pymin; if(s1 != null && s1.coord.y > pymin){ y1 = s1.coord.y; } if(y1 > pymax){ y1 = pymax; } x1 = e.c - e.b * y1; y2 = pymax; if(s2 != null && s2.coord.y < pymax){ y2 = s2.coord.y; } if(y2 < pymin){ y2 = pymin; } x2 = (e.c) - (e.b) * y2; if(((x1 > pxmax) & (x2 > pxmax)) | ((x1 < pxmin) & (x2 < pxmin))){ return; } if(x1 > pxmax){ x1 = pxmax; y1 = (e.c - x1) / e.b; } if(x1 < pxmin){ x1 = pxmin; y1 = (e.c - x1) / e.b; } if(x2 > pxmax){ x2 = pxmax; y2 = (e.c - x2) / e.b; } if(x2 < pxmin){ x2 = pxmin; y2 = (e.c - x2) / e.b; } }else{ x1 = pxmin; if(s1 != null && s1.coord.x > pxmin){ x1 = s1.coord.x; } if(x1 > pxmax){ x1 = pxmax; } y1 = e.c - e.a * x1; x2 = pxmax; if(s2 != null && s2.coord.x < pxmax){ x2 = s2.coord.x; } if(x2 < pxmin){ x2 = pxmin; } y2 = e.c - e.a * x2; if(((y1 > pymax) & (y2 > pymax)) | ((y1 < pymin) & (y2 < pymin))){ return; } if(y1 > pymax){ y1 = pymax; x1 = (e.c - y1) / e.a; } if(y1 < pymin){ y1 = pymin; x1 = (e.c - y1) / e.a; } if(y2 > pymax){ y2 = pymax; x2 = (e.c - y2) / e.a; } if(y2 < pymin){ y2 = pymin; x2 = (e.c - y2) / e.a; } } GraphEdge newEdge = new GraphEdge(); allEdges.add(newEdge); newEdge.x1 = x1; newEdge.y1 = y1; newEdge.x2 = x2; newEdge.y2 = y2; newEdge.site1 = e.reg[0].sitenbr; newEdge.site2 = e.reg[1].sitenbr; } private void endpoint(Edge e, int lr, Site s){ e.ep[lr] = s; if(e.ep[RE - lr] == null){ return; } clipLine(e); } private boolean right(Halfedge el, Vec2 p){ Edge e = el.ELedge; Site topsite = e.reg[1]; boolean rightOf = p.x > topsite.coord.x; if(rightOf && el.ELpm == LE){ return true; } if(!rightOf && el.ELpm == RE){ return false; } boolean above; if(e.a == 1.0){ float dyp = p.y - topsite.coord.y; float dxp = p.x - topsite.coord.x; boolean fast = false; if((!rightOf & (e.b < 0.0)) | (rightOf & (e.b >= 0.0))){ above = dyp >= e.b * dxp; fast = above; }else{ above = p.x + p.y * e.b > e.c; if(e.b < 0.0){ above = !above; } if(!above){ fast = true; } } if(!fast){ float dxs = topsite.coord.x - (e.reg[0]).coord.x; above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1.0 + 2.0 * dxp / dxs + e.b * e.b); if(e.b < 0.0){ above = !above; } } }else{ float yl = e.c - e.a * p.x; float t1 = p.y - yl; float t2 = p.x - topsite.coord.x; float t3 = yl - topsite.coord.y; above = t1 * t1 > t2 * t2 + t3 * t3; } return ((el.ELpm == LE) == above); } private Site rightreg(Halfedge he){ if(he.ELedge == null) return bottomsite; return (he.ELpm == LE ? he.ELedge.reg[RE] : he.ELedge.reg[LE]); } private Site intersect(Halfedge el1, Halfedge el2){ Edge e1, e2, e; Halfedge el; float d, xint, yint; boolean right_of_site; Site v; e1 = el1.ELedge; e2 = el2.ELedge; if(e1 == null || e2 == null){ return null; } if(e1.reg[1] == e2.reg[1]){ return null; } d = e1.a * e2.b - e1.b * e2.a; if(-1.0e-10 < d && d < 1.0e-10){ return null; } xint = (e1.c * e2.b - e2.c * e1.b) / d; yint = (e2.c * e1.a - e1.c * e2.a) / d; if((e1.reg[1].coord.y < e2.reg[1].coord.y) || (e1.reg[1].coord.y == e2.reg[1].coord.y && e1.reg[1].coord.x < e2.reg[1].coord.x)){ el = el1; e = e1; }else{ el = el2; e = e2; } right_of_site = xint >= e.reg[1].coord.x; if((right_of_site && el.ELpm == LE) || (!right_of_site && el.ELpm == RE)){ return null; } v = new Site(); v.coord.x = xint; v.coord.y = yint; return (v); } static class Site{ Vec2 coord = new Vec2(); int sitenbr; } static class Halfedge{ Halfedge ELleft, ELright; Edge ELedge; boolean deleted; int ELpm; Site vertex; float ystar; Halfedge PQnext; } public static class GraphEdge{ public float x1, y1, x2, y2; public int site1, site2; } static class Edge{ float a = 0, b = 0, c = 0; Site[] ep = new Site[2]; Site[] reg = new Site[2]; int edgenbr; } }