package com.mobidevelop.maps.basic; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.math.EarClippingTriangulator; import com.badlogic.gdx.math.Polygon; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.ShortArray; import com.mobidevelop.maps.Map; import com.mobidevelop.maps.MapLayer; import com.mobidevelop.maps.MapObject; import com.mobidevelop.maps.objects.PolygonMapObject; import com.mobidevelop.maps.objects.TextureRegionMapObject; public class BasicMapRenderer { protected Map map; protected SpriteBatch batcher; protected ShapeRenderer renderer; protected float[] vertices = new float[8]; protected boolean debug = true; public BasicMapRenderer(Map map) { this.map = map; this.batcher = new SpriteBatch(); this.renderer = new ShapeRenderer(); } protected Rectangle view = new Rectangle(); protected Rectangle temp = new Rectangle(); public void setView(OrthographicCamera camera) { batcher.setProjectionMatrix(camera.combined); renderer.setProjectionMatrix(camera.combined); float width = camera.viewportWidth * camera.zoom; float height = camera.viewportHeight * camera.zoom; view.set(camera.position.x - width / 2, camera.position.y - height / 2, width, height); } EarClippingTriangulator t = new EarClippingTriangulator(); Vector2 v = new Vector2(); boolean spriteBatchDrawing = false; int objectsTotal = 0; int objectsDrawn = 0; public void render() { objectsTotal = 0; objectsDrawn = 0; batcher.begin(); float mapX = map.getX(); float mapY = map.getY(); for (MapLayer layer : map.getLayers()) { if (layer.getVisible()) { float layerX = mapX + layer.getX(); float layerY = mapY + layer.getY(); temp.set(layerX, layerY, layer.getWidth(), layer.getHeight()); /* Skip the layer if it is completely out of the view. * Because objects are not necessarily constrained to the layer bounds * this could result in objects that would otherwise have been visible * to be not rendered. This is not likely to be a common issue, and it * can be avoided by having the layer size grow to encompass all of * its objects. * * TODO: Is this the desired behavior? */ if (view.overlaps(temp) || view.contains(temp)) { for (MapObject object : layer.getObjects()) { objectsTotal += 1; if (object.getVisible()) { float objectX = layerX + object.getX(); float objectY = layerY + object.getY(); temp.set(objectX, objectY, object.getWidth(), object.getHeight()); /* Skip the object if it is completely out of the view. * Because objects are not necessarily constrained to the layer bounds * we only check that they are in the view, and not whether they are * outside of the layer bounds. * * Note this assumes that the object bounds encompass the entire object. * * TODO: Is this the desired behavior? */ float rotation = object.getRotation(); if (rotation == 0) { temp.set(objectX, objectY, object.getWidth(), object.getHeight()); } else { float minX = +Float.MAX_VALUE; float maxX = -Float.MAX_VALUE; float minY = +Float.MAX_VALUE; float maxY = -Float.MAX_VALUE; float r = (float) Math.toRadians(object.getRotation()); float c = (float) Math.cos(r); float s = (float) Math.sin(r); for (int i = 0; i < 4; i++) { switch (i) { case 0: v.set(0, 0); break; case 1: v.set(object.getWidth(), 0); break; case 2: v.set(object.getWidth(), object.getHeight()); break; case 3: v.set(0, object.getHeight()); break; } float x = objectX + c * (v.x - object.getOriginX()) + -s * (v.y - object.getOriginY()); float y = objectY + s * (v.x - object.getOriginX()) + c * (v.y - object.getOriginY()); minX = Math.min(minX, x + object.getOriginX()); maxX = Math.max(maxX, x + object.getOriginX()); minY = Math.min(minY, y + object.getOriginY()); maxY = Math.max(maxY, y + object.getOriginY()); } temp.set(minX, minY, maxX - minX, maxY - minY); } /* TODO: It would be nice to be able to batch filled polygons here * Using a custom batching solution might provide some interesting * opportunities on this front. For now, we'll just draw all * shapes as separate ShapeRenderer batches. * * FIXME: The following is terribly inefficient.*/ if (view.overlaps(temp) || view.contains(temp)) { objectsDrawn += 1; if (object instanceof TextureRegionMapObject) { TextureRegion region = map.getResources().get(((TextureRegionMapObject) object).getTextureRegionName(), TextureRegion.class); batcher.draw(region, objectX, objectY, object.getOriginX(), object.getOriginY(), object.getWidth(), object.getHeight(), 1, 1, object.getRotation()); } if (object instanceof PolygonMapObject) { batcher.end(); renderer.begin(ShapeType.Filled); Polygon poly = new Polygon(((PolygonMapObject) object).getVertices()); poly.setOrigin(object.getOriginX(), object.getOriginY()); poly.setPosition(objectX, objectY); poly.setRotation(object.getRotation()); float[] verts = poly.getTransformedVertices(); ShortArray indices = t.computeTriangles(verts); for (int i = 0; i < indices.size; i += 3) { int v1 = indices.get(i) * 2; int v2 = indices.get(i + 1) * 2; int v3 = indices.get(i + 2) * 2; renderer.triangle( verts[v1 + 0], verts[v1 + 1], verts[v2 + 0], verts[v2 + 1], verts[v3 + 0], verts[v3 + 1] ); } renderer.end(); batcher.begin(); } } } } } } } batcher.end(); } /** Renders debug lines and shape based objects * <p><b>Note</b>: This draws atop anything rendered previously, * meaning shape based objects will not be drawn beneath * texture based objects on layers above them.</p> */ public void renderDebug() { renderer.begin(ShapeType.Line); float mapX = map.getX(); float mapY = map.getY(); renderer.setColor(0,0,1,1); renderer.rect(mapX, mapY, map.getWidth(), map.getHeight()); for (MapLayer layer : map.getLayers()) { if (layer.getVisible()) { float layerX = mapX + layer.getX(); float layerY = mapY + layer.getY(); temp.set(layerX, layerY, layer.getWidth(), layer.getHeight()); // See above for notes if (view.overlaps(temp) || view.contains(temp)) { renderer.setColor(1,0,0,1); renderer.rect(layerX, layerY, layer.getWidth(), layer.getHeight()); for (MapObject object : layer.getObjects()) { if (object.getVisible()) { float objectX = layerX + object.getX(); float objectY = layerY + object.getY(); temp.set(objectX, objectY, object.getWidth(), object.getHeight()); // See above for notes float rotation = object.getRotation(); if (rotation == 0) { temp.set(objectX, objectY, object.getWidth(), object.getHeight()); } else { float minX = +Float.MAX_VALUE; float maxX = -Float.MAX_VALUE; float minY = +Float.MAX_VALUE; float maxY = -Float.MAX_VALUE; float r = (float) Math.toRadians(object.getRotation()); float c = (float) Math.cos(r); float s = (float) Math.sin(r); for (int i = 0; i < 4; i++) { switch (i) { case 0: v.set(0, 0); break; case 1: v.set(object.getWidth(), 0); break; case 2: v.set(object.getWidth(), object.getHeight()); break; case 3: v.set(0, object.getHeight()); break; } float x = objectX + c * (v.x - object.getOriginX()) + -s * (v.y - object.getOriginY()); float y = objectY + s * (v.x - object.getOriginX()) + c * (v.y - object.getOriginY()); minX = Math.min(minX, x + object.getOriginX()); maxX = Math.max(maxX, x + object.getOriginX()); minY = Math.min(minY, y + object.getOriginY()); maxY = Math.max(maxY, y + object.getOriginY()); } temp.set(minX, minY, maxX - minX, maxY - minY); } if (view.overlaps(temp) || view.contains(temp)) { if (rotation == 0) { renderer.setColor(0,1,0,1); renderer.rect(objectX, objectY, object.getWidth(), object.getHeight()); } else { renderer.setColor(0,0.333f,0,1); renderer.rect(temp.x, temp.y, temp.width, temp.height); renderer.setColor(0,1.0f,0,1); renderer.rect(objectX, objectY, object.getWidth(), object.getHeight(), object.getOriginX(), object.getOriginY(), object.getRotation()); } Vector2 origin = new Vector2(objectX + object.getOriginX(), objectY + object.getOriginY()); Vector2 dir = new Vector2((object.getWidth() - object.getOriginX()) + 1, 0).rotate(rotation).add(origin); renderer.setColor(0,0.5f,0,1); renderer.line(origin, dir); renderer.circle(dir.x, dir.y, 0.1f, 10); } } } } } } renderer.end(); } }