mirror of
https://github.com/ScrelliCopter/tmx2gba.git
synced 2025-02-21 03:29:25 +11:00
Compare commits
7 Commits
835b80256f
...
e6053f9472
| Author | SHA1 | Date | |
|---|---|---|---|
| e6053f9472 | |||
| 6b786d36fb | |||
| 708fd13d08 | |||
| 6a6d589817 | |||
| fcb9eceec3 | |||
| 2638bf2667 | |||
| b29c61774c |
2
.github/workflows/cmake.yml
vendored
2
.github/workflows/cmake.yml
vendored
@@ -54,4 +54,4 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{env.ARTIFACT_NAME}}-${{matrix.config.artifact}}
|
||||
path: build/src/${{env.ARTIFACT_NAME}}${{startsWith(matrix.config.os, 'windows') && '.exe' || ''}}
|
||||
path: build/src/${{env.ARTIFACT_NAME}}${{startsWith(matrix.config.os, 'windows') && '.[ep][xd][eb]' || ''}}
|
||||
|
||||
@@ -8,7 +8,6 @@ project(tmx2gba
|
||||
option(USE_ZLIB "Use zlib instead of bundled miniz" "${UNIX}")
|
||||
option(USE_BUNDLED_PUGIXML "Use bundled PUGIXML" ON)
|
||||
option(USE_BUNDLED_ZSTD "Use bundled libzstd" ON)
|
||||
option(USE_BUNDLED_TMXLITE "Use bundled tmxlite" ON)
|
||||
|
||||
option(TMX2GBA_DKP_INSTALL "Install into DEVKITPRO prefix" OFF)
|
||||
|
||||
|
||||
@@ -10,13 +10,12 @@ tmx2gba is a simple command line utility that converts [Tiled](http://www.mapedi
|
||||
|
||||
## Usage ##
|
||||
```
|
||||
tmx2gba [-hv] [-r offset] [-lyc name] [-p 0-15] [-m name;id] <-i inpath> <-o outpath>
|
||||
tmx2gba [-h] [-r offset] [-lyc name] [-p 0-15] [-m name;id] <-i inpath> <-o outpath>
|
||||
```
|
||||
|
||||
| Command | Required | Notes |
|
||||
|--------------|----------|------------------------------------------------------------------------------------|
|
||||
| -h | N/A | Display help & command info |
|
||||
| -v | No | Display version & quit |
|
||||
| -h | N/A | Display program help & command info then quit |
|
||||
| -l (name) | No | Name of layer to use (default first layer in TMX) |
|
||||
| -y (name) | No | Layer for palette mappings |
|
||||
| -c (name) | No | Output a separate 8bit collision map of the specified layer |
|
||||
|
||||
@@ -22,6 +22,7 @@ add_library(pugixml STATIC
|
||||
src/pugixml.hpp
|
||||
src/pugixml.cpp)
|
||||
add_library(pugixml::static ALIAS pugixml)
|
||||
add_library(pugixml::pugixml ALIAS pugixml)
|
||||
|
||||
set_target_properties(pugixml PROPERTIES
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
add_executable(tmx2gba
|
||||
strtools.hpp strtools.cpp
|
||||
argparse.hpp argparse.cpp
|
||||
$<$<NOT:$<TARGET_EXISTS:ZLIB::ZLIB>>:gzip.hpp gzip.cpp>
|
||||
tmxlayer.hpp
|
||||
@@ -20,8 +21,7 @@ set_target_properties(tmx2gba PROPERTIES
|
||||
CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||
$<$<BOOL:${MSVC}>:_CRT_SECURE_NO_WARNINGS> # disable msvc warning
|
||||
$<$<TARGET_EXISTS:ZLIB::ZLIB>:USE_ZLIB>)
|
||||
$<$<BOOL:${MSVC}>:_CRT_SECURE_NO_WARNINGS>) # Disable msvc warning
|
||||
|
||||
# Enable strong warnings
|
||||
target_compile_options(tmx2gba PRIVATE
|
||||
@@ -29,7 +29,8 @@ target_compile_options(tmx2gba PRIVATE
|
||||
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall -Wextra -pedantic>
|
||||
$<$<CXX_COMPILER_ID:Clang,AppleClang>:-Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded>)
|
||||
|
||||
target_link_libraries(tmx2gba pugixml base64::base64 Zstd::Zstd
|
||||
target_link_libraries(tmx2gba
|
||||
pugixml::pugixml base64::base64 Zstd::Zstd
|
||||
$<$<TARGET_EXISTS:ZLIB::ZLIB>:ZLIB::ZLIB>
|
||||
$<$<TARGET_EXISTS:miniz::miniz>:miniz::miniz>)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* argparse.cpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
|
||||
|
||||
#include "argparse.hpp"
|
||||
#include <optional>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* argparse.hpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
|
||||
|
||||
#ifndef ARGPARSE_HPP
|
||||
#define ARGPARSE_HPP
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#define TMX2GBA_VERSION "@PROJECT_VERSION@"
|
||||
#define TMX2GBA_VERSION "@PROJECT_VERSION@"
|
||||
#define TMX2GBA_DESCRIPTION "@PROJECT_DESCRIPTION@"
|
||||
#define TMX2GBA_HOMEPAGE "@PROJECT_HOMEPAGE_URL@"
|
||||
|
||||
#cmakedefine USE_ZLIB
|
||||
|
||||
#endif//CONFIG_H
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* converter.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
|
||||
|
||||
#include "convert.hpp"
|
||||
#include "tmxreader.hpp"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* converter.hpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
|
||||
|
||||
#ifndef CONVERT_HPP
|
||||
#define CONVERT_HPP
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* headerwriter.cpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
|
||||
|
||||
#include "headerwriter.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* headerwriter.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
|
||||
|
||||
#ifndef HEADERWRITER_HPP
|
||||
#define HEADERWRITER_HPP
|
||||
|
||||
37
src/strtools.cpp
Normal file
37
src/strtools.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
|
||||
|
||||
#include "strtools.hpp"
|
||||
#include <cctype>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
const std::string_view TrimWhitespace(const std::string_view str)
|
||||
{
|
||||
auto beg = std::find_if_not(str.begin(), str.end(), ::isspace);
|
||||
if (beg == std::end(str))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
auto end = std::find_if_not(str.rbegin(), str.rend(), ::isspace);
|
||||
auto begOff = std::distance(str.begin(), beg);
|
||||
auto endOff = std::distance(end, str.rend()) - begOff;
|
||||
using size_type = std::string::size_type;
|
||||
return str.substr(static_cast<size_type>(begOff), static_cast<size_type>(endOff));
|
||||
}
|
||||
|
||||
std::string SanitiseLabel(const std::string_view ident)
|
||||
{
|
||||
std::string out;
|
||||
out.reserve(ident.length());
|
||||
|
||||
int last = '_';
|
||||
for (int i : ident)
|
||||
{
|
||||
if (out.empty() && std::isdigit(i)) { continue; }
|
||||
if (!std::isalnum(i)) { i = '_'; }
|
||||
if (i != '_' || last != '_') { out.push_back(i); }
|
||||
last = i;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
112
src/strtools.hpp
Normal file
112
src/strtools.hpp
Normal file
@@ -0,0 +1,112 @@
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
|
||||
|
||||
#ifndef STRTOOLS_HPP
|
||||
#define STRTOOLS_HPP
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
// Cut leading & trailing whitespace (including newlines)
|
||||
[[nodiscard]] const std::string_view TrimWhitespace(const std::string_view str);
|
||||
|
||||
// Convert string to valid C identifier
|
||||
[[nodiscard]] std::string SanitiseLabel(const std::string_view ident);
|
||||
|
||||
|
||||
#include <ostream>
|
||||
|
||||
// Template functions for converting unsigned ints to C/GNU style hex
|
||||
|
||||
static inline constexpr char CHexU(uint8_t h) { return "0123456789ABCDEF"[h >> 4]; }
|
||||
static inline constexpr char CHexL(uint8_t l) { return "0123456789ABCDEF"[l & 15]; }
|
||||
|
||||
template <typename T> static void CHex(std::ostream& s, T x);
|
||||
template <> void CHex(std::ostream& s, uint8_t x)
|
||||
{
|
||||
if (x > 9) s << "0x";
|
||||
if (x > 15) s << CHexU(x);
|
||||
s << CHexL(x);
|
||||
}
|
||||
template <> void CHex(std::ostream& s, uint16_t x)
|
||||
{
|
||||
if (x > 9) s << "0x";
|
||||
if (x > 4095) s << CHexU(static_cast<uint8_t>(x >> 8));
|
||||
if (x > 255) s << CHexL(static_cast<uint8_t>(x >> 8));
|
||||
if (x > 15) s << CHexU(static_cast<uint8_t>(x));
|
||||
s << CHexL(static_cast<uint8_t>(x));
|
||||
}
|
||||
template <> void CHex(std::ostream& s, uint32_t x)
|
||||
{
|
||||
if (x > 9) s << "0x";
|
||||
if (x > 0xFFFFFFF) s << CHexU(static_cast<uint8_t>(x >> 24));
|
||||
if (x > 0xFFFFFF) s << CHexL(static_cast<uint8_t>(x >> 24));
|
||||
if (x > 0xFFFFF) s << CHexU(static_cast<uint8_t>(x >> 16));
|
||||
if (x > 65535) s << CHexL(static_cast<uint8_t>(x >> 16));
|
||||
if (x > 4095) s << CHexU(static_cast<uint8_t>(x >> 8));
|
||||
if (x > 255) s << CHexL(static_cast<uint8_t>(x >> 8));
|
||||
if (x > 15) s << CHexU(static_cast<uint8_t>(x));
|
||||
s << CHexL(static_cast<uint8_t>(x));
|
||||
}
|
||||
|
||||
|
||||
#include <limits>
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
|
||||
// Templated string to int/float w/ exception-less error handling
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] static std::optional<T> IntFromStr(const char* str, int base = 0) noexcept
|
||||
{
|
||||
using std::numeric_limits;
|
||||
|
||||
errno = 0;
|
||||
char* end = nullptr;
|
||||
long res = std::strtol(str, &end, base);
|
||||
if (errno == ERANGE) { return std::nullopt; }
|
||||
if (str == end) { return std::nullopt; }
|
||||
if constexpr (sizeof(long) > sizeof(T))
|
||||
{
|
||||
if (res > numeric_limits<T>::max() || res < numeric_limits<T>::min())
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return static_cast<T>(res);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] static std::optional<T> UintFromStr(const char* str, int base = 0) noexcept
|
||||
{
|
||||
using std::numeric_limits;
|
||||
|
||||
char* end = nullptr;
|
||||
errno = 0;
|
||||
unsigned long res = std::strtoul(str, &end, base);
|
||||
if (errno == ERANGE) { return std::nullopt; }
|
||||
if (str == end) { return std::nullopt; }
|
||||
if constexpr (numeric_limits<unsigned long>::max() > numeric_limits<T>::max())
|
||||
{
|
||||
if (res > numeric_limits<T>::max()) { return std::nullopt; }
|
||||
}
|
||||
|
||||
return static_cast<T>(res);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] static std::optional<T> FloatFromStr(const char* str) noexcept
|
||||
{
|
||||
char* end = nullptr;
|
||||
T res;
|
||||
errno = 0;
|
||||
if constexpr (std::is_same_v<T, float>)
|
||||
res = std::strtof(str, &end);
|
||||
else
|
||||
res = static_cast<T>(std::strtod(str, &end));
|
||||
if (errno == ERANGE) { return std::nullopt; }
|
||||
if (str == end) { return std::nullopt; }
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#endif//STRTOOLS_HPP
|
||||
@@ -1,43 +1,12 @@
|
||||
/* swwriter.cpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
|
||||
|
||||
#include "swriter.hpp"
|
||||
#include "strtools.hpp"
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
static inline constexpr char HexU(uint8_t h) { return "0123456789ABCDEF"[h >> 4]; }
|
||||
static inline constexpr char HexL(uint8_t l) { return "0123456789ABCDEF"[l & 15]; }
|
||||
|
||||
template <typename T> static void CHex(std::ostream& s, T x);
|
||||
template <> void CHex(std::ostream& s, uint8_t x)
|
||||
{
|
||||
if (x > 9) s << "0x";
|
||||
if (x > 15) s << HexU(x);
|
||||
s << HexL(x);
|
||||
}
|
||||
template <> void CHex(std::ostream& s, uint16_t x)
|
||||
{
|
||||
if (x > 9) s << "0x";
|
||||
if (x > 4095) s << HexU(static_cast<uint8_t>(x >> 8));
|
||||
if (x > 255) s << HexL(static_cast<uint8_t>(x >> 8));
|
||||
if (x > 15) s << HexU(static_cast<uint8_t>(x));
|
||||
s << HexL(static_cast<uint8_t>(x));
|
||||
}
|
||||
template <> void CHex(std::ostream& s, uint32_t x)
|
||||
{
|
||||
if (x > 9) s << "0x";
|
||||
if (x > 0xFFFFFFF) s << HexU(static_cast<uint8_t>(x >> 24));
|
||||
if (x > 0xFFFFFF) s << HexL(static_cast<uint8_t>(x >> 24));
|
||||
if (x > 0xFFFFF) s << HexU(static_cast<uint8_t>(x >> 16));
|
||||
if (x > 65535) s << HexL(static_cast<uint8_t>(x >> 16));
|
||||
if (x > 4095) s << HexU(static_cast<uint8_t>(x >> 8));
|
||||
if (x > 255) s << HexL(static_cast<uint8_t>(x >> 8));
|
||||
if (x > 15) s << HexU(static_cast<uint8_t>(x));
|
||||
s << HexL(static_cast<uint8_t>(x));
|
||||
}
|
||||
|
||||
|
||||
template <typename T> static constexpr const std::string_view DataType();
|
||||
template <> constexpr const std::string_view DataType<uint8_t>() { return ".byte"; }
|
||||
template <> constexpr const std::string_view DataType<uint16_t>() { return ".hword"; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* swwriter.hpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
|
||||
|
||||
#ifndef SWRITER_HPP
|
||||
#define SWRITER_HPP
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
/* tmx2gba.cpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// tmx2gba.cpp - main entry point
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
|
||||
|
||||
#include <string_view>
|
||||
constexpr std::string_view copyrightStr("(c) 2015-2024 a dinosaur");
|
||||
|
||||
#include "argparse.hpp"
|
||||
#include "tmxreader.hpp"
|
||||
#include "convert.hpp"
|
||||
#include "headerwriter.hpp"
|
||||
#include "swriter.hpp"
|
||||
#include "strtools.hpp"
|
||||
#include "config.h"
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
@@ -19,15 +25,14 @@ struct Arguments
|
||||
int offset = 0;
|
||||
int palette = 0;
|
||||
std::vector<std::string> objMappings;
|
||||
bool help = false, showVersion = false;
|
||||
bool help = false;
|
||||
};
|
||||
|
||||
using ArgParse::Option;
|
||||
|
||||
static const ArgParse::Options options =
|
||||
{
|
||||
Option::Optional('h', {}, "Display this help & command info"),
|
||||
Option::Optional('v', {}, "Display version & quit"),
|
||||
Option::Optional('h', {}, "Display help & command info"),
|
||||
Option::Optional('l', "name", "Name of layer to use (default first layer in TMX)"),
|
||||
Option::Optional('y', "name", "Layer for palette mappings"),
|
||||
Option::Optional('c', "name", "Output a separate 8bit collision map of the specified layer"),
|
||||
@@ -51,7 +56,6 @@ static bool ParseArgs(int argc, char** argv, Arguments& params)
|
||||
switch (opt)
|
||||
{
|
||||
case 'h': params.help = true; return ParseCtrl::QUIT_EARLY;
|
||||
case 'v': params.showVersion = true; return ParseCtrl::QUIT_EARLY;
|
||||
case 'l': params.layer = arg; return ParseCtrl::CONTINUE;
|
||||
case 'c': params.collisionlay = arg; return ParseCtrl::CONTINUE;
|
||||
case 'y': params.paletteLay = arg; return ParseCtrl::CONTINUE;
|
||||
@@ -69,11 +73,8 @@ static bool ParseArgs(int argc, char** argv, Arguments& params)
|
||||
catch (std::out_of_range const&) { return ParseCtrl::QUIT_ERR_RANGE; }
|
||||
});
|
||||
|
||||
if (!parser.Parse(std::span(argv + 1, argc - 1)))
|
||||
return false;
|
||||
|
||||
if (params.help || params.showVersion)
|
||||
return true;
|
||||
if (!parser.Parse(std::span(argv + 1, argc - 1))) { return false; }
|
||||
if (params.help) { return true; }
|
||||
|
||||
if (!params.flagFile.empty())
|
||||
{
|
||||
@@ -115,41 +116,16 @@ static bool ParseArgs(int argc, char** argv, Arguments& params)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static std::string SanitiseLabel(const std::string_view ident)
|
||||
{
|
||||
std::string out;
|
||||
out.reserve(ident.length());
|
||||
|
||||
int last = '_';
|
||||
for (int i : ident)
|
||||
{
|
||||
if (out.empty() && std::isdigit(i))
|
||||
continue;
|
||||
if (!std::isalnum(i))
|
||||
i = '_';
|
||||
if (i != '_' || last != '_')
|
||||
out.push_back(i);
|
||||
last = i;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Arguments p;
|
||||
if (!ParseArgs(argc, argv, p))
|
||||
return 1;
|
||||
if (!ParseArgs(argc, argv, p)) { return 1; }
|
||||
if (p.help)
|
||||
{
|
||||
std::cout << "tmx2gba v" << TMX2GBA_VERSION << ", " << copyrightStr << std::endl;
|
||||
options.ShowHelpUsage(argv[0], std::cout);
|
||||
return 0;
|
||||
}
|
||||
if (p.showVersion)
|
||||
{
|
||||
std::cout << "tmx2gba version " << TMX2GBA_VERSION << ", (c) 2015-2024 a dinosaur" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Object mappings
|
||||
std::map<std::string, uint32_t> objMapping;
|
||||
@@ -222,8 +198,7 @@ int main(int argc, char** argv)
|
||||
// Convert to GBA-friendly charmap data
|
||||
{
|
||||
std::vector<uint16_t> charDat;
|
||||
if (!convert::ConvertCharmap(charDat, p.offset, p.palette, tmx))
|
||||
return 1;
|
||||
if (!convert::ConvertCharmap(charDat, p.offset, p.palette, tmx)) { return 1; }
|
||||
|
||||
// Write out charmap
|
||||
outH.WriteSize(tmx.GetSize().width, tmx.GetSize().height);
|
||||
@@ -235,8 +210,7 @@ int main(int argc, char** argv)
|
||||
if (tmx.HasCollisionTiles())
|
||||
{
|
||||
std::vector<uint8_t> collisionDat;
|
||||
if (!convert::ConvertCollision(collisionDat, tmx))
|
||||
return 1;
|
||||
if (!convert::ConvertCollision(collisionDat, tmx)) { return 1; }
|
||||
|
||||
outH.WriteCollision(collisionDat);
|
||||
outS.WriteArray("Collision", collisionDat, 32);
|
||||
@@ -245,8 +219,7 @@ int main(int argc, char** argv)
|
||||
if (tmx.HasObjects())
|
||||
{
|
||||
std::vector<uint32_t> objDat;
|
||||
if (!convert::ConvertObjects(objDat, tmx))
|
||||
return 1;
|
||||
if (!convert::ConvertObjects(objDat, tmx)) { return 1; }
|
||||
|
||||
outH.WriteObjects(objDat);
|
||||
outS.WriteArray("Objdat", objDat);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* tmxlayer.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
|
||||
|
||||
#ifndef TMXLAYER_HPP
|
||||
#define TMXLAYER_HPP
|
||||
|
||||
168
src/tmxmap.cpp
168
src/tmxmap.cpp
@@ -2,6 +2,8 @@
|
||||
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
|
||||
|
||||
#include "tmxmap.hpp"
|
||||
#include "strtools.hpp"
|
||||
#include "config.h"
|
||||
#include <pugixml.hpp>
|
||||
#include <base64.h>
|
||||
#ifdef USE_ZLIB
|
||||
@@ -10,80 +12,10 @@
|
||||
# include "gzip.hpp"
|
||||
#endif
|
||||
#include <zstd.h>
|
||||
#include <limits>
|
||||
#include <cerrno>
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] static std::optional<T> IntFromStr(const char* str, int base = 0) noexcept
|
||||
{
|
||||
using std::numeric_limits;
|
||||
|
||||
errno = 0;
|
||||
char* end = nullptr;
|
||||
long res = std::strtol(str, &end, base);
|
||||
if (errno == ERANGE) { return std::nullopt; }
|
||||
if (str == end) { return std::nullopt; }
|
||||
if constexpr (sizeof(long) > sizeof(T))
|
||||
{
|
||||
if (res > numeric_limits<T>::max() || res < numeric_limits<T>::min())
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return static_cast<T>(res);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] static std::optional<T> UintFromStr(const char* str, int base = 0) noexcept
|
||||
{
|
||||
using std::numeric_limits;
|
||||
|
||||
char* end = nullptr;
|
||||
errno = 0;
|
||||
unsigned long res = std::strtoul(str, &end, base);
|
||||
if (errno == ERANGE) { return std::nullopt; }
|
||||
if (str == end) { return std::nullopt; }
|
||||
if constexpr (numeric_limits<unsigned long>::max() > numeric_limits<T>::max())
|
||||
{
|
||||
if (res > numeric_limits<T>::max()) { return std::nullopt; }
|
||||
}
|
||||
|
||||
return static_cast<T>(res);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] static std::optional<T> FloatFromStr(const char* str) noexcept
|
||||
{
|
||||
char* end = nullptr;
|
||||
T res;
|
||||
errno = 0;
|
||||
if constexpr (std::is_same_v<T, float>)
|
||||
res = std::strtof(str, &end);
|
||||
else
|
||||
res = static_cast<T>(std::strtod(str, &end));
|
||||
if (errno == ERANGE) { return std::nullopt; }
|
||||
if (str == end) { return std::nullopt; }
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
[[nodiscard]] static std::optional<std::string> UnBase64(const std::string_view base64)
|
||||
{
|
||||
// Cut leading & trailing whitespace (including newlines)
|
||||
auto beg = std::find_if_not(base64.begin(), base64.end(), ::isspace);
|
||||
if (beg == std::end(base64)) { return std::nullopt; }
|
||||
auto end = std::find_if_not(base64.rbegin(), base64.rend(), ::isspace);
|
||||
auto begOff = std::distance(base64.begin(), beg);
|
||||
auto endOff = std::distance(end, base64.rend()) - begOff;
|
||||
using size_type = std::string::size_type;
|
||||
const auto trimmed = base64.substr(static_cast<size_type>(begOff), static_cast<size_type>(endOff));
|
||||
|
||||
// Decode base64 string
|
||||
return base64_decode(trimmed);
|
||||
}
|
||||
|
||||
enum class Encoding { XML, BASE64, CSV, INVALID };
|
||||
enum class Compression { NONE, GZIP, ZLIB, ZSTD, INVALID };
|
||||
|
||||
@@ -104,32 +36,40 @@ enum class Compression { NONE, GZIP, ZLIB, ZSTD, INVALID };
|
||||
return Compression::INVALID;
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool Decompress(Compression compression, std::span<uint32_t> out, const std::string_view decoded)
|
||||
{
|
||||
//FIXME: lmao what is big endian
|
||||
const std::span source(reinterpret_cast<const uint8_t*>(decoded.data()), decoded.size());
|
||||
std::span destination(reinterpret_cast<uint8_t*>(out.data()), sizeof(uint32_t) * out.size());
|
||||
|
||||
[[nodiscard]] static bool DecodeBase64(
|
||||
std::vector<uint32_t>& out, size_t numTiles,
|
||||
const std::string_view base64, Compression compression)
|
||||
{
|
||||
auto decoded = base64_decode(TrimWhitespace(base64));
|
||||
if (decoded.empty()) { return false; }
|
||||
const std::span source(reinterpret_cast<const uint8_t*>(decoded.data()), decoded.size());
|
||||
|
||||
//FIXME: lmao what is big endian
|
||||
switch (compression)
|
||||
{
|
||||
case Compression::GZIP:
|
||||
#ifndef USE_ZLIB
|
||||
{
|
||||
out.resize(numTiles);
|
||||
GZipReader reader;
|
||||
if (!reader.OpenMemory(source) || !reader.Read(destination) || !reader.Check())
|
||||
if (!reader.OpenMemory(source) ||
|
||||
!reader.Read({ reinterpret_cast<uint8_t*>(out.data()), sizeof(uint32_t) * numTiles }) ||
|
||||
!reader.Check())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
case Compression::ZLIB:
|
||||
{
|
||||
out.resize(numTiles);
|
||||
// Decompress gzip/zlib data with zlib/zlib data miniz
|
||||
z_stream s =
|
||||
{
|
||||
.next_in = const_cast<Bytef*>(source.data()),
|
||||
.avail_in = static_cast<unsigned int>(source.size()),
|
||||
.next_out = static_cast<Bytef*>(destination.data()),
|
||||
.avail_out = static_cast<unsigned int>(destination.size()),
|
||||
.next_out = reinterpret_cast<Bytef*>(out.data()),
|
||||
.avail_out = static_cast<unsigned int>(sizeof(uint32_t) * numTiles),
|
||||
.zalloc = nullptr, .zfree = nullptr, .opaque = nullptr
|
||||
};
|
||||
#ifdef USE_ZLIB
|
||||
@@ -145,16 +85,29 @@ enum class Compression { NONE, GZIP, ZLIB, ZSTD, INVALID };
|
||||
}
|
||||
case Compression::ZSTD:
|
||||
{
|
||||
out.resize(numTiles);
|
||||
auto res = ZSTD_decompress(
|
||||
destination.data(), destination.size(),
|
||||
reinterpret_cast<void*>(out.data()),
|
||||
sizeof(uint32_t) * numTiles,
|
||||
source.data(), source.size());
|
||||
return !ZSTD_isError(res);
|
||||
}
|
||||
// Define all labels to shut up linters
|
||||
case Compression::NONE:
|
||||
{
|
||||
out.reserve(numTiles);
|
||||
const auto end = source.end();
|
||||
for (auto it = source.begin(); it < end - 3;)
|
||||
{
|
||||
uint32_t tile = *it++;
|
||||
tile |= static_cast<uint32_t>(*it++) << 8u;
|
||||
tile |= static_cast<uint32_t>(*it++) << 16u;
|
||||
tile |= static_cast<uint32_t>(*it++) << 24u;
|
||||
out.emplace_back(tile);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case Compression::INVALID:
|
||||
//default:
|
||||
return false;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,32 +143,13 @@ void TmxMap::ReadLayer(const pugi::xml_node& xNode)
|
||||
auto encoding = EncodingFromStr(xData.attribute("encoding").value());
|
||||
if (encoding == Encoding::BASE64)
|
||||
{
|
||||
// Decode base64 string
|
||||
auto decoded = UnBase64(xData.child_value());
|
||||
if (!decoded.has_value())
|
||||
const std::string_view base64(xData.child_value());
|
||||
if (base64.empty())
|
||||
return;
|
||||
|
||||
auto compression = CompressionFromStr(xData.attribute("compression").value());
|
||||
if (compression == Compression::GZIP || compression == Compression::ZLIB || compression == Compression::ZSTD)
|
||||
{
|
||||
tileDat.resize(numTiles);
|
||||
if (!Decompress(compression, tileDat, decoded.value()))
|
||||
return;
|
||||
}
|
||||
else if (compression == Compression::NONE)
|
||||
{
|
||||
tileDat.reserve(numTiles);
|
||||
const auto end = decoded.value().end();
|
||||
for (auto it = decoded.value().begin(); it < end - 3;)
|
||||
{
|
||||
uint32_t tile = static_cast<uint32_t>(static_cast<uint8_t>(*it++));
|
||||
tile |= static_cast<uint32_t>(static_cast<uint8_t>(*it++)) << 8u;
|
||||
tile |= static_cast<uint32_t>(static_cast<uint8_t>(*it++)) << 16u;
|
||||
tile |= static_cast<uint32_t>(static_cast<uint8_t>(*it++)) << 24u;
|
||||
tileDat.emplace_back(tile);
|
||||
}
|
||||
}
|
||||
else { return; }
|
||||
const auto compression = CompressionFromStr(xData.attribute("compression").value());
|
||||
if (compression == Compression::INVALID || !DecodeBase64(tileDat, numTiles, base64, compression))
|
||||
return;
|
||||
}
|
||||
else if (encoding == Encoding::XML)
|
||||
{
|
||||
@@ -246,18 +180,30 @@ void TmxMap::ReadLayer(const pugi::xml_node& xNode)
|
||||
mLayers.emplace_back(TmxLayer(width, height, name, std::move(tileDat)));
|
||||
}
|
||||
|
||||
void TmxMap::ReadObjects(const pugi::xml_node& xNode)
|
||||
void TmxMap::ReadObjectGroup(const pugi::xml_node& xNode)
|
||||
{
|
||||
for (const auto it : xNode.children("object"))
|
||||
std::string_view name(xNode.value());
|
||||
std::vector<TmxObject> objects;
|
||||
|
||||
const auto xObjects = xNode.children("object");
|
||||
//mObjects.reserve(xObjects.size())
|
||||
for (const auto it : xObjects)
|
||||
{
|
||||
int id = IntFromStr<int>(it.attribute("id").value()).value_or(0);
|
||||
std::string_view name = it.attribute("name").value();
|
||||
|
||||
// Read position
|
||||
// Read axis-aligned bounding box
|
||||
auto x = FloatFromStr<float>(it.attribute("x").value()).value_or(0.0f);
|
||||
auto y = FloatFromStr<float>(it.attribute("y").value()).value_or(0.0f);
|
||||
auto width = FloatFromStr<float>(it.attribute("width").value()).value_or(0.0f);
|
||||
auto height = FloatFromStr<float>(it.attribute("height").value()).value_or(0.0f);
|
||||
|
||||
mObjects.emplace_back(TmxObject(name, x, y));
|
||||
objects.emplace_back(TmxObject(id, name, { x, y, width, height }));
|
||||
}
|
||||
|
||||
if (objects.empty())
|
||||
return; //FIXME: log this
|
||||
mObjectGroups.emplace_back(TmxObjectGroup(name, std::move(objects)));
|
||||
}
|
||||
|
||||
bool TmxMap::Load(const std::string& inPath)
|
||||
@@ -283,7 +229,7 @@ bool TmxMap::Load(const std::string& inPath)
|
||||
std::string_view name(it.name());
|
||||
if (!name.compare("layer")) { ReadLayer(it); }
|
||||
else if (!name.compare("tileset")) { ReadTileset(it); }
|
||||
else if (!name.compare("objectgroup")) { ReadObjects(it); }
|
||||
else if (!name.compare("objectgroup")) { ReadObjectGroup(it); }
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -18,20 +18,21 @@ class TmxMap
|
||||
{
|
||||
int mWidth = 0, mHeight = 0;
|
||||
|
||||
std::vector<TmxLayer> mLayers;
|
||||
std::vector<TmxTileset> mTilesets;
|
||||
std::vector<TmxObject> mObjects;
|
||||
std::vector<TmxLayer> mLayers;
|
||||
std::vector<TmxTileset> mTilesets;
|
||||
std::vector<TmxObjectGroup> mObjectGroups;
|
||||
|
||||
void ReadTileset(const pugi::xml_node& xNode);
|
||||
void ReadLayer(const pugi::xml_node& xNode);
|
||||
void ReadObjects(const pugi::xml_node& xNode);
|
||||
void ReadObjectGroup(const pugi::xml_node& xNode);
|
||||
|
||||
public:
|
||||
[[nodiscard]] bool Load(const std::string& inPath);
|
||||
|
||||
constexpr std::pair<int, int> TileCount() const noexcept { return { mWidth, mHeight }; }
|
||||
constexpr const std::vector<TmxTileset>& Tilesets() const noexcept { return mTilesets; }
|
||||
constexpr const std::vector<TmxLayer>& Layers() const noexcept { return mLayers; }
|
||||
[[nodiscard]] constexpr std::pair<int, int> TileCount() const noexcept { return { mWidth, mHeight }; }
|
||||
[[nodiscard]] constexpr const std::vector<TmxTileset>& Tilesets() const noexcept { return mTilesets; }
|
||||
[[nodiscard]] constexpr const std::vector<TmxLayer>& Layers() const noexcept { return mLayers; }
|
||||
[[nodiscard]] constexpr const std::vector<TmxObjectGroup>& ObjectGroups() const noexcept { return mObjectGroups; }
|
||||
};
|
||||
|
||||
#endif//TMXMAP_HPP
|
||||
|
||||
@@ -1,25 +1,43 @@
|
||||
/* tmxobject.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
|
||||
|
||||
#ifndef TMXOBJECT_HPP
|
||||
#define TMXOBJECT_HPP
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
class TmxObject
|
||||
{
|
||||
public:
|
||||
TmxObject(std::string_view name, float x, float y) : mName(name), mPos{ x, y } {}
|
||||
|
||||
template <typename T>
|
||||
struct Position { T x, y; };
|
||||
struct AABB { T x, y, w, h; };
|
||||
|
||||
TmxObject(int id, std::string_view name, AABB<float>&& box) : mId(id), mName(name), mBox(std::move(box)) {}
|
||||
|
||||
constexpr int Id() const noexcept { return mId; }
|
||||
const std::string_view Name() const noexcept { return mName; }
|
||||
constexpr Position<float> Pos() const noexcept { return mPos; }
|
||||
constexpr const AABB<float>& Box() const noexcept { return mBox; }
|
||||
|
||||
private:
|
||||
int mId;
|
||||
std::string mName;
|
||||
Position<float> mPos;
|
||||
AABB<float> mBox;
|
||||
};
|
||||
|
||||
class TmxObjectGroup
|
||||
{
|
||||
std::string mName;
|
||||
std::vector<TmxObject> mObjects;
|
||||
|
||||
public:
|
||||
TmxObjectGroup(std::string_view name, std::vector<TmxObject>&& objects)
|
||||
: mName(name), mObjects(std::move(objects)) {}
|
||||
|
||||
const std::string_view Name() const noexcept { return mName; }
|
||||
constexpr const std::vector<TmxObject>& Objects() const noexcept { return mObjects; }
|
||||
};
|
||||
|
||||
#endif//TMXOBJECT_HPP
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* tmxreader.cpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
|
||||
|
||||
#include "tmxreader.hpp"
|
||||
#include "tmxmap.hpp"
|
||||
@@ -20,10 +21,7 @@ TmxReader::Error TmxReader::Open(const std::string& inPath,
|
||||
using std::optional;
|
||||
using std::reference_wrapper;
|
||||
|
||||
optional<reference_wrapper<const TmxLayer>> layerGfx;
|
||||
optional<reference_wrapper<const TmxLayer>> layerCls;
|
||||
optional<reference_wrapper<const TmxLayer>> layerPal;
|
||||
optional<reference_wrapper<std::vector<const TmxObject>>> objGroups;
|
||||
optional<reference_wrapper<const TmxLayer>> layerGfx, layerCls, layerPal;
|
||||
|
||||
// Read layers
|
||||
for (const auto& layer : map.Layers())
|
||||
@@ -36,12 +34,6 @@ TmxReader::Error TmxReader::Open(const std::string& inPath,
|
||||
if (!layerGfx.has_value() && (graphicsName.empty() || name == graphicsName)) { layerGfx = layer; }
|
||||
if (!collisionName.empty() && !layerCls.has_value() && name == collisionName) { layerCls = layer; }
|
||||
if (!paletteName.empty() && !layerPal.has_value() && name == paletteName) { layerPal = layer; }
|
||||
/*
|
||||
else if (!objMapping.empty() && layer->getType() == tmx::Layer::Type::Object)
|
||||
{
|
||||
objGroups.emplace_back(layer->getLayerAs<ObjectGroup>());
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// Check layers
|
||||
@@ -88,31 +80,30 @@ TmxReader::Error TmxReader::Open(const std::string& inPath,
|
||||
[](const auto& it) { return it.GidRange(); });
|
||||
|
||||
// Read objects
|
||||
/*
|
||||
if (!objMapping.empty())
|
||||
if (!map.ObjectGroups().empty())
|
||||
{
|
||||
for (const auto& group : objGroups)
|
||||
std::vector<Object> objs;
|
||||
for (const auto& group : map.ObjectGroups())
|
||||
{
|
||||
const auto& tmxObjects = group.get().Objects();
|
||||
v.reserve(v.size() + tmxObjects.size());
|
||||
const auto& tmxObjects = group.Objects();
|
||||
objs.reserve(objs.size() + tmxObjects.size());
|
||||
for (const auto& tmxObj : tmxObjects)
|
||||
{
|
||||
auto it = objMapping.find(std::string(tmxObj.Name()));
|
||||
if (it == objMapping.end())
|
||||
continue;
|
||||
|
||||
const auto& pos = tmxObj.Pos();
|
||||
Object obj;
|
||||
obj.id = it->second;
|
||||
obj.x = pos.x;
|
||||
obj.y = pos.y;
|
||||
|
||||
v.emplace_back(obj);
|
||||
const auto& aabb = tmxObj.Box();
|
||||
objs.emplace_back(Object
|
||||
{
|
||||
.id = it->second,
|
||||
.x = aabb.x,
|
||||
.y = aabb.y
|
||||
});
|
||||
}
|
||||
}
|
||||
mObjects.emplace(v);
|
||||
mObjects.emplace(objs);
|
||||
}
|
||||
*/
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* tmxreader.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
|
||||
|
||||
#ifndef TMXREADER_HPP
|
||||
#define TMXREADER_HPP
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* tmxtileset.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
// SPDX-License-Identifier: Zlib
|
||||
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
|
||||
|
||||
#ifndef TMXTILESET_HPP
|
||||
#define TMXTILESET_HPP
|
||||
|
||||
Reference in New Issue
Block a user