package br.com.etyllica.util.triangulation; import java.util.ArrayList; import java.util.List; import java.util.Vector; import br.com.etyllica.linear.Triangle2; import com.badlogic.gdx.math.Vector2; /** * Based on http://www.sunshine2k.de/coding/java/Polygon/Kong/Kong.html * */ public class KongTriangulation { private List<Vector2> points; private Vector<Vector2> nonconvexPoints; // orientation of polygon - true = clockwise, false = counterclockwise private boolean isCw; public KongTriangulation(List<Vector2> points) { // we have to copy the point vector as we modify it this.points = new Vector<Vector2>(); for (int i = 0; i < points.size(); i++) this.points.add(new Vector2(points.get(i))); nonconvexPoints = new Vector<Vector2>(); calcPolyOrientation(); calcNonConvexPoints(); } /* * This determines all concave vertices of the polygon. */ private void calcNonConvexPoints() { // safety check, with less than 4 points we have to do nothing if (points.size() <= 3) return; // actual three points Vector2 p; Vector2 v; Vector2 u; // result value of test function float res = 0; for (int i = 0; i < points.size() - 1; i++) { p = points.get(i); Vector2 tmp = points.get(i+1); v = new Vector2(); // interpret v as vector from i to i+1 v.x = tmp.x - p.x; v.y = tmp.y - p.y; // ugly - last polygon segment goes from last point to first point if (i == points.size() - 2) u = points.get(0); else u = points.get(i+2); res = u.x * v.y - u.y * v.x + v.x * p.y - v.y * p.x; // note: cw means res/newres is <= 0 if ( (res > 0 && isCw) || (res <= 0 && !isCw) ) { nonconvexPoints.add(tmp); //System.out.println("konkav point #" + (i+1) + " Coords: " + tmp.x + "/" + tmp.y); } } } /* * Get the orientation of the polygon - clockwise (cw) or counter-clockwise (ccw) */ private void calcPolyOrientation() { if (points.size() < 3) return; // first find point with minimum x-coord - if there are several ones take // the one with maximal y-coord int index = 0; // index of point in vector to find Vector2 pointOfIndex = points.get(0); for (int i = 1; i < points.size(); i++) { if (points.get(i).x < pointOfIndex.x) { pointOfIndex = points.get(i); index = i; } else if (points.get(i).x == pointOfIndex.x && points.get(i).y > pointOfIndex.y) { pointOfIndex = points.get(i); index = i; } } // get vector from index-1 to index Vector2 prevPointOfIndex; if (index == 0) prevPointOfIndex = points.get(points.size() - 1); else prevPointOfIndex = points.get(index - 1); Vector2 v1 = new Vector2(pointOfIndex.x - prevPointOfIndex.x, pointOfIndex.y - prevPointOfIndex.y); // get next point Vector2 succPointOfIndex; if (index == points.size() - 1) succPointOfIndex = points.get(0); else succPointOfIndex = points.get(index + 1); // get orientation float res = succPointOfIndex.x * v1.y - succPointOfIndex.y * v1.x + v1.x * prevPointOfIndex.y - v1.y * prevPointOfIndex.x; isCw = (res <= 0 ? true : false); //System.out.println("isCw : " + isCw); } /* * Returns true if the triangle formed by the three given points is an * ear considering the polygon - thus if no other point is inside and it is * convex. Otherwise false. */ private boolean isEar(Vector2 p1, Vector2 p2, Vector2 p3) { // not convex, bye if (!(isConvex(p1, p2, p3))) return false; // iterate over all konkav points and check if one of them lies inside the given triangle for (int i = 0; i < nonconvexPoints.size(); i++) { if (Triangle2.isInside(p1, p2, p3, nonconvexPoints.get(i) )) return false; } return true; } /* * Returns true if the point p2 is convex considered the actual polygon. * p1, p2 and p3 are three consecutive points of the polygon. */ private boolean isConvex(Vector2 p1, Vector2 p2, Vector2 p3) { Vector2 v = new Vector2(p2.x - p1.x, p2.y - p1.y); float res = p3.x * v.y - p3.y * v.x + v.x * p1.y - v.y * p1.x; return !( (res > 0 && isCw) || (res <= 0 && !isCw) ); } /* * This is a helper function for accessing consecutive points of the polygon * vector. It ensures that no IndexOutofBoundsException occurs. * @param index is the base index of the point to be accessed * @param offset to be added/subtracted to the index value */ private int getIndex(int index, int offset) { int newindex; //System.out.println("size " + points.size() + " index:" + index + " offset:" + offset); if (index + offset >= points.size()) newindex = points.size() - (index + offset); else { if (index + offset < 0) newindex = points.size() + (index + offset); else newindex = index + offset; } //System.out.println("new index = " + newindex); return newindex; } public List<Triangle2> triangulate() { List<Triangle2> triangles = new ArrayList<Triangle2>(); if (points.size() <= 3) return triangles; int index = 1; while (points.size() > 3) { if (isEar(points.get(getIndex(index, -1)), points.get(index), points.get(getIndex(index, 1)))) { // cut ear triangles.add(new Triangle2(points.get(getIndex(index, -1)), points.get(index), points.get(getIndex(index, 1)))); points.remove(points.get(index)); index = getIndex(index, -1); } else { index = getIndex(index, 1); } } // add last triangle triangles.add(new Triangle2(points.get(0), points.get(1), points.get(2))); return triangles; } }