• 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"); you may not use this file except in compliance with the
5  * License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
10  * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
11  * governing permissions and limitations under the License.
12  ******************************************************************************/
13 
14 package com.badlogic.gdx.tiledmappacker;
15 
16 import java.awt.Graphics;
17 import java.awt.image.BufferedImage;
18 import java.io.File;
19 import java.io.FilenameFilter;
20 import java.io.IOException;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 
24 import javax.xml.parsers.DocumentBuilder;
25 import javax.xml.parsers.DocumentBuilderFactory;
26 import javax.xml.parsers.ParserConfigurationException;
27 import javax.xml.transform.Transformer;
28 import javax.xml.transform.TransformerConfigurationException;
29 import javax.xml.transform.TransformerException;
30 import javax.xml.transform.TransformerFactory;
31 import javax.xml.transform.dom.DOMSource;
32 import javax.xml.transform.stream.StreamResult;
33 
34 import org.w3c.dom.Attr;
35 import org.w3c.dom.Document;
36 import org.w3c.dom.NamedNodeMap;
37 import org.w3c.dom.Node;
38 import org.w3c.dom.NodeList;
39 import org.xml.sax.SAXException;
40 
41 import com.badlogic.gdx.ApplicationListener;
42 import com.badlogic.gdx.Gdx;
43 import com.badlogic.gdx.assets.loaders.resolvers.AbsoluteFileHandleResolver;
44 import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
45 import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
46 import com.badlogic.gdx.files.FileHandle;
47 import com.badlogic.gdx.graphics.g2d.TextureAtlas;
48 import com.badlogic.gdx.maps.MapLayer;
49 import com.badlogic.gdx.maps.tiled.TiledMap;
50 import com.badlogic.gdx.maps.tiled.TiledMapTile;
51 import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
52 import com.badlogic.gdx.maps.tiled.TiledMapTileSet;
53 import com.badlogic.gdx.maps.tiled.TmxMapLoader;
54 import com.badlogic.gdx.maps.tiled.tiles.AnimatedTiledMapTile;
55 import com.badlogic.gdx.maps.tiled.tiles.StaticTiledMapTile;
56 import com.badlogic.gdx.math.Vector2;
57 import com.badlogic.gdx.tools.texturepacker.TexturePacker;
58 import com.badlogic.gdx.tools.texturepacker.TexturePacker.Settings;
59 import com.badlogic.gdx.utils.GdxRuntimeException;
60 import com.badlogic.gdx.utils.IntArray;
61 import com.badlogic.gdx.utils.ObjectMap;
62 
63 /** Given one or more TMX tilemaps, packs all tileset resources used across the maps, or the resources used per map, into a single,
64  * or multiple (one per map), {@link TextureAtlas} and produces a new TMX file to be loaded with an AtlasTiledMapLoader loader.
65  * Optionally, it can keep track of unused tiles and omit them from the generated atlas, reducing the resource size.
66  *
67  * The original TMX map file will be parsed by using the {@link TmxMapLoader} loader, thus access to a valid OpenGL context is
68  * <b>required</b>, that's why an LwjglApplication is created by this preprocessor.
69  *
70  * The new TMX map file will contains a new property, namely "atlas", whose value will enable the AtlasTiledMapLoader to correctly
71  * read the associated TextureAtlas representing the tileset.
72  *
73  * @author David Fraska and others (initial implementation, tell me who you are!)
74  * @author Manuel Bua */
75 public class TiledMapPacker {
76 	private TexturePacker packer;
77 	private TiledMap map;
78 
79 	private TmxMapLoader mapLoader = new TmxMapLoader(new AbsoluteFileHandleResolver());
80 	private TiledMapPackerSettings settings;
81 
82 	private static final String TilesetsOutputDir = "tileset";
83 	static String AtlasOutputName = "packed";
84 
85 	private HashMap<String, IntArray> tilesetUsedIds = new HashMap<String, IntArray>();
86 	private ObjectMap<String, TiledMapTileSet> tilesetsToPack;
87 
88 	static File inputDir;
89 	static File outputDir;
90 	private FileHandle currentDir;
91 
92 	private static class TmxFilter implements FilenameFilter {
TmxFilter()93 		public TmxFilter () {
94 		}
95 
96 		@Override
accept(File dir, String name)97 		public boolean accept (File dir, String name) {
98 			return (name.endsWith(".tmx"));
99 		}
100 	}
101 
102 	private static class DirFilter implements FilenameFilter {
DirFilter()103 		public DirFilter () {
104 		}
105 
106 		@Override
accept(File f, String s)107 		public boolean accept (File f, String s) {
108 			return (new File(f, s).isDirectory());
109 		}
110 	}
111 
112 	/** Constructs a new preprocessor by using the default packing settings */
TiledMapPacker()113 	public TiledMapPacker () {
114 		this(new TiledMapPackerSettings());
115 	}
116 
117 	/** Constructs a new preprocessor by using the specified packing settings */
TiledMapPacker(TiledMapPackerSettings settings)118 	public TiledMapPacker (TiledMapPackerSettings settings) {
119 		this.settings = settings;
120 	}
121 
122 	/** You can either run the {@link TiledMapPacker#main(String[])} method or reference this class in your own project and call
123 	 * this method. If working with libGDX sources, you can also run this file to create a run configuration then export it as a
124 	 * Runnable Jar. To run from a nightly build:
125 	 *
126 	 * <code> <br><br>
127 	 * Linux / OS X <br>
128 	   java -cp gdx.jar:gdx-natives.jar:gdx-backend-lwjgl.jar:gdx-backend-lwjgl-natives.jar:gdx-tiled-preprocessor.jar:extensions/gdx-tools/gdx-tools.jar
129 	    com.badlogic.gdx.tiledmappacker.TiledMapPacker inputDir [outputDir]  [--strip-unused] [--combine-tilesets] [-v]
130 	 * <br><br>
131 	 *
132 	 * Windows <br>
133 	   java -cp gdx.jar;gdx-natives.jar;gdx-backend-lwjgl.jar;gdx-backend-lwjgl-natives.jar;gdx-tiled-preprocessor.jar;extensions/gdx-tools/gdx-tools.jar
134 	    com.badlogic.gdx.tiledmappacker.TiledMapPacker inputDir [outputDir]  [--strip-unused] [--combine-tilesets] [-v]
135 	 * <br><br> </code>
136 	 *
137 	 * Keep in mind that this preprocessor will need to load the maps by using the {@link TmxMapLoader} loader and this in turn
138 	 * will need a valid OpenGL context to work.
139 	 *
140 	 * Process a directory containing TMX map files representing Tiled maps and produce multiple, or a single, TextureAtlas as well
141 	 * as new processed TMX map files, correctly referencing the generated {@link TextureAtlas} by using the "atlas" custom map
142 	 * property. */
processInputDir(Settings texturePackerSettings)143 	public void processInputDir (Settings texturePackerSettings) throws IOException {
144 		FileHandle inputDirHandle = new FileHandle(inputDir.getCanonicalPath());
145 		File[] mapFilesInCurrentDir = inputDir.listFiles(new TmxFilter());
146 		tilesetsToPack = new ObjectMap<String, TiledMapTileSet>();
147 
148 		// Processes the maps inside inputDir
149 		for (File mapFile : mapFilesInCurrentDir) {
150 			processSingleMap(mapFile, inputDirHandle, texturePackerSettings);
151 		}
152 
153 		processSubdirectories(inputDirHandle, texturePackerSettings);
154 
155 		boolean combineTilesets = this.settings.combineTilesets;
156 		if (combineTilesets == true) {
157 			packTilesets(inputDirHandle, texturePackerSettings);
158 		}
159 	}
160 
161 	/** Looks for subdirectories inside parentHandle, processes maps in subdirectory, repeat.
162 	 * @param currentDir The directory to look for maps and other directories
163 	 * @throws IOException */
processSubdirectories(FileHandle currentDir, Settings texturePackerSettings)164 	private void processSubdirectories (FileHandle currentDir, Settings texturePackerSettings) throws IOException {
165 		File parentPath = new File(currentDir.path());
166 		File[] directories = parentPath.listFiles(new DirFilter());
167 
168 		for (File directory : directories) {
169 			currentDir = new FileHandle(directory.getCanonicalPath());
170 			File[] mapFilesInCurrentDir = directory.listFiles(new TmxFilter());
171 
172 			for (File mapFile : mapFilesInCurrentDir) {
173 				processSingleMap(mapFile, currentDir, texturePackerSettings);
174 			}
175 
176 			processSubdirectories(currentDir, texturePackerSettings);
177 		}
178 	}
179 
processSingleMap(File mapFile, FileHandle dirHandle, Settings texturePackerSettings)180 	private void processSingleMap (File mapFile, FileHandle dirHandle, Settings texturePackerSettings) throws IOException {
181 		boolean combineTilesets = this.settings.combineTilesets;
182 		if (combineTilesets == false) {
183 			tilesetUsedIds = new HashMap<String, IntArray>();
184 			tilesetsToPack = new ObjectMap<String, TiledMapTileSet>();
185 		}
186 
187 		map = mapLoader.load(mapFile.getCanonicalPath());
188 
189 		// if enabled, build a list of used tileids for the tileset used by this map
190 		boolean stripUnusedTiles = this.settings.stripUnusedTiles;
191 		if (stripUnusedTiles) {
192 			stripUnusedTiles();
193 		} else {
194 			for (TiledMapTileSet tileset : map.getTileSets()) {
195 				String tilesetName = tileset.getName();
196 				if (!tilesetsToPack.containsKey(tilesetName)) {
197 					tilesetsToPack.put(tilesetName, tileset);
198 				}
199 			}
200 		}
201 
202 		if (combineTilesets == false) {
203 			FileHandle tmpHandle = new FileHandle(mapFile.getName());
204 			this.settings.atlasOutputName = tmpHandle.nameWithoutExtension();
205 
206 			packTilesets(dirHandle, texturePackerSettings);
207 		}
208 
209 		FileHandle tmxFile = new FileHandle(mapFile.getCanonicalPath());
210 		writeUpdatedTMX(map, tmxFile);
211 	}
212 
stripUnusedTiles()213 	private void stripUnusedTiles () {
214 		int mapWidth = map.getProperties().get("width", Integer.class);
215 		int mapHeight = map.getProperties().get("height", Integer.class);
216 		int numlayers = map.getLayers().getCount();
217 		int bucketSize = mapWidth * mapHeight * numlayers;
218 
219 		Iterator<MapLayer> it = map.getLayers().iterator();
220 		while (it.hasNext()) {
221 			MapLayer layer = it.next();
222 
223 			// some layers can be plain MapLayer instances (ie. object groups), just ignore them
224 			if (layer instanceof TiledMapTileLayer) {
225 				TiledMapTileLayer tlayer = (TiledMapTileLayer)layer;
226 
227 				for (int y = 0; y < mapHeight; ++y) {
228 					for (int x = 0; x < mapWidth; ++x) {
229 						if (tlayer.getCell(x, y) != null) {
230 							TiledMapTile tile = tlayer.getCell(x, y).getTile();
231 							if (tile instanceof AnimatedTiledMapTile) {
232 								AnimatedTiledMapTile aTile = (AnimatedTiledMapTile)tile;
233 								for (StaticTiledMapTile t : aTile.getFrameTiles()) {
234 									addTile(t, bucketSize);
235 								}
236 							}
237 							// Adds non-animated tiles and the base animated tile
238 							addTile(tile, bucketSize);
239 						}
240 					}
241 				}
242 			}
243 		}
244 	}
245 
addTile(TiledMapTile tile, int bucketSize)246 	private void addTile (TiledMapTile tile, int bucketSize) {
247 		int tileid = tile.getId() & ~0xE0000000;
248 		String tilesetName = tilesetNameFromTileId(map, tileid);
249 		IntArray usedIds = getUsedIdsBucket(tilesetName, bucketSize);
250 		usedIds.add(tileid);
251 
252 		// track this tileset to be packed if not already tracked
253 		if (!tilesetsToPack.containsKey(tilesetName)) {
254 			tilesetsToPack.put(tilesetName, map.getTileSets().getTileSet(tilesetName));
255 		}
256 	}
257 
tilesetNameFromTileId(TiledMap map, int tileid)258 	private String tilesetNameFromTileId (TiledMap map, int tileid) {
259 		String name = "";
260 		if (tileid == 0) {
261 			return "";
262 		}
263 
264 		for (TiledMapTileSet tileset : map.getTileSets()) {
265 			int firstgid = tileset.getProperties().get("firstgid", -1, Integer.class);
266 			if (firstgid == -1) continue; // skip this tileset
267 			if (tileid >= firstgid) {
268 				name = tileset.getName();
269 			} else {
270 				return name;
271 			}
272 		}
273 
274 		return name;
275 	}
276 
277 	/** Returns the usedIds bucket for the given tileset name. If it doesn't exist one will be created with the specified size if
278 	 * its > 0, else null will be returned.
279 	 *
280 	 * @param size The size to use to create a new bucket if it doesn't exist, else specify 0 or lower to return null instead
281 	 * @return a bucket */
getUsedIdsBucket(String tilesetName, int size)282 	private IntArray getUsedIdsBucket (String tilesetName, int size) {
283 		if (tilesetUsedIds.containsKey(tilesetName)) {
284 			return tilesetUsedIds.get(tilesetName);
285 		}
286 
287 		if (size <= 0) {
288 			return null;
289 		}
290 
291 		IntArray bucket = new IntArray(size);
292 		tilesetUsedIds.put(tilesetName, bucket);
293 		return bucket;
294 	}
295 
296 	/** Traverse the specified tilesets, optionally lookup the used ids and pass every tile image to the {@link TexturePacker},
297 	 * optionally ignoring unused tile ids */
packTilesets(FileHandle inputDirHandle, Settings texturePackerSettings)298 	private void packTilesets (FileHandle inputDirHandle, Settings texturePackerSettings) throws IOException {
299 		BufferedImage tile;
300 		Vector2 tileLocation;
301 		Graphics g;
302 
303 		packer = new TexturePacker(texturePackerSettings);
304 
305 		for (TiledMapTileSet set : tilesetsToPack.values()) {
306 			String tilesetName = set.getName();
307 			System.out.println("Processing tileset " + tilesetName);
308 
309 			IntArray usedIds = this.settings.stripUnusedTiles ? getUsedIdsBucket(tilesetName, -1) : null;
310 
311 			int tileWidth = set.getProperties().get("tilewidth", Integer.class);
312 			int tileHeight = set.getProperties().get("tileheight", Integer.class);
313 			int firstgid = set.getProperties().get("firstgid", Integer.class);
314 			String imageName = set.getProperties().get("imagesource", String.class);
315 
316 			TileSetLayout layout = new TileSetLayout(firstgid, set, inputDirHandle);
317 
318 			for (int gid = layout.firstgid, i = 0; i < layout.numTiles; gid++, i++) {
319 				boolean verbose = this.settings.verbose;
320 
321 				if (usedIds != null && !usedIds.contains(gid)) {
322 					if (verbose) {
323 						System.out.println("Stripped id #" + gid + " from tileset \"" + tilesetName + "\"");
324 					}
325 					continue;
326 				}
327 
328 				tileLocation = layout.getLocation(gid);
329 				tile = new BufferedImage(tileWidth, tileHeight, BufferedImage.TYPE_4BYTE_ABGR);
330 
331 				g = tile.createGraphics();
332 				g.drawImage(layout.image, 0, 0, tileWidth, tileHeight, (int)tileLocation.x, (int)tileLocation.y, (int)tileLocation.x
333 					+ tileWidth, (int)tileLocation.y + tileHeight, null);
334 
335 				if (verbose) {
336 					System.out.println("Adding " + tileWidth + "x" + tileHeight + " (" + (int)tileLocation.x + ", "
337 						+ (int)tileLocation.y + ")");
338 				}
339 				// AtlasTmxMapLoader expects every tileset's index to begin at zero for the first tile in every tileset.
340 				// so the region's adjusted gid is (gid - layout.firstgid). firstgid will be added back in AtlasTmxMapLoader on load
341 				int adjustedGid = gid - layout.firstgid;
342 				final String separator = "_";
343 				String regionName = tilesetName + separator + adjustedGid;
344 
345 				packer.addImage(tile, regionName);
346 			}
347 		}
348 		String tilesetOutputDir = outputDir.toString() + "/" + this.settings.tilesetOutputDirectory;
349 		File relativeTilesetOutputDir = new File(tilesetOutputDir);
350 		File outputDirTilesets = new File(relativeTilesetOutputDir.getCanonicalPath());
351 
352 		outputDirTilesets.mkdirs();
353 		packer.pack(outputDirTilesets, this.settings.atlasOutputName + ".atlas");
354 	}
355 
writeUpdatedTMX(TiledMap tiledMap, FileHandle tmxFileHandle)356 	private void writeUpdatedTMX (TiledMap tiledMap, FileHandle tmxFileHandle) throws IOException {
357 		Document doc;
358 		DocumentBuilder docBuilder;
359 		DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
360 
361 		try {
362 			docBuilder = docFactory.newDocumentBuilder();
363 			doc = docBuilder.parse(tmxFileHandle.read());
364 
365 			Node map = doc.getFirstChild();
366 			while (map.getNodeType() != Node.ELEMENT_NODE || map.getNodeName() != "map") {
367 				if ((map = map.getNextSibling()) == null) {
368 					throw new GdxRuntimeException("Couldn't find map node!");
369 				}
370 			}
371 
372 			setProperty(doc, map, "atlas", settings.tilesetOutputDirectory + "/" + settings.atlasOutputName + ".atlas");
373 
374 			TransformerFactory transformerFactory = TransformerFactory.newInstance();
375 			Transformer transformer = transformerFactory.newTransformer();
376 			DOMSource source = new DOMSource(doc);
377 
378 			outputDir.mkdirs();
379 			StreamResult result = new StreamResult(new File(outputDir, tmxFileHandle.name()));
380 			transformer.transform(source, result);
381 
382 		} catch (ParserConfigurationException e) {
383 			throw new RuntimeException("ParserConfigurationException: " + e.getMessage());
384 		} catch (SAXException e) {
385 			throw new RuntimeException("SAXException: " + e.getMessage());
386 		} catch (TransformerConfigurationException e) {
387 			throw new RuntimeException("TransformerConfigurationException: " + e.getMessage());
388 		} catch (TransformerException e) {
389 			throw new RuntimeException("TransformerException: " + e.getMessage());
390 		}
391 	}
392 
setProperty(Document doc, Node parent, String name, String value)393 	private static void setProperty (Document doc, Node parent, String name, String value) {
394 		Node properties = getFirstChildNodeByName(parent, "properties");
395 		Node property = getFirstChildByNameAttrValue(properties, "property", "name", name);
396 
397 		NamedNodeMap attributes = property.getAttributes();
398 		Node valueNode = attributes.getNamedItem("value");
399 		if (valueNode == null) {
400 			valueNode = doc.createAttribute("value");
401 			valueNode.setNodeValue(value);
402 			attributes.setNamedItem(valueNode);
403 		} else {
404 			valueNode.setNodeValue(value);
405 		}
406 	}
407 
408 	/** If the child node doesn't exist, it is created. */
getFirstChildNodeByName(Node parent, String child)409 	private static Node getFirstChildNodeByName (Node parent, String child) {
410 		NodeList childNodes = parent.getChildNodes();
411 		for (int i = 0; i < childNodes.getLength(); i++) {
412 			if (childNodes.item(i).getNodeName().equals(child)) {
413 				return childNodes.item(i);
414 			}
415 		}
416 
417 		Node newNode = parent.getOwnerDocument().createElement(child);
418 
419 		if (childNodes.item(0) != null)
420 			return parent.insertBefore(newNode, childNodes.item(0));
421 		else
422 			return parent.appendChild(newNode);
423 	}
424 
425 	/** If the child node or attribute doesn't exist, it is created. Usage example: Node property =
426 	 * getFirstChildByAttrValue(properties, "property", "name"); */
getFirstChildByNameAttrValue(Node node, String childName, String attr, String value)427 	private static Node getFirstChildByNameAttrValue (Node node, String childName, String attr, String value) {
428 		NodeList childNodes = node.getChildNodes();
429 		for (int i = 0; i < childNodes.getLength(); i++) {
430 			if (childNodes.item(i).getNodeName().equals(childName)) {
431 				NamedNodeMap attributes = childNodes.item(i).getAttributes();
432 				Node attribute = attributes.getNamedItem(attr);
433 				if (attribute.getNodeValue().equals(value)) return childNodes.item(i);
434 			}
435 		}
436 
437 		Node newNode = node.getOwnerDocument().createElement(childName);
438 		NamedNodeMap attributes = newNode.getAttributes();
439 
440 		Attr nodeAttr = node.getOwnerDocument().createAttribute(attr);
441 		nodeAttr.setNodeValue(value);
442 		attributes.setNamedItem(nodeAttr);
443 
444 		if (childNodes.item(0) != null) {
445 			return node.insertBefore(newNode, childNodes.item(0));
446 		} else {
447 			return node.appendChild(newNode);
448 		}
449 	}
450 
451 	/** Processes a directory of Tile Maps, compressing each tile set contained in any map once.
452 	 *
453 	 * @param args args[0]: the input directory containing the tmx files (and tile sets, relative to the path listed in the tmx
454 	 *           file). args[1]: The output directory for the tmx files, should be empty before running. args[2-4] options */
main(String[] args)455 	public static void main (String[] args) {
456 		final Settings texturePackerSettings = new Settings();
457 		texturePackerSettings.paddingX = 2;
458 		texturePackerSettings.paddingY = 2;
459 		texturePackerSettings.edgePadding = true;
460 		texturePackerSettings.duplicatePadding = true;
461 		texturePackerSettings.bleed = true;
462 		texturePackerSettings.alias = true;
463 		texturePackerSettings.useIndexes = true;
464 
465 		final TiledMapPackerSettings packerSettings = new TiledMapPackerSettings();
466 
467 		if (args.length == 0) {
468 			printUsage();
469 			System.exit(0);
470 		} else if (args.length == 1) {
471 			inputDir = new File(args[0]);
472 			outputDir = new File(inputDir, "../output/");
473 		} else if (args.length == 2) {
474 			inputDir = new File(args[0]);
475 			outputDir = new File(args[1]);
476 		} else {
477 			inputDir = new File(args[0]);
478 			outputDir = new File(args[1]);
479 			processExtraArgs(args, packerSettings);
480 		}
481 
482 		TiledMapPacker packer = new TiledMapPacker(packerSettings);
483 		LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
484 		config.forceExit = false;
485 		config.width = 100;
486 		config.height = 50;
487 		config.title = "TiledMapPacker";
488 		new LwjglApplication(new ApplicationListener() {
489 
490 			@Override
491 			public void resume () {
492 			}
493 
494 			@Override
495 			public void resize (int width, int height) {
496 			}
497 
498 			@Override
499 			public void render () {
500 			}
501 
502 			@Override
503 			public void pause () {
504 			}
505 
506 			@Override
507 			public void dispose () {
508 			}
509 
510 			@Override
511 			public void create () {
512 				TiledMapPacker packer = new TiledMapPacker(packerSettings);
513 
514 				if (!inputDir.exists()) {
515 					System.out.println(inputDir.getAbsolutePath());
516 					throw new RuntimeException("Input directory does not exist: " + inputDir);
517 				}
518 
519 				try {
520 					packer.processInputDir(texturePackerSettings);
521 				} catch (IOException e) {
522 					throw new RuntimeException("Error processing map: " + e.getMessage());
523 				}
524 				System.out.println("Finished processing.");
525 				Gdx.app.exit();
526 			}
527 		}, config);
528 	}
529 
processExtraArgs(String[] args, TiledMapPackerSettings packerSettings)530 	private static void processExtraArgs (String[] args, TiledMapPackerSettings packerSettings) {
531 		String stripUnused = "--strip-unused";
532 		String combineTilesets = "--combine-tilesets";
533 		String verbose = "-v";
534 
535 		int length = args.length - 2;
536 		String[] argsNotDir = new String[length];
537 		System.arraycopy(args, 2, argsNotDir, 0, length);
538 
539 		for (String string : argsNotDir) {
540 			if (stripUnused.equals(string)) {
541 				packerSettings.stripUnusedTiles = true;
542 
543 			} else if (combineTilesets.equals(string)) {
544 				packerSettings.combineTilesets = true;
545 
546 			} else if (verbose.equals(string)) {
547 				packerSettings.verbose = true;
548 
549 			} else {
550 				System.out.println("\nOption \"" + string + "\" not recognized.\n");
551 				printUsage();
552 				System.exit(0);
553 			}
554 		}
555 	}
556 
printUsage()557 	private static void printUsage () {
558 		System.out.println("Usage: INPUTDIR [OUTPUTDIR] [--strip-unused] [--combine-tilesets] [-v]");
559 		System.out.println("Processes a directory of Tiled .tmx maps. Unable to process maps with XML");
560 		System.out.println("tile layer format.");
561 		System.out.println("  --strip-unused             omits all tiles that are not used. Speeds up");
562 		System.out.println("                             the processing. Smaller tilesets.");
563 		System.out.println("  --combine-tilesets         instead of creating a tileset for each map,");
564 		System.out.println("                             this combines the tilesets into some kind");
565 		System.out.println("                             of monster tileset. Has problems with tileset");
566 		System.out.println("                             location. Has problems with nested folders.");
567 		System.out.println("                             Not recommended.");
568 		System.out.println("  -v                         outputs which tiles are stripped and included");
569 		System.out.println();
570 	}
571 
572 	public static class TiledMapPackerSettings {
573 		public boolean stripUnusedTiles = false;
574 		public boolean combineTilesets = false;
575 		public boolean verbose = false;
576 		public String tilesetOutputDirectory = TilesetsOutputDir;
577 		public String atlasOutputName = AtlasOutputName;
578 	}
579 }
580