package org.newdawn.slick.geom; /** * A second triangulator that seems slightly more robust * * @author Online examples */ public class NeatTriangulator implements Triangulator { /** The error factor */ static final float EPSILON = 1E-006F; /** The x coordinates */ private float pointsX[]; /** The y coordiantes */ private float pointsY[]; /** The number of points that have been added */ private int numPoints; /** The edges defines by triangulation */ private Edge edges[]; /** Voroni */ private int V[]; /** The number of edges found */ private int numEdges; /** The triangles that have been found */ private Triangle triangles[]; /** The number of triangles found */ private int numTriangles; /** The current offset */ private float offset = EPSILON; /** * Create a new triangulator */ public NeatTriangulator() { pointsX = new float[100]; pointsY = new float[100]; numPoints = 0; edges = new Edge[100]; numEdges = 0; triangles = new Triangle[100]; numTriangles = 0; } /** * Clear the triangulator status */ public void clear() { numPoints = 0; numEdges = 0; numTriangles = 0; } /** * Find an edge between two verts * * @param i The index of the first vert * @param j The index of the second vert * @return The index of the dge */ private int findEdge(int i, int j) { int k; int l; if(i < j) { k = i; l = j; } else { k = j; l = i; } for(int i1 = 0; i1 < numEdges; i1++) if(edges[i1].v0 == k && edges[i1].v1 == l) return i1; return -1; } /** * Add a discovered edge * * @param i The index of the first vert * @param j The index of the second vert * @param k The index of the spread vert */ private void addEdge(int i, int j, int k) { int l1 = findEdge(i, j); int j1; int k1; Edge edge; if(l1 < 0) { if(numEdges == edges.length) { Edge aedge[] = new Edge[edges.length * 2]; System.arraycopy(edges, 0, aedge, 0, numEdges); edges = aedge; } j1 = -1; k1 = -1; l1 = numEdges++; edge = edges[l1] = new Edge(); } else { edge = edges[l1]; j1 = edge.t0; k1 = edge.t1; } int l; int i1; if(i < j) { l = i; i1 = j; j1 = k; } else { l = j; i1 = i; k1 = k; } edge.v0 = l; edge.v1 = i1; edge.t0 = j1; edge.t1 = k1; edge.suspect = true; } /** * Remove and edge identified by it's verts * * @param i The index of the first vert * @param j The index of the second vert * @throws InternalException Indicates the edge didn't exist */ private void deleteEdge(int i, int j) throws InternalException { int k; if(0 > (k = findEdge(i, j))) { throw new InternalException("Attempt to delete unknown edge"); } else { edges[k] = edges[--numEdges]; return; } } /** * Mark an edge as either a suspect or not * * @param i The index of the first vert * @param j The index of the second vert * @param flag True if the edge is a suspect * @throws InternalException Indicates the edge didn't exist */ void markSuspect(int i, int j, boolean flag) throws InternalException { int k; if(0 > (k = findEdge(i, j))) { throw new InternalException("Attempt to mark unknown edge"); } else { edges[k].suspect = flag; return; } } /** * Choose the suspect to become part of the triangle * * @return The edge selected */ private Edge chooseSuspect() { for(int i = 0; i < numEdges; i++) { Edge edge = edges[i]; if(edge.suspect) { edge.suspect = false; if(edge.t0 >= 0 && edge.t1 >= 0) return edge; } } return null; } /** * Factor rho. * * @param f Factor 1 * @param f1 Factor 2 * @param f2 Factor 3 * @param f3 Factor 4 * @param f4 Factor 5 * @param f5 Factor 6 * @return The computation of rho */ private static float rho(float f, float f1, float f2, float f3, float f4, float f5) { float f6 = f4 - f2; float f7 = f5 - f3; float f8 = f - f4; float f9 = f1 - f5; float f18 = f6 * f9 - f7 * f8; if(f18 > 0.0F) { if(f18 < 1E-006F) f18 = 1E-006F; float f12 = f6 * f6; float f13 = f7 * f7; float f14 = f8 * f8; float f15 = f9 * f9; float f10 = f2 - f; float f11 = f3 - f1; float f16 = f10 * f10; float f17 = f11 * f11; return ((f12 + f13) * (f14 + f15) * (f16 + f17)) / (f18 * f18); } else { return -1F; } } /** * Check if the point P is inside the triangle defined by * the points A,B,C * * @param f Point A x-coordinate * @param f1 Point A y-coordinate * @param f2 Point B x-coordinate * @param f3 Point B y-coordinate * @param f4 Point C x-coordinate * @param f5 Point C y-coordinate * @param f6 Point P x-coordinate * @param f7 Point P y-coordinate * @return True if the point specified is within the triangle */ private static boolean insideTriangle(float f, float f1, float f2, float f3, float f4, float f5, float f6, float f7) { float f8 = f4 - f2; float f9 = f5 - f3; float f10 = f - f4; float f11 = f1 - f5; float f12 = f2 - f; float f13 = f3 - f1; float f14 = f6 - f; float f15 = f7 - f1; float f16 = f6 - f2; float f17 = f7 - f3; float f18 = f6 - f4; float f19 = f7 - f5; float f22 = f8 * f17 - f9 * f16; float f20 = f12 * f15 - f13 * f14; float f21 = f10 * f19 - f11 * f18; return f22 >= 0.0D && f21 >= 0.0D && f20 >= 0.0D; } /** * Cut a the contour and add a triangle into V to describe the * location of the cut * * @param i The index of the first point * @param j The index of the second point * @param k The index of the third point * @param l ? * @return True if a triangle was found */ private boolean snip(int i, int j, int k, int l) { float f = pointsX[V[i]]; float f1 = pointsY[V[i]]; float f2 = pointsX[V[j]]; float f3 = pointsY[V[j]]; float f4 = pointsX[V[k]]; float f5 = pointsY[V[k]]; if(1E-006F > (f2 - f) * (f5 - f1) - (f3 - f1) * (f4 - f)) return false; for(int i1 = 0; i1 < l; i1++) if(i1 != i && i1 != j && i1 != k) { float f6 = pointsX[V[i1]]; float f7 = pointsY[V[i1]]; if(insideTriangle(f, f1, f2, f3, f4, f5, f6, f7)) return false; } return true; } /** * Get the area defined by the points * * @return The area defined by the points */ private float area() { float f = 0.0F; int i = numPoints - 1; for(int j = 0; j < numPoints;) { f += pointsX[i] * pointsY[j] - pointsY[i] * pointsX[j]; i = j++; } return f * 0.5F; } /** * Perform simple triangulation * * @throws InternalException Indicates a polygon that can't be triangulated */ public void basicTriangulation() throws InternalException { int i = numPoints; if(i < 3) return; numEdges = 0; numTriangles = 0; V = new int[i]; if(0.0D < area()) { for(int k = 0; k < i; k++) V[k] = k; } else { for(int l = 0; l < i; l++) V[l] = numPoints - 1 - l; } int k1 = 2 * i; int i1 = i - 1; while(i > 2) { if(0 >= k1--) { throw new InternalException("Bad polygon"); } int j = i1; if(i <= j) j = 0; i1 = j + 1; if(i <= i1) i1 = 0; int j1 = i1 + 1; if(i <= j1) j1 = 0; if(snip(j, i1, j1, i)) { int l1 = V[j]; int i2 = V[i1]; int j2 = V[j1]; if(numTriangles == triangles.length) { Triangle atriangle[] = new Triangle[triangles.length * 2]; System.arraycopy(triangles, 0, atriangle, 0, numTriangles); triangles = atriangle; } triangles[numTriangles] = new Triangle(l1, i2, j2); addEdge(l1, i2, numTriangles); addEdge(i2, j2, numTriangles); addEdge(j2, l1, numTriangles); numTriangles++; int k2 = i1; for(int l2 = i1 + 1; l2 < i; l2++) { V[k2] = V[l2]; k2++; } i--; k1 = 2 * i; } } V = null; } /** * Optimize the triangulation by applying delauney rules * * @throws InternalException Indicates an invalid polygon */ private void optimize() throws InternalException { do { Edge edge; if ((edge = chooseSuspect()) == null) { break; } int i1 = edge.v0; int k1 = edge.v1; int i = edge.t0; int j = edge.t1; int j1 = -1; int l1 = -1; for (int k = 0; k < 3; k++) { int i2 = triangles[i].v[k]; if(i1 == i2 || k1 == i2) { continue; } l1 = i2; break; } for (int l = 0; l < 3; l++) { int j2 = triangles[j].v[l]; if(i1 == j2 || k1 == j2) { continue; } j1 = j2; break; } if(-1 == j1 || -1 == l1) { throw new InternalException("can't find quad"); } float f = pointsX[i1]; float f1 = pointsY[i1]; float f2 = pointsX[j1]; float f3 = pointsY[j1]; float f4 = pointsX[k1]; float f5 = pointsY[k1]; float f6 = pointsX[l1]; float f7 = pointsY[l1]; float f8 = rho(f, f1, f2, f3, f4, f5); float f9 = rho(f, f1, f4, f5, f6, f7); float f10 = rho(f2, f3, f4, f5, f6, f7); float f11 = rho(f2, f3, f6, f7, f, f1); if(0.0F > f8 || 0.0F > f9) { throw new InternalException("original triangles backwards"); } if(0.0F <= f10 && 0.0F <= f11) { if(f8 > f9) { f8 = f9; } if(f10 > f11) { f10 = f11; } if(f8 > f10) { deleteEdge(i1, k1); triangles[i].v[0] = j1; triangles[i].v[1] = k1; triangles[i].v[2] = l1; triangles[j].v[0] = j1; triangles[j].v[1] = l1; triangles[j].v[2] = i1; addEdge(j1, k1, i); addEdge(k1, l1, i); addEdge(l1, j1, i); addEdge(l1, i1, j); addEdge(i1, j1, j); addEdge(j1, l1, j); markSuspect(j1, l1, false); } } } while(true); } /** * Upate the triangles */ public boolean triangulate() { try { basicTriangulation(); //optimize(); return true; } catch (InternalException e) { numEdges = 0; } return false; } /** * Add a point to the polygon */ public void addPolyPoint(float x, float y) { for (int i=0;i<numPoints;i++) { if ((pointsX[i] == x) && (pointsY[i] == y)) { //return; y += offset; offset += EPSILON; } } if(numPoints == pointsX.length) { float af[] = new float[numPoints * 2]; System.arraycopy(pointsX, 0, af, 0, numPoints); pointsX = af; af = new float[numPoints * 2]; System.arraycopy(pointsY, 0, af, 0, numPoints); pointsY = af; } pointsX[numPoints] = x; pointsY[numPoints] = y; numPoints++; } /** * A single triangle * * @author Online Source */ class Triangle { /** The verticies index */ int v[]; /** * Create a new triangle * * @param i The index of vert 1 * @param j The index of vert 2 * @param k The index of vert 3 */ Triangle(int i, int j, int k) { v = new int[3]; v[0] = i; v[1] = j; v[2] = k; } } /** * A single edge between two points * * @author Online Source */ class Edge { /** The start vert */ int v0; /** The end vert */ int v1; /** The start tangent vert */ int t0; /** The end tangent vert */ int t1; /** True if the edge is marked as a suspect */ boolean suspect; /** * Create a new empty edge */ Edge() { v0 = -1; v1 = -1; t0 = -1; t1 = -1; } } /** * A failure to triangulate, hidden from outside and handled * * @author Online Source */ class InternalException extends Exception { /** * Create an internal exception * * @param msg The message describing the exception */ public InternalException(String msg) { super(msg); } } /** * @see org.newdawn.slick.geom.Triangulator#getTriangleCount() */ public int getTriangleCount() { return numTriangles; } /** * @see org.newdawn.slick.geom.Triangulator#getTrianglePoint(int, int) */ public float[] getTrianglePoint(int tri, int i) { float xp = pointsX[triangles[tri].v[i]]; float yp = pointsY[triangles[tri].v[i]]; return new float[] {xp,yp}; } /** * @see org.newdawn.slick.geom.Triangulator#startHole() */ public void startHole() { } }