Home | History | Annotate | Download | only in tiled
      1 
      2 package com.badlogic.gdx.maps.tiled;
      3 
      4 import java.io.BufferedInputStream;
      5 import java.io.ByteArrayInputStream;
      6 import java.io.IOException;
      7 import java.io.InputStream;
      8 import java.util.StringTokenizer;
      9 import java.util.zip.GZIPInputStream;
     10 import java.util.zip.InflaterInputStream;
     11 
     12 import com.badlogic.gdx.assets.AssetLoaderParameters;
     13 import com.badlogic.gdx.assets.loaders.AsynchronousAssetLoader;
     14 import com.badlogic.gdx.assets.loaders.FileHandleResolver;
     15 import com.badlogic.gdx.files.FileHandle;
     16 import com.badlogic.gdx.graphics.Texture.TextureFilter;
     17 import com.badlogic.gdx.graphics.g2d.TextureRegion;
     18 import com.badlogic.gdx.maps.ImageResolver;
     19 import com.badlogic.gdx.maps.MapLayer;
     20 import com.badlogic.gdx.maps.MapObject;
     21 import com.badlogic.gdx.maps.MapProperties;
     22 import com.badlogic.gdx.maps.objects.EllipseMapObject;
     23 import com.badlogic.gdx.maps.objects.PolygonMapObject;
     24 import com.badlogic.gdx.maps.objects.PolylineMapObject;
     25 import com.badlogic.gdx.maps.objects.RectangleMapObject;
     26 import com.badlogic.gdx.maps.tiled.TiledMapTileLayer.Cell;
     27 import com.badlogic.gdx.maps.tiled.objects.TiledMapTileMapObject;
     28 import com.badlogic.gdx.math.Polygon;
     29 import com.badlogic.gdx.math.Polyline;
     30 import com.badlogic.gdx.utils.Base64Coder;
     31 import com.badlogic.gdx.utils.GdxRuntimeException;
     32 import com.badlogic.gdx.utils.StreamUtils;
     33 import com.badlogic.gdx.utils.XmlReader;
     34 import com.badlogic.gdx.utils.XmlReader.Element;
     35 
     36 public abstract class BaseTmxMapLoader<P extends AssetLoaderParameters<TiledMap>> extends AsynchronousAssetLoader<TiledMap, P> {
     37 
     38 	public static class Parameters extends AssetLoaderParameters<TiledMap> {
     39 		/** generate mipmaps? **/
     40 		public boolean generateMipMaps = false;
     41 		/** The TextureFilter to use for minification **/
     42 		public TextureFilter textureMinFilter = TextureFilter.Nearest;
     43 		/** The TextureFilter to use for magnification **/
     44 		public TextureFilter textureMagFilter = TextureFilter.Nearest;
     45 		/** Whether to convert the objects' pixel position and size to the equivalent in tile space. **/
     46 		public boolean convertObjectToTileSpace = false;
     47 		/** Whether to flip all Y coordinates so that Y positive is down. All LibGDX renderers require flipped Y coordinates, and
     48 		 * thus flipY set to true. This parameter is included for non-rendering related purposes of TMX files, or custom renderers. */
     49 		public boolean flipY = true;
     50 	}
     51 
     52 	protected static final int FLAG_FLIP_HORIZONTALLY = 0x80000000;
     53 	protected static final int FLAG_FLIP_VERTICALLY = 0x40000000;
     54 	protected static final int FLAG_FLIP_DIAGONALLY = 0x20000000;
     55 	protected static final int MASK_CLEAR = 0xE0000000;
     56 
     57 	protected XmlReader xml = new XmlReader();
     58 	protected Element root;
     59 	protected boolean convertObjectToTileSpace;
     60 	protected boolean flipY = true;
     61 
     62 	protected int mapTileWidth;
     63 	protected int mapTileHeight;
     64 	protected int mapWidthInPixels;
     65 	protected int mapHeightInPixels;
     66 
     67 	protected TiledMap map;
     68 
     69 	public BaseTmxMapLoader (FileHandleResolver resolver) {
     70 		super(resolver);
     71 	}
     72 
     73 	protected void loadTileLayer (TiledMap map, Element element) {
     74 		if (element.getName().equals("layer")) {
     75 			int width = element.getIntAttribute("width", 0);
     76 			int height = element.getIntAttribute("height", 0);
     77 			int tileWidth = element.getParent().getIntAttribute("tilewidth", 0);
     78 			int tileHeight = element.getParent().getIntAttribute("tileheight", 0);
     79 			TiledMapTileLayer layer = new TiledMapTileLayer(width, height, tileWidth, tileHeight);
     80 
     81 			loadBasicLayerInfo(layer, element);
     82 
     83 			int[] ids = getTileIds(element, width, height);
     84 			TiledMapTileSets tilesets = map.getTileSets();
     85 			for (int y = 0; y < height; y++) {
     86 				for (int x = 0; x < width; x++) {
     87 					int id = ids[y * width + x];
     88 					boolean flipHorizontally = ((id & FLAG_FLIP_HORIZONTALLY) != 0);
     89 					boolean flipVertically = ((id & FLAG_FLIP_VERTICALLY) != 0);
     90 					boolean flipDiagonally = ((id & FLAG_FLIP_DIAGONALLY) != 0);
     91 
     92 					TiledMapTile tile = tilesets.getTile(id & ~MASK_CLEAR);
     93 					if (tile != null) {
     94 						Cell cell = createTileLayerCell(flipHorizontally, flipVertically, flipDiagonally);
     95 						cell.setTile(tile);
     96 						layer.setCell(x, flipY ? height - 1 - y : y, cell);
     97 					}
     98 				}
     99 			}
    100 
    101 			Element properties = element.getChildByName("properties");
    102 			if (properties != null) {
    103 				loadProperties(layer.getProperties(), properties);
    104 			}
    105 			map.getLayers().add(layer);
    106 		}
    107 	}
    108 
    109 	protected void loadObjectGroup (TiledMap map, Element element) {
    110 		if (element.getName().equals("objectgroup")) {
    111 			String name = element.getAttribute("name", null);
    112 			MapLayer layer = new MapLayer();
    113 			layer.setName(name);
    114 			Element properties = element.getChildByName("properties");
    115 			if (properties != null) {
    116 				loadProperties(layer.getProperties(), properties);
    117 			}
    118 
    119 			for (Element objectElement : element.getChildrenByName("object")) {
    120 				loadObject(map, layer, objectElement);
    121 			}
    122 
    123 			map.getLayers().add(layer);
    124 		}
    125 	}
    126 
    127 	protected void loadImageLayer (TiledMap map, Element element, FileHandle tmxFile, ImageResolver imageResolver) {
    128 		if (element.getName().equals("imagelayer")) {
    129 			int x = Integer.parseInt(element.getAttribute("x", "0"));
    130 			int y = Integer.parseInt(element.getAttribute("y", "0"));
    131 
    132 			if (flipY) y = mapHeightInPixels - y;
    133 
    134 			TextureRegion texture = null;
    135 
    136 			Element image = element.getChildByName("image");
    137 
    138 			if (image != null) {
    139 				String source = image.getAttribute("source");
    140 				FileHandle handle = getRelativeFileHandle(tmxFile, source);
    141 				texture = imageResolver.getImage(handle.path());
    142 				y -= texture.getRegionHeight();
    143 			}
    144 
    145 			TiledMapImageLayer layer = new TiledMapImageLayer(texture, x, y);
    146 
    147 			loadBasicLayerInfo(layer, element);
    148 
    149 			Element properties = element.getChildByName("properties");
    150 			if (properties != null) {
    151 				loadProperties(layer.getProperties(), properties);
    152 			}
    153 
    154 			map.getLayers().add(layer);
    155 		}
    156 	}
    157 
    158 	protected void loadBasicLayerInfo (MapLayer layer, Element element) {
    159 		String name = element.getAttribute("name", null);
    160 		float opacity = Float.parseFloat(element.getAttribute("opacity", "1.0"));
    161 		boolean visible = element.getIntAttribute("visible", 1) == 1;
    162 
    163 		layer.setName(name);
    164 		layer.setOpacity(opacity);
    165 		layer.setVisible(visible);
    166 	}
    167 
    168 	protected void loadObject (TiledMap map, MapLayer layer, Element element) {
    169 		if (element.getName().equals("object")) {
    170 			MapObject object = null;
    171 
    172 			float scaleX = convertObjectToTileSpace ? 1.0f / mapTileWidth : 1.0f;
    173 			float scaleY = convertObjectToTileSpace ? 1.0f / mapTileHeight : 1.0f;
    174 
    175 			float x = element.getFloatAttribute("x", 0) * scaleX;
    176 			float y = (flipY ? (mapHeightInPixels - element.getFloatAttribute("y", 0)) : element.getFloatAttribute("y", 0)) * scaleY;
    177 
    178 			float width = element.getFloatAttribute("width", 0) * scaleX;
    179 			float height = element.getFloatAttribute("height", 0) * scaleY;
    180 
    181 			if (element.getChildCount() > 0) {
    182 				Element child = null;
    183 				if ((child = element.getChildByName("polygon")) != null) {
    184 					String[] points = child.getAttribute("points").split(" ");
    185 					float[] vertices = new float[points.length * 2];
    186 					for (int i = 0; i < points.length; i++) {
    187 						String[] point = points[i].split(",");
    188 						vertices[i * 2] = Float.parseFloat(point[0]) * scaleX;
    189 						vertices[i * 2 + 1] = Float.parseFloat(point[1]) * scaleY * (flipY ? -1 : 1);
    190 					}
    191 					Polygon polygon = new Polygon(vertices);
    192 					polygon.setPosition(x, y);
    193 					object = new PolygonMapObject(polygon);
    194 				} else if ((child = element.getChildByName("polyline")) != null) {
    195 					String[] points = child.getAttribute("points").split(" ");
    196 					float[] vertices = new float[points.length * 2];
    197 					for (int i = 0; i < points.length; i++) {
    198 						String[] point = points[i].split(",");
    199 						vertices[i * 2] = Float.parseFloat(point[0]) * scaleX;
    200 						vertices[i * 2 + 1] = Float.parseFloat(point[1]) * scaleY * (flipY ? -1 : 1);
    201 					}
    202 					Polyline polyline = new Polyline(vertices);
    203 					polyline.setPosition(x, y);
    204 					object = new PolylineMapObject(polyline);
    205 				} else if ((child = element.getChildByName("ellipse")) != null) {
    206 					object = new EllipseMapObject(x, flipY ? y - height : y, width, height);
    207 				}
    208 			}
    209 			if (object == null) {
    210 				String gid = null;
    211 				if ((gid = element.getAttribute("gid", null)) != null) {
    212 					int id = (int)Long.parseLong(gid);
    213 					boolean flipHorizontally = ((id & FLAG_FLIP_HORIZONTALLY) != 0);
    214 					boolean flipVertically = ((id & FLAG_FLIP_VERTICALLY) != 0);
    215 
    216 					TiledMapTile tile = map.getTileSets().getTile(id & ~MASK_CLEAR);
    217 					TiledMapTileMapObject tiledMapTileMapObject = new TiledMapTileMapObject(tile, flipHorizontally, flipVertically);
    218 					TextureRegion textureRegion = tiledMapTileMapObject.getTextureRegion();
    219 					tiledMapTileMapObject.getProperties().put("gid", id);
    220 					tiledMapTileMapObject.setX(x);
    221 					tiledMapTileMapObject.setY(flipY ? y : y - height);
    222 					float objectWidth = element.getFloatAttribute("width", textureRegion.getRegionWidth());
    223 					float objectHeight = element.getFloatAttribute("height", textureRegion.getRegionHeight());
    224 					tiledMapTileMapObject.setScaleX(scaleX * (objectWidth / textureRegion.getRegionWidth()));
    225 					tiledMapTileMapObject.setScaleY(scaleY * (objectHeight / textureRegion.getRegionHeight()));
    226 					tiledMapTileMapObject.setRotation(element.getFloatAttribute("rotation", 0));
    227 					object = tiledMapTileMapObject;
    228 				} else {
    229 					object = new RectangleMapObject(x, flipY ? y - height : y, width, height);
    230 				}
    231 			}
    232 			object.setName(element.getAttribute("name", null));
    233 			String rotation = element.getAttribute("rotation", null);
    234 			if (rotation != null) {
    235 				object.getProperties().put("rotation", Float.parseFloat(rotation));
    236 			}
    237 			String type = element.getAttribute("type", null);
    238 			if (type != null) {
    239 				object.getProperties().put("type", type);
    240 			}
    241 			int id = element.getIntAttribute("id", 0);
    242 			if (id != 0) {
    243 				object.getProperties().put("id", id);
    244 			}
    245 			object.getProperties().put("x", x);
    246 
    247 			if (object instanceof TiledMapTileMapObject) {
    248 				object.getProperties().put("y", y);
    249 			} else {
    250 				object.getProperties().put("y", (flipY ? y - height : y));
    251 			}
    252 			object.getProperties().put("width", width);
    253 			object.getProperties().put("height", height);
    254 			object.setVisible(element.getIntAttribute("visible", 1) == 1);
    255 			Element properties = element.getChildByName("properties");
    256 			if (properties != null) {
    257 				loadProperties(object.getProperties(), properties);
    258 			}
    259 			layer.getObjects().add(object);
    260 		}
    261 	}
    262 
    263 	protected void loadProperties (MapProperties properties, Element element) {
    264 		if (element == null) return;
    265 		if (element.getName().equals("properties")) {
    266 			for (Element property : element.getChildrenByName("property")) {
    267 				String name = property.getAttribute("name", null);
    268 				String value = property.getAttribute("value", null);
    269 				if (value == null) {
    270 					value = property.getText();
    271 				}
    272 				properties.put(name, value);
    273 			}
    274 		}
    275 	}
    276 
    277 	protected Cell createTileLayerCell (boolean flipHorizontally, boolean flipVertically, boolean flipDiagonally) {
    278 		Cell cell = new Cell();
    279 		if (flipDiagonally) {
    280 			if (flipHorizontally && flipVertically) {
    281 				cell.setFlipHorizontally(true);
    282 				cell.setRotation(Cell.ROTATE_270);
    283 			} else if (flipHorizontally) {
    284 				cell.setRotation(Cell.ROTATE_270);
    285 			} else if (flipVertically) {
    286 				cell.setRotation(Cell.ROTATE_90);
    287 			} else {
    288 				cell.setFlipVertically(true);
    289 				cell.setRotation(Cell.ROTATE_270);
    290 			}
    291 		} else {
    292 			cell.setFlipHorizontally(flipHorizontally);
    293 			cell.setFlipVertically(flipVertically);
    294 		}
    295 		return cell;
    296 	}
    297 
    298 	static public int[] getTileIds (Element element, int width, int height) {
    299 		Element data = element.getChildByName("data");
    300 		String encoding = data.getAttribute("encoding", null);
    301 		if (encoding == null) { // no 'encoding' attribute means that the encoding is XML
    302 			throw new GdxRuntimeException("Unsupported encoding (XML) for TMX Layer Data");
    303 		}
    304 		int[] ids = new int[width * height];
    305 		if (encoding.equals("csv")) {
    306 			String[] array = data.getText().split(",");
    307 			for (int i = 0; i < array.length; i++)
    308 				ids[i] = (int)Long.parseLong(array[i].trim());
    309 		} else {
    310 			if (true)
    311 				if (encoding.equals("base64")) {
    312 					InputStream is = null;
    313 					try {
    314 						String compression = data.getAttribute("compression", null);
    315 						byte[] bytes = Base64Coder.decode(data.getText());
    316 						if (compression == null)
    317 							is = new ByteArrayInputStream(bytes);
    318 						else if (compression.equals("gzip"))
    319 							is = new BufferedInputStream(new GZIPInputStream(new ByteArrayInputStream(bytes), bytes.length));
    320 						else if (compression.equals("zlib"))
    321 							is = new BufferedInputStream(new InflaterInputStream(new ByteArrayInputStream(bytes)));
    322 						else
    323 							throw new GdxRuntimeException("Unrecognised compression (" + compression + ") for TMX Layer Data");
    324 
    325 						byte[] temp = new byte[4];
    326 						for (int y = 0; y < height; y++) {
    327 							for (int x = 0; x < width; x++) {
    328 								int read = is.read(temp);
    329 								while (read < temp.length) {
    330 									int curr = is.read(temp, read, temp.length - read);
    331 									if (curr == -1) break;
    332 									read += curr;
    333 								}
    334 								if (read != temp.length)
    335 									throw new GdxRuntimeException("Error Reading TMX Layer Data: Premature end of tile data");
    336 								ids[y * width + x] = unsignedByteToInt(temp[0]) | unsignedByteToInt(temp[1]) << 8
    337 									| unsignedByteToInt(temp[2]) << 16 | unsignedByteToInt(temp[3]) << 24;
    338 							}
    339 						}
    340 					} catch (IOException e) {
    341 						throw new GdxRuntimeException("Error Reading TMX Layer Data - IOException: " + e.getMessage());
    342 					} finally {
    343 						StreamUtils.closeQuietly(is);
    344 					}
    345 				} else {
    346 					// any other value of 'encoding' is one we're not aware of, probably a feature of a future version of Tiled
    347 					// or another editor
    348 					throw new GdxRuntimeException("Unrecognised encoding (" + encoding + ") for TMX Layer Data");
    349 				}
    350 		}
    351 		return ids;
    352 	}
    353 
    354 	protected static int unsignedByteToInt (byte b) {
    355 		return b & 0xFF;
    356 	}
    357 
    358 	protected static FileHandle getRelativeFileHandle (FileHandle file, String path) {
    359 		StringTokenizer tokenizer = new StringTokenizer(path, "\\/");
    360 		FileHandle result = file.parent();
    361 		while (tokenizer.hasMoreElements()) {
    362 			String token = tokenizer.nextToken();
    363 			if (token.equals(".."))
    364 				result = result.parent();
    365 			else {
    366 				result = result.child(token);
    367 			}
    368 		}
    369 		return result;
    370 	}
    371 
    372 }
    373