package com.vitco.low.triangulate.util;
import com.vitco.util.misc.IntegerTools;
import gnu.trove.list.array.TShortArrayList;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.util.ArrayList;
/**
* Helper class to convert a binary grid into a polygon with holes
*
* The method first extracts the edges and then puts them together while determining
* which hole belongs to which hole.
*
* Reference: http://www.lsi.upc.edu/~jmartinez/publications/VPAM12.pdf
*
* This particular implementation should be very fast!
*/
public class Grid2PolyHelper {
// extract edges from an bit array (data)
// When the method terminates, the vertEdges ArrayList holds the vertical edges sorted in x direction (but not sorted
// in any y direction) and the edges HashMap, meshes the first coordinate point hash "makeInt" to a short[] array.
// The short array contains in that order: x1, y1, x2, y2, direction (1 or 0), polygonid (always initialized with -1)
private static void extractEdges(boolean[][] data, ArrayList<short[]> vertEdges, TIntObjectHashMap<short[]> edges) {
// temporary array to hold current edge
short[] edge;
// prepare dimension variables
short lenX = (short) data.length;
short lenXM = (short) (lenX-1);
short lenY = (short) data[0].length;
short lenYM = (short) (lenY-1);
// used for edge computation to memorize starting/stop position
// when traversing the edges
short start = -1;
short stop = -1;
// ==================
// compute vertical edges
// find inner vertical edges
for (short x = 1, xM = 0; x < lenX; xM = x, x++) {
for (short y = 0; y < lenY; y++) {
// ---------------
if ((data[xM][y] || data[xM][y] == data[x][y]) && start != -1) {
// add vertex
edge = new short[] {x,start,x,y,1,-1};
edges.put(-IntegerTools.makeInt(edge[0], edge[1]),edge);
vertEdges.add(edge);
start = -1;
}
if ((data[x][y] || data[xM][y] == data[x][y]) && stop != -1) {
// add vertex
edge = new short[] {x,y,x,stop,0,-1};
edges.put(IntegerTools.makeInt(edge[0], edge[1]),edge);
vertEdges.add(edge);
stop = -1;
}
if (data[xM][y] ^ data[x][y]) {
if (data[xM][y] && stop == -1) {
stop = y;
} else if (!data[xM][y] && start == -1) {
start = y;
}
}
// --------------
}
// ---------------
// finish vertical inner edges that start/end at the bottom of the column
if (start != -1) {
edge = new short[] {x,start,x,lenY,1,-1};
edges.put(-IntegerTools.makeInt(edge[0], edge[1]),edge);
vertEdges.add(edge);
start = -1;
}
if (stop != -1) {
edge = new short[] {x,lenY,x,stop,0,-1};
edges.put(IntegerTools.makeInt(edge[0], edge[1]),edge);
vertEdges.add(edge);
stop = -1;
}
// -------------
}
// =============
// find outside vertical edges
for (short y = 0; y < lenY; y++) {
if (!data[0][y] && start != -1) {
// add vertex
edge = new short[] {0,start,0,y,1,-1};
edges.put(-IntegerTools.makeInt(edge[0], edge[1]), edge);
vertEdges.add(0,edge);
start = -1;
}
if (!data[lenXM][y] && stop != -1) {
// add vertex
edge = new short[] {lenX,y,lenX,stop,0,-1};
edges.put(IntegerTools.makeInt(edge[0], edge[1]),edge);
vertEdges.add(edge);
stop = -1;
}
if (data[0][y] && start == -1) {
start = y;
}
if (data[lenXM][y] && stop == -1) {
stop = y;
}
}
// finish vertical outside edges that start/end at the bottom of the two columns
if (start != -1) {
edge = new short[] {0,start,0,lenY,1,-1};
edges.put(-IntegerTools.makeInt(edge[0], edge[1]),edge);
vertEdges.add(0,edge);
start = -1;
}
if (stop != -1) {
edge = new short[] {lenX, lenY, lenX, stop,0,-1};
edges.put(IntegerTools.makeInt(edge[0], edge[1]),edge);
vertEdges.add(edge);
stop = -1;
}
// ======================
// compute horizontal edges
// find inner horizontal edges
for (short y = 1, yM = 0; y < lenY; yM = y, y++) {
for (short x = 0; x < lenX; x++) {
// ---------------
if ((data[x][yM] || data[x][yM] == data[x][y]) && start != -1) {
// add vertex
edge = new short[] {x,y,start,y,0,-1};
edges.put(-IntegerTools.makeInt(edge[0], edge[1]), edge);
start = -1;
}
if ((data[x][y] || data[x][yM] == data[x][y]) && stop != -1) {
// add vertex
edge = new short[] {stop,y,x,y,1,-1};
edges.put(IntegerTools.makeInt(edge[0], edge[1]), edge);
stop = -1;
}
if (data[x][yM] ^ data[x][y]) {
if (data[x][yM] && stop == -1) {
stop = x;
} else if (!data[x][yM] && start == -1) {
start = x;
}
}
// --------------
}
// ---------------
// finish inner horizontal edges that start/end at the end of the row
if (start != -1) {
edge = new short[] {lenX,y,start,y,0,-1};
edges.put(-IntegerTools.makeInt(edge[0], edge[1]), edge);
start = -1;
}
if (stop != -1) {
edge = new short[] {stop,y,lenX,y,1,-1};
edges.put(IntegerTools.makeInt(edge[0], edge[1]), edge);
stop = -1;
}
// -------------
}
// =============
// compute outside horizontal edges
for (short x = 0; x < lenX; x++) {
if (!data[x][0] && start != -1) {
// add vertex
edge = new short[] {x,0,start,0,0,-1};
edges.put(-IntegerTools.makeInt(edge[0], edge[1]), edge);
start = -1;
}
if (!data[x][lenYM] && stop != -1) {
// add vertex
edge = new short[] {stop,lenY,x,lenY,1,-1};
edges.put(IntegerTools.makeInt(edge[0], edge[1]), edge);
stop = -1;
}
if (data[x][0] && start == -1) {
start = x;
}
if (data[x][lenYM] && stop == -1) {
stop = x;
}
}
// finish horizontal edges that start/end at the end of the two rows
if (start != -1) {
edge = new short[] {lenX,0,start,0,0,-1};
edges.put(-IntegerTools.makeInt(edge[0], edge[1]), edge);
//start = -1;
}
if (stop != -1) {
edge = new short[] {stop,lenY,lenX,lenY,1,-1};
edges.put(IntegerTools.makeInt(edge[0], edge[1]), edge);
//stop = -1;
}
}
// convert bit data into polygon, the result is structured as following:
// the first array holds the different polygons, the first inner array holds as
// a first entry the polygon outline and the following entries describe the outline
// of the holes. The inner most arrays finally describe outlines and are structured
// as x1,y1,x2,y2,x3,y3,....,xn,yn,x1,y1
public static short[][][] convert(boolean[][] data) {
// result list that still needs conversion into array, each array list holds a polygon.
// The outline is stored in the first entry and the holes as further entries (optionally).
TIntObjectHashMap<ArrayList<short[]>> result = new TIntObjectHashMap<ArrayList<short[]>>();
// ----- extract unprocessed edges
// holds the vertically edges and all edges
ArrayList<short[]> vertEdges = new ArrayList<short[]>();
TIntObjectHashMap<short[]> edges = new TIntObjectHashMap<short[]>();
// extract the edges from out input data
extractEdges(data, vertEdges, edges);
// ----- initialize variables
// temporary variables
TShortArrayList outlineValues = new TShortArrayList();
int id;
short[] edgeR;
// holds poly id information for current column
short[] polyIds = new short[data[0].length];
// holds current poly id
short polyId = 0;
// ----- combine the edges in a "smart" way that allows us to extract poly/hole relationship while
// ----- building the outlines, similar concept: http://www.lsi.upc.edu/~jmartinez/publications/VPAM12.pdf
// loop over vertical edges
for (short[] edge : vertEdges) {
// if this edge is not analysed yet
if (edge[5] == -1) {
// check edge orientation
if (edge[4] == 1) { // down orientation
// find the outline and add as polygon
edgeR = edge;
outlineValues.add(edgeR[0]);
outlineValues.add(edgeR[1]);
// traverse out HashMap into the "correct" direction
// until we find the starting edge again
do {
outlineValues.add(edgeR[2]);
outlineValues.add(edgeR[3]);
edgeR[5] = polyId; // mark edge with polygon id
id = IntegerTools.makeInt(edgeR[2], edgeR[3]) * (edgeR[4] == 1 ? 1 : -1);
edgeR = edges.get(id);
if (edgeR == null) edgeR = edges.get(-id);
} while (edgeR != edge);
// store the polygon
short[] polyArray = new short[outlineValues.size()];
outlineValues.toArray(polyArray);
outlineValues.clear();
ArrayList<short[]> poly = new ArrayList<short[]>();
poly.add(polyArray);
result.put(polyId, poly);
polyId++;
} else { // up orientation
// find the outline and add as hole
edge[5] = polyIds[edge[1]]; // check which polygon this hole belongs to
edgeR = edge;
outlineValues.add(edgeR[0]);
outlineValues.add(edgeR[1]);
// traverse out HashMap into the "correct" direction
// until we find the starting edge again
do {
outlineValues.add(edgeR[2]);
outlineValues.add(edgeR[3]);
edgeR[5] = edge[5]; // mark edge with polygon id
id = IntegerTools.makeInt(edgeR[2], edgeR[3]) * (edgeR[4] == 1 ? 1 : -1);
edgeR = edges.get(id);
if (edgeR == null) edgeR = edges.get(-id);
} while (edgeR != edge);
// store the polygon
short[] polyArray = new short[outlineValues.size()];
outlineValues.toArray(polyArray);
outlineValues.clear();
ArrayList<short[]> poly = result.get(edge[5]);
poly.add(polyArray);
}
}
// update our sweep line with the information which polygon is currently
// processed in the corresponding row
if (edge[4] == 1) {
for (int y = edge[1]; y < edge[3]; y++) {
polyIds[y] = edge[5];
}
} else {
for (int y = edge[3]; y < edge[1]; y++) {
polyIds[y] = 0;
}
}
}
// convert and return result
short[][][] resultArray = new short[result.size()][][];
for (int i = 0; i < result.size(); i++) {
ArrayList<short[]> list = result.get(i);
resultArray[i] = new short[list.size()][];
list.toArray(resultArray[i]);
}
return resultArray;
}
}