Files
Mindustry/core/src/mindustry/graphics/Voronoi.java
2021-06-20 09:50:25 -04:00

644 lines
18 KiB
Java
Executable File

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<GraphEdge> allEdges;
float minDistanceBetweenSites = 1f;
public static Seq<GraphEdge> generate(Vec2[] values, float minX, float maxX, float minY, float maxY){
return new Voronoi().generateVoronoi(values, minX, maxX, minY, maxY);
}
Seq<GraphEdge> 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;
}
}