| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642 |
- #include <string>
- #include <zlib.h>
- #include "Base64.h"
- #include "TMXSceneEncoder.h"
- using namespace gameplay;
- using namespace tinyxml2;
- using std::string;
- #ifdef GP_4_SPACE_TABS
- #define TAB_STRING(count) string((count) * 4, ' ')
- #else
- #define TAB_STRING(count) string((count), '\t')
- #endif
- #define BUFFER_SIZE 256
- #ifdef WIN32
- #define snprintf(s, n, fmt, ...) sprintf((s), (fmt), __VA_ARGS__)
- #endif
- TMXSceneEncoder::TMXSceneEncoder() :
- _tabCount(0)
- {
- }
- TMXSceneEncoder::~TMXSceneEncoder()
- {
- }
- void TMXSceneEncoder::write(const string& filepath, const EncoderArguments& arguments)
- {
- XMLDocument xmlDoc;
- XMLError err;
- if ((err = xmlDoc.LoadFile(filepath.c_str())) != XML_NO_ERROR)
- {
- LOG(1, "Call to XMLDocument::LoadFile() failed.\n");
- LOG(1, "Error returned: %d\n\n", err);
- return;
- }
-
- // Parse the Tiled map
- string outputFilePath = arguments.getOutputFilePath();
- int pos = outputFilePath.find_last_of('/');
- string outputDirectory = (pos == -1 ? outputFilePath : outputFilePath.substr(0, pos));
- TMXMap map;
- if (!parseTmx(xmlDoc, map, outputDirectory))
- {
- return;
- }
- //XXX arguments.generateTextureGutter()
- // Write the tile map
- string fileName = arguments.getFileName();
- pos = fileName.find_last_of('.');
- writeScene(map, outputFilePath, (pos == -1 ? fileName : fileName.substr(0, pos)));
- }
- bool TMXSceneEncoder::parseTmx(const XMLDocument& xmlDoc, TMXMap& map, const std::string& outputDirectory) const
- {
- auto xmlMap = xmlDoc.FirstChildElement("map");
- if (!xmlMap)
- {
- LOG(1, "Missing root <map> element.\n");
- return false;
- }
- // Read in the map values //XXX should compact this so XML attribute parsing is a little nicer
- unsigned int uiValue;
- auto attValue = xmlMap->Attribute("width");
- sscanf(attValue, "%u", &uiValue);
- map.setWidth(uiValue);
- attValue = xmlMap->Attribute("height");
- sscanf(attValue, "%u", &uiValue);
- map.setHeight(uiValue);
- float fValue;
- attValue = xmlMap->Attribute("tilewidth");
- sscanf(attValue, "%f", &fValue);
- map.setTileWidth(fValue);
- attValue = xmlMap->Attribute("tileheight");
- sscanf(attValue, "%f", &fValue);
- map.setTileHeight(fValue);
- // Now we load all tilesets
- auto xmlTileSet = xmlMap->FirstChildElement("tileset");
- while (xmlTileSet)
- {
- TMXTileSet tileSet;
- attValue = xmlTileSet->Attribute("firstgid");
- sscanf(attValue, "%u", &uiValue);
- tileSet.setFirstGid(uiValue);
- XMLDocument sourceXmlDoc;
- const XMLElement* xmlTileSetToLoad;
- attValue = xmlTileSet->Attribute("source");
- if (attValue)
- {
- string tsxLocation = outputDirectory + "/" + attValue;
- tsxLocation = EncoderArguments::getRealPath(tsxLocation);
- XMLError err;
- if ((err = sourceXmlDoc.LoadFile(tsxLocation.c_str())) != XML_NO_ERROR)
- {
- LOG(1, "Could not load tileset's source TSX.\n");
- return false;
- }
- xmlTileSetToLoad = sourceXmlDoc.RootElement();
- }
- else
- {
- xmlTileSetToLoad = xmlTileSet;
- }
- // Maximum tile size
- attValue = xmlTileSetToLoad->Attribute("tilewidth");
- if (attValue)
- {
- sscanf(attValue, "%u", &uiValue);
- tileSet.setMaxTileWidth(uiValue);
- }
- else
- {
- tileSet.setMaxTileWidth(map.getTileWidth());
- }
- attValue = xmlTileSetToLoad->Attribute("tileheight");
- if (attValue)
- {
- sscanf(attValue, "%u", &uiValue);
- tileSet.setMaxTileHeight(uiValue);
- }
- else
- {
- tileSet.setMaxTileHeight(map.getTileHeight());
- }
- // Spacing and margin
- attValue = xmlTileSetToLoad->Attribute("spacing");
- if (attValue)
- {
- sscanf(attValue, "%u", &uiValue);
- tileSet.setSpacing(uiValue);
- }
- attValue = xmlTileSetToLoad->Attribute("margin");
- if (attValue)
- {
- sscanf(attValue, "%u", &uiValue);
- tileSet.setMargin(uiValue);
- }
- // Tile offset
- auto xmlTileOffset = xmlTileSetToLoad->FirstChildElement("tileoffset");
- if (xmlTileOffset)
- {
- Vector2 offset;
- int iValue;
- attValue = xmlTileOffset->Attribute("x");
- sscanf(attValue, "%d", &iValue);
- offset.x = iValue;
- attValue = xmlTileOffset->Attribute("y");
- sscanf(attValue, "%d", &iValue);
- offset.y = iValue;
- tileSet.setOffset(offset);
- }
- // Load image source. Don't worry about <data>, trans, or height
- auto xmlTileSetImage = xmlTileSetToLoad->FirstChildElement("image");
- tileSet.setImagePath(xmlTileSetImage->Attribute("source"));
- // We only care about image width...
- attValue = xmlTileSetImage->Attribute("width");
- if (attValue)
- {
- sscanf(attValue, "%u", &uiValue);
- tileSet.setImageWidth(uiValue);
- }
- else
- {
- // Load the image itself to get the width
- string imageLocation = outputDirectory + "/" + tileSet.getImagePath();
- imageLocation = EncoderArguments::getRealPath(imageLocation);
- int pos = imageLocation.find_last_of('.');
- if (imageLocation.substr(pos).compare(".png") != 0)
- {
- LOG(1, "TileSet image must be a PNG. %s\n", imageLocation.c_str());
- return false;
- }
- Image* img = Image::create(imageLocation.c_str());
- if (!img)
- {
- LOG(1, "Could not load TileSet image. %s\n", imageLocation.c_str());
- return false;
- }
- tileSet.setImageWidth(img->getWidth());
- SAFE_DELETE(img);
- }
- //TODO: tiles (specifically, tile animations)
- //<tile id="relId"><animation><frame tileid="relId" duration="numInMS"></...></...></...>
- // Save the tileset
- map.addTileSet(tileSet);
- xmlTileSet = xmlTileSet->NextSiblingElement("tileset");
- }
- // Finally we load the layers
- auto xmlLayer = xmlMap->FirstChildElement("layer");
- while (xmlLayer)
- {
- TMXLayer layer;
- //XXX what if name doesn't exist?
- layer.setName(xmlLayer->Attribute("name"));
- // Load properties
- attValue = xmlLayer->Attribute("width");
- if (attValue)
- {
- sscanf(attValue, "%u", &uiValue);
- layer.setWidth(uiValue);
- }
- else
- {
- layer.setWidth(map.getWidth());
- }
- attValue = xmlLayer->Attribute("height");
- if (attValue)
- {
- sscanf(attValue, "%u", &uiValue);
- layer.setHeight(uiValue);
- }
- else
- {
- layer.setHeight(map.getHeight());
- }
- attValue = xmlLayer->Attribute("opacity");
- if (attValue)
- {
- sscanf(attValue, "%f", &fValue);
- layer.setOpacity(fValue);
- }
- attValue = xmlLayer->Attribute("visible");
- if (attValue)
- {
- sscanf(attValue, "%u", &uiValue);
- layer.setVisible(uiValue == 1);
- }
- // Load tiles
- layer.setupTiles();
- auto data = loadDataElement(xmlLayer->FirstChildElement("data"));
- auto data_size = data.size();
- for (int i = 0; i < data_size; i++)
- {
- //XXX this might depend on map's renderorder
- auto x = i % layer.getWidth();
- auto y = i / layer.getWidth();
- layer.setTile(x, y, data[i]);
- }
- // Save layer
- map.addLayer(layer);
- xmlLayer = xmlLayer->NextSiblingElement("layer");
- }
- //TODO: imagelayer (a kind of individual sprite)
- return true;
- }
- //XXX could probably seperate the writing process to a seperate class (PropertyWriter...)
- #define WRITE_PROPERTY_BLOCK_START(str) writeLine(file, (str)); \
- writeLine(file, "{"); \
- _tabCount++
- #define WRITE_PROPERTY_BLOCK_END() _tabCount--; \
- writeLine(file, "}")
- #define WRITE_PROPERTY_BLOCK_VALUE(name, value) writeLine(file, string(name) + " = " + (value))
- #define WRITE_PROPERTY_DIRECT(value) writeLine(file, (value))
- #define WRITE_PROPERTY_NEWLINE() file << std::endl
- void TMXSceneEncoder::writeScene(const TMXMap& map, const string& outputFilepath, const string& sceneName)
- {
- // Prepare for writing the scene
- std::ofstream file(outputFilepath.c_str(), std::ofstream::out | std::ofstream::trunc);
- auto tilesetCount = map.getLayerCount();
- bool singleLayer = tilesetCount <= 1;
- //XXX should really build an ordered list, and draw based on that (for z-depth)
- // Write initial scene
- WRITE_PROPERTY_BLOCK_START("scene " + sceneName);
- if (!singleLayer)
- {
- WRITE_PROPERTY_BLOCK_START("node all_tilesets"); //XXX what if there are sprites between tilesets?
- }
- // Write all tilesets
- for (auto i = 0u; i < tilesetCount; i++)
- {
- writeTileset(map, i, file);
- WRITE_PROPERTY_NEWLINE();
- }
- if (!singleLayer)
- {
- WRITE_PROPERTY_BLOCK_END(); // node tilesets
- }
- //TODO: other sprites
- WRITE_PROPERTY_BLOCK_END(); // scene
- // Cleanup
- file.flush();
- file.close();
- }
- // This is actually a misnomer. What is a Layer in Tiled/TMX is a TileSet for GamePlay3d. TileSet in Tiled/TMX is something different.
- void TMXSceneEncoder::writeTileset(const TMXMap& map, unsigned int tileSetIndex, std::ofstream& file)
- {
- auto tileset = map.getLayer(tileSetIndex);
- if (!tileset.hasTiles())
- {
- return;
- }
- auto tilesets = tileset.getTilesetsUsed(map);
- if (tilesets.size() == 0)
- {
- return;
- }
- char buffer[BUFFER_SIZE];
- WRITE_PROPERTY_BLOCK_START("node " + tileset.getName());
- if (tilesets.size() > 1)
- {
- auto i = 0u;
- for (auto it = tilesets.begin(); it != tilesets.end(); it++, i++)
- {
- snprintf(buffer, BUFFER_SIZE, "node tileset_%d", i);
- WRITE_PROPERTY_BLOCK_START(buffer);
- auto tmxTileset = map.getTileSet(*it);
- writeSoloTileset(map, tmxTileset, tileset, file, *it);
- auto tileOffset = tmxTileset.getOffset();
- if (!(tileOffset == Vector2::zero()))
- {
- // Tile offset moves the tiles, not the origin of each tile
- snprintf(buffer, BUFFER_SIZE, "translate = %d, %d, 0", static_cast<int>(tileOffset.x), static_cast<int>(tileOffset.y));
- WRITE_PROPERTY_NEWLINE();
- WRITE_PROPERTY_DIRECT(buffer);
- }
- WRITE_PROPERTY_BLOCK_END();
- if ((i + 1) != tilesets.size())
- {
- WRITE_PROPERTY_NEWLINE();
- }
- }
- }
- else
- {
- auto tmxTileset = map.getTileSet(*(tilesets.begin()));
- writeSoloTileset(map, tmxTileset, tileset, file);
- auto tileOffset = tmxTileset.getOffset();
- if (!(tileOffset == Vector2::zero()))
- {
- // Tile offset moves the tiles, not the origin of each tile
- snprintf(buffer, BUFFER_SIZE, "translate = %d, %d, 0", static_cast<int>(tileOffset.x), static_cast<int>(tileOffset.y));
- WRITE_PROPERTY_NEWLINE();
- WRITE_PROPERTY_DIRECT(buffer);
- }
- }
- if (!tileset.getVisible())
- {
- // Default is true, so only write it false
- WRITE_PROPERTY_NEWLINE();
- WRITE_PROPERTY_DIRECT("enabled = false");
- }
- WRITE_PROPERTY_BLOCK_END();
- }
- void TMXSceneEncoder::writeSoloTileset(const TMXMap& map, const gameplay::TMXTileSet& tmxTileset, const TMXLayer& tileset, std::ofstream& file, unsigned int resultOnlyForTileset)
- {
- WRITE_PROPERTY_BLOCK_START("tileset");
- // Write tile path
- WRITE_PROPERTY_BLOCK_VALUE("path", tmxTileset.getImagePath());
- WRITE_PROPERTY_NEWLINE();
- // Write tile size
- //XXX if tile sizes are incorrect, make sure to update TMXLayer::getTileStart too
- char buffer[BUFFER_SIZE];
- snprintf(buffer, BUFFER_SIZE, "tileWidth = %u", tmxTileset.getMaxTileWidth());
- WRITE_PROPERTY_DIRECT(buffer);
- snprintf(buffer, BUFFER_SIZE, "tileHeight = %u", tmxTileset.getMaxTileHeight());
- WRITE_PROPERTY_DIRECT(buffer);
- // Write tileset size
- snprintf(buffer, BUFFER_SIZE, "columns = %u", tileset.getWidth());
- WRITE_PROPERTY_DIRECT(buffer);
- snprintf(buffer, BUFFER_SIZE, "rows = %u", tileset.getHeight());
- WRITE_PROPERTY_DIRECT(buffer);
- WRITE_PROPERTY_NEWLINE();
- // Write opacity
- if (tileset.getOpacity() < 1.0f)
- {
- snprintf(buffer, BUFFER_SIZE, "opacity = %f", tileset.getOpacity());
- WRITE_PROPERTY_DIRECT(buffer);
- WRITE_PROPERTY_NEWLINE();
- }
- // Write tiles
- auto tileset_height = tileset.getHeight();
- auto tileset_width = tileset.getWidth();
- for (auto y = 0u; y < tileset_height; y++)
- {
- bool tilesWritten = false;
- for (auto x = 0u; x < tileset_width; x++)
- {
- Vector2 startPos = tileset.getTileStart(x, y, map, resultOnlyForTileset);
- if (startPos.x < 0 || startPos.y < 0)
- {
- continue;
- }
- tilesWritten = true;
- WRITE_PROPERTY_BLOCK_START("tile");
- snprintf(buffer, BUFFER_SIZE, "cell = %u, %u", x, y);
- WRITE_PROPERTY_DIRECT(buffer);
- snprintf(buffer, BUFFER_SIZE, "source = %u, %u", static_cast<unsigned int>(startPos.x), static_cast<unsigned int>(startPos.y));
- WRITE_PROPERTY_DIRECT(buffer);
- WRITE_PROPERTY_BLOCK_END();
- }
- if (tilesWritten && ((y + 1) != tileset_height))
- {
- WRITE_PROPERTY_NEWLINE();
- }
- }
- WRITE_PROPERTY_BLOCK_END();
- }
- #undef WRITE_PROPERTY_NEWLINE
- #undef WRITE_PROPERTY_DIRECT
- #undef WRITE_PROPERTY_BLOCK_VALUE
- #undef WRITE_PROPERTY_BLOCK_END
- #undef WRITE_PROPERTY_BLOCK_START
- void TMXSceneEncoder::writeLine(std::ofstream& file, const string& line) const
- {
- file << TAB_STRING(_tabCount) << line << std::endl;
- }
- bool isBase64(char c)
- {
- return (c >= 'a' && c <= 'z') ||
- (c >= 'A' && c <= 'Z') ||
- (c >= '0' && c <= '9') ||
- (c == '=');
- }
- std::vector<unsigned int> TMXSceneEncoder::loadDataElement(const XMLElement* data)
- {
- if (!data)
- {
- return std::vector<unsigned int>();
- }
- const char* encoding = data->Attribute("encoding");
- const char* compression = data->Attribute("compression");
- const char* attValue = "0";
- unsigned int tileGid;
- std::vector<unsigned int> tileData;
- if (!compression && !encoding)
- {
- auto xmlTile = data->FirstChildElement("tile");
- while (xmlTile)
- {
- attValue = xmlTile->Attribute("gid");
- sscanf(attValue, "%u", &tileGid);
- tileData.push_back(tileGid);
- xmlTile = xmlTile->NextSiblingElement("tile");
- }
- }
- else
- {
- if (strcmp(encoding, "csv") == 0)
- {
- if (compression)
- {
- LOG(1, "Compression is not supported with CSV encoding.\n");
- exit(-1);
- }
- auto rawCsvData = data->GetText();
- int start = 0;
- char* endptr;
- // Skip everything before values
- while (rawCsvData[start] == ' ' || rawCsvData[start] == '\n' || rawCsvData[start] == '\r' || rawCsvData[start] == '\t')
- {
- start++;
- }
- // Iterate through values. Skipping to next value when done
- while (rawCsvData[start])
- {
- tileData.push_back(strtoul(rawCsvData + start, &endptr, 10));
- start = endptr - rawCsvData;
- while (rawCsvData[start] == ' ' || rawCsvData[start] == ',' || rawCsvData[start] == '\n' || rawCsvData[start] == '\r' || rawCsvData[start] == '\t')
- {
- start++;
- }
- }
- }
- else if (strcmp(encoding, "base64") == 0)
- {
- string base64Data = data->GetText();
- if (base64Data.size() > 0 && (!isBase64(base64Data[0]) || !isBase64(base64Data[base64Data.size() - 1])))
- {
- int start = 0;
- auto end = base64Data.size() - 1;
- while (!isBase64(base64Data[start]))
- {
- start++;
- }
- while (!isBase64(base64Data[end]))
- {
- end--;
- }
- base64Data = base64Data.substr(start, end - start + 1);
- }
- auto byteData = base64_decode(base64Data);
- if (compression)
- {
- int err;
- const unsigned int buffer_size = 4096;
- char buffer[buffer_size];
- string decomData;
- if (strcmp(compression, "zlib") == 0 || strcmp(compression, "gzip") == 0)
- {
- z_stream d_stream;
- d_stream.zalloc = Z_NULL;
- d_stream.zfree = Z_NULL;
- d_stream.opaque = Z_NULL;
- d_stream.next_in = (Bytef*)byteData.c_str();
- d_stream.avail_in = byteData.size();
- if (strcmp(compression, "zlib") == 0)
- {
- // zlib
- if ((err = inflateInit(&d_stream)) != Z_OK)
- {
- LOG(1, "ZLIB inflateInit failed. Error: %d.\n", err);
- exit(-1);
- }
- }
- else
- {
- // gzip
- if ((err = inflateInit2(&d_stream, 16+MAX_WBITS)) != Z_OK)
- {
- LOG(1, "ZLIB inflateInit2 failed. Error: %d.\n", err);
- exit(-1);
- }
- }
- do
- {
- d_stream.next_out = (Bytef*)buffer;
- d_stream.avail_out = buffer_size;
- err = inflate(&d_stream, Z_NO_FLUSH);
- switch (err)
- {
- case Z_NEED_DICT:
- case Z_DATA_ERROR:
- case Z_MEM_ERROR:
- inflateEnd(&d_stream);
- LOG(1, "ZLIB inflate failed. Error: %d.\n", err);
- exit(-1);
- break;
- }
- string decomBlock(buffer, buffer_size - d_stream.avail_out);
- decomData += decomBlock;
- } while (err != Z_STREAM_END);
- inflateEnd(&d_stream);
- }
- else
- {
- LOG(1, "Unknown compression: %s.\n", compression);
- exit(-1);
- }
- byteData = decomData;
- }
- auto byteDataSize = byteData.size();
- for (unsigned int i = 0; i < byteDataSize; i += 4)
- {
- unsigned int gid = static_cast<unsigned char>(byteData[i + 0]) |
- (static_cast<unsigned char>(byteData[i + 1]) << 8u) |
- (static_cast<unsigned char>(byteData[i + 2]) << 16u) |
- (static_cast<unsigned char>(byteData[i + 3]) << 24u);
- tileData.push_back(gid);
- }
- }
- else
- {
- LOG(1, "Unknown encoding: %s.\n", encoding);
- exit(-1);
- }
- }
- return tileData;
- }
|