package com.vitco.util.graphic;
import org.junit.Test;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* Test the G2DUtil class
*/
public class G2DUtilTest {
// very basic manual test for line grid intersection
@Test
public void getLineGridIntersection() throws Exception {
int[][] result;
// test dot
result = G2DUtil.getLineGridIntersection(10, 10, 10, 10);
assert result.length == 4;
// for (int[] res : result) {
// System.out.println(res[0] + " " + res[1]);
// }
// test horizontal line
result = G2DUtil.getLineGridIntersection(10, 10, 13, 10);
assert result.length == 6;
// for (int[] res : result) {
// System.out.println(res[0] + " " + res[1]);
// }
// test vertical line
result = G2DUtil.getLineGridIntersection(10, 10, 10, 13);
assert result.length == 6;
// for (int[] res : result) {
// System.out.println(res[0] + " " + res[1]);
// }
// test general case
result = G2DUtil.getLineGridIntersection(0, 0, 4, 3);
assert result.length == 6;
// for (int[] res : result) {
// System.out.println(res[0] + " " + res[1]);
// }
// for (int x = 0; x < 10; x++) {
// for (int y = 0; y < 10; y++) {
// System.out.println(x + " " + y + " " + G2DUtil.getLineGridIntersection(0, 0, x, y).length);
// }
// }
}
// manual test for the triangle grid intersection that outputs an image
@Test
public void getTriangleGridIntersection() throws Exception {
// define the triangle
float xf1 = 34.5f;
float yf1 = 43.5f;
float xf2 = 8.5f;
float yf2 = 9.5f;
float xf3 = 48.5f;
float yf3 = 19.5f;
// obtain the intersected points
int[][] points = G2DUtil.getTriangleGridIntersection(
(int)(xf1 + 0.5f), (int)(yf1 + 0.5f),
(int)(xf2 + 0.5f), (int)(yf2 + 0.5f),
(int)(xf3 + 0.5f), (int)(yf3 + 0.5f)
);
// -----------
// -- draw the image
int zoom = 50;
BufferedImage img = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
// print found voxels
for (int[] p : points) {
if (p[0] > -1 && p[1] > -1 && p[0] < img.getWidth() && p[1] < img.getHeight()) {
img.setRGB(p[0], p[1], Color.BLACK.getRGB());
}
}
// enlarge so we can draw the triangle line
BufferedImage img2 = new BufferedImage(50 * zoom, 50 * zoom, BufferedImage.TYPE_INT_ARGB);
img2.getGraphics().drawImage(img.getScaledInstance(50 * zoom, 50 * zoom, 0), 0, 0, null);
Graphics2D g2 = (Graphics2D) img2.getGraphics();
// draw outlines of the voxels
g2.setColor(Color.WHITE);
for (int[] p : points) {
g2.drawRect( (p[0])*zoom, (p[1])*zoom, zoom, zoom);
}
// draw the triangle
g2.setColor(Color.RED);
g2.drawLine((int) ((xf1 + 0.5f) * zoom), (int) ((yf1 + 0.5f) * zoom), (int) ((xf2 + 0.5f) * zoom), (int) ((yf2 + 0.5f) * zoom));
g2.drawLine((int) ((xf3 + 0.5f) * zoom), (int) ((yf3 + 0.5f) * zoom), (int) ((xf2 + 0.5f) * zoom), (int) ((yf2 + 0.5f) * zoom));
g2.drawLine((int) ((xf1 + 0.5f) * zoom), (int) ((yf1 + 0.5f) * zoom), (int) ((xf3 + 0.5f) * zoom), (int) ((yf3 + 0.5f) * zoom));
// write the file
try {
ImageIO.write(img2, "png", new File("test.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
// test that triangulation always runs without error (no validation!)
@Test
public void getTriangleGridIntersectionMass() throws Exception {
Random rand = new Random();
for (int i = 0; i < 100000; i++) {
if (i%1000 == 0) {
System.out.println(":: " + i);
}
float xf1 = rand.nextInt(512) - 0.5f;
float yf1 = rand.nextInt(512) - 0.5f;
float xf2 = rand.nextInt(512) - 0.5f;
float yf2 = rand.nextInt(512) - 0.5f;
float xf3 = rand.nextInt(512) - 0.5f;
float yf3 = rand.nextInt(512) - 0.5f;
G2DUtil.getTriangleGridIntersection(
(int) (xf1 + 0.5f), (int) (yf1 + 0.5f),
(int) (xf2 + 0.5f), (int) (yf2 + 0.5f),
(int) (xf3 + 0.5f), (int) (yf3 + 0.5f)
);
}
}
// helper - check if two line segments intersect (this can even happen if they're parallel)
private boolean lineIntersectionParallel(float x1, float y1, float x2, float y2,
float x3, float y3, float x4, float y4, boolean includeEndPoints) {
// check if line intersects
boolean result = G2DUtil.lineIntersection(x1, y1, x2, y2, x3, y3, x4, y4, includeEndPoints) != null;
if (!result) { // no intersection
//System.out.println(x1 + " " + y1 + " " + x2 + " " + y2 + " " + x3 + " " + y3 + " " + x4 + " " + y4);
// check if lines are parallel
if (G2DUtil.parallel(x1, y1, x2, y2, x3, y3, x4, y4)) {
// check if parallel lines share a point
boolean onLine1 = G2DUtil.onLine(x1, y1, x2, y2, x3, y3) || (x1 == x3 && y1 == y3) || (x2 == x3 && y2 == y3);
if (onLine1
// include end points or overlap point is not end point of other line
&& (includeEndPoints || (x1 != x3) || (y1 != y3))
&& (includeEndPoints || (x2 != x3) || (y2 != y3))) {
return true;
}
boolean onLine2 = G2DUtil.onLine(x1, y1, x2, y2, x4, y4) || (x1 == x4 && y1 == y4) || (x2 == x4 && y2 == y4);
if (onLine2
// include end points or overlap point is not end point of other line
&& (includeEndPoints || (x1 != x4) || (y1 != y4))
&& (includeEndPoints || (x2 != x4) || (y2 != y4))) {
return true;
}
// check if lines are identical (have same start and stop point)
if (onLine1 && onLine2) {
return true;
}
}
// check if this is a "point line" (consisting of just one point - degenerated)
if (x1 == x2 && y1 == y2) {
if ((x1 == x3 && y1 == y3) || (x1 == x4 && y1 == y4)) {
return true;
}
}
}
return result;
}
// helper - check if line intersects quad (defined by top left point (x,y))
private boolean intersects(float x1, float y1, float x2, float y2, float x, float y) {
// return true if
// -- line intersects a grid line (not the end point!) or
// -- line intersects the end points of all grid lines
return lineIntersectionParallel(x1, y1, x2, y2, x, y, x + 1, y, false) ||
lineIntersectionParallel(x1, y1, x2, y2, x, y, x, y + 1, false) ||
lineIntersectionParallel(x1, y1, x2, y2, x, y + 1, x + 1, y + 1, false) ||
lineIntersectionParallel(x1, y1, x2, y2, x + 1, y, x + 1, y + 1, false) ||
(lineIntersectionParallel(x1, y1, x2, y2, x, y, x + 1, y, true) &&
lineIntersectionParallel(x1, y1, x2, y2, x, y, x, y + 1, true) &&
lineIntersectionParallel(x1, y1, x2, y2, x, y + 1, x + 1, y + 1, true) &&
lineIntersectionParallel(x1, y1, x2, y2, x + 1, y, x + 1, y + 1, true));
}
// verify the DDA supercover algorithm (for line on grid)
// this is the integer version where the line starts and ends
// in between four grid cells
@Test
public void getLineGridIntersectionVerify() throws Exception {
for (int j = 0; j < 1000000; j++) {
if (j % 1000 == 0) {
System.out.println(":: " + j);
}
// define the line on the grid
Random rand = new Random(j);
float xf1 = rand.nextInt(512) - 0.5f;
float yf1 = rand.nextInt(512) - 0.5f;
float xf2 = rand.nextInt(512) - 0.5f;
float yf2 = rand.nextInt(512) - 0.5f;
//System.out.println(xf1 + " " + yf1 + " " + xf2 + " " + yf2);
// compute result
int[][] result = G2DUtil.getLineGridIntersection(
(int) (xf1 + 0.5f), (int) (yf1 + 0.5f),
(int) (xf2 + 0.5f), (int) (yf2 + 0.5f)
);
// sort result
Arrays.sort(result, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
int sign = o1[0] - o2[0];
if (sign != 0) {
return sign;
} else {
return o1[1] - o2[1];
}
}
});
// compute "save" result by computing for every grid cell if it intersects with the line
ArrayList<int[]> saveResult = new ArrayList<int[]>();
for (float x = Math.min(xf1,xf2) - 1; x <= Math.max(xf1,xf2); x++) {
for (float y = Math.min(yf1,yf2) - 1; y <= Math.max(yf1,yf2); y++) {
if (intersects(xf1, yf1, xf2, yf2, x, y)) {
saveResult.add(new int[]{(int) (x + 0.5f), (int) (y + 0.5f)});
}
}
}
int[][] saveResultArray = new int[saveResult.size()][2];
saveResult.toArray(saveResultArray);
// sort save result
Arrays.sort(saveResultArray, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
int sign = o1[0] - o2[0];
if (sign != 0) {
return sign;
} else {
return o1[1] - o2[1];
}
}
});
// compare results
assert result.length == saveResultArray.length;
for (int i = 0; i < result.length; i++) {
assert result[i][0] == saveResultArray[i][0];
assert result[i][1] == saveResultArray[i][1];
}
}
}
// verify the DDA supercover algorithm (for line on grid)
// this is the precise version where the line starts and ends
// anywhere on the grid
@Test
public void getLineGridIntersectionVerifyDouble() throws Exception {
for (int j = 3707165; j < 10000000; j++) {
if (j % 1000 == 0) {
System.out.println(":: " + j);
}
// indicates the floating point precision that is used for generating
// the test values and for computing the "save" result
double scaleFactor;
// generate the line on the grid
Random rand = new Random(j);
double xf1, yf1, xf2, yf2;
switch (rand.nextInt(4)) {
case 0:
scaleFactor = 100d;
xf1 = rand.nextInt(51200) / scaleFactor;
yf1 = rand.nextInt(51200) / scaleFactor;
xf2 = rand.nextInt(51200) / scaleFactor;
yf2 = rand.nextInt(51200) / scaleFactor;
break;
case 1:
scaleFactor = 100d;
xf1 = rand.nextInt(512) / scaleFactor;
yf1 = rand.nextInt(512) / scaleFactor;
xf2 = rand.nextInt(512) / scaleFactor;
yf2 = rand.nextInt(512) / scaleFactor;
break;
case 2:
scaleFactor = 100d;
xf1 = (rand.nextInt(512) - 256) / scaleFactor;
yf1 = (rand.nextInt(512) - 256) / scaleFactor;
xf2 = (rand.nextInt(512) - 256) / scaleFactor;
yf2 = (rand.nextInt(512) - 256) / scaleFactor;
break;
default:
scaleFactor = 1000d;
xf1 = rand.nextInt(5120) / scaleFactor;
yf1 = rand.nextInt(5120) / scaleFactor;
xf2 = rand.nextInt(5120) / scaleFactor;
yf2 = rand.nextInt(5120) / scaleFactor;
break;
}
// System.out.println("============ " + j);
// System.out.println((xf1) + " " + (yf1) + " " + (xf2) + " " + (yf2));
// compute result
int[][] result = G2DUtil.getLineGridIntersection(
xf1, yf1, xf2, yf2
);
// sort result
Arrays.sort(result, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
int sign = o1[0] - o2[0];
if (sign != 0) {
return sign;
} else {
return o1[1] - o2[1];
}
}
});
//System.out.println("=======");
// compute "save" result
// we do this by scaling the points and using the integer version
// the resulting grid cells are then scaled down again
int[][] saveResult = G2DUtil.getLineGridIntersection(
(int)Math.round(xf1 * scaleFactor), (int)Math.round (yf1 * scaleFactor),
(int)Math.round (xf2 * scaleFactor), (int)Math.round (yf2 * scaleFactor)
);
HashSet<Point> translation = new HashSet<Point>();
for (int[] cell : saveResult) {
translation.add(new Point((int)Math.floor(cell[0]/scaleFactor), (int)Math.floor(cell[1]/scaleFactor)));
}
int k = 0;
int[][] saveResultArray = new int[translation.size()][2];
for (Point p : translation) {
saveResultArray[k][0] = p.x;
saveResultArray[k++][1] = p.y;
}
// sort save result
Arrays.sort(saveResultArray, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
int sign = o1[0] - o2[0];
if (sign != 0) {
return sign;
} else {
return o1[1] - o2[1];
}
}
});
// if (result.length != saveResultArray.length) {
// for (int i = 0; i < Math.max(result.length, saveResultArray.length); i++) {
// if (result.length > i && saveResultArray.length > i) {
// System.out.println(result[i][0] + "," + result[i][1] + " vs " + saveResultArray[i][0] + "," + saveResultArray[i][1]);
// } else if (result.length > i) {
// System.out.println(result[i][0] + "," + result[i][1] + " vs " + "...");
// } else {
// System.out.println("..." + " vs " + saveResultArray[i][0] + "," + saveResultArray[i][1]);
// }
// }
// }
// for (int i = 0; i < Math.max(result.length, saveResultArray.length); i++) {
// if (result.length > i && saveResultArray.length > i) {
// if (result[i][0] != saveResultArray[i][0] || result[i][1] != saveResultArray[i][1]) {
// System.out.println("@@@@@@@@@@@@");
// }
// System.out.println(result[i][0] + "," + result[i][1] + " vs " + saveResultArray[i][0] + "," + saveResultArray[i][1]);
// } else if (result.length > i) {
// System.out.println(result[i][0] + "," + result[i][1] + " vs " + "...");
// } else {
// System.out.println("..." + " vs " + saveResultArray[i][0] + "," + saveResultArray[i][1]);
// }
// }
// compare results
assert result.length == saveResultArray.length;
for (int i = 0; i < result.length; i++) {
assert result[i][0] == saveResultArray[i][0];
assert result[i][1] == saveResultArray[i][1];
}
// boolean error = false;
// int errorCount = 0;
// int diff = 0;
// if (result.length != saveResultArray.length) {
// error = true;
// diff = Math.abs(result.length - saveResultArray.length);
// } else {
// for (int i = 0; i < result.length; i++) {
// if (result[i][0] != saveResultArray[i][0] || result[i][1] != saveResultArray[i][1]) {
// errorCount++;
// error = true;
// }
// }
// }
// if (error) {
// System.out.println((xf1) + " " + (yf1) + " " + (xf2) + " " + (yf2) + " @@ " + errorCount + " diff " + diff);
// }
}
}
// manual test for line intersection
@Test
public void printLineGridIntersection() throws Exception {
// define the line
double xf1 = 15.0;
double yf1 = 0.0;
double xf2 = 0.0;
double yf2 = 7.5;
System.out.println(xf1 + " " + yf1 + " " + xf2 + " " + yf2);
// how many pixels is the image high and width
int imgSize = 50;
// =================
// obtain the intersected points
int[][] points = G2DUtil.getLineGridIntersection(
xf1, yf1, xf2, yf2
);
// ===============
// // compute scaled result with integer values
// int[][] saveResult = G2DUtil.getLineGridIntersection(
// Math.round (xf1 * 100), Math.round (yf1 * 100),
// Math.round (xf2 * 100), Math.round (yf2 * 100)
// );
// HashSet<Point> translation = new HashSet<Point>();
// for (int[] cell : saveResult) {
// translation.add(new Point(cell[0]/100, cell[1]/100));
// }
// int k = 0;
// int[][] points = new int[translation.size()][2];
// for (Point p : translation) {
// points[k][0] = p.x;
// points[k++][1] = p.y;
// }
// ===============
// -----------
// -- draw the image
int zoom = 50;
BufferedImage img = new BufferedImage(imgSize, imgSize, BufferedImage.TYPE_INT_ARGB);
// print found voxels
for (int[] p : points) {
if (p[0] > -1 && p[1] > -1 && p[0] < img.getWidth() && p[1] < img.getHeight()) {
img.setRGB(p[0], p[1], Color.BLACK.getRGB());
}
}
// enlarge so we can draw the triangle line
BufferedImage img2 = new BufferedImage(imgSize * zoom, imgSize * zoom, BufferedImage.TYPE_INT_ARGB);
img2.getGraphics().drawImage(img.getScaledInstance(imgSize * zoom, imgSize * zoom, 0), 0, 0, null);
Graphics2D g2 = (Graphics2D) img2.getGraphics();
// draw outlines of the voxels
g2.setColor(Color.WHITE);
for (int[] p : points) {
g2.drawRect( (p[0])*zoom, (p[1])*zoom, zoom, zoom);
}
// draw the triangle
g2.setColor(Color.RED);
g2.drawLine((int) ((xf1) * zoom), (int) ((yf1) * zoom), (int) ((xf2) * zoom), (int) ((yf2) * zoom));
// write the file
try {
ImageIO.write(img2, "png", new File("test.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
}