• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright 2011 See AUTHORS file.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  ******************************************************************************/
16 
17 package com.badlogic.gdx.maps.tiled;
18 
19 import java.io.IOException;
20 import java.util.StringTokenizer;
21 
22 import com.badlogic.gdx.assets.AssetDescriptor;
23 import com.badlogic.gdx.assets.AssetLoaderParameters;
24 import com.badlogic.gdx.assets.AssetManager;
25 import com.badlogic.gdx.assets.loaders.AsynchronousAssetLoader;
26 import com.badlogic.gdx.assets.loaders.FileHandleResolver;
27 import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver;
28 import com.badlogic.gdx.files.FileHandle;
29 import com.badlogic.gdx.graphics.Texture;
30 import com.badlogic.gdx.graphics.Texture.TextureFilter;
31 import com.badlogic.gdx.graphics.g2d.TextureAtlas;
32 import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
33 import com.badlogic.gdx.maps.MapLayer;
34 import com.badlogic.gdx.maps.MapObject;
35 import com.badlogic.gdx.maps.MapProperties;
36 import com.badlogic.gdx.maps.objects.EllipseMapObject;
37 import com.badlogic.gdx.maps.objects.PolygonMapObject;
38 import com.badlogic.gdx.maps.objects.PolylineMapObject;
39 import com.badlogic.gdx.maps.objects.RectangleMapObject;
40 import com.badlogic.gdx.maps.tiled.TiledMapTileLayer.Cell;
41 import com.badlogic.gdx.maps.tiled.tiles.AnimatedTiledMapTile;
42 import com.badlogic.gdx.maps.tiled.tiles.StaticTiledMapTile;
43 import com.badlogic.gdx.math.Polygon;
44 import com.badlogic.gdx.math.Polyline;
45 import com.badlogic.gdx.utils.Array;
46 import com.badlogic.gdx.utils.GdxRuntimeException;
47 import com.badlogic.gdx.utils.IntArray;
48 import com.badlogic.gdx.utils.LongArray;
49 import com.badlogic.gdx.utils.ObjectMap;
50 import com.badlogic.gdx.utils.XmlReader;
51 import com.badlogic.gdx.utils.XmlReader.Element;
52 
53 /** A TiledMap Loader which loads tiles from a TextureAtlas instead of separate images.
54  *
55  * It requires a map-level property called 'atlas' with its value being the relative path to the TextureAtlas. The atlas must have
56  * in it indexed regions named after the tilesets used in the map. The indexes shall be local to the tileset (not the global id).
57  * Strip whitespace and rotation should not be used when creating the atlas.
58  *
59  * @author Justin Shapcott
60  * @author Manuel Bua */
61 public class AtlasTmxMapLoader extends BaseTmxMapLoader<AtlasTmxMapLoader.AtlasTiledMapLoaderParameters> {
62 
63 	public static class AtlasTiledMapLoaderParameters extends BaseTmxMapLoader.Parameters {
64 		/** force texture filters? **/
65 		public boolean forceTextureFilters = false;
66 	}
67 
68 	protected Array<Texture> trackedTextures = new Array<Texture>();
69 
70 	private interface AtlasResolver {
71 
getAtlas(String name)72 		public TextureAtlas getAtlas (String name);
73 
74 		public static class DirectAtlasResolver implements AtlasResolver {
75 
76 			private final ObjectMap<String, TextureAtlas> atlases;
77 
DirectAtlasResolver(ObjectMap<String, TextureAtlas> atlases)78 			public DirectAtlasResolver (ObjectMap<String, TextureAtlas> atlases) {
79 				this.atlases = atlases;
80 			}
81 
82 			@Override
getAtlas(String name)83 			public TextureAtlas getAtlas (String name) {
84 				return atlases.get(name);
85 			}
86 
87 		}
88 
89 		public static class AssetManagerAtlasResolver implements AtlasResolver {
90 			private final AssetManager assetManager;
91 
AssetManagerAtlasResolver(AssetManager assetManager)92 			public AssetManagerAtlasResolver (AssetManager assetManager) {
93 				this.assetManager = assetManager;
94 			}
95 
96 			@Override
getAtlas(String name)97 			public TextureAtlas getAtlas (String name) {
98 				return assetManager.get(name, TextureAtlas.class);
99 			}
100 		}
101 	}
102 
AtlasTmxMapLoader()103 	public AtlasTmxMapLoader () {
104 		super(new InternalFileHandleResolver());
105 	}
106 
AtlasTmxMapLoader(FileHandleResolver resolver)107 	public AtlasTmxMapLoader (FileHandleResolver resolver) {
108 		super(resolver);
109 	}
110 
load(String fileName)111 	public TiledMap load (String fileName) {
112 		return load(fileName, new AtlasTiledMapLoaderParameters());
113 	}
114 
115 	@Override
getDependencies(String fileName, FileHandle tmxFile, AtlasTiledMapLoaderParameters parameter)116 	public Array<AssetDescriptor> getDependencies (String fileName, FileHandle tmxFile, AtlasTiledMapLoaderParameters parameter) {
117 		Array<AssetDescriptor> dependencies = new Array<AssetDescriptor>();
118 		try {
119 			root = xml.parse(tmxFile);
120 
121 			Element properties = root.getChildByName("properties");
122 			if (properties != null) {
123 				for (Element property : properties.getChildrenByName("property")) {
124 					String name = property.getAttribute("name");
125 					String value = property.getAttribute("value");
126 					if (name.startsWith("atlas")) {
127 						FileHandle atlasHandle = getRelativeFileHandle(tmxFile, value);
128 						dependencies.add(new AssetDescriptor(atlasHandle, TextureAtlas.class));
129 					}
130 				}
131 			}
132 		} catch (IOException e) {
133 			throw new GdxRuntimeException("Unable to parse .tmx file.");
134 		}
135 		return dependencies;
136 	}
137 
load(String fileName, AtlasTiledMapLoaderParameters parameter)138 	public TiledMap load (String fileName, AtlasTiledMapLoaderParameters parameter) {
139 		try {
140 			if (parameter != null) {
141 				convertObjectToTileSpace = parameter.convertObjectToTileSpace;
142 				flipY = parameter.flipY;
143 			} else {
144 				convertObjectToTileSpace = false;
145 				flipY = true;
146 			}
147 
148 			FileHandle tmxFile = resolve(fileName);
149 			root = xml.parse(tmxFile);
150 			ObjectMap<String, TextureAtlas> atlases = new ObjectMap<String, TextureAtlas>();
151 			FileHandle atlasFile = loadAtlas(root, tmxFile);
152 			if (atlasFile == null) {
153 				throw new GdxRuntimeException("Couldn't load atlas");
154 			}
155 
156 			TextureAtlas atlas = new TextureAtlas(atlasFile);
157 			atlases.put(atlasFile.path(), atlas);
158 
159 			AtlasResolver.DirectAtlasResolver atlasResolver = new AtlasResolver.DirectAtlasResolver(atlases);
160 			TiledMap map = loadMap(root, tmxFile, atlasResolver);
161 			map.setOwnedResources(atlases.values().toArray());
162 			setTextureFilters(parameter.textureMinFilter, parameter.textureMagFilter);
163 			return map;
164 		} catch (IOException e) {
165 			throw new GdxRuntimeException("Couldn't load tilemap '" + fileName + "'", e);
166 		}
167 	}
168 
169 	/** May return null. */
loadAtlas(Element root, FileHandle tmxFile)170 	protected FileHandle loadAtlas (Element root, FileHandle tmxFile) throws IOException {
171 		Element e = root.getChildByName("properties");
172 
173 		if (e != null) {
174 			for (Element property : e.getChildrenByName("property")) {
175 				String name = property.getAttribute("name", null);
176 				String value = property.getAttribute("value", null);
177 				if (name.equals("atlas")) {
178 					if (value == null) {
179 						value = property.getText();
180 					}
181 
182 					if (value == null || value.length() == 0) {
183 						// keep trying until there are no more atlas properties
184 						continue;
185 					}
186 
187 					return getRelativeFileHandle(tmxFile, value);
188 				}
189 			}
190 		}
191 		FileHandle atlasFile = tmxFile.sibling(tmxFile.nameWithoutExtension() + ".atlas");
192 		return atlasFile.exists() ? atlasFile : null;
193 	}
194 
setTextureFilters(TextureFilter min, TextureFilter mag)195 	private void setTextureFilters (TextureFilter min, TextureFilter mag) {
196 		for (Texture texture : trackedTextures) {
197 			texture.setFilter(min, mag);
198 		}
199 		trackedTextures.clear();
200 	}
201 
202 	@Override
loadAsync(AssetManager manager, String fileName, FileHandle tmxFile, AtlasTiledMapLoaderParameters parameter)203 	public void loadAsync (AssetManager manager, String fileName, FileHandle tmxFile, AtlasTiledMapLoaderParameters parameter) {
204 		map = null;
205 
206 		if (parameter != null) {
207 			convertObjectToTileSpace = parameter.convertObjectToTileSpace;
208 			flipY = parameter.flipY;
209 		} else {
210 			convertObjectToTileSpace = false;
211 			flipY = true;
212 		}
213 
214 		try {
215 			map = loadMap(root, tmxFile, new AtlasResolver.AssetManagerAtlasResolver(manager));
216 		} catch (Exception e) {
217 			throw new GdxRuntimeException("Couldn't load tilemap '" + fileName + "'", e);
218 		}
219 	}
220 
221 	@Override
loadSync(AssetManager manager, String fileName, FileHandle file, AtlasTiledMapLoaderParameters parameter)222 	public TiledMap loadSync (AssetManager manager, String fileName, FileHandle file, AtlasTiledMapLoaderParameters parameter) {
223 		if (parameter != null) {
224 			setTextureFilters(parameter.textureMinFilter, parameter.textureMagFilter);
225 		}
226 
227 		return map;
228 	}
229 
loadMap(Element root, FileHandle tmxFile, AtlasResolver resolver)230 	protected TiledMap loadMap (Element root, FileHandle tmxFile, AtlasResolver resolver) {
231 		TiledMap map = new TiledMap();
232 
233 		String mapOrientation = root.getAttribute("orientation", null);
234 		int mapWidth = root.getIntAttribute("width", 0);
235 		int mapHeight = root.getIntAttribute("height", 0);
236 		int tileWidth = root.getIntAttribute("tilewidth", 0);
237 		int tileHeight = root.getIntAttribute("tileheight", 0);
238 		String mapBackgroundColor = root.getAttribute("backgroundcolor", null);
239 
240 		MapProperties mapProperties = map.getProperties();
241 		if (mapOrientation != null) {
242 			mapProperties.put("orientation", mapOrientation);
243 		}
244 		mapProperties.put("width", mapWidth);
245 		mapProperties.put("height", mapHeight);
246 		mapProperties.put("tilewidth", tileWidth);
247 		mapProperties.put("tileheight", tileHeight);
248 		if (mapBackgroundColor != null) {
249 			mapProperties.put("backgroundcolor", mapBackgroundColor);
250 		}
251 
252 		mapTileWidth = tileWidth;
253 		mapTileHeight = tileHeight;
254 		mapWidthInPixels = mapWidth * tileWidth;
255 		mapHeightInPixels = mapHeight * tileHeight;
256 
257 		if (mapOrientation != null) {
258 			if ("staggered".equals(mapOrientation)) {
259 				if (mapHeight > 1) {
260 					mapWidthInPixels += tileWidth / 2;
261 					mapHeightInPixels = mapHeightInPixels / 2 + tileHeight / 2;
262 				}
263 			}
264 		}
265 
266 		for (int i = 0, j = root.getChildCount(); i < j; i++) {
267 			Element element = root.getChild(i);
268 			String elementName = element.getName();
269 			if (elementName.equals("properties")) {
270 				loadProperties(map.getProperties(), element);
271 			} else if (elementName.equals("tileset")) {
272 				loadTileset(map, element, tmxFile, resolver);
273 			} else if (elementName.equals("layer")) {
274 				loadTileLayer(map, element);
275 			} else if (elementName.equals("objectgroup")) {
276 				loadObjectGroup(map, element);
277 			}
278 		}
279 		return map;
280 	}
281 
loadTileset(TiledMap map, Element element, FileHandle tmxFile, AtlasResolver resolver)282 	protected void loadTileset (TiledMap map, Element element, FileHandle tmxFile, AtlasResolver resolver) {
283 		if (element.getName().equals("tileset")) {
284 			String name = element.get("name", null);
285 			int firstgid = element.getIntAttribute("firstgid", 1);
286 			int tilewidth = element.getIntAttribute("tilewidth", 0);
287 			int tileheight = element.getIntAttribute("tileheight", 0);
288 			int spacing = element.getIntAttribute("spacing", 0);
289 			int margin = element.getIntAttribute("margin", 0);
290 			String source = element.getAttribute("source", null);
291 
292 			int offsetX = 0;
293 			int offsetY = 0;
294 
295 			String imageSource = "";
296 			int imageWidth = 0, imageHeight = 0;
297 
298 			FileHandle image = null;
299 			if (source != null) {
300 				FileHandle tsx = getRelativeFileHandle(tmxFile, source);
301 				try {
302 					element = xml.parse(tsx);
303 					name = element.get("name", null);
304 					tilewidth = element.getIntAttribute("tilewidth", 0);
305 					tileheight = element.getIntAttribute("tileheight", 0);
306 					spacing = element.getIntAttribute("spacing", 0);
307 					margin = element.getIntAttribute("margin", 0);
308 					Element offset = element.getChildByName("tileoffset");
309 					if (offset != null) {
310 						offsetX = offset.getIntAttribute("x", 0);
311 						offsetY = offset.getIntAttribute("y", 0);
312 					}
313 					Element imageElement = element.getChildByName("image");
314 					if (imageElement != null) {
315 						imageSource = imageElement.getAttribute("source");
316 						imageWidth = imageElement.getIntAttribute("width", 0);
317 						imageHeight = imageElement.getIntAttribute("height", 0);
318 						image = getRelativeFileHandle(tsx, imageSource);
319 					}
320 				} catch (IOException e) {
321 					throw new GdxRuntimeException("Error parsing external tileset.");
322 				}
323 			} else {
324 				Element offset = element.getChildByName("tileoffset");
325 				if (offset != null) {
326 					offsetX = offset.getIntAttribute("x", 0);
327 					offsetY = offset.getIntAttribute("y", 0);
328 				}
329 				Element imageElement = element.getChildByName("image");
330 				if (imageElement != null) {
331 					imageSource = imageElement.getAttribute("source");
332 					imageWidth = imageElement.getIntAttribute("width", 0);
333 					imageHeight = imageElement.getIntAttribute("height", 0);
334 					image = getRelativeFileHandle(tmxFile, imageSource);
335 				}
336 			}
337 
338 			String atlasFilePath = map.getProperties().get("atlas", String.class);
339 			if (atlasFilePath == null) {
340 				FileHandle atlasFile = tmxFile.sibling(tmxFile.nameWithoutExtension() + ".atlas");
341 				if (atlasFile.exists()) atlasFilePath = atlasFile.name();
342 			}
343 			if (atlasFilePath == null) {
344 				throw new GdxRuntimeException("The map is missing the 'atlas' property");
345 			}
346 
347 			// get the TextureAtlas for this tileset
348 			FileHandle atlasHandle = getRelativeFileHandle(tmxFile, atlasFilePath);
349 			atlasHandle = resolve(atlasHandle.path());
350 			TextureAtlas atlas = resolver.getAtlas(atlasHandle.path());
351 			String regionsName = name;
352 
353 			for (Texture texture : atlas.getTextures()) {
354 				trackedTextures.add(texture);
355 			}
356 
357 			TiledMapTileSet tileset = new TiledMapTileSet();
358 			MapProperties props = tileset.getProperties();
359 			tileset.setName(name);
360 			props.put("firstgid", firstgid);
361 			props.put("imagesource", imageSource);
362 			props.put("imagewidth", imageWidth);
363 			props.put("imageheight", imageHeight);
364 			props.put("tilewidth", tilewidth);
365 			props.put("tileheight", tileheight);
366 			props.put("margin", margin);
367 			props.put("spacing", spacing);
368 
369 			if (imageSource != null && imageSource.length() > 0) {
370 				int lastgid = firstgid + ((imageWidth / tilewidth) * (imageHeight / tileheight)) - 1;
371 				for (AtlasRegion region : atlas.findRegions(regionsName)) {
372 					// handle unused tile ids
373 					if (region != null) {
374 						int tileid = region.index + 1;
375 						if (tileid >= firstgid && tileid <= lastgid) {
376 							StaticTiledMapTile tile = new StaticTiledMapTile(region);
377 							tile.setId(tileid);
378 							tile.setOffsetX(offsetX);
379 							tile.setOffsetY(flipY ? -offsetY : offsetY);
380 							tileset.putTile(tileid, tile);
381 						}
382 					}
383 				}
384 			}
385 
386 			for (Element tileElement : element.getChildrenByName("tile")) {
387 				int tileid = firstgid + tileElement.getIntAttribute("id", 0);
388 				TiledMapTile tile = tileset.getTile(tileid);
389 				if (tile == null) {
390 					Element imageElement = tileElement.getChildByName("image");
391 					if (imageElement != null) {
392 						// Is a tilemap with individual images.
393 						String regionName = imageElement.getAttribute("source");
394 						regionName = regionName.substring(0, regionName.lastIndexOf('.'));
395 						AtlasRegion region = atlas.findRegion(regionName);
396 						if (region == null) throw new GdxRuntimeException("Tileset region not found: " + regionName);
397 						tile = new StaticTiledMapTile(region);
398 						tile.setId(tileid);
399 						tile.setOffsetX(offsetX);
400 						tile.setOffsetY(flipY ? -offsetY : offsetY);
401 						tileset.putTile(tileid, tile);
402 					}
403 				}
404 				if (tile != null) {
405 					String terrain = tileElement.getAttribute("terrain", null);
406 					if (terrain != null) {
407 						tile.getProperties().put("terrain", terrain);
408 					}
409 					String probability = tileElement.getAttribute("probability", null);
410 					if (probability != null) {
411 						tile.getProperties().put("probability", probability);
412 					}
413 					Element properties = tileElement.getChildByName("properties");
414 					if (properties != null) {
415 						loadProperties(tile.getProperties(), properties);
416 					}
417 				}
418 			}
419 
420 			Array<Element> tileElements = element.getChildrenByName("tile");
421 
422 			Array<AnimatedTiledMapTile> animatedTiles = new Array<AnimatedTiledMapTile>();
423 
424 			for (Element tileElement : tileElements) {
425 				int localtid = tileElement.getIntAttribute("id", 0);
426 				TiledMapTile tile = tileset.getTile(firstgid + localtid);
427 				if (tile != null) {
428 					Element animationElement = tileElement.getChildByName("animation");
429 					if (animationElement != null) {
430 
431 						Array<StaticTiledMapTile> staticTiles = new Array<StaticTiledMapTile>();
432 						IntArray intervals = new IntArray();
433 						for (Element frameElement: animationElement.getChildrenByName("frame")) {
434 							staticTiles.add((StaticTiledMapTile) tileset.getTile(firstgid + frameElement.getIntAttribute("tileid")));
435 							intervals.add(frameElement.getIntAttribute("duration"));
436 						}
437 
438 						AnimatedTiledMapTile animatedTile = new AnimatedTiledMapTile(intervals, staticTiles);
439 						animatedTile.setId(tile.getId());
440 						animatedTiles.add(animatedTile);
441 						tile = animatedTile;
442 					}
443 
444 					String terrain = tileElement.getAttribute("terrain", null);
445 					if (terrain != null) {
446 						tile.getProperties().put("terrain", terrain);
447 					}
448 					String probability = tileElement.getAttribute("probability", null);
449 					if (probability != null) {
450 						tile.getProperties().put("probability", probability);
451 					}
452 					Element properties = tileElement.getChildByName("properties");
453 					if (properties != null) {
454 						loadProperties(tile.getProperties(), properties);
455 					}
456 				}
457 			}
458 
459 			for (AnimatedTiledMapTile tile : animatedTiles) {
460 				tileset.putTile(tile.getId(), tile);
461 			}
462 
463 			Element properties = element.getChildByName("properties");
464 			if (properties != null) {
465 				loadProperties(tileset.getProperties(), properties);
466 			}
467 			map.getTileSets().addTileSet(tileset);
468 		}
469 	}
470 
471 }
472