/* tmxreader.cpp - Copyright (C) 2015-2022 a dinosaur (zlib, see COPYING.txt) */ #include "tmxreader.hpp" #include "tmxtileset.hpp" #include "tmxobject.hpp" #include "tmxlayer.hpp" #include #include #include #include #include #include TmxReader::~TmxReader() { // Delete old tilesets. for (auto pTileset : mTilesets) delete pTileset; mTilesets.clear(); // Delete old layers. for (auto pLay : mLayers) delete pLay; mLayers.clear(); } bool TmxReader::DecodeMap(uint32_t* aOut, size_t aOutSize, const std::string& aBase64Dat) { // Cut leading & trailing whitespace (including newlines) auto beg = std::find_if_not(aBase64Dat.begin(), aBase64Dat.end(), ::isspace); if (beg == std::end(aBase64Dat)) return false; auto end = std::find_if_not(aBase64Dat.rbegin(), aBase64Dat.rend(), ::isspace); std::size_t begOff = std::distance(aBase64Dat.begin(), beg); std::size_t endOff = std::distance(end, aBase64Dat.rend()) - begOff; auto trimmed = aBase64Dat.substr(begOff, endOff); // Decode base64 string. std::string decoded = base64_decode(trimmed); // Decompress compressed data. auto dstSize = static_cast(aOutSize); int res = uncompress( (unsigned char*)aOut, &dstSize, reinterpret_cast(decoded.data()), static_cast(decoded.size())); decoded.clear(); if (res < 0) return false; return true; } void TmxReader::ReadTileset(rapidxml::xml_node<>* aXNode) { rapidxml::xml_attribute<>* xAttrib; const char* name = ""; const char* source = ""; uint32_t firstGid = 0; // Read name. xAttrib = aXNode->first_attribute("name"); if (xAttrib != nullptr) name = xAttrib->value(); // Read source. xAttrib = aXNode->first_attribute("source"); if (xAttrib != nullptr) source = xAttrib->value(); // Read first global ID. xAttrib = aXNode->first_attribute("firstgid"); if (xAttrib != nullptr) firstGid = std::stoul(xAttrib->value()); mTilesets.push_back(new TmxTileset(name, source, firstGid)); } void TmxReader::ReadLayer(rapidxml::xml_node<>* aXNode) { rapidxml::xml_attribute<>* xAttrib; const char* name = ""; int width = 0; int height = 0; uint32_t* tileDat = nullptr; // Read name. xAttrib = aXNode->first_attribute("name"); if (xAttrib != nullptr) name = xAttrib->value(); // Read width. xAttrib = aXNode->first_attribute("width"); if (xAttrib != nullptr) width = std::stoi(xAttrib->value()); // Read height. xAttrib = aXNode->first_attribute("height"); if (xAttrib != nullptr) height = std::stoi(xAttrib->value()); // Read tile data. auto xData = aXNode->first_node("data"); if (xData != nullptr) { // TODO: don't assume base64 & zlib. tileDat = new uint32_t[width * height]; if (!DecodeMap(tileDat, width * height * sizeof(uint32_t), std::string(xData->value()))) { delete[] tileDat; tileDat = nullptr; } } mLayers.push_back(new TmxLayer(width, height, name, tileDat)); } void TmxReader::ReadObjects(rapidxml::xml_node<>* aXNode) { for (auto xNode = aXNode->first_node(); xNode != nullptr; xNode = xNode->next_sibling()) { if (strcmp(xNode->name(), "object") != 0) continue; rapidxml::xml_attribute<>* xAttrib; const char* name = ""; float x = 0.0f; float y = 0.0f; // Read name. xAttrib = xNode->first_attribute("name"); if (xAttrib != nullptr) name = xAttrib->value(); // Read X pos. xAttrib = xNode->first_attribute("x"); if (xAttrib != nullptr) x = std::stof(xAttrib->value()); // Read Y pos. xAttrib = xNode->first_attribute("y"); if (xAttrib != nullptr) y = std::stof(xAttrib->value()); mObjects.push_back(new TmxObject(name, x, y)); } } void TmxReader::Open(std::istream& aIn) { // Delete old tilesets. for (auto tileset : mTilesets) delete tileset; mTilesets.clear(); // Delete old layers. for (auto layer : mLayers) delete layer; mLayers.clear(); mGidTable.clear(); // Read string into a buffer. std::stringstream buf; buf << aIn.rdbuf(); std::string strXml = buf.str(); buf.clear(); // Parse document. rapidxml::xml_document<> xDoc; xDoc.parse<0>((char*)strXml.c_str()); // Get map node. auto xMap = xDoc.first_node("map"); if (xMap == nullptr) return; // Read map attribs. rapidxml::xml_attribute<>* xAttrib = nullptr; if ((xAttrib = xMap->first_attribute("width")) != nullptr) mWidth = std::stoi(xAttrib->value()); if ((xAttrib = xMap->first_attribute("height")) != nullptr) mHeight = std::stoi(xAttrib->value()); // Read nodes. for (auto xNode = xMap->first_node(); xNode != nullptr; xNode = xNode->next_sibling()) { // Read layer nodes. if (strcmp(xNode->name(), "layer") == 0) ReadLayer(xNode); else if (strcmp(xNode->name(), "tileset") == 0) ReadTileset(xNode); else if (strcmp(xNode->name(), "objectgroup") == 0) ReadObjects(xNode); } // Generate global id table. for (auto tileset : mTilesets) mGidTable.push_back(tileset->GetFirstGid()); std::sort(mGidTable.rbegin(), mGidTable.rend()); } const TmxLayer* TmxReader::GetLayer(const std::string& aName) const { for (auto layer : mLayers) { if (layer->GetName() == aName) return layer; } return nullptr; } uint32_t TmxReader::LidFromGid(uint32_t aGid) { for (uint32_t first : mGidTable) { if (first <= aGid) return aGid - (first - 1); } return aGid; }