package com.vitco.util.graphic; import com.threed.jpct.SimpleVector; import java.awt.*; import java.util.ArrayList; /** * Helps with basic drawing tasks for Graphic2D objects. */ public class G2DUtil { // draw a point with a border public static void drawPoint(SimpleVector point, Graphics2D ig, Color innerColor, Color outerColor, float radius, float borderSize) { // set outer line size ig.setStroke(new BasicStroke(borderSize, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL)); ig.setColor(innerColor); int rad2times = Math.round(radius * 2); ig.fillOval(Math.round(point.x - radius), Math.round(point.y - radius), rad2times, rad2times); ig.setColor(outerColor); ig.drawOval(Math.round(point.x - radius), Math.round(point.y - radius), rad2times, rad2times); } // draw a line with an outline public static void drawLine(SimpleVector p1, SimpleVector p2, Graphics2D ig, Color innerColor, Color outerColor, float size) { if (p1 != null && p2 != null) { // outer line ig.setColor(outerColor); // line color ig.setStroke(new BasicStroke(size * 1.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL)); // line size ig.drawLine(Math.round(p1.x), Math.round(p1.y), Math.round(p2.x), Math.round(p2.y)); // inner line ig.setColor(innerColor); // line color ig.setStroke(new BasicStroke(size, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL)); // line size ig.drawLine(Math.round(p1.x), Math.round(p1.y), Math.round(p2.x), Math.round(p2.y)); } } // return intersection of two line segments (or null if none exists) public static float[] lineIntersection( float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, boolean includeEndPoints ) { float s1_x = x2 - x1; float s1_y = y2 - y1; float s2_x = x4 - x3; float s2_y = y4 - y3; float s = (-s1_y * (x1 - x3) + s1_x * (y1 - y3)) / (-s2_x * s1_y + s1_x * s2_y); float t = (s2_x * (y1 - y3) - s2_y * (x1 - x3)) / (-s2_x * s1_y + s1_x * s2_y); if (includeEndPoints) { if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { // intersection found return new float[]{x1 + (t * s1_x), y1 + (t * s1_y)}; } } else { if (s > 0 && s < 1 && t > 0 && t < 1) { // intersection found return new float[]{x1 + (t * s1_x), y1 + (t * s1_y)}; } } return null; } // return intersection of two line segments (or null if none exists) // integer variant public static float[] lineIntersection( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, boolean includeEndPoints ) { int s1_x = x2 - x1; int s1_y = y2 - y1; int s2_x = x4 - x3; int s2_y = y4 - y3; float s = (-s1_y * (x1 - x3) + s1_x * (y1 - y3)) / (float)(-s2_x * s1_y + s1_x * s2_y); float t = (s2_x * (y1 - y3) - s2_y * (x1 - x3)) / (float)(-s2_x * s1_y + s1_x * s2_y); if (includeEndPoints) { if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { // intersection found return new float[]{x1 + (t * s1_x), y1 + (t * s1_y)}; } } else { if (s > 0 && s < 1 && t > 0 && t < 1) { // intersection found return new float[]{x1 + (t * s1_x), y1 + (t * s1_y)}; } } return null; } // test if a point is in between two other points (uses floating point "accuracy") public static boolean onLine(double ax, double ay, double bx, double by, double cx, double cy) { return ((float)((bx - ax) * (cy - ay)) == (float)((cx - ax) * (by - ay))) && // on one line ((ax < cx == cx < bx) && (ay < cy == cy < by)); // in between on that line } // test if two lines are parallel (uses floating point accuracy) public static boolean parallel(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { double d1 = x2 - x1; double d2 = x4 - x3; if (d1 == 0 || d2 == 0) { return d1 == d2; } return (float)(((y2 - y1)/d1)) == (float)(((y4 - y3)/d2)); } // test if point is in triangle public static boolean inTriangle(float px, float py, float p0x, float p0y, float p1x, float p1y, float p2x, float p2y) { float s = p0y * p2x - p0x * p2y + (p2y - p0y) * px + (p0x - p2x) * py; float t = p0x * p1y - p0y * p1x + (p0y - p1y) * px + (p1x - p0x) * py; if ((s < 0) != (t < 0)) return false; float A = -p1y * p2x + p0y * (p2x - p1x) + p0x * (p1y - p2y) + p1x * p2y; if (A < 0) { s = -s; t = -t; A = -A; } return s > 0 && t > 0 && (s + t) < A; } // ========================= // get the intersection of a line with a grid // assuming that the line starts and ends between four grid corners // Note: The line coordinates are given as (x1+0.5f, y1+0.5f, x2+0.5f, y2+0.5f); // adapted from: http://www.cse.yorku.ca/~amana/research/grid.pdf public static int[][] getLineGridIntersection(int x1, int y1, int x2, int y2) { // -- case: dot if (y1 == y2 && x1 == x2) { // return all four grid cells surrounding this dot int[][] result = new int[4][2]; result[0][0] = x1; result[0][1] = y1; result[1][0] = x1 - 1; result[1][1] = y1; result[2][0] = x1; result[2][1] = y1 - 1; result[3][0] = x1 - 1; result[3][1] = y1 - 1; return result; } // -- case: horizontal line if (y1 == y2) { int[][] result = new int[Math.abs(x2-x1)*2][2]; int i = 0; for (int x = Math.min(x1,x2), max = Math.max(x1, x2); x < max; x++) { result[i][0] = x; result[i++][1] = y1; result[i][0] = x; result[i++][1] = y1 - 1; } return result; } // -- case: vertical line if (x1 == x2) { int[][] result = new int[Math.abs(y2-y1)*2][2]; int i = 0; for (int y = Math.min(y1,y2), max = Math.max(y1, y2); y < max; y++) { result[i][0] = x1; result[i++][1] = y; result[i][0] = x1 - 1; result[i++][1] = y; } return result; } // -- case: general // result array list ArrayList<int[]> result = new ArrayList<int[]>(); // step direction int stepX = (x2 > x1) ? 1 : -1; int stepY = (y2 > y1) ? 1 : -1; // starting grid coordinates int x = x1 + (stepX == 1 ? 0 : -1); int y = y1 + (stepY == 1 ? 0 : -1); // stop grid coordinates int stopX = x2 + (stepX == 1 ? -1 : 0); int stopY = y2 + (stepY == 1 ? -1 : 0); // the "progress" value double val = Math.abs((y2 - y1) / (double)(x2 - x1)); int tMaxX = 1; int tMaxY = 1; // add the initial point result.add(new int[] {x, y}); // the stop position should always be reached, but if the alternative is // to be stuck in an infinity loop the slightly more expensive check is preferred while ( //x != stopX || y != stopY ((stepX == 1 && x < stopX) || (stepX == -1 && x > stopX)) || ((stepY == 1 && y < stopY) || (stepY == -1 && y > stopY)) ) { double cTMaxX = val * tMaxX - tMaxY; if (Math.abs(cTMaxX) < 0.0000000001) { tMaxX++; tMaxY++; x = x + stepX; y = y + stepY; } else if (cTMaxX < 0) { tMaxX++; x = x + stepX; } else { tMaxY++; y = y + stepY; } result.add(new int[] {x, y}); } // convert result int[][] resultArray = new int[result.size()][2]; result.toArray(resultArray); return resultArray; } // get the interior grid points of a triangle // assuming that the triangle points are between four grid corners // Note: The point coordinates are given as (x1+0.5f, y1+0.5f); public static int[][] getTriangleGridIntersection(int x1, int y1, int x2, int y2, int x3, int y3) { int minX = Math.min(Math.min(x1, x2), x3); int maxX = Math.max(Math.max(x1, x2), x3); int minY = Math.min(Math.min(y1, y2), y3); int maxY = Math.max(Math.max(y1, y2), y3); // initialize range int[][] range = new int[maxY - minY][2]; for (int i = 0; i < range.length; i++) { range[i][0] = Integer.MAX_VALUE; range[i][1] = Integer.MIN_VALUE; } // get the line points int[][] p1 = getLineGridIntersection(x1, y1, x2, y2); int[][] p2 = getLineGridIntersection(x1, y1, x3, y3); int[][] p3 = getLineGridIntersection(x2, y2, x3, y3); // fill in range for (int[] p : p1) { int val = p[1]-minY; if (val > -1 && val < range.length) { int[] rangeEntry = range[val]; rangeEntry[0] = Math.min(rangeEntry[0], p[0]); rangeEntry[1] = Math.max(rangeEntry[1], p[0]); } } for (int[] p : p2) { int val = p[1]-minY; if (val > -1 && val < range.length) { int[] rangeEntry = range[val]; rangeEntry[0] = Math.min(rangeEntry[0], p[0]); rangeEntry[1] = Math.max(rangeEntry[1], p[0]); } } for (int[] p : p3) { int val = p[1]-minY; if (val > -1 && val < range.length) { int[] rangeEntry = range[val]; rangeEntry[0] = Math.min(rangeEntry[0], p[0]); rangeEntry[1] = Math.max(rangeEntry[1], p[0]); } } // result array list ArrayList<int[]> result = new ArrayList<int[]>(); // compute interior voxels for (int i = 0; i < range.length; i++) { int[] row = range[i]; for (int x = Math.max(minX, row[0]), lenX = Math.min(row[1] + 1, maxX); x < lenX; x++) { result.add(new int[] { x, i + minY }); } } // convert result int[][] resultArray = new int[result.size()][2]; result.toArray(resultArray); return resultArray; } // ========================== // get the intersection of a line with a grid // Note: The line coordinates are given as (x1+0.5f, y1+0.5f, x2+0.5f, y2+0.5f); // adapted from: http://www.cse.yorku.ca/~amana/research/grid.pdf public static int[][] getLineGridIntersection(double x1, double y1, double x2, double y2) { // prevent rounding problems (we're basically working with float accuracy here) x1 = (double)Math.round(x1 * 1000000000) / 1000000000; y1 = (double)Math.round(y1 * 1000000000) / 1000000000; x2 = (double)Math.round(x2 * 1000000000) / 1000000000; y2 = (double)Math.round(y2 * 1000000000) / 1000000000; // -- case: dot if (y1 == y2 && x1 == x2) { if (x1%1 == 0 && y1%1 == 0) { // return all four grid cells surrounding this dot int[][] result = new int[4][2]; result[0][0] = (int) Math.floor(x1 - 0.5); result[0][1] = (int) Math.floor(y1 - 0.5); result[1][0] = (int) Math.floor(x1 + 0.5); result[1][1] = (int) Math.floor(y1 - 0.5); result[2][0] = (int) Math.floor(x1 - 0.5); result[2][1] = (int) Math.floor(y1 + 0.5); result[3][0] = (int) Math.floor(x1 + 0.5); result[3][1] = (int) Math.floor(y1 + 0.5); return result; } else if (x1%1 == 0) { int[][] result = new int[2][2]; result[0][0] = (int) Math.floor(x1 - 0.5); result[0][1] = (int) Math.floor(y1); result[1][0] = (int) Math.floor(x1 + 0.5); result[1][1] = (int) Math.floor(y1); return result; } else if (y1%1 == 0) { int[][] result = new int[2][2]; result[0][0] = (int) Math.floor(x1); result[0][1] = (int) Math.floor(y1 - 0.5); result[1][0] = (int) Math.floor(x1); result[1][1] = (int) Math.floor(y1 + 0.5); return result; } else { return new int[][] { new int[] {(int) Math.floor(x1), (int) Math.floor(y1)} }; } } // -- case: horizontal line if (y1 == y2) { double minX = Math.min(x1, x2); double maxX = Math.max(x1, x2); if (minX%1 == 0) { minX += 0.5; } if (maxX%1 == 0) { maxX -= 0.5; } if (y1%1 == 0) { int[][] result = new int[((int) Math.floor(maxX) - (int) Math.floor(minX)) * 2 + 2][2]; int i = 0; for (int x = (int) Math.floor(minX), max = (int) Math.floor(maxX) + 1; x < max; x++) { result[i][0] = x; result[i++][1] = (int) Math.floor(y1 - 0.5); result[i][0] = x; result[i++][1] = (int) Math.floor(y1 + 0.5); } return result; } else { int[][] result = new int[(int) Math.floor(maxX) - (int) Math.floor(minX) + 1][2]; int i = 0; for (int x = (int) Math.floor(minX), max = (int) Math.floor(maxX) + 1; x < max; x++) { result[i][0] = x; result[i++][1] = (int) Math.floor(y1); } return result; } } // -- case: vertical line if (x1 == x2) { double minY = Math.min(y1, y2); double maxY = Math.max(y1, y2); if (minY%1 == 0) { minY += 0.5; } if (maxY%1 == 0) { maxY -= 0.5; } if (x1%1 == 0) { int[][] result = new int[((int) Math.floor(maxY) - (int) Math.floor(minY)) * 2 + 2][2]; int i = 0; for (int y = (int) Math.floor(minY), max = (int) Math.floor(maxY) + 1; y < max; y++) { result[i][0] = (int) Math.floor(x1 - 0.5); result[i++][1] = y; result[i][0] = (int) Math.floor(x1 + 0.5); result[i++][1] = y; } return result; } else { int[][] result = new int[(int) Math.floor(maxY) - (int) Math.floor(minY) + 1][2]; int i = 0; for (int y = (int) Math.floor(minY), max = (int) Math.floor(maxY) + 1; y < max; y++) { result[i][0] = (int) Math.floor(x1); result[i++][1] = y; } return result; } } // -- case: general // result array list ArrayList<int[]> result = new ArrayList<int[]>(); // step direction int stepX = (x2 > x1) ? 1 : -1; int stepY = (y2 > y1) ? 1 : -1; // starting grid coordinates int x = (int) Math.floor(x1 + (stepX == -1 && x1%1 == 0 ? -0.5f : 0)); int y = (int) Math.floor(y1 + (stepY == -1 && y1%1 == 0 ? -0.5f : 0)); // stop grid coordinates int stopX = (int) Math.floor(x2 + (stepX == 1 && x2%1 == 0 ? -0.5f : 0)); int stopY = (int) Math.floor(y2 + (stepY == 1 && y2%1 == 0 ? -0.5f : 0)); double offX = stepX == Math.signum(x1) ? (1 - Math.abs(x1%1d)) : Math.abs(x1%1d); double offY = stepY == Math.signum(y1) ? (1 - Math.abs(y1%1d)) : Math.abs(y1%1d); offX = (double)Math.round(offX * 1000000000) / 1000000000; offY = (double)Math.round(offY * 1000000000) / 1000000000; if (offX == 0) { offX = 1; } if (offY == 0) { offY = 1; } // the "progress" value double val = Math.abs((y2 - y1) / (x2 - x1)); // System.out.println("Start " + x + " " + y); // System.out.println("Stop " + stopX + " " + stopY); // System.out.println("Step " + stepX + " " + stepY); // System.out.println("Val: " + val); // System.out.println("OFF " + offX + " " + offY); int tMaxX = 0; int tMaxY = 0; // add the initial point result.add(new int[] {x, y}); // the stop position should always be reached, but if the alternative is // to be stuck in an infinity loop the slightly more expensive check is preferred while ( // x != stopX || y != stopY ((stepX == 1 && x < stopX) || (stepX == -1 && x > stopX)) || ((stepY == 1 && y < stopY) || (stepY == -1 && y > stopY)) ) { double diff = val * (tMaxX + offX) - (tMaxY + offY); //System.out.println("Diff: " + diff); if (Math.abs(diff) < 0.0000000001) { tMaxX++; tMaxY++; x = x + stepX; y = y + stepY; } else if (diff < 0) { tMaxX++; x = x + stepX; } else { tMaxY++; y = y + stepY; } result.add(new int[] {x, y}); //System.out.println(x + " vs " + y); } // convert result int[][] resultArray = new int[result.size()][2]; result.toArray(resultArray); return resultArray; } // get the interior grid points of a triangle // Note: The point coordinates are given as (x1+0.5f, y1+0.5f); public static int[][] getTriangleGridIntersection(double x1, double y1, double x2, double y2, double x3, double y3) { int minX = (int) Math.floor(Math.min(Math.min(x1, x2), x3)); int maxX = (int) Math.ceil(Math.max(Math.max(x1, x2), x3)); int minY = (int) Math.floor(Math.min(Math.min(y1, y2), y3)); int maxY = (int) Math.ceil(Math.max(Math.max(y1, y2), y3)); // initialize range int[][] range = new int[maxY - minY][2]; for (int i = 0; i < range.length; i++) { range[i][0] = Integer.MAX_VALUE; range[i][1] = Integer.MIN_VALUE; } // get the line points int[][] p1 = getLineGridIntersection(x1, y1, x2, y2); int[][] p2 = getLineGridIntersection(x1, y1, x3, y3); int[][] p3 = getLineGridIntersection(x2, y2, x3, y3); // fill in range for (int[] p : p1) { int val = p[1]-minY; if (val > -1 && val < range.length) { int[] rangeEntry = range[val]; rangeEntry[0] = Math.min(rangeEntry[0], p[0]); rangeEntry[1] = Math.max(rangeEntry[1], p[0]); } } for (int[] p : p2) { int val = p[1]-minY; if (val > -1 && val < range.length) { int[] rangeEntry = range[val]; rangeEntry[0] = Math.min(rangeEntry[0], p[0]); rangeEntry[1] = Math.max(rangeEntry[1], p[0]); } } for (int[] p : p3) { int val = p[1]-minY; if (val > -1 && val < range.length) { int[] rangeEntry = range[val]; rangeEntry[0] = Math.min(rangeEntry[0], p[0]); rangeEntry[1] = Math.max(rangeEntry[1], p[0]); } } // result array list ArrayList<int[]> result = new ArrayList<int[]>(); // compute interior voxels for (int i = 0; i < range.length; i++) { int[] row = range[i]; for (int x = Math.max(minX, row[0]), lenX = Math.min(row[1] + 1, maxX); x < lenX; x++) { result.add(new int[] { x, i + minY }); } } // convert result int[][] resultArray = new int[result.size()][2]; result.toArray(resultArray); return resultArray; } }