diff options
author | Kelly Rauchenberger <fefferburbia@gmail.com> | 2021-02-01 13:14:40 -0500 |
---|---|---|
committer | Kelly Rauchenberger <fefferburbia@gmail.com> | 2021-02-01 13:14:40 -0500 |
commit | 362c332000a65acc060660dcb6bb0ec6f99cbafe (patch) | |
tree | b8f64b23c45e1e016ef708421830e83a0b4bc63c | |
parent | e37026ea3b9d7b121aeb1fd38fe81ab82a1e2e33 (diff) | |
download | tanetane-362c332000a65acc060660dcb6bb0ec6f99cbafe.tar.gz tanetane-362c332000a65acc060660dcb6bb0ec6f99cbafe.tar.bz2 tanetane-362c332000a65acc060660dcb6bb0ec6f99cbafe.zip |
Tileset dumper can now export to Tiled
-rw-r--r-- | tools/sprite_dumper/identifier.h | 59 | ||||
-rw-r--r-- | tools/sprite_dumper/tileset_dumper.cpp | 275 |
2 files changed, 254 insertions, 80 deletions
diff --git a/tools/sprite_dumper/identifier.h b/tools/sprite_dumper/identifier.h new file mode 100644 index 0000000..74d83ce --- /dev/null +++ b/tools/sprite_dumper/identifier.h | |||
@@ -0,0 +1,59 @@ | |||
1 | #ifndef IDENTIFIER_H_D7EE5679 | ||
2 | #define IDENTIFIER_H_D7EE5679 | ||
3 | |||
4 | #include <map> | ||
5 | #include <vector> | ||
6 | |||
7 | template <typename T> | ||
8 | class identifier { | ||
9 | public: | ||
10 | |||
11 | using value_type = T; | ||
12 | |||
13 | private: | ||
14 | |||
15 | using vector_type = std::vector<value_type>; | ||
16 | |||
17 | public: | ||
18 | |||
19 | using key_type = typename vector_type::size_type; | ||
20 | |||
21 | key_type add(const value_type& val) | ||
22 | { | ||
23 | auto it = ids_.find(val); | ||
24 | |||
25 | if (it == std::end(ids_)) | ||
26 | { | ||
27 | key_type ret = ids_.size(); | ||
28 | ids_[val] = ret; | ||
29 | |||
30 | uniq_.push_back(val); | ||
31 | |||
32 | return ret; | ||
33 | } else { | ||
34 | return it->second; | ||
35 | } | ||
36 | } | ||
37 | |||
38 | void compile() | ||
39 | { | ||
40 | ids_.clear(); | ||
41 | } | ||
42 | |||
43 | inline const value_type& get(key_type i) const | ||
44 | { | ||
45 | return uniq_.at(i); | ||
46 | } | ||
47 | |||
48 | inline key_type size() const | ||
49 | { | ||
50 | return uniq_.size(); | ||
51 | } | ||
52 | |||
53 | private: | ||
54 | |||
55 | std::map<value_type, key_type> ids_; | ||
56 | vector_type uniq_; | ||
57 | }; | ||
58 | |||
59 | #endif /* end of include guard: IDENTIFIER_H_D7EE5679 */ | ||
diff --git a/tools/sprite_dumper/tileset_dumper.cpp b/tools/sprite_dumper/tileset_dumper.cpp index de6e1f4..d8997a5 100644 --- a/tools/sprite_dumper/tileset_dumper.cpp +++ b/tools/sprite_dumper/tileset_dumper.cpp | |||
@@ -3,7 +3,9 @@ | |||
3 | #include <vector> | 3 | #include <vector> |
4 | #include <string> | 4 | #include <string> |
5 | #include <map> | 5 | #include <map> |
6 | #include <fstream> | ||
6 | #include "common.h" | 7 | #include "common.h" |
8 | #include "identifier.h" | ||
7 | 9 | ||
8 | constexpr int NUM_ROOMS = 1000; | 10 | constexpr int NUM_ROOMS = 1000; |
9 | constexpr int NUM_TILESETS = 12; | 11 | constexpr int NUM_TILESETS = 12; |
@@ -102,6 +104,112 @@ std::vector<std::vector<char>> GetMapLayers(BufferView m3, int roomId) { | |||
102 | return layers; | 104 | return layers; |
103 | } | 105 | } |
104 | 106 | ||
107 | using metatile_id = identifier<unsigned short>::key_type; | ||
108 | |||
109 | struct TileUse { | ||
110 | metatile_id id; | ||
111 | bool tflipx = false; | ||
112 | bool tflipy = false; | ||
113 | }; | ||
114 | |||
115 | Magick::Image renderTile(unsigned short ch, bool tflipx, bool tflipy, const std::vector<Palette>& palettes, const std::vector<char>& mapTiles, const std::vector<std::vector<char>>& tilesets) { | ||
116 | Magick::Image result("16x16", "transparent"); | ||
117 | |||
118 | unsigned short tile16 = ch & 0x3FF; | ||
119 | if ((tile16 >> 6) >= 12) return result; | ||
120 | |||
121 | result.modifyImage(); | ||
122 | Magick::Pixels view(result); | ||
123 | |||
124 | int tpal = (ch >> 10) & 0xF; | ||
125 | const Palette& palette = palettes[tpal]; | ||
126 | |||
127 | // bool tflipx = tu.tflipx;//(ch & 0x4000) != 0; | ||
128 | // bool tflipy = tu.tflipy;//(ch & 0x8000) != 0; | ||
129 | |||
130 | int tile8[2][2]; | ||
131 | bool sflipx[2][2]; | ||
132 | bool sflipy[2][2]; | ||
133 | |||
134 | unsigned int magic = BufferView(mapTiles).ReadFourBytes(tile16 * 8); | ||
135 | |||
136 | tile8[0][0] = mapTiles[(tile16 * 8) + 4]; | ||
137 | tile8[0][1] = mapTiles[(tile16 * 8) + 5]; | ||
138 | tile8[1][0] = mapTiles[(tile16 * 8) + 6]; | ||
139 | tile8[1][1] = mapTiles[(tile16 * 8) + 7]; | ||
140 | |||
141 | for (int i=0; i<2; i++) { | ||
142 | for (int j=0; j<2; j++) { | ||
143 | sflipx[i][j] = (tile8[i][j] & 0x40) != 0; | ||
144 | sflipy[i][j] = (tile8[i][j] & 0x80) != 0; | ||
145 | |||
146 | tile8[i][j] &= 0x3f; | ||
147 | tile8[i][j] |= (ch & 0x3c0); | ||
148 | } | ||
149 | } | ||
150 | |||
151 | unsigned int mask = (magic >> 16) & 0xf; | ||
152 | if ((mask & 0x1) == 0) tile8[0][0] = -1; | ||
153 | if ((mask & 0x2) == 0) tile8[0][1] = -1; | ||
154 | if ((mask & 0x4) == 0) tile8[1][0] = -1; | ||
155 | if ((mask & 0x8) == 0) tile8[1][1] = -1; | ||
156 | |||
157 | for (int tiley=0; tiley<2; tiley++) { | ||
158 | for (int tilex=0; tilex<2; tilex++) { | ||
159 | if (tile8[tiley][tilex] < 0) continue; | ||
160 | |||
161 | int tileset = tile8[tiley][tilex] >> 6; | ||
162 | int subtile = tile8[tiley][tilex] & 0x3f; | ||
163 | |||
164 | int tileData[8][8]; | ||
165 | BufferView tilesetData(tilesets[tileset]); | ||
166 | tilesetData.Seek(subtile << 5); | ||
167 | for (int ty=0; ty<8; ty++) { | ||
168 | for (int tx=0; tx<4; tx++) { | ||
169 | unsigned char vvvv = tilesetData.ReadNextByte(); | ||
170 | tileData[tx*2][ty] = static_cast<unsigned char>(vvvv & 0xF); | ||
171 | tileData[tx*2+1][ty] = static_cast<unsigned char>((vvvv >> 4) & 0xF); | ||
172 | } | ||
173 | } | ||
174 | |||
175 | int stx = tflipx ? 1 - tilex : tilex; | ||
176 | int sty = tflipy ? 1 - tiley : tiley; | ||
177 | |||
178 | int destX = /*(mapx << 4) +*/ (stx << 3); | ||
179 | int destY = /*(mapy << 4) +*/ (sty << 3); | ||
180 | |||
181 | bool reallyFlipX = (sflipx[tiley][tilex] ^ tflipx); | ||
182 | bool reallyFlipY = (sflipy[tiley][tilex] ^ tflipy); | ||
183 | |||
184 | Magick::PixelPacket* pixels = view.get(destX,destY,8,8); | ||
185 | |||
186 | for (int ty=0; ty<8; ty++) { | ||
187 | int actualTy = reallyFlipY ? (7-ty) : ty; | ||
188 | |||
189 | for (int tx=0; tx<8; tx++) { | ||
190 | int actualTx = reallyFlipX ? (7-tx) : tx; | ||
191 | |||
192 | if (tileData[actualTx][actualTy] != 0) { | ||
193 | //auto& c = palette.Colors().at(tileData[actualTx][actualTy]); | ||
194 | //std::cout << c.redQuantum() << "," << c.greenQuantum() << "," << c.blueQuantum() << std::endl; | ||
195 | *pixels = palette.Colors().at(tileData[actualTx][actualTy]); | ||
196 | //std::cout << tileData[actualTx][actualTy] << std::endl; | ||
197 | } | ||
198 | pixels++; | ||
199 | } | ||
200 | } | ||
201 | |||
202 | view.sync(); | ||
203 | } | ||
204 | } | ||
205 | |||
206 | return result; | ||
207 | } | ||
208 | |||
209 | unsigned short stripFlipInfo(unsigned short ch) { | ||
210 | return ch & ~(0x4000 | 0x8000); | ||
211 | } | ||
212 | |||
105 | int main(int argc, char** argv) { | 213 | int main(int argc, char** argv) { |
106 | if (argc < 3) { | 214 | if (argc < 3) { |
107 | std::cout << "Usage: ./tileset_dumper [path to rom] {map ID}" << std::endl; | 215 | std::cout << "Usage: ./tileset_dumper [path to rom] {map ID}" << std::endl; |
@@ -125,8 +233,6 @@ int main(int argc, char** argv) { | |||
125 | int height = roomInfos[roomNum].height; | 233 | int height = roomInfos[roomNum].height; |
126 | 234 | ||
127 | Magick::Image image(Magick::Geometry(width*16, height*16), "transparent"); | 235 | Magick::Image image(Magick::Geometry(width*16, height*16), "transparent"); |
128 | image.modifyImage(); | ||
129 | Magick::Pixels view(image); | ||
130 | 236 | ||
131 | auto mapTiles = GetMapTiles(m3.buffer(), roomNum); | 237 | auto mapTiles = GetMapTiles(m3.buffer(), roomNum); |
132 | auto mapLayers = GetMapLayers(m3.buffer(), roomNum); | 238 | auto mapLayers = GetMapLayers(m3.buffer(), roomNum); |
@@ -135,102 +241,111 @@ int main(int argc, char** argv) { | |||
135 | auto palettes = gfxPal.GetPalettes(m3.buffer()); | 241 | auto palettes = gfxPal.GetPalettes(m3.buffer()); |
136 | auto tilesets = gfxPal.GetTilesets(m3.buffer()); | 242 | auto tilesets = gfxPal.GetTilesets(m3.buffer()); |
137 | 243 | ||
138 | for (int layer=mapLayers.size()-1; layer>=0; layer--) { | 244 | identifier<unsigned short> metatiles; |
245 | std::vector<std::vector<TileUse>> itemised; | ||
246 | |||
247 | for (int layer=0; layer<mapLayers.size(); layer++) { | ||
139 | const std::vector<char>& ml = mapLayers[layer]; | 248 | const std::vector<char>& ml = mapLayers[layer]; |
140 | if (ml.empty()) continue; | 249 | if (ml.empty()) continue; |
141 | 250 | ||
251 | std::vector<TileUse> newLayer; | ||
252 | |||
142 | for (int mapy = 0; mapy < height; mapy++) { | 253 | for (int mapy = 0; mapy < height; mapy++) { |
143 | for (int mapx = 0; mapx < width; mapx++) { | 254 | for (int mapx = 0; mapx < width; mapx++) { |
144 | unsigned short ch = BufferView(ml).ReadTwoBytes((mapx + (mapy * width)) * 2); | 255 | unsigned short ch = BufferView(ml).ReadTwoBytes((mapx + (mapy * width)) * 2); |
145 | unsigned short tile16 = ch & 0x3FF; | 256 | TileUse tu; |
146 | 257 | tu.id = metatiles.add(stripFlipInfo(ch)); | |
147 | if ((tile16 >> 6) >= 12) continue; | 258 | tu.tflipx = (ch & 0x4000) != 0; |
259 | tu.tflipy = (ch & 0x8000) != 0; | ||
260 | newLayer.push_back(std::move(tu)); | ||
261 | } | ||
262 | } | ||
148 | 263 | ||
149 | int tpal = (ch >> 10) & 0xF; | 264 | itemised.push_back(std::move(newLayer)); |
150 | const Palette& palette = palettes[tpal]; | 265 | } |
151 | 266 | ||
152 | bool tflipx = (ch & 0x4000) != 0; | ||
153 | bool tflipy = (ch & 0x8000) != 0; | ||
154 | 267 | ||
155 | int tile8[2][2]; | ||
156 | bool sflipx[2][2]; | ||
157 | bool sflipy[2][2]; | ||
158 | 268 | ||
159 | unsigned int magic = BufferView(mapTiles).ReadFourBytes(tile16 * 8); | 269 | std::ofstream mapfile("out.tmx"); |
270 | mapfile << R"(<map version="1.0" orientation="orthogonal" renderorder="right-down" width=")"; | ||
271 | mapfile << width; | ||
272 | mapfile << R"(" height=")"; | ||
273 | mapfile << height; | ||
274 | mapfile << R"(" tilewidth="16" tileheight="16">)" << std::endl; | ||
275 | mapfile << R"( <tileset firstgid="0" source="out.tsx" />)" << std::endl; | ||
276 | |||
277 | for (int layer=itemised.size()-1; layer>=0; layer--) { | ||
278 | mapfile << R"( <layer id=")"; | ||
279 | mapfile << layer; | ||
280 | mapfile << R"(" name="Layer )"; | ||
281 | mapfile << layer; | ||
282 | mapfile << R"(" width=")"; | ||
283 | mapfile << width; | ||
284 | mapfile << R"(" height=")"; | ||
285 | mapfile << height; | ||
286 | mapfile << R"(">)" << std::endl; | ||
287 | mapfile << R"( <data encoding="csv">)"; | ||
288 | |||
289 | bool first = true; | ||
290 | for (const TileUse& tu : itemised[layer]) { | ||
291 | if (first) { | ||
292 | first = false; | ||
293 | } else { | ||
294 | mapfile << ","; | ||
295 | } | ||
160 | 296 | ||
161 | tile8[0][0] = mapTiles[(tile16 * 8) + 4]; | 297 | unsigned int outChar = tu.id; |
162 | tile8[0][1] = mapTiles[(tile16 * 8) + 5]; | 298 | if (tu.tflipx) outChar |= 0x80000000; |
163 | tile8[1][0] = mapTiles[(tile16 * 8) + 6]; | 299 | if (tu.tflipy) outChar |= 0x40000000; |
164 | tile8[1][1] = mapTiles[(tile16 * 8) + 7]; | 300 | mapfile << outChar; |
301 | } | ||
165 | 302 | ||
166 | for (int i=0; i<2; i++) { | 303 | mapfile << R"(</data>)" << std::endl; |
167 | for (int j=0; j<2; j++) { | 304 | mapfile << R"( </layer>)" << std::endl; |
168 | sflipx[i][j] = (tile8[i][j] & 0x40) != 0; | 305 | } |
169 | sflipy[i][j] = (tile8[i][j] & 0x80) != 0; | ||
170 | 306 | ||
171 | tile8[i][j] &= 0x3f; | 307 | mapfile << R"(</map>)" << std::endl; |
172 | tile8[i][j] |= (ch & 0x3c0); | ||
173 | } | ||
174 | } | ||
175 | 308 | ||
176 | unsigned int mask = (magic >> 16) & 0xf; | 309 | for (int layer=itemised.size()-1; layer>=0; layer--) { |
177 | if ((mask & 0x1) == 0) tile8[0][0] = -1; | 310 | for (int mapy = 0; mapy < height; mapy++) { |
178 | if ((mask & 0x2) == 0) tile8[0][1] = -1; | 311 | for (int mapx = 0; mapx < width; mapx++) { |
179 | if ((mask & 0x4) == 0) tile8[1][0] = -1; | 312 | const TileUse& tu = itemised[layer][mapx+mapy*width]; |
180 | if ((mask & 0x8) == 0) tile8[1][1] = -1; | 313 | Magick::Image tileRender = renderTile(metatiles.get(tu.id), tu.tflipx, tu.tflipy, palettes, mapTiles, tilesets); |
181 | 314 | image.composite(tileRender, mapx << 4, mapy << 4, Magick::OverCompositeOp); | |
182 | for (int tiley=0; tiley<2; tiley++) { | ||
183 | for (int tilex=0; tilex<2; tilex++) { | ||
184 | if (tile8[tiley][tilex] < 0) continue; | ||
185 | |||
186 | int tileset = tile8[tiley][tilex] >> 6; | ||
187 | int subtile = tile8[tiley][tilex] & 0x3f; | ||
188 | |||
189 | int tileData[8][8]; | ||
190 | BufferView tilesetData(tilesets[tileset]); | ||
191 | tilesetData.Seek(subtile << 5); | ||
192 | for (int ty=0; ty<8; ty++) { | ||
193 | for (int tx=0; tx<4; tx++) { | ||
194 | unsigned char vvvv = tilesetData.ReadNextByte(); | ||
195 | tileData[tx*2][ty] = static_cast<unsigned char>(vvvv & 0xF); | ||
196 | tileData[tx*2+1][ty] = static_cast<unsigned char>((vvvv >> 4) & 0xF); | ||
197 | } | ||
198 | } | ||
199 | |||
200 | int stx = tflipx ? 1 - tilex : tilex; | ||
201 | int sty = tflipy ? 1 - tiley : tiley; | ||
202 | |||
203 | int destX = (mapx << 4) + (stx << 3); | ||
204 | int destY = (mapy << 4) + (sty << 3); | ||
205 | |||
206 | bool reallyFlipX = (sflipx[tiley][tilex] ^ tflipx); | ||
207 | bool reallyFlipY = (sflipy[tiley][tilex] ^ tflipy); | ||
208 | |||
209 | Magick::PixelPacket* pixels = view.get(destX,destY,8,8); | ||
210 | |||
211 | for (int ty=0; ty<8; ty++) { | ||
212 | int actualTy = reallyFlipY ? (7-ty) : ty; | ||
213 | |||
214 | for (int tx=0; tx<8; tx++) { | ||
215 | int actualTx = reallyFlipX ? (7-tx) : tx; | ||
216 | |||
217 | if (tileData[actualTx][actualTy] != 0) { | ||
218 | //auto& c = palette.Colors().at(tileData[actualTx][actualTy]); | ||
219 | //std::cout << c.redQuantum() << "," << c.greenQuantum() << "," << c.blueQuantum() << std::endl; | ||
220 | *pixels = palette.Colors().at(tileData[actualTx][actualTy]); | ||
221 | //std::cout << tileData[actualTx][actualTy] << std::endl; | ||
222 | } | ||
223 | pixels++; | ||
224 | } | ||
225 | } | ||
226 | |||
227 | view.sync(); | ||
228 | } | ||
229 | } | ||
230 | } | 315 | } |
231 | } | 316 | } |
232 | } | 317 | } |
233 | 318 | ||
234 | image.magick("png"); | 319 | image.magick("png"); |
235 | image.write("out.png"); | 320 | image.write("out.png"); |
321 | |||
322 | constexpr int TILES_PER_ROW = 10; | ||
323 | int sheetWidth; | ||
324 | int sheetHeight; | ||
325 | |||
326 | if (metatiles.size() < TILES_PER_ROW) { | ||
327 | sheetWidth = metatiles.size() * 16; | ||
328 | sheetHeight = 16; | ||
329 | } else { | ||
330 | sheetWidth = TILES_PER_ROW * 16; | ||
331 | sheetHeight = (metatiles.size() / TILES_PER_ROW + 1) * 16; | ||
332 | } | ||
333 | |||
334 | std::ofstream tilesetfile("out.tsx"); | ||
335 | tilesetfile << R"(<tileset name="fromRom" tilewidth="16" tileheight="16" tilecount=")"; | ||
336 | tilesetfile << metatiles.size(); | ||
337 | tilesetfile << R"(" columns=")"; | ||
338 | tilesetfile << TILES_PER_ROW; | ||
339 | tilesetfile << R"(">)" << std::endl; | ||
340 | tilesetfile << R"( <image source="tiles.png" />)" << std::endl; | ||
341 | tilesetfile << R"(</tileset>)" << std::endl; | ||
342 | |||
343 | Magick::Image tilesetImage(Magick::Geometry(sheetWidth, sheetHeight), "transparent"); | ||
344 | for (int i=0; i<metatiles.size(); i++) { | ||
345 | Magick::Image tileRender = renderTile(metatiles.get(i), false, false, palettes, mapTiles, tilesets); | ||
346 | tilesetImage.composite(tileRender, (i % TILES_PER_ROW) << 4, (i / TILES_PER_ROW) << 4, Magick::OverCompositeOp); | ||
347 | } | ||
348 | |||
349 | tilesetImage.magick("png"); | ||
350 | tilesetImage.write("tiles.png"); | ||
236 | } \ No newline at end of file | 351 | } \ No newline at end of file |