644 lines
18 KiB
Java
Executable File
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;
|
|
}
|
|
}
|