TMXSceneEncoder.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. #include <string>
  2. #include <zlib.h>
  3. #include "Base64.h"
  4. #include "TMXSceneEncoder.h"
  5. using namespace gameplay;
  6. using namespace tinyxml2;
  7. using std::string;
  8. #ifdef GP_4_SPACE_TABS
  9. #define TAB_STRING(count) string((count) * 4, ' ')
  10. #else
  11. #define TAB_STRING(count) string((count), '\t')
  12. #endif
  13. #define BUFFER_SIZE 256
  14. #ifdef WIN32
  15. #define snprintf(s, n, fmt, ...) sprintf((s), (fmt), __VA_ARGS__)
  16. #endif
  17. TMXSceneEncoder::TMXSceneEncoder() :
  18. _tabCount(0)
  19. {
  20. }
  21. TMXSceneEncoder::~TMXSceneEncoder()
  22. {
  23. }
  24. void TMXSceneEncoder::write(const string& filepath, const EncoderArguments& arguments)
  25. {
  26. XMLDocument xmlDoc;
  27. XMLError err;
  28. if ((err = xmlDoc.LoadFile(filepath.c_str())) != XML_NO_ERROR)
  29. {
  30. LOG(1, "Call to XMLDocument::LoadFile() failed.\n");
  31. LOG(1, "Error returned: %d\n\n", err);
  32. return;
  33. }
  34. // Parse the Tiled map
  35. string outputFilePath = arguments.getOutputFilePath();
  36. int pos = outputFilePath.find_last_of('/');
  37. string outputDirectory = (pos == -1 ? outputFilePath : outputFilePath.substr(0, pos));
  38. TMXMap map;
  39. if (!parseTmx(xmlDoc, map, outputDirectory))
  40. {
  41. return;
  42. }
  43. //XXX arguments.generateTextureGutter()
  44. // Write the tile map
  45. string fileName = arguments.getFileName();
  46. pos = fileName.find_last_of('.');
  47. writeScene(map, outputFilePath, (pos == -1 ? fileName : fileName.substr(0, pos)));
  48. }
  49. bool TMXSceneEncoder::parseTmx(const XMLDocument& xmlDoc, TMXMap& map, const std::string& outputDirectory) const
  50. {
  51. auto xmlMap = xmlDoc.FirstChildElement("map");
  52. if (!xmlMap)
  53. {
  54. LOG(1, "Missing root <map> element.\n");
  55. return false;
  56. }
  57. // Read in the map values //XXX should compact this so XML attribute parsing is a little nicer
  58. unsigned int uiValue;
  59. auto attValue = xmlMap->Attribute("width");
  60. sscanf(attValue, "%u", &uiValue);
  61. map.setWidth(uiValue);
  62. attValue = xmlMap->Attribute("height");
  63. sscanf(attValue, "%u", &uiValue);
  64. map.setHeight(uiValue);
  65. float fValue;
  66. attValue = xmlMap->Attribute("tilewidth");
  67. sscanf(attValue, "%f", &fValue);
  68. map.setTileWidth(fValue);
  69. attValue = xmlMap->Attribute("tileheight");
  70. sscanf(attValue, "%f", &fValue);
  71. map.setTileHeight(fValue);
  72. // Now we load all tilesets
  73. auto xmlTileSet = xmlMap->FirstChildElement("tileset");
  74. while (xmlTileSet)
  75. {
  76. TMXTileSet tileSet;
  77. attValue = xmlTileSet->Attribute("firstgid");
  78. sscanf(attValue, "%u", &uiValue);
  79. tileSet.setFirstGid(uiValue);
  80. XMLDocument sourceXmlDoc;
  81. const XMLElement* xmlTileSetToLoad;
  82. attValue = xmlTileSet->Attribute("source");
  83. if (attValue)
  84. {
  85. string tsxLocation = outputDirectory + "/" + attValue;
  86. tsxLocation = EncoderArguments::getRealPath(tsxLocation);
  87. XMLError err;
  88. if ((err = sourceXmlDoc.LoadFile(tsxLocation.c_str())) != XML_NO_ERROR)
  89. {
  90. LOG(1, "Could not load tileset's source TSX.\n");
  91. return false;
  92. }
  93. xmlTileSetToLoad = sourceXmlDoc.RootElement();
  94. }
  95. else
  96. {
  97. xmlTileSetToLoad = xmlTileSet;
  98. }
  99. // Maximum tile size
  100. attValue = xmlTileSetToLoad->Attribute("tilewidth");
  101. if (attValue)
  102. {
  103. sscanf(attValue, "%u", &uiValue);
  104. tileSet.setMaxTileWidth(uiValue);
  105. }
  106. else
  107. {
  108. tileSet.setMaxTileWidth(map.getTileWidth());
  109. }
  110. attValue = xmlTileSetToLoad->Attribute("tileheight");
  111. if (attValue)
  112. {
  113. sscanf(attValue, "%u", &uiValue);
  114. tileSet.setMaxTileHeight(uiValue);
  115. }
  116. else
  117. {
  118. tileSet.setMaxTileHeight(map.getTileHeight());
  119. }
  120. // Spacing and margin
  121. attValue = xmlTileSetToLoad->Attribute("spacing");
  122. if (attValue)
  123. {
  124. sscanf(attValue, "%u", &uiValue);
  125. tileSet.setSpacing(uiValue);
  126. }
  127. attValue = xmlTileSetToLoad->Attribute("margin");
  128. if (attValue)
  129. {
  130. sscanf(attValue, "%u", &uiValue);
  131. tileSet.setMargin(uiValue);
  132. }
  133. // Tile offset
  134. auto xmlTileOffset = xmlTileSetToLoad->FirstChildElement("tileoffset");
  135. if (xmlTileOffset)
  136. {
  137. Vector2 offset;
  138. int iValue;
  139. attValue = xmlTileOffset->Attribute("x");
  140. sscanf(attValue, "%d", &iValue);
  141. offset.x = iValue;
  142. attValue = xmlTileOffset->Attribute("y");
  143. sscanf(attValue, "%d", &iValue);
  144. offset.y = iValue;
  145. tileSet.setOffset(offset);
  146. }
  147. // Load image source. Don't worry about <data>, trans, or height
  148. auto xmlTileSetImage = xmlTileSetToLoad->FirstChildElement("image");
  149. tileSet.setImagePath(xmlTileSetImage->Attribute("source"));
  150. // We only care about image width...
  151. attValue = xmlTileSetImage->Attribute("width");
  152. if (attValue)
  153. {
  154. sscanf(attValue, "%u", &uiValue);
  155. tileSet.setImageWidth(uiValue);
  156. }
  157. else
  158. {
  159. // Load the image itself to get the width
  160. string imageLocation = outputDirectory + "/" + tileSet.getImagePath();
  161. imageLocation = EncoderArguments::getRealPath(imageLocation);
  162. int pos = imageLocation.find_last_of('.');
  163. if (imageLocation.substr(pos).compare(".png") != 0)
  164. {
  165. LOG(1, "TileSet image must be a PNG. %s\n", imageLocation.c_str());
  166. return false;
  167. }
  168. Image* img = Image::create(imageLocation.c_str());
  169. if (!img)
  170. {
  171. LOG(1, "Could not load TileSet image. %s\n", imageLocation.c_str());
  172. return false;
  173. }
  174. tileSet.setImageWidth(img->getWidth());
  175. SAFE_DELETE(img);
  176. }
  177. //TODO: tiles (specifically, tile animations)
  178. //<tile id="relId"><animation><frame tileid="relId" duration="numInMS"></...></...></...>
  179. // Save the tileset
  180. map.addTileSet(tileSet);
  181. xmlTileSet = xmlTileSet->NextSiblingElement("tileset");
  182. }
  183. // Finally we load the layers
  184. auto xmlLayer = xmlMap->FirstChildElement("layer");
  185. while (xmlLayer)
  186. {
  187. TMXLayer layer;
  188. //XXX what if name doesn't exist?
  189. layer.setName(xmlLayer->Attribute("name"));
  190. // Load properties
  191. attValue = xmlLayer->Attribute("width");
  192. if (attValue)
  193. {
  194. sscanf(attValue, "%u", &uiValue);
  195. layer.setWidth(uiValue);
  196. }
  197. else
  198. {
  199. layer.setWidth(map.getWidth());
  200. }
  201. attValue = xmlLayer->Attribute("height");
  202. if (attValue)
  203. {
  204. sscanf(attValue, "%u", &uiValue);
  205. layer.setHeight(uiValue);
  206. }
  207. else
  208. {
  209. layer.setHeight(map.getHeight());
  210. }
  211. attValue = xmlLayer->Attribute("opacity");
  212. if (attValue)
  213. {
  214. sscanf(attValue, "%f", &fValue);
  215. layer.setOpacity(fValue);
  216. }
  217. attValue = xmlLayer->Attribute("visible");
  218. if (attValue)
  219. {
  220. sscanf(attValue, "%u", &uiValue);
  221. layer.setVisible(uiValue == 1);
  222. }
  223. // Load tiles
  224. layer.setupTiles();
  225. auto data = loadDataElement(xmlLayer->FirstChildElement("data"));
  226. auto data_size = data.size();
  227. for (int i = 0; i < data_size; i++)
  228. {
  229. //XXX this might depend on map's renderorder
  230. auto x = i % layer.getWidth();
  231. auto y = i / layer.getWidth();
  232. layer.setTile(x, y, data[i]);
  233. }
  234. // Save layer
  235. map.addLayer(layer);
  236. xmlLayer = xmlLayer->NextSiblingElement("layer");
  237. }
  238. //TODO: imagelayer (a kind of individual sprite)
  239. return true;
  240. }
  241. //XXX could probably seperate the writing process to a seperate class (PropertyWriter...)
  242. #define WRITE_PROPERTY_BLOCK_START(str) writeLine(file, (str)); \
  243. writeLine(file, "{"); \
  244. _tabCount++
  245. #define WRITE_PROPERTY_BLOCK_END() _tabCount--; \
  246. writeLine(file, "}")
  247. #define WRITE_PROPERTY_BLOCK_VALUE(name, value) writeLine(file, string(name) + " = " + (value))
  248. #define WRITE_PROPERTY_DIRECT(value) writeLine(file, (value))
  249. #define WRITE_PROPERTY_NEWLINE() file << std::endl
  250. void TMXSceneEncoder::writeScene(const TMXMap& map, const string& outputFilepath, const string& sceneName)
  251. {
  252. // Prepare for writing the scene
  253. std::ofstream file(outputFilepath.c_str(), std::ofstream::out | std::ofstream::trunc);
  254. auto tilesetCount = map.getLayerCount();
  255. bool singleLayer = tilesetCount <= 1;
  256. //XXX should really build an ordered list, and draw based on that (for z-depth)
  257. // Write initial scene
  258. WRITE_PROPERTY_BLOCK_START("scene " + sceneName);
  259. if (!singleLayer)
  260. {
  261. WRITE_PROPERTY_BLOCK_START("node all_tilesets"); //XXX what if there are sprites between tilesets?
  262. }
  263. // Write all tilesets
  264. for (auto i = 0u; i < tilesetCount; i++)
  265. {
  266. writeTileset(map, i, file);
  267. WRITE_PROPERTY_NEWLINE();
  268. }
  269. if (!singleLayer)
  270. {
  271. WRITE_PROPERTY_BLOCK_END(); // node tilesets
  272. }
  273. //TODO: other sprites
  274. WRITE_PROPERTY_BLOCK_END(); // scene
  275. // Cleanup
  276. file.flush();
  277. file.close();
  278. }
  279. // This is actually a misnomer. What is a Layer in Tiled/TMX is a TileSet for GamePlay3d. TileSet in Tiled/TMX is something different.
  280. void TMXSceneEncoder::writeTileset(const TMXMap& map, unsigned int tileSetIndex, std::ofstream& file)
  281. {
  282. auto tileset = map.getLayer(tileSetIndex);
  283. if (!tileset.hasTiles())
  284. {
  285. return;
  286. }
  287. auto tilesets = tileset.getTilesetsUsed(map);
  288. if (tilesets.size() == 0)
  289. {
  290. return;
  291. }
  292. char buffer[BUFFER_SIZE];
  293. WRITE_PROPERTY_BLOCK_START("node " + tileset.getName());
  294. if (tilesets.size() > 1)
  295. {
  296. auto i = 0u;
  297. for (auto it = tilesets.begin(); it != tilesets.end(); it++, i++)
  298. {
  299. snprintf(buffer, BUFFER_SIZE, "node tileset_%d", i);
  300. WRITE_PROPERTY_BLOCK_START(buffer);
  301. auto tmxTileset = map.getTileSet(*it);
  302. writeSoloTileset(map, tmxTileset, tileset, file, *it);
  303. auto tileOffset = tmxTileset.getOffset();
  304. if (!(tileOffset == Vector2::zero()))
  305. {
  306. // Tile offset moves the tiles, not the origin of each tile
  307. snprintf(buffer, BUFFER_SIZE, "translate = %d, %d, 0", static_cast<int>(tileOffset.x), static_cast<int>(tileOffset.y));
  308. WRITE_PROPERTY_NEWLINE();
  309. WRITE_PROPERTY_DIRECT(buffer);
  310. }
  311. WRITE_PROPERTY_BLOCK_END();
  312. if ((i + 1) != tilesets.size())
  313. {
  314. WRITE_PROPERTY_NEWLINE();
  315. }
  316. }
  317. }
  318. else
  319. {
  320. auto tmxTileset = map.getTileSet(*(tilesets.begin()));
  321. writeSoloTileset(map, tmxTileset, tileset, file);
  322. auto tileOffset = tmxTileset.getOffset();
  323. if (!(tileOffset == Vector2::zero()))
  324. {
  325. // Tile offset moves the tiles, not the origin of each tile
  326. snprintf(buffer, BUFFER_SIZE, "translate = %d, %d, 0", static_cast<int>(tileOffset.x), static_cast<int>(tileOffset.y));
  327. WRITE_PROPERTY_NEWLINE();
  328. WRITE_PROPERTY_DIRECT(buffer);
  329. }
  330. }
  331. if (!tileset.getVisible())
  332. {
  333. // Default is true, so only write it false
  334. WRITE_PROPERTY_NEWLINE();
  335. WRITE_PROPERTY_DIRECT("enabled = false");
  336. }
  337. WRITE_PROPERTY_BLOCK_END();
  338. }
  339. void TMXSceneEncoder::writeSoloTileset(const TMXMap& map, const gameplay::TMXTileSet& tmxTileset, const TMXLayer& tileset, std::ofstream& file, unsigned int resultOnlyForTileset)
  340. {
  341. WRITE_PROPERTY_BLOCK_START("tileset");
  342. // Write tile path
  343. WRITE_PROPERTY_BLOCK_VALUE("path", tmxTileset.getImagePath());
  344. WRITE_PROPERTY_NEWLINE();
  345. // Write tile size
  346. //XXX if tile sizes are incorrect, make sure to update TMXLayer::getTileStart too
  347. char buffer[BUFFER_SIZE];
  348. snprintf(buffer, BUFFER_SIZE, "tileWidth = %u", tmxTileset.getMaxTileWidth());
  349. WRITE_PROPERTY_DIRECT(buffer);
  350. snprintf(buffer, BUFFER_SIZE, "tileHeight = %u", tmxTileset.getMaxTileHeight());
  351. WRITE_PROPERTY_DIRECT(buffer);
  352. // Write tileset size
  353. snprintf(buffer, BUFFER_SIZE, "columns = %u", tileset.getWidth());
  354. WRITE_PROPERTY_DIRECT(buffer);
  355. snprintf(buffer, BUFFER_SIZE, "rows = %u", tileset.getHeight());
  356. WRITE_PROPERTY_DIRECT(buffer);
  357. WRITE_PROPERTY_NEWLINE();
  358. // Write opacity
  359. if (tileset.getOpacity() < 1.0f)
  360. {
  361. snprintf(buffer, BUFFER_SIZE, "opacity = %f", tileset.getOpacity());
  362. WRITE_PROPERTY_DIRECT(buffer);
  363. WRITE_PROPERTY_NEWLINE();
  364. }
  365. // Write tiles
  366. auto tileset_height = tileset.getHeight();
  367. auto tileset_width = tileset.getWidth();
  368. for (auto y = 0u; y < tileset_height; y++)
  369. {
  370. bool tilesWritten = false;
  371. for (auto x = 0u; x < tileset_width; x++)
  372. {
  373. Vector2 startPos = tileset.getTileStart(x, y, map, resultOnlyForTileset);
  374. if (startPos.x < 0 || startPos.y < 0)
  375. {
  376. continue;
  377. }
  378. tilesWritten = true;
  379. WRITE_PROPERTY_BLOCK_START("tile");
  380. snprintf(buffer, BUFFER_SIZE, "cell = %u, %u", x, y);
  381. WRITE_PROPERTY_DIRECT(buffer);
  382. snprintf(buffer, BUFFER_SIZE, "source = %u, %u", static_cast<unsigned int>(startPos.x), static_cast<unsigned int>(startPos.y));
  383. WRITE_PROPERTY_DIRECT(buffer);
  384. WRITE_PROPERTY_BLOCK_END();
  385. }
  386. if (tilesWritten && ((y + 1) != tileset_height))
  387. {
  388. WRITE_PROPERTY_NEWLINE();
  389. }
  390. }
  391. WRITE_PROPERTY_BLOCK_END();
  392. }
  393. #undef WRITE_PROPERTY_NEWLINE
  394. #undef WRITE_PROPERTY_DIRECT
  395. #undef WRITE_PROPERTY_BLOCK_VALUE
  396. #undef WRITE_PROPERTY_BLOCK_END
  397. #undef WRITE_PROPERTY_BLOCK_START
  398. void TMXSceneEncoder::writeLine(std::ofstream& file, const string& line) const
  399. {
  400. file << TAB_STRING(_tabCount) << line << std::endl;
  401. }
  402. bool isBase64(char c)
  403. {
  404. return (c >= 'a' && c <= 'z') ||
  405. (c >= 'A' && c <= 'Z') ||
  406. (c >= '0' && c <= '9') ||
  407. (c == '=');
  408. }
  409. std::vector<unsigned int> TMXSceneEncoder::loadDataElement(const XMLElement* data)
  410. {
  411. if (!data)
  412. {
  413. return std::vector<unsigned int>();
  414. }
  415. const char* encoding = data->Attribute("encoding");
  416. const char* compression = data->Attribute("compression");
  417. const char* attValue = "0";
  418. unsigned int tileGid;
  419. std::vector<unsigned int> tileData;
  420. if (!compression && !encoding)
  421. {
  422. auto xmlTile = data->FirstChildElement("tile");
  423. while (xmlTile)
  424. {
  425. attValue = xmlTile->Attribute("gid");
  426. sscanf(attValue, "%u", &tileGid);
  427. tileData.push_back(tileGid);
  428. xmlTile = xmlTile->NextSiblingElement("tile");
  429. }
  430. }
  431. else
  432. {
  433. if (strcmp(encoding, "csv") == 0)
  434. {
  435. if (compression)
  436. {
  437. LOG(1, "Compression is not supported with CSV encoding.\n");
  438. exit(-1);
  439. }
  440. auto rawCsvData = data->GetText();
  441. int start = 0;
  442. char* endptr;
  443. // Skip everything before values
  444. while (rawCsvData[start] == ' ' || rawCsvData[start] == '\n' || rawCsvData[start] == '\r' || rawCsvData[start] == '\t')
  445. {
  446. start++;
  447. }
  448. // Iterate through values. Skipping to next value when done
  449. while (rawCsvData[start])
  450. {
  451. tileData.push_back(strtoul(rawCsvData + start, &endptr, 10));
  452. start = endptr - rawCsvData;
  453. while (rawCsvData[start] == ' ' || rawCsvData[start] == ',' || rawCsvData[start] == '\n' || rawCsvData[start] == '\r' || rawCsvData[start] == '\t')
  454. {
  455. start++;
  456. }
  457. }
  458. }
  459. else if (strcmp(encoding, "base64") == 0)
  460. {
  461. string base64Data = data->GetText();
  462. if (base64Data.size() > 0 && (!isBase64(base64Data[0]) || !isBase64(base64Data[base64Data.size() - 1])))
  463. {
  464. int start = 0;
  465. auto end = base64Data.size() - 1;
  466. while (!isBase64(base64Data[start]))
  467. {
  468. start++;
  469. }
  470. while (!isBase64(base64Data[end]))
  471. {
  472. end--;
  473. }
  474. base64Data = base64Data.substr(start, end - start + 1);
  475. }
  476. auto byteData = base64_decode(base64Data);
  477. if (compression)
  478. {
  479. int err;
  480. const unsigned int buffer_size = 4096;
  481. char buffer[buffer_size];
  482. string decomData;
  483. if (strcmp(compression, "zlib") == 0 || strcmp(compression, "gzip") == 0)
  484. {
  485. z_stream d_stream;
  486. d_stream.zalloc = Z_NULL;
  487. d_stream.zfree = Z_NULL;
  488. d_stream.opaque = Z_NULL;
  489. d_stream.next_in = (Bytef*)byteData.c_str();
  490. d_stream.avail_in = byteData.size();
  491. if (strcmp(compression, "zlib") == 0)
  492. {
  493. // zlib
  494. if ((err = inflateInit(&d_stream)) != Z_OK)
  495. {
  496. LOG(1, "ZLIB inflateInit failed. Error: %d.\n", err);
  497. exit(-1);
  498. }
  499. }
  500. else
  501. {
  502. // gzip
  503. if ((err = inflateInit2(&d_stream, 16+MAX_WBITS)) != Z_OK)
  504. {
  505. LOG(1, "ZLIB inflateInit2 failed. Error: %d.\n", err);
  506. exit(-1);
  507. }
  508. }
  509. do
  510. {
  511. d_stream.next_out = (Bytef*)buffer;
  512. d_stream.avail_out = buffer_size;
  513. err = inflate(&d_stream, Z_NO_FLUSH);
  514. switch (err)
  515. {
  516. case Z_NEED_DICT:
  517. case Z_DATA_ERROR:
  518. case Z_MEM_ERROR:
  519. inflateEnd(&d_stream);
  520. LOG(1, "ZLIB inflate failed. Error: %d.\n", err);
  521. exit(-1);
  522. break;
  523. }
  524. string decomBlock(buffer, buffer_size - d_stream.avail_out);
  525. decomData += decomBlock;
  526. } while (err != Z_STREAM_END);
  527. inflateEnd(&d_stream);
  528. }
  529. else
  530. {
  531. LOG(1, "Unknown compression: %s.\n", compression);
  532. exit(-1);
  533. }
  534. byteData = decomData;
  535. }
  536. auto byteDataSize = byteData.size();
  537. for (unsigned int i = 0; i < byteDataSize; i += 4)
  538. {
  539. unsigned int gid = static_cast<unsigned char>(byteData[i + 0]) |
  540. (static_cast<unsigned char>(byteData[i + 1]) << 8u) |
  541. (static_cast<unsigned char>(byteData[i + 2]) << 16u) |
  542. (static_cast<unsigned char>(byteData[i + 3]) << 24u);
  543. tileData.push_back(gid);
  544. }
  545. }
  546. else
  547. {
  548. LOG(1, "Unknown encoding: %s.\n", encoding);
  549. exit(-1);
  550. }
  551. }
  552. return tileData;
  553. }