Skip to content

Commit 355caab

Browse files
committed
parse base64 encoded infinite maps
1 parent cfb6799 commit 355caab

3 files changed

Lines changed: 123 additions & 37 deletions

File tree

ParseTest/src/main.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ source distribution.
2828
#include <tmxlite/Map.hpp>
2929
#include <tmxlite/ObjectGroup.hpp>
3030
#include <tmxlite/LayerGroup.hpp>
31+
#include <tmxlite/TileLayer.hpp>
3132

3233
#include <iostream>
3334

3435
int main()
3536
{
3637
tmx::Map map;
3738

38-
if (map.load("maps/tilesettest.tmx"))
39+
if (map.load("maps/untitled.tmx"))
3940
{
4041
std::cout << "Loaded Map version: " << map.getVersion().upper << ", " << map.getVersion().lower << std::endl;
4142

@@ -90,6 +91,27 @@ int main()
9091
}
9192
}
9293

94+
if (layer->getType() == tmx::Layer::Type::Tile)
95+
{
96+
const auto& tiles = layer->getLayerAs<tmx::TileLayer>().getTiles();
97+
if (tiles.empty())
98+
{
99+
const auto& chunks = layer->getLayerAs<tmx::TileLayer>().getChunks();
100+
if (chunks.empty())
101+
{
102+
std::cout << "Layer has missing tile data\n";
103+
}
104+
else
105+
{
106+
std::cout << "Layer has " << chunks.size() << " tile chunks.\n";
107+
}
108+
}
109+
else
110+
{
111+
std::cout << "Layer has " << tiles.size() << " tiles.\n";
112+
}
113+
}
114+
93115
const auto& properties = layer->getProperties();
94116
std::cout << properties.size() << " Layer Properties:" << std::endl;
95117
for (const auto& prop : properties)

tmxlite/include/tmxlite/TileLayer.hpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ source distribution.
2828
#pragma once
2929

3030
#include <tmxlite/Layer.hpp>
31+
#include <tmxlite/Types.hpp>
3132

3233
namespace tmx
3334
{
@@ -46,6 +47,16 @@ namespace tmx
4647
std::uint8_t flipFlags = 0; //!< Flags marking if the tile should be flipped when drawn
4748
};
4849

50+
/*!
51+
\brief Represents a chunk of tile data, if this is an infinite map
52+
*/
53+
struct Chunk final
54+
{
55+
Vector2i position; //<! coordinate in tiles, not pixels
56+
Vector2i size; //!< size in tiles, not pixels
57+
std::vector<Tile> tiles;
58+
};
59+
4960
/*!
5061
\brief Flags used to tell if a tile is flipped when drawn
5162
*/
@@ -64,18 +75,30 @@ namespace tmx
6475

6576
/*!
6677
\brief Returns the list of tiles used to make up the layer
78+
If this is empty then the map is most likely infinite, in
79+
which case the tile data is stored in chunks.
80+
\see getChunks()
6781
*/
6882
const std::vector<Tile>& getTiles() const { return m_tiles; }
6983

84+
/*!
85+
\brief Returns a vector of chunks which make up this layer
86+
if the map is set to infinite. This will be empty if the map
87+
is not infinite.
88+
\see getTiles()
89+
*/
90+
const std::vector<Chunk>& getChunks() const { return m_chunks; }
91+
7092
private:
7193
std::vector<Tile> m_tiles;
94+
std::vector<Chunk> m_chunks;
7295
std::size_t m_tileCount;
7396

7497
void parseBase64(const pugi::xml_node&);
7598
void parseCSV(const pugi::xml_node&);
7699
void parseUnencoded(const pugi::xml_node&);
77100

78-
void createTiles(const std::vector<std::uint32_t>&);
101+
void createTiles(const std::vector<std::uint32_t>&, std::vector<Tile>& destination);
79102
};
80103

81104
template <>

tmxlite/src/TileLayer.cpp

Lines changed: 76 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -88,47 +88,88 @@ void TileLayer::parse(const pugi::xml_node& node, Map*)
8888
//private
8989
void TileLayer::parseBase64(const pugi::xml_node& node)
9090
{
91-
std::string data = node.text().as_string();
92-
if (data.empty())
91+
auto processDataString = [](std::string dataString, std::size_t tileCount, bool compressed)->std::vector<std::uint32_t>
9392
{
94-
Logger::log("Layer " + getName() + " has no layer data. Layer skipped.", Logger::Type::Error);
95-
return;
96-
}
93+
std::stringstream ss;
94+
ss << dataString;
95+
ss >> dataString;
96+
dataString = base64_decode(dataString);
97+
98+
std::size_t expectedSize = tileCount * 4; //4 bytes per tile
99+
std::vector<unsigned char> byteData;
100+
byteData.reserve(expectedSize);
101+
102+
if (compressed)
103+
{
104+
//unzip
105+
std::size_t dataSize = dataString.length() * sizeof(unsigned char);
106+
if (!decompress(dataString.c_str(), byteData, dataSize, expectedSize))
107+
{
108+
LOG("Failed to decompress layer data, node skipped.", Logger::Type::Error);
109+
return {};
110+
}
111+
}
112+
else
113+
{
114+
byteData.insert(byteData.end(), dataString.begin(), dataString.end());
115+
}
116+
117+
//data stream is in bytes so we need to OR into 32 bit values
118+
std::vector<std::uint32_t> IDs;
119+
IDs.reserve(tileCount);
120+
for (auto i = 0u; i < expectedSize - 3u; i += 4u)
121+
{
122+
std::uint32_t id = byteData[i] | byteData[i + 1] << 8 | byteData[i + 2] << 16 | byteData[i + 3] << 24;
123+
IDs.push_back(id);
124+
}
97125

98-
//using a string stream we can remove whitespace such as tabs
99-
std::stringstream ss;
100-
ss << data;
101-
ss >> data;
102-
data = base64_decode(data);
126+
return IDs;
127+
};
103128

104-
std::size_t expectedSize = m_tileCount * 4; //4 bytes per tile
105-
std::vector<unsigned char> byteData;
106-
byteData.reserve(expectedSize);
107129

108-
if (node.attribute("compression"))
130+
std::string data = node.text().as_string();
131+
if (data.empty())
109132
{
110-
//unzip
111-
std::size_t dataSize = data.length() * sizeof(unsigned char);
112-
if (!decompress(data.c_str(), byteData, dataSize, expectedSize))
133+
//check for chunk nodes
134+
auto dataCount = 0;
135+
for (const auto& childNode : node.children())
136+
{
137+
std::string childName = childNode.name();
138+
if (childName == "chunk")
139+
{
140+
std::string dataString = childNode.text().as_string();
141+
if (!dataString.empty())
142+
{
143+
Chunk chunk;
144+
chunk.position.x = childNode.attribute("x").as_int();
145+
chunk.position.y = childNode.attribute("y").as_int();
146+
147+
chunk.size.x = childNode.attribute("width").as_int();
148+
chunk.size.y = childNode.attribute("height").as_int();
149+
150+
auto IDs = processDataString(dataString, (chunk.size.x * chunk.size.y), node.attribute("compression"));
151+
152+
if (!IDs.empty())
153+
{
154+
createTiles(IDs, chunk.tiles);
155+
m_chunks.push_back(chunk);
156+
dataCount++;
157+
}
158+
}
159+
}
160+
}
161+
162+
if (dataCount == 0)
113163
{
114-
LOG("Failed to decompress layer data, node skipped.", Logger::Type::Error);
164+
Logger::log("Layer " + getName() + " has no layer data. Layer skipped.", Logger::Type::Error);
115165
return;
116166
}
117167
}
118168
else
119169
{
120-
byteData.insert(byteData.end(), data.begin(), data.end());
121-
}
122-
123-
//data stream is in bytes so whe need to OR into 32 bit values
124-
std::vector<std::uint32_t> IDs;
125-
IDs.reserve(m_tileCount);
126-
for (auto i = 0u; i < expectedSize - 3u; i += 4u)
127-
{
128-
std::uint32_t id = byteData[i] | byteData[i + 1] << 8 | byteData[i + 2] << 16 | byteData[i + 3] << 24;
129-
IDs.push_back(id);
170+
auto IDs = processDataString(data, m_tileCount, node.attribute("compression"));
171+
createTiles(IDs, m_tiles);
130172
}
131-
createTiles(IDs);
132173
}
133174

134175
void TileLayer::parseCSV(const pugi::xml_node& node)
@@ -156,7 +197,7 @@ void TileLayer::parseCSV(const pugi::xml_node& node)
156197
}
157198
}
158199

159-
createTiles(IDs);
200+
createTiles(IDs, m_tiles);
160201
}
161202

162203
void TileLayer::parseUnencoded(const pugi::xml_node& node)
@@ -174,19 +215,19 @@ void TileLayer::parseUnencoded(const pugi::xml_node& node)
174215
}
175216
}
176217

177-
createTiles(IDs);
218+
createTiles(IDs, m_tiles);
178219
}
179220

180-
void TileLayer::createTiles(const std::vector<std::uint32_t>& IDs)
221+
void TileLayer::createTiles(const std::vector<std::uint32_t>& IDs, std::vector<Tile>& destination)
181222
{
182223
//LOG(IDs.size() != m_tileCount, "Layer tile count does not match expected size. Found: "
183224
// + std::to_string(IDs.size()) + ", expected: " + std::to_string(m_tileCount));
184225

185226
static const std::uint32_t mask = 0xf0000000;
186227
for (const auto& id : IDs)
187228
{
188-
m_tiles.emplace_back();
189-
m_tiles.back().flipFlags = ((id & mask) >> 28);
190-
m_tiles.back().ID = id & ~mask;
229+
destination.emplace_back();
230+
destination.back().flipFlags = ((id & mask) >> 28);
231+
destination.back().ID = id & ~mask;
191232
}
192233
}

0 commit comments

Comments
 (0)