package org.newdawn.slick.tests; import java.util.ArrayList; import java.util.HashSet; import org.newdawn.slick.AppGameContainer; import org.newdawn.slick.BasicGame; import org.newdawn.slick.Color; import org.newdawn.slick.GameContainer; import org.newdawn.slick.Graphics; import org.newdawn.slick.SlickException; import org.newdawn.slick.geom.Circle; import org.newdawn.slick.geom.GeomUtil; import org.newdawn.slick.geom.GeomUtilListener; import org.newdawn.slick.geom.Polygon; import org.newdawn.slick.geom.Shape; import org.newdawn.slick.geom.Vector2f; /** * A test to try shape building from multiple tiles * * @author Kevin Glass */ public class GeomUtilTileTest extends BasicGame implements GeomUtilListener { /** The shape we're cutting out of */ private Shape source; /** The shape we're cutting */ private Shape cut; /** The resulting shape */ private Shape[] result; /** The util under test */ private GeomUtil util = new GeomUtil(); /** The original list of shapes */ private ArrayList original = new ArrayList(); /** The original list of shapes */ private ArrayList combined = new ArrayList(); /** The list of intersection points */ private ArrayList intersections = new ArrayList(); /** The list of used points */ private ArrayList used = new ArrayList(); /** The quad space of shapes that need to be checked against each other */ private ArrayList[][] quadSpace; /** The shapes present in each quad space - used to optimize generation */ private Shape[][] quadSpaceShapes; /** * Create a simple test */ public GeomUtilTileTest() { super("GeomUtilTileTest"); } /** * So this is going to generate a quad space that holds that segments the * shapes into quads across the map. This makes it tunable and limits the number * of comparisons that need to be done for each shape * * @param shapes The shapes to be segments * @param minx The minimum x value of the map * @param miny The mimimum y value of the map * @param maxx The maximum x value of the map * @param maxy The maximum y value of the map * @param segments The number of segments to split the map into */ private void generateSpace(ArrayList shapes, float minx, float miny, float maxx, float maxy, int segments) { quadSpace = new ArrayList[segments][segments]; quadSpaceShapes = new Shape[segments][segments]; float dx = (maxx - minx) / segments; float dy = (maxy - miny) / segments; for (int x=0;x<segments;x++) { for (int y=0;y<segments;y++) { quadSpace[x][y] = new ArrayList(); // quad for this segment Polygon segmentPolygon = new Polygon(); segmentPolygon.addPoint(minx+(dx*x), miny+(dy*y)); segmentPolygon.addPoint(minx+(dx*x)+dx, miny+(dy*y)); segmentPolygon.addPoint(minx+(dx*x)+dx, miny+(dy*y)+dy); segmentPolygon.addPoint(minx+(dx*x), miny+(dy*y)+dy); for (int i=0;i<shapes.size();i++) { Shape shape = (Shape) shapes.get(i); if (collides(shape, segmentPolygon)) { quadSpace[x][y].add(shape); } } quadSpaceShapes[x][y] = segmentPolygon; } } } /** * Remove the given shape from the quad space * * @param shape The shape to remove */ private void removeFromQuadSpace(Shape shape) { int segments = quadSpace.length; for (int x=0;x<segments;x++) { for (int y=0;y<segments;y++) { quadSpace[x][y].remove(shape); } } } /** * Add a particular shape to quad space * * @param shape The shape to be added */ private void addToQuadSpace(Shape shape) { int segments = quadSpace.length; for (int x=0;x<segments;x++) { for (int y=0;y<segments;y++) { if (collides(shape, quadSpaceShapes[x][y])) { quadSpace[x][y].add(shape); } } } } /** * Perform the cut */ public void init() { int size = 10; int[][] map = new int[][] { { 0, 0, 0, 0, 0, 0, 0, 3, 0, 0 }, { 0, 1, 1, 1, 0, 0, 1, 1, 1, 0 }, { 0, 1, 1, 0, 0, 0, 5, 1, 6, 0 }, { 0, 1, 2, 0, 0, 0, 4, 1, 1, 0 }, { 0, 1, 1, 0, 0, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 3, 0, 1, 1, 0, 0 }, { 0, 0, 0, 1, 1, 0, 0, 0, 1, 0 }, { 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }; // size = 100; // map = new int[size][size]; // for (int x=0;x<size;x++) { // for (int y=0;y<size;y++) { // if ((x+y) % 2 == 0) { // map[y][x] = 1; // } // } // } for (int x = 0; x < map[0].length; x++) { for (int y = 0; y < map.length; y++) { if (map[y][x] != 0) { switch (map[y][x]) { case 1: Polygon p2 = new Polygon(); p2.addPoint(x * 32, y * 32); p2.addPoint((x * 32) + 32, y * 32); p2.addPoint((x * 32) + 32, (y * 32) + 32); p2.addPoint(x * 32, (y * 32) + 32); original.add(p2); break; case 2: Polygon poly = new Polygon(); poly.addPoint(x * 32, y * 32); poly.addPoint((x * 32) + 32, y * 32); poly.addPoint(x * 32, (y * 32) + 32); original.add(poly); break; case 3: Circle ellipse = new Circle((x*32)+16,(y*32)+32,16,16); original.add(ellipse); break; case 4: Polygon p = new Polygon(); p.addPoint((x * 32) + 32, (y * 32)); p.addPoint((x * 32) + 32, (y * 32)+32); p.addPoint(x * 32, (y * 32) + 32); original.add(p); break; case 5: Polygon p3 = new Polygon(); p3.addPoint((x * 32), (y * 32)); p3.addPoint((x * 32) + 32, (y * 32)); p3.addPoint((x * 32) + 32, (y * 32)+32); original.add(p3); break; case 6: Polygon p4 = new Polygon(); p4.addPoint((x * 32), (y * 32)); p4.addPoint((x * 32) + 32, (y * 32)); p4.addPoint((x * 32), (y * 32)+32); original.add(p4); break; } } } } long before = System.currentTimeMillis(); // the quad spaced method generateSpace(original, 0, 0, (size+1)*32,(size+1)*32,8); combined = combineQuadSpace(); // the brute force method //combined = combine(original); long after = System.currentTimeMillis(); System.out.println("Combine took: "+(after-before)); System.out.println("Combine result: "+combined.size()); } /** * Combine the shapes in the quad space * * @return The newly combined list of shapes */ private ArrayList combineQuadSpace() { boolean updated = true; while (updated) { updated = false; for (int x=0;x<quadSpace.length;x++) { for (int y=0;y<quadSpace.length;y++) { ArrayList shapes = quadSpace[x][y]; int before = shapes.size(); combine(shapes); int after = shapes.size(); updated |= before != after; } } } // at this stage all the shapes that can be combined within their quads // will have gone on - we may need to combine stuff on the boundary tho HashSet result = new HashSet(); for (int x=0;x<quadSpace.length;x++) { for (int y=0;y<quadSpace.length;y++) { result.addAll(quadSpace[x][y]); } } return new ArrayList(result); } /** * Combine a set of shapes together * * @param shapes * The shapes to be combined * @return The list of combined shapes */ private ArrayList combine(ArrayList shapes) { ArrayList last = shapes; ArrayList current = shapes; boolean first = true; while ((current.size() != last.size()) || (first)) { first = false; last = current; current = combineImpl(current); } ArrayList pruned = new ArrayList(); for (int i = 0; i < current.size(); i++) { pruned.add(((Shape) current.get(i)).prune()); } return pruned; } /** * Attempt to find a simple combination that can be performed * * @param shapes * The shapes to be combined * @return The new list of shapes - this will be the same length as the * input if there are no new combinations */ private ArrayList combineImpl(ArrayList shapes) { ArrayList result = new ArrayList(shapes); if (quadSpace != null) { result = shapes; } for (int i = 0; i < shapes.size(); i++) { Shape first = (Shape) shapes.get(i); for (int j = i + 1; j < shapes.size(); j++) { Shape second = (Shape) shapes.get(j); if (!first.intersects(second)) { continue; } Shape[] joined = util.union(first, second); if (joined.length == 1) { if (quadSpace != null) { removeFromQuadSpace(first); removeFromQuadSpace(second); addToQuadSpace(joined[0]); } else { result.remove(first); result.remove(second); result.add(joined[0]); } return result; } } } return result; } /** * Check if two shapes collide * * @param shape1 The first shape * @param shape2 The second shape * @return True if the shapes collide (i.e. intersection or overlap) */ public boolean collides(Shape shape1, Shape shape2) { if (shape1.intersects(shape2)) { return true; } for (int i=0;i<shape1.getPointCount();i++) { float[] pt = shape1.getPoint(i); if (shape2.contains(pt[0], pt[1])) { return true; } } for (int i=0;i<shape2.getPointCount();i++) { float[] pt = shape2.getPoint(i); if (shape1.contains(pt[0], pt[1])) { return true; } } return false; } /** * @see BasicGame#init(GameContainer) */ public void init(GameContainer container) throws SlickException { util.setListener(this); init(); //container.setVSync(true); } /** * @see BasicGame#update(GameContainer, int) */ public void update(GameContainer container, int delta) throws SlickException { } /** * @see org.newdawn.slick.Game#render(GameContainer, Graphics) */ public void render(GameContainer container, Graphics g) throws SlickException { g.setColor(Color.green); for (int i = 0; i < original.size(); i++) { Shape shape = (Shape) original.get(i); g.draw(shape); } g.setColor(Color.white); if (quadSpaceShapes != null) { g.draw(quadSpaceShapes[0][0]); } g.translate(0, 320); for (int i = 0; i < combined.size(); i++) { g.setColor(Color.white); Shape shape = (Shape) combined.get(i); g.draw(shape); for (int j = 0; j < shape.getPointCount(); j++) { g.setColor(Color.yellow); float[] pt = shape.getPoint(j); g.fillOval(pt[0] - 1, pt[1] - 1, 3, 3); } } } /** * Entry point to our test * * @param argv * The arguments passed to the test */ public static void main(String[] argv) { try { AppGameContainer container = new AppGameContainer( new GeomUtilTileTest()); container.setDisplayMode(800, 600, false); container.start(); } catch (SlickException e) { e.printStackTrace(); } } public void pointExcluded(float x, float y) { } public void pointIntersected(float x, float y) { intersections.add(new Vector2f(x, y)); } public void pointUsed(float x, float y) { used.add(new Vector2f(x, y)); } }