From c9219874e3d21ab24cbc31264dd5073b7bc3b83b Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 27 Mar 2018 10:04:12 +0200 Subject: [PATCH] Refactor mapper class --- Minetestmapper/CMakeLists.txt | 2 + Minetestmapper/main.cpp | 28 + Minetestmapper/mapper.cpp | 2056 ++++++++++++++++----------------- Minetestmapper/mapper.h | 164 +++ 4 files changed, 1177 insertions(+), 1073 deletions(-) create mode 100644 Minetestmapper/main.cpp create mode 100644 Minetestmapper/mapper.h diff --git a/Minetestmapper/CMakeLists.txt b/Minetestmapper/CMakeLists.txt index f88ed35..9553f70 100644 --- a/Minetestmapper/CMakeLists.txt +++ b/Minetestmapper/CMakeLists.txt @@ -21,6 +21,8 @@ set(sources BlockPos.cpp BlockPos.h mapper.cpp + mapper.h + main.cpp CharEncodingConverter.cpp CharEncodingConverter.h PaintEngine_libgd.cpp diff --git a/Minetestmapper/main.cpp b/Minetestmapper/main.cpp new file mode 100644 index 0000000..9db8118 --- /dev/null +++ b/Minetestmapper/main.cpp @@ -0,0 +1,28 @@ +#include "mapper.h" +#include + +int main(int argc, char *argv[]) +{ + std::string executableName; + std::string executablePath; + if (argc) { + std::string argv0 = argv[0]; + size_t pos = argv0.find_last_of(PATH_SEPARATOR); + if (pos == std::string::npos) { + if (!argv0.empty()) + executableName = argv0; + else + executableName = DEFAULT_PROGRAM_NAME; + executablePath = ""; + + } + else { + executableName = argv0.substr(pos + 1); + executablePath = argv0.substr(0, pos); + } + } + + Mapper m(executableName, executablePath); + return m.start(argc, argv); + +} diff --git a/Minetestmapper/mapper.cpp b/Minetestmapper/mapper.cpp index f988d7b..1ce730c 100644 --- a/Minetestmapper/mapper.cpp +++ b/Minetestmapper/mapper.cpp @@ -7,6 +7,8 @@ * ===================================================================== */ +#include "mapper.h" + #include #include @@ -32,177 +34,958 @@ using namespace std; -// Will be used in error messages if nothing better is available. -#define DEFAULT_PROGRAM_NAME "minetestmapper" -#define OPT_SQLITE_CACHEWORLDROW 0x81 -#define OPT_PROGRESS_INDICATOR 0x82 -#define OPT_DRAW_OBJECT 0x83 -#define OPT_BLOCKCOLOR 0x84 -#define OPT_DRAWAIR 0x85 -#define OPT_VERBOSE_SEARCH_COLORS 0x86 -#define OPT_CHUNKSIZE 0x87 -#define OPT_HEIGHTMAP 0x88 -#define OPT_HEIGHTMAPYSCALE 0x89 -#define OPT_HEIGHT_LEVEL0 0x8a -#define OPT_HEIGHTMAPNODESFILE 0x8b -#define OPT_HEIGHTMAPCOLORSFILE 0x8c -#define OPT_DRAWHEIGHTSCALE 0x8d -#define OPT_SCALEFACTOR 0x8e -#define OPT_SCALEINTERVAL 0x8f -#define OPT_NO_BLOCKLIST_PREFETCH 0x90 -#define OPT_DATABASE_FORMAT 0x91 -#define OPT_SILENCE_SUGGESTIONS 0x92 -#define OPT_PRESCAN_WORLD 0x93 -#define OPT_DRAWNODES 0x94 -#define OPT_SQLITE_LIMIT_PRESCAN_QUERY 0x95 - -#define DRAW_ARROW_LENGTH 10 -#define DRAW_ARROW_ANGLE 30 - - -// Will be replaced with the actual name and location of the executable (if found) -string executableName = DEFAULT_PROGRAM_NAME; -string executablePath; // ONLY for use on windows -//string installPrefix = INSTALL_PREFIX; -string nodeColorsDefaultFile = "colors.txt"; -string heightMapNodesDefaultFile = "heightmap-nodes.txt"; -string heightMapColorsDefaultFile = "heightmap-colors.txt"; - -class FuzzyBool { -private: - int m_value; - FuzzyBool(int i) : m_value(i) {} -public: - FuzzyBool() : m_value(0) {} - FuzzyBool(bool b) : m_value(b ? Yes.m_value : No.m_value) {} - static const FuzzyBool Yes; - static const FuzzyBool Maybe; - static const FuzzyBool No; -inline friend bool operator==(FuzzyBool f1, FuzzyBool f2) { return f1.m_value == f2.m_value; } -inline friend bool operator!=(FuzzyBool f1, FuzzyBool f2) { return f1.m_value != f2.m_value; } -inline friend bool operator>=(FuzzyBool f1, FuzzyBool f2) { return f1.m_value >= f2.m_value; } -inline friend bool operator<=(FuzzyBool f1, FuzzyBool f2) { return f1.m_value <= f2.m_value; } -inline friend bool operator<(FuzzyBool f1, FuzzyBool f2) { return f1.m_value < f2.m_value; } -inline friend bool operator>(FuzzyBool f1, FuzzyBool f2) { return f1.m_value < f2.m_value; } -}; const FuzzyBool FuzzyBool::Yes = 1; const FuzzyBool FuzzyBool::Maybe = 0; const FuzzyBool FuzzyBool::No = -1; -static inline bool validStreamAtWsOrEof(std::istream &is) +Mapper::Mapper(const string &executableName, const string &executablePath) : + executablePath(executablePath), + executableName(executableName) { - // May need to peek before knowing it's EOF. - // However, peeking twice results in fail()... - return !is.fail() && (is.eof() || is.peek() == ' ' || is.eof() || is.peek() == '\t'); + charConvUTF8 = CharEncodingConverter::createStandardConverter("UTF-8"); } -static inline bool validStreamAtEof(std::istream &is) -{ - // May need to peek before knowing it's EOF. - // However, peeking twice at eof results in fail()... - return !is.fail() && (is.eof() || (is.peek(), is.eof())); +int Mapper::start(int argc, char *argv[]) { + + static struct option long_options[] = + { + { "help", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'V' }, + { "input", required_argument, 0, 'i' }, + { "output", required_argument, 0, 'o' }, + { "colors", required_argument, 0, 'C' }, + { "heightmap-nodes", required_argument, 0, OPT_HEIGHTMAPNODESFILE }, + { "heightmap-colors", required_argument, 0, OPT_HEIGHTMAPCOLORSFILE }, + { "heightmap", optional_argument, 0, OPT_HEIGHTMAP }, + { "heightmap-yscale", required_argument, 0, OPT_HEIGHTMAPYSCALE }, + { "height-level-0", required_argument, 0, OPT_HEIGHT_LEVEL0 }, + { "bgcolor", required_argument, 0, 'b' }, + { "blockcolor", required_argument, 0, OPT_BLOCKCOLOR }, + { "scalecolor", required_argument, 0, 's' }, + { "origincolor", required_argument, 0, 'r' }, + { "playercolor", required_argument, 0, 'p' }, + { "draworigin", no_argument, 0, 'R' }, + { "drawplayers", no_argument, 0, 'P' }, + { "drawscale", optional_argument, 0, 'S' }, + { "sidescale-interval", required_argument, 0, OPT_SCALEINTERVAL }, + { "drawheightscale", no_argument, 0, OPT_DRAWHEIGHTSCALE }, + { "heightscale-interval", required_argument, 0, OPT_SCALEINTERVAL }, + { "drawalpha", optional_argument, 0, 'e' }, + { "drawair", no_argument, 0, OPT_DRAWAIR }, + { "drawnodes", required_argument, 0, OPT_DRAWNODES }, + { "ignorenodes", required_argument, 0, OPT_DRAWNODES }, + { "drawpoint", required_argument, 0, OPT_DRAW_OBJECT }, + { "drawline", required_argument, 0, OPT_DRAW_OBJECT }, + { "drawcircle", required_argument, 0, OPT_DRAW_OBJECT }, + { "drawellipse", required_argument, 0, OPT_DRAW_OBJECT }, + { "drawrectangle", required_argument, 0, OPT_DRAW_OBJECT }, + { "drawarrow", required_argument, 0, OPT_DRAW_OBJECT }, + { "drawtext", required_argument, 0, OPT_DRAW_OBJECT }, + { "drawmappoint", required_argument, 0, OPT_DRAW_OBJECT }, + { "drawmapline", required_argument, 0, OPT_DRAW_OBJECT }, + { "drawmapcircle", required_argument, 0, OPT_DRAW_OBJECT }, + { "drawmapellipse", required_argument, 0, OPT_DRAW_OBJECT }, + { "drawmaprectangle", required_argument, 0, OPT_DRAW_OBJECT }, + { "drawmaparrow", required_argument, 0, OPT_DRAW_OBJECT }, + { "drawmaptext", required_argument, 0, OPT_DRAW_OBJECT }, + { "noshading", no_argument, 0, 'H' }, + { "geometry", required_argument, 0, 'g' }, + { "cornergeometry", required_argument, 0, 'g' }, + { "centergeometry", required_argument, 0, 'g' }, + { "geometrymode", required_argument, 0, 'G' }, + { "forcegeometry", no_argument, 0, 'G' }, + { "min-y", required_argument, 0, 'a' }, + { "max-y", required_argument, 0, 'c' }, + { "backend", required_argument, 0, 'd' }, + { "disable-blocklist-prefetch", optional_argument, 0, OPT_NO_BLOCKLIST_PREFETCH }, + { "database-format", required_argument, 0, OPT_DATABASE_FORMAT }, + { "prescan-world", required_argument, 0, OPT_PRESCAN_WORLD }, + { "sqlite-cacheworldrow", no_argument, 0, OPT_SQLITE_CACHEWORLDROW }, + { "sqlite3-limit-prescan-query-size", optional_argument, 0, OPT_SQLITE_LIMIT_PRESCAN_QUERY }, + { "tiles", required_argument, 0, 't' }, + { "tileorigin", required_argument, 0, 'T' }, + { "tilecenter", required_argument, 0, 'T' }, + { "tilebordercolor", required_argument, 0, 'B' }, + { "scalefactor", required_argument, 0, OPT_SCALEFACTOR }, + { "chunksize", required_argument, 0, OPT_CHUNKSIZE }, + { "silence-suggestions", required_argument, 0, OPT_SILENCE_SUGGESTIONS }, + { "verbose", optional_argument, 0, 'v' }, + { "verbose-search-colors", optional_argument, 0, OPT_VERBOSE_SEARCH_COLORS }, + { "progress", no_argument, 0, OPT_PROGRESS_INDICATOR }, + { NULL, 0, 0, 0 } + }; + + try { + int option_index = 0; + int c = 0; + while (1) { + c = getopt_long(argc, argv, "hi:o:", long_options, &option_index); + if (c == -1) { + if (input.empty() || output.empty()) { + std::cerr << "Input (world directory) or output (PNG filename) missing" << std::endl; + usage(); + return 0; + } + break; + } + switch (c) { + case 'h': + usage(); + return 0; + break; + case 'V': + cout << "Minetestmapper - Version-ID: " << PROJECT_VERSION_MAJOR << "." << PROJECT_VERSION_MINOR << std::endl; + return 0; + break; + case 'i': + input = optarg; + break; + case 'o': + output = optarg; + break; + case 'C': + nodeColorsFile = optarg; + break; + case OPT_HEIGHTMAPNODESFILE: + heightMapNodesFile = optarg; + break; + case OPT_HEIGHTMAPCOLORSFILE: + heightMapColorsFile = optarg; + break; + case 'b': + generator.setBgColor(Color(optarg, 0)); + break; + case OPT_NO_BLOCKLIST_PREFETCH: + if (optarg && *optarg) { + if (strlower(optarg) == "force") + generator.setGenerateNoPrefetch(2); + else { + std::cerr << "Invalid parameter to '" << long_options[option_index].name << "'; expected 'force' or nothing." << std::endl; + usage(); + exit(1); + } + } + else { + generator.setGenerateNoPrefetch(1); + } + break; + case OPT_DATABASE_FORMAT: { + std::string opt = strlower(optarg); + if (opt == "minetest-i64") + generator.setDBFormat(BlockPos::I64, false); + else if (opt == "freeminer-axyz") + generator.setDBFormat(BlockPos::AXYZ, false); + else if (opt == "mixed") + generator.setDBFormat(BlockPos::Unknown, false); + else if (opt == "query") + generator.setDBFormat(BlockPos::Unknown, true); + else { + std::cerr << "Invalid parameter to '" << long_options[option_index].name << "': '" << optarg << "'" << std::endl; + usage(); + exit(1); + } + } + break; + case OPT_PRESCAN_WORLD: { + std::string opt = strlower(optarg); + generator.setGenerateNoPrefetch(0); + if (opt == "disabled-force") + generator.setGenerateNoPrefetch(2); + else if (opt == "disabled") + generator.setGenerateNoPrefetch(1); + else if (opt == "auto") + generator.setScanEntireWorld(false); + else if (opt == "full") + generator.setScanEntireWorld(true); + else { + std::cerr << "Invalid parameter to '" << long_options[option_index].name << "': '" << optarg << "'" << std::endl; + usage(); + exit(1); + } + } + break; + case OPT_SQLITE_LIMIT_PRESCAN_QUERY: + if (!optarg || !*optarg) { +#ifdef USE_SQLITE3 + DBSQLite3::setLimitBlockListQuerySize(); +#endif + } + else { + if (!isdigit(optarg[0])) { + std::cerr << "Invalid parameter to '" << long_options[option_index].name << "': must be a positive number" << std::endl; + usage(); + exit(1); + } +#ifdef USE_SQLITE3 + int size = atoi(optarg); + DBSQLite3::setLimitBlockListQuerySize(size); +#endif + } + break; + case OPT_HEIGHTMAP: + generator.setHeightMap(true); + heightMap = true; + if (optarg && *optarg) { + loadHeightMapColorsFile = false; + std::string color = strlower(optarg); + if (color == "grey" || color == "gray") + generator.setHeightMapColor(Color(0, 0, 0), Color(255, 255, 255)); + else if (color == "black") + generator.setHeightMapColor(Color(0, 0, 0), Color(0, 0, 0)); + else if (color == "white") + generator.setHeightMapColor(Color(255, 255, 255), Color(255, 255, 255)); + else + generator.setHeightMapColor(Color(0, 0, 0), Color(color, 0)); + break; + } + else { + loadHeightMapColorsFile = true; + } + break; + case OPT_HEIGHTMAPYSCALE: + if (isdigit(optarg[0]) || ((optarg[0] == '-' || optarg[0] == '+') && isdigit(optarg[1]))) { + float scale = static_cast(atof(optarg)); + generator.setHeightMapYScale(scale); + } + else { + std::cerr << "Invalid parameter to '" << long_options[option_index].name << "': '" << optarg << "'" << std::endl; + usage(); + exit(1); + } + break; + case OPT_HEIGHT_LEVEL0: + if (isdigit(optarg[0]) || ((optarg[0] == '-' || optarg[0] == '+') && isdigit(optarg[1]))) { + int level = atoi(optarg); + generator.setSeaLevel(level); + } + else { + std::cerr << "Invalid parameter to '" << long_options[option_index].name << "': '" << optarg << "'" << std::endl; + usage(); + exit(1); + } + break; + case OPT_BLOCKCOLOR: + generator.setBlockDefaultColor(Color(optarg, 0)); + break; + case 's': + generator.setScaleColor(Color(optarg, 0)); + break; + case 'r': + generator.setOriginColor(Color(optarg, 1)); + break; + case 'p': + generator.setPlayerColor(Color(optarg, 1)); + break; + case 'B': + generator.setTileBorderColor(Color(optarg, 0)); + break; + case 'R': + generator.setDrawOrigin(true); + break; + case 'P': + generator.setDrawPlayers(true); + break; + case 'S': + if (optarg && *optarg) { + std::string opt = strlower(optarg); + if (opt == "left") + generator.setDrawScale(DRAWSCALE_LEFT); + else if (opt == "top") + generator.setDrawScale(DRAWSCALE_TOP); + else if (opt == "left,top") + generator.setDrawScale(DRAWSCALE_LEFT | DRAWSCALE_TOP); + else if (opt == "top,left") + generator.setDrawScale(DRAWSCALE_LEFT | DRAWSCALE_TOP); + else { + std::cerr << "Invalid parameter to '" << long_options[option_index].name + << "': '" << optarg << "' (expected: left,top)" << std::endl; + usage(); + exit(1); + } + } + else { + generator.setDrawScale(DRAWSCALE_LEFT | DRAWSCALE_TOP); + } + break; + case OPT_DRAWHEIGHTSCALE: + generator.setDrawHeightScale(DRAWHEIGHTSCALE_BOTTOM); + break; + case OPT_SCALEINTERVAL: { + istringstream arg; + arg.str(optarg); + int major; + int minor; + char sep; + arg >> major; + if (major < 0 || !isdigit(*optarg) || arg.fail()) { + std::cerr << "Invalid parameter to '" << long_options[option_index].name + << "': '" << optarg << "' (expected: [,]" << std::endl; + usage(); + exit(1); + } + arg >> std::ws >> sep >> std::ws; + if (!arg.fail()) { + if ((sep != ',' && sep != ':') || !isdigit(arg.peek())) { + std::cerr << "Invalid parameter to '" << long_options[option_index].name + << "': '" << optarg << "' (expected: [,]" << std::endl; + usage(); + exit(1); + } + arg >> minor; + if (minor < 0) { + std::cerr << "Invalid parameter to '" << long_options[option_index].name + << "': '" << optarg << "' (expected: [,]" << std::endl; + usage(); + exit(1); + } + } + else { + minor = 0; + } + if (minor && sep == ':') { + if (major % minor) { + std::cerr << long_options[option_index].name << ": Cannot divide major interval in " + << minor << " subintervals (not divisible)" << std::endl; + exit(1); + } + minor = major / minor; + } + if ((minor % major) == 0) + minor = 0; + if (long_options[option_index].name[0] == 's') { + generator.setSideScaleInterval(major, minor); + } + else if (long_options[option_index].name[0] == 'h') { + generator.setHeightScaleInterval(major, minor); + } + else { + std::cerr << "Internal error: option " << long_options[option_index].name << " not handled" << std::endl; + exit(1); + } + } + break; + case OPT_SILENCE_SUGGESTIONS: { + for (size_t i = 0; i < strlen(optarg); i++) { + optarg[i] = tolower(optarg[i]); + if (optarg[i] == ',') + optarg[i] = ' '; + } + std::istringstream iss(optarg); + std::string flag; + iss >> std::skipws >> flag; + while (!iss.fail()) { + if (flag == "all") { + generator.setSilenceSuggestion(SUGGESTION_ALL); + DBSQLite3::warnDatabaseLockDelay = false; + } + else if (flag == "prefetch") { + generator.setSilenceSuggestion(SUGGESTION_PREFETCH); + } + else if (flag == "sqlite3-lock") { +#ifdef USE_SQLITE3 + DBSQLite3::warnDatabaseLockDelay = false; +#endif + } + else { + std::cerr << "Invalid flag to '" << long_options[option_index].name << "': '" << flag << "'" << std::endl; + usage(); + exit(1); + } + iss >> flag; + } + } + break; + case 'v': + if (optarg && isdigit(optarg[0]) && optarg[1] == '\0') { + generator.verboseStatistics = optarg[0] - '0'; + generator.verboseCoordinates = optarg[0] - '0'; + } + else { + generator.verboseStatistics = 1; + generator.verboseCoordinates = 1; + } + break; + case OPT_VERBOSE_SEARCH_COLORS: + if (optarg && isdigit(optarg[0]) && optarg[1] == '\0') { + generator.verboseReadColors = optarg[0] - '0'; + } + else { + generator.verboseReadColors++; + } + break; + case 'e': + generator.setDrawAlpha(true); + if (!optarg || !*optarg) + PixelAttribute::setMixMode(PixelAttribute::AlphaMixAverage); + else if (string(optarg) == "cumulative" || strlower(optarg) == "nodarken") + // "nodarken" is supported for backwards compatibility + PixelAttribute::setMixMode(PixelAttribute::AlphaMixCumulative); + else if (string(optarg) == "darken" || strlower(optarg) == "cumulative-darken") + // "darken" is supported for backwards compatibility + PixelAttribute::setMixMode(PixelAttribute::AlphaMixCumulativeDarken); + else if (strlower(optarg) == "average") + PixelAttribute::setMixMode(PixelAttribute::AlphaMixAverage); + else if (strlower(optarg) == "none") + generator.setDrawAlpha(false); + else { + std::cerr << "Invalid parameter to '" << long_options[option_index].name << "': '" << optarg << "'" << std::endl; + usage(); + exit(1); + } + break; + case OPT_DRAWAIR: + generator.setDrawAir(true); + break; + case OPT_DRAWNODES: { + bool draw = long_options[option_index].name[0] == 'd'; + for (char *c = optarg; *c; c++) { + *c = tolower(*c); + if (*c == ',') *c = ' '; + } + istringstream iss(optarg); + string flag; + iss >> std::skipws >> flag; + while (!iss.fail()) { + bool enable = draw; + if (flag.substr(0, 3) == "no-") { + flag = flag.substr(3); + enable = !enable; + } + if (flag == "") + (void) true; // Empty flag - ignore + else if (flag == "ignore") + generator.setDrawIgnore(enable); + else if (flag == "air") + generator.setDrawAir(enable); + else { + std::cerr << "Invalid " << long_options[option_index].name << " flag '" << flag << "'" << std::endl; + usage(); + exit(1); + } + iss >> flag; + } + } + break; + case 'H': + generator.setShading(false); + break; + case OPT_SQLITE_CACHEWORLDROW: + // This option is recognised for backward compatibility. + // Tests with a (large) world on SSD and on HDD showed a performance decrease + // on all map sizes with this option enabled. + // (Next: print a message when this option is used. + // Later: remove it completely) + break; + case OPT_PROGRESS_INDICATOR: + generator.enableProgressIndicator(); + break; + case 'a': { + istringstream iss; + iss.str(optarg); + int miny; + iss >> miny; + generator.setMinY(miny); + } + break; + case 'c': { + istringstream iss; + iss.str(optarg); + int maxy; + iss >> maxy; + generator.setMaxY(maxy); + } + break; + case OPT_CHUNKSIZE: { + istringstream iss; + iss.str(optarg); + int size; + iss >> size; + if (iss.fail() || size < 0) { + std::cerr << "Invalid chunk size (" << optarg << ")" << std::endl; + usage(); + exit(1); + } + generator.setChunkSize(size); + } + break; + case OPT_SCALEFACTOR: { + istringstream arg; + arg.str(optarg); + int one; + char colon; + int factor = 1; + arg >> one >> std::ws; + if (arg.fail() || one != 1) { + std::cerr << "Invalid scale factor specification (" << optarg << ") - expected: 1:" << std::endl; + exit(1); + } + if (!arg.eof()) { + arg >> colon >> factor >> std::ws; + if (arg.fail() || colon != ':' || factor<0 || !arg.eof()) { + std::cerr << "Invalid scale factor specification (" << optarg << ") - expected: 1:" << std::endl; + usage(); + exit(1); + } + if (factor != 1 && factor != 2 && factor != 4 && factor != 8 && factor != 16) { + std::cerr << "Scale factor must be 1:1, 1:2, 1:4, 1:8 or 1:16" << std::endl; + exit(1); + } + } + generator.setScaleFactor(factor); + } + break; + case 't': { + istringstream tilesize; + tilesize.str(strlower(optarg)); + if (tilesize.str() == "block") { + generator.setTileSize(BLOCK_SIZE, BLOCK_SIZE); + generator.setTileOrigin(TILECORNER_AT_WORLDCENTER, TILECORNER_AT_WORLDCENTER); + } + else if (tilesize.str() == "chunk") { + generator.setTileSize(TILESIZE_CHUNK, TILESIZE_CHUNK); + generator.setTileOrigin(TILECENTER_AT_CHUNKCENTER, TILECENTER_AT_CHUNKCENTER); + } + else { + int size, border; + char c; + tilesize >> size; + if (tilesize.fail() || size<0) { + std::cerr << "Invalid tile size specification (" << optarg << ")" << std::endl; + usage(); + exit(1); + } + generator.setTileSize(size, size); + tilesize >> c >> border; + if (!tilesize.fail()) { + if (c != '+' || border < 1) { + std::cerr << "Invalid tile border size specification (" << optarg << ")" << std::endl; + usage(); + exit(1); + } + generator.setTileBorderSize(border); + } + } + } + break; + case 'T': { + bool origin = long_options[option_index].name[4] == 'o'; + istringstream iss; + iss.str(strlower(optarg)); + NodeCoord coord; + if (iss.str() == "world") { + if (origin) + generator.setTileOrigin(TILECORNER_AT_WORLDCENTER, TILECORNER_AT_WORLDCENTER); + else + generator.setTileCenter(TILECENTER_AT_WORLDCENTER, TILECENTER_AT_WORLDCENTER); + } + else if (iss.str() == "map") { + if (origin) + generator.setTileOrigin(TILECORNER_AT_MAPCENTER, TILECORNER_AT_MAPCENTER); + else + generator.setTileCenter(TILECENTER_AT_MAPCENTER, TILECENTER_AT_MAPCENTER); + } + else { + bool result = true; + if (!parseCoordinates(iss, coord, 2, 0, ',')) { + iss.str(optarg); + result = parseCoordinates(iss, coord, 2, 0, ':'); + } + if (result) { + if (origin) { + convertBlockToNodeCoordinates(coord, 0, 2); + generator.setTileOrigin(coord.x(), coord.y()); + } + else { + convertBlockToNodeCoordinates(coord, 8, 2); + generator.setTileCenter(coord.x(), coord.y()); + } + } + else { + std::cerr << "Invalid " << long_options[option_index].name << " parameter (" << optarg << ")" << std::endl; + usage(); + exit(1); + } + } + } + break; + case 'G': + if (long_options[option_index].name[0] == 'f') { + // '--forcegeometry' + // Old behavior - for compatibility. + generator.setShrinkGeometry(false); + setFixedOrShrinkGeometry = true; + if (!foundGeometrySpec) + generator.setBlockGeometry(true); + } + else if (optarg && *optarg) { + for (char *c = optarg; *c; c++) { + *c = tolower(*c); + if (*c == ',') *c = ' '; + } + istringstream iss; + iss.str(optarg); + string flag; + iss >> std::skipws >> flag; + while (!iss.fail()) { + if (flag == "") + (void) true; // Empty flag - ignore + else if (flag == "pixel") + generator.setBlockGeometry(false); + else if (flag == "block") + generator.setBlockGeometry(true); + else if (flag == "fixed") + generator.setShrinkGeometry(false); + else if (flag == "shrink") + generator.setShrinkGeometry(true); + else { + std::cerr << "Invalid geometry mode flag '" << flag << "'" << std::endl; + usage(); + exit(1); + } + if (flag == "fixed" || flag == "shrink") + setFixedOrShrinkGeometry = true; + iss >> flag; + } + } + foundGeometrySpec = true; + break; + case 'g': { + istringstream iss; + iss.str(optarg); + NodeCoord coord1; + NodeCoord coord2; + bool legacy; + FuzzyBool center = FuzzyBool::Maybe; + if (long_options[option_index].name[0] == 'c' && long_options[option_index].name[1] == 'e') + center = FuzzyBool::Yes; + if (long_options[option_index].name[0] == 'c' && long_options[option_index].name[1] == 'o') + center = FuzzyBool::No; + if (!parseMapGeometry(iss, coord1, coord2, legacy, center)) { + std::cerr << "Invalid geometry specification '" << optarg << "'" << std::endl; + usage(); + exit(1); + } + // Set defaults + if (!foundGeometrySpec) { + if (long_options[option_index].name[0] == 'g' && legacy) { + // Compatibility when using the option 'geometry' + generator.setBlockGeometry(true); + generator.setShrinkGeometry(true); + } + else { + generator.setBlockGeometry(false); + generator.setShrinkGeometry(false); + } + setFixedOrShrinkGeometry = true; + } + if (!setFixedOrShrinkGeometry) { + // Special treatement is needed, because: + // - without any -[...]geometry option, default is shrink + // - with any -[...]geometry option, default is fixed + generator.setShrinkGeometry(false); + setFixedOrShrinkGeometry = true; + } + generator.setGeometry(coord1, coord2); + foundGeometrySpec = true; + } + break; + case OPT_DRAW_OBJECT: { + TileGenerator::DrawObject drawObject; + drawObject.world = long_options[option_index].name[4] != 'm'; + char object = long_options[option_index].name[4 + (drawObject.world ? 0 : 3)]; + switch (object) { + case 'p': + drawObject.type = TileGenerator::DrawObject::Point; + break; + case 'l': + drawObject.type = TileGenerator::DrawObject::Line; + break; + case 'r': + drawObject.type = TileGenerator::DrawObject::Rectangle; + break; + case 'e': + case 'c': + drawObject.type = TileGenerator::DrawObject::Ellipse; + break; + case 'a': + drawObject.type = TileGenerator::DrawObject::Line; + break; + case 't': + drawObject.type = TileGenerator::DrawObject::Text; + break; + default: + std::cerr << "Internal error: unrecognised object (" + << long_options[option_index].name + << ")" << std::endl; + exit(1); + break; + } + + istringstream iss; + iss.str(optarg); + NodeCoord coord1; + NodeCoord coord2; + NodeCoord dimensions; + FuzzyBool needDimensions; + bool legacy; + bool centered; + + if (object == 'p' || object == 't') + needDimensions = FuzzyBool::No; + else + needDimensions = FuzzyBool::Yes; + if (!parseGeometry(iss, coord1, coord2, dimensions, legacy, centered, 2, needDimensions)) { + std::cerr << "Invalid drawing geometry specification for " + << long_options[option_index].name + << " '" << optarg << "'" << std::endl; + usage(); + exit(1); + } + bool haveCoord2 = coord2.dimension[0] != NodeCoord::Invalid + && coord2.dimension[1] != NodeCoord::Invalid; + bool haveDimensions = dimensions.dimension[0] != NodeCoord::Invalid + && dimensions.dimension[1] != NodeCoord::Invalid; + + if (object == 'p' || object == 't') { + for (int i = 0; i < 2; i++) + if (coord1.isBlock[i]) { + coord1.dimension[i] *= 16; + coord1.isBlock[i] = false; + } + drawObject.setCenter(coord1); + drawObject.setDimensions(NodeCoord(1, 1, 1)); + } + else { + if (haveDimensions) { + if (centered) + drawObject.setCenter(coord1); + else + drawObject.setCorner1(coord1); + drawObject.setDimensions(dimensions); + } + else if (haveCoord2) { + drawObject.setCorner1(coord1); + drawObject.setCorner2(coord2); + } + else { +#ifdef DEBUG + assert(!haveDimensions && !haveCoord2); +#else + break; +#endif + } + } + + string colorStr; + iss >> std::ws >> colorStr; + if (iss.fail()) { + std::cerr << "Missing color for " + << long_options[option_index].name + << " '" << optarg << "'" << std::endl; + usage(); + exit(1); + } + drawObject.color = colorStr; + + if (object == 't') { + iss >> std::ws; + std::string localizedText; + std::getline(iss, localizedText); + if (localizedText.empty() || iss.fail()) { + std::cerr << "Invalid or missing text for " + << long_options[option_index].name + << " '" << optarg << "'" << std::endl; + usage(); + exit(1); + } + drawObject.text = charConvUTF8->convert(localizedText); + } + + generator.drawObject(drawObject); + if (object == 'a') { + if (drawObject.haveCenter) { + std::cerr << "Arrow cannot use a centered dimension." + << " Specify at least one corner." << std::endl; + exit(1); + } + bool useDimensions = drawObject.haveDimensions; + + if (drawObject.haveDimensions) + convertDimensionToCornerCoordinates(drawObject.corner1, drawObject.corner2, drawObject.dimensions, 2); + double angle, length; + convertCartesianToPolarCoordinates(drawObject.corner1, drawObject.corner2, angle, length); + convertPolarToCartesianCoordinates(drawObject.corner1, drawObject.corner2, angle + DRAW_ARROW_ANGLE, DRAW_ARROW_LENGTH); + if (useDimensions) { + convertCornerToDimensionCoordinates(drawObject.corner1, drawObject.corner2, drawObject.dimensions, 2); + drawObject.haveDimensions = useDimensions; + } + generator.drawObject(drawObject); + convertPolarToCartesianCoordinates(drawObject.corner1, drawObject.corner2, angle - DRAW_ARROW_ANGLE, DRAW_ARROW_LENGTH); + if (useDimensions) { + convertCornerToDimensionCoordinates(drawObject.corner1, drawObject.corner2, drawObject.dimensions, 2); + drawObject.haveDimensions = useDimensions; + } + generator.drawObject(drawObject); + } + } + break; + case 'd': + generator.setBackend(strlower(optarg)); + break; + default: + exit(1); + } + } + } + catch (std::runtime_error e) { + std::cout << "Command-line error: " << e.what() << std::endl; + return 1; + } + + try { + if (heightMap) { + parseDataFile(generator, input, heightMapNodesFile, heightMapNodesDefaultFile, &TileGenerator::parseHeightMapNodesFile); + if (loadHeightMapColorsFile) + parseDataFile(generator, input, heightMapColorsFile, heightMapColorsDefaultFile, &TileGenerator::parseHeightMapColorsFile); + } + else { + parseDataFile(generator, input, nodeColorsFile, nodeColorsDefaultFile, &TileGenerator::parseNodeColorsFile); + } + generator.generate(input, output); + } + catch (std::runtime_error e) { + std::cout << "Exception: " << e.what() << std::endl; + return 1; + } + return 0; } -static inline int safePeekStream(std::istream &is) -{ - // Don't peek at EOF, else stream fails... - return is.eof() ? EOF : is.peek(); -} - -void usage() +void Mapper::usage() { const char *options_text = "[options]\n" - " -h/--help\n" - " -V/--version\n" - " -i/--input \n" - " -o/--output \n" - " --colors \n" - " --heightmap-nodes \n" - " --heightmap-colors[=]\n" - " --height-level-0 \n" - " --heightmap[=]\n" - " --heightmap-yscale \n" - " --bgcolor \n" - " --blockcolor \n" - " --scalecolor \n" - " --playercolor \n" - " --origincolor \n" - " --tilebordercolor \n" - " --drawscale[=left,top]\n" - " --sidescale-interval [[,:]]\n" - " --drawheightscale\n" - " --heightscale-interval [[,:]]\n" - " --drawplayers\n" - " --draworigin\n" - " --drawalpha[=cumulative|cumulative-darken|average|none]\n" - " --drawair\n" - " --drawnodes [no-]air,[no-]ignore\n" - " --ignorenodes [no-]air,[no-]ignore\n" - " --draw[map]point \", color\"\n" - " --draw[map]line \" color\"\n" - " --draw[map]line \", [np] color\"\n" - " --draw[map]circle \" color\"\n" - " --draw[map]ellipse \" color\"\n" - " --draw[map]rectangle \" color\"\n" - " --draw[map]arrow \", , color\"\n" - " --draw[map]arrow \", [np] color\"\n" - " --draw[map]text \", color text\"\n" - " --noshading\n" - " --min-y \n" - " --max-y \n" - " --backend <" USAGE_DATABASES ">\n" - " --disable-blocklist-prefetch[=force]\n" - " --database-format minetest-i64|freeminer-axyz|mixed|query\n" - " --prescan-world=full|auto|disabled\n" - #ifdef USE_SQLITE3 - " --sqlite3-limit-prescan-query-size[=n]\n" - #endif - " --geometry \n" - "\t(Warning: has a compatibility mode - see README.rst)\n" - " --cornergeometry \n" - " --centergeometry \n" - " --geometrymode pixel,block,fixed,shrink\n" - "\tpixel: interpret geometry as pixel-accurate\n" - "\tblock: round geometry away from zero, to entire map blocks (16 nodes)\n" - "\tfixed: generate a map of exactly the requested geometry\n" - "\tshrink: generate a smaller map if possible\n" - " --tiles [+]|block|chunk\n" - " --tileorigin ,|world|map\n" - " --tilecenter ,|world|map\n" - " --scalefactor 1:\n" - " --chunksize \n" - " --silence-suggestions all,prefetch,sqlite3-lock\n" - " --verbose[=n]\n" - " --verbose-search-colors[=n]\n" - " --progress\n" - "Color formats:\n" - "\t'#000' or '#000000' (RGB)\n" - "\t'#0000' or '#0000000' (ARGB - usable if an alpha value is allowed)\n" - "\tSome symbolic color names:\n" - "\t\twhite, black, gray, grey, red, green, blue,\n" - "\t\tyellow, magenta, fuchsia, cyan, aqua,\n" - "\t\torange, chartreuse, pink, violet, springgreen, azure\n" - "\t\tbrown (= 50% orange)\n" - "\tAs well as [+-][wkrgbcmy], where n = 0.0..1.0 (or 1.00001 .. 255)\n" - "\t\t[+-][wkrgbcmy] mixes in or out white, black, red, green, blue, cyan, magenta, yellow\n" - "Geometry formats:\n" - "\tx[+|-+|-] (dimensions and corner)\n" - "\t,++ (corner and dimensions)\n" - "\t,:x (center and dimensions)\n" - "\t,:, (corners of area)\n" - "\tOriginal/legacy format - see note under '--geometry' option:\n" - "\t:++ (corner and dimensions)\n" - "X and Y coordinate formats:\n" - "\t[+-] (node +/- )\n" - "\t[+-]#[] (node in block +/- )\n" - "\t[+-].[] (node +/- (b * 16 + n))\n" - ; + " -h/--help\n" + " -V/--version\n" + " -i/--input \n" + " -o/--output \n" + " --colors \n" + " --heightmap-nodes \n" + " --heightmap-colors[=]\n" + " --height-level-0 \n" + " --heightmap[=]\n" + " --heightmap-yscale \n" + " --bgcolor \n" + " --blockcolor \n" + " --scalecolor \n" + " --playercolor \n" + " --origincolor \n" + " --tilebordercolor \n" + " --drawscale[=left,top]\n" + " --sidescale-interval [[,:]]\n" + " --drawheightscale\n" + " --heightscale-interval [[,:]]\n" + " --drawplayers\n" + " --draworigin\n" + " --drawalpha[=cumulative|cumulative-darken|average|none]\n" + " --drawair\n" + " --drawnodes [no-]air,[no-]ignore\n" + " --ignorenodes [no-]air,[no-]ignore\n" + " --draw[map]point \", color\"\n" + " --draw[map]line \" color\"\n" + " --draw[map]line \", [np] color\"\n" + " --draw[map]circle \" color\"\n" + " --draw[map]ellipse \" color\"\n" + " --draw[map]rectangle \" color\"\n" + " --draw[map]arrow \", , color\"\n" + " --draw[map]arrow \", [np] color\"\n" + " --draw[map]text \", color text\"\n" + " --noshading\n" + " --min-y \n" + " --max-y \n" + " --backend <" USAGE_DATABASES ">\n" + " --disable-blocklist-prefetch[=force]\n" + " --database-format minetest-i64|freeminer-axyz|mixed|query\n" + " --prescan-world=full|auto|disabled\n" +#ifdef USE_SQLITE3 + " --sqlite3-limit-prescan-query-size[=n]\n" +#endif + " --geometry \n" + "\t(Warning: has a compatibility mode - see README.rst)\n" + " --cornergeometry \n" + " --centergeometry \n" + " --geometrymode pixel,block,fixed,shrink\n" + "\tpixel: interpret geometry as pixel-accurate\n" + "\tblock: round geometry away from zero, to entire map blocks (16 nodes)\n" + "\tfixed: generate a map of exactly the requested geometry\n" + "\tshrink: generate a smaller map if possible\n" + " --tiles [+]|block|chunk\n" + " --tileorigin ,|world|map\n" + " --tilecenter ,|world|map\n" + " --scalefactor 1:\n" + " --chunksize \n" + " --silence-suggestions all,prefetch,sqlite3-lock\n" + " --verbose[=n]\n" + " --verbose-search-colors[=n]\n" + " --progress\n" + "Color formats:\n" + "\t'#000' or '#000000' (RGB)\n" + "\t'#0000' or '#0000000' (ARGB - usable if an alpha value is allowed)\n" + "\tSome symbolic color names:\n" + "\t\twhite, black, gray, grey, red, green, blue,\n" + "\t\tyellow, magenta, fuchsia, cyan, aqua,\n" + "\t\torange, chartreuse, pink, violet, springgreen, azure\n" + "\t\tbrown (= 50% orange)\n" + "\tAs well as [+-][wkrgbcmy], where n = 0.0..1.0 (or 1.00001 .. 255)\n" + "\t\t[+-][wkrgbcmy] mixes in or out white, black, red, green, blue, cyan, magenta, yellow\n" + "Geometry formats:\n" + "\tx[+|-+|-] (dimensions and corner)\n" + "\t,++ (corner and dimensions)\n" + "\t,:x (center and dimensions)\n" + "\t,:, (corners of area)\n" + "\tOriginal/legacy format - see note under '--geometry' option:\n" + "\t:++ (corner and dimensions)\n" + "X and Y coordinate formats:\n" + "\t[+-] (node +/- )\n" + "\t[+-]#[] (node in block +/- )\n" + "\t[+-].[] (node +/- (b * 16 + n))\n" + ; std::cout << executableName << ' ' << options_text; } -void parseDataFile(TileGenerator &generator, const string &input, string dataFile, string defaultFile, - void (TileGenerator::*parseFile)(const std::string &fileName)) + +// Parse the following geometry formats: +// x[++] +// (dimensions, and position) +// (if x and y are omitted, they default to -w/2 and -h/2) +// ,:, +// (2 corners of the area) +// ,:x +// (center of the area, and dimensions) +// [,:]++ +// (corner of the area, and dimensions) +// ,@+ + + +// is: stream to read from +// coord: set to coordinate value that was read +// isBlockCoord: set to true if the coordinate read was a block coordinate +// wildcard: if non-zero, accept '*' as a coordinate, and return this value instead. +// (suggested values for 'wildcard': INT_MIN or INT_MAX) +// +// Accepted coordinate syntax: +// [+-]: node coordinate: node +/- n +// [+-]#: block coordinate: block +/- b (isBlockCoord will be set to true) +// [+-]#: node coordinate: node in block +/- +// [+-].: node coordinate: node +/- (b * 16 + n) +// As a special feature, double signs are also supported. E.g.: +// +-3 +// Which allows shell command-lines like the following +// ${width}x${height}+$xoffs+$yoffs +// (which otherwise require special measures to cope with xoffs or yoffs being negative...) +// Other uses of this feature are left as an excercise to the reader. +// Hint: --3.5 is *not* the same as 3.5 + +void Mapper::parseDataFile(TileGenerator & generator, const string & input, string dataFile, string defaultFile, void(TileGenerator::* parseFile)(const std::string &fileName)) { if (!dataFile.empty()) { (generator.*parseFile)(dataFile); @@ -215,7 +998,7 @@ void parseDataFile(TileGenerator &generator, const string &input, string dataFil // Check if input/../.. looks like a valid minetest directory string minetestPath = input + PATH_SEPARATOR + ".." + PATH_SEPARATOR + ".."; string minetestConf = minetestPath + PATH_SEPARATOR + "minetest.conf"; - if (FILE *file = fopen(minetestConf.c_str(), "r")) { + if (FILE *file = fopen(minetestConf.c_str(), "r")) { fclose(file); colorPaths.push_back(minetestPath); } @@ -223,12 +1006,12 @@ void parseDataFile(TileGenerator &generator, const string &input, string dataFil if ((homedir = getenv("HOME"))) { colorPaths.push_back(string(homedir) + PATH_SEPARATOR + ".minetest"); } -// TODO: test/verify this (probably another subdirectory ('application data' or so) should be preferred) -//#if MSDOS || __OS2__ || __NT__ || _WIN32 -// if ((homedir = getenv("USERPROFILE"))) { -// colorPaths.push_back(string(homedir) + PATH_SEPARATOR + ".minetest"); -// } -//#endif + // TODO: test/verify this (probably another subdirectory ('application data' or so) should be preferred) + //#if MSDOS || __OS2__ || __NT__ || _WIN32 + // if ((homedir = getenv("USERPROFILE"))) { + // colorPaths.push_back(string(homedir) + PATH_SEPARATOR + ".minetest"); + // } + //#endif #if MSDOS || __OS2__ || __NT__ || _WIN32 || DEBUG // On windows, assume that argv[0] contains the full path location of minetestmapper.exe @@ -256,14 +1039,14 @@ void parseDataFile(TileGenerator &generator, const string &input, string dataFil } } #endif -// if (!installPrefix.empty()) { -//#if PACKAGING_FLAT -// colorPaths.push_back(installPrefix + PATH_SEPARATOR + "colors"); -// colorPaths.push_back(installPrefix); -//#else -// colorPaths.push_back(installPrefix + "/share/games/minetestmapper"); -//#endif -// } + // if (!installPrefix.empty()) { + //#if PACKAGING_FLAT + // colorPaths.push_back(installPrefix + PATH_SEPARATOR + "colors"); + // colorPaths.push_back(installPrefix); + //#else + // colorPaths.push_back(installPrefix + "/share/games/minetestmapper"); + //#endif + // } colorPaths.push_back(""); std::vector fileNames; @@ -274,19 +1057,20 @@ void parseDataFile(TileGenerator &generator, const string &input, string dataFil if (path->empty()) dataFile = *name; else - dataFile = *path + PATH_SEPARATOR + *name; + dataFile = *path + PATH_SEPARATOR + *name; try { (generator.*parseFile)(dataFile); if (path->empty()) { // I hope this is not obnoxious to windows users ? - cerr << "Warning: Using " << *name << " in current directory as a last resort." << std::endl + cerr << "Warning: Using " << *name << " in current directory as a last resort." << std::endl << " Preferably, store the colors file in the world directory" << std::endl; if (homedir) - cerr << " or in the private minetest directory ($HOME/.minetest)." << std::endl; - cerr << " It can also be specified on the command-line" << std::endl; + cerr << " or in the private minetest directory ($HOME/.minetest)." << std::endl; + cerr << " It can also be specified on the command-line" << std::endl; } return; - } catch (std::runtime_error e) { + } + catch (std::runtime_error e) { // Ignore failure to locate colors file in standard location // (we have more places to search) if (path->empty()) { @@ -300,25 +1084,7 @@ void parseDataFile(TileGenerator &generator, const string &input, string dataFil throw std::runtime_error(oss.str().c_str()); } -// is: stream to read from -// coord: set to coordinate value that was read -// isBlockCoord: set to true if the coordinate read was a block coordinate -// wildcard: if non-zero, accept '*' as a coordinate, and return this value instead. -// (suggested values for 'wildcard': INT_MIN or INT_MAX) -// -// Accepted coordinate syntax: -// [+-]: node coordinate: node +/- n -// [+-]#: block coordinate: block +/- b (isBlockCoord will be set to true) -// [+-]#: node coordinate: node in block +/- -// [+-].: node coordinate: node +/- (b * 16 + n) -// As a special feature, double signs are also supported. E.g.: -// +-3 -// Which allows shell command-lines like the following -// ${width}x${height}+$xoffs+$yoffs -// (which otherwise require special measures to cope with xoffs or yoffs being negative...) -// Other uses of this feature are left as an excercise to the reader. -// Hint: --3.5 is *not* the same as 3.5 -static bool parseNodeCoordinate(istream &is, int &coord, bool &isBlockCoord, int wildcard) +bool Mapper::parseNodeCoordinate(istream & is, int & coord, bool & isBlockCoord, int wildcard) { char c; int i; @@ -381,7 +1147,7 @@ static bool parseNodeCoordinate(istream &is, int &coord, bool &isBlockCoord, int return (!is.fail()); } -static bool parseCoordinates(istream &is, NodeCoord &coord, int n, int wildcard = 0, char separator = ',') +bool Mapper::parseCoordinates(istream & is, NodeCoord & coord, int n, int wildcard, char separator) { bool result; result = true; @@ -402,7 +1168,7 @@ static bool parseCoordinates(istream &is, NodeCoord &coord, int n, int wildcard return result; } -static void convertBlockToNodeCoordinates(NodeCoord &coord, int offset, int n) +void Mapper::convertBlockToNodeCoordinates(NodeCoord & coord, int offset, int n) { for (int i = 0; i < n; i++) { if (coord.isBlock[i]) { @@ -412,14 +1178,14 @@ static void convertBlockToNodeCoordinates(NodeCoord &coord, int offset, int n) } } -static void convertBlockToNodeCoordinates(NodeCoord &coord1, NodeCoord &coord2, int n) +void Mapper::convertBlockToNodeCoordinates(NodeCoord & coord1, NodeCoord & coord2, int n) { for (int i = 0; i < n; i++) { int c1 = coord1.isBlock[i] ? coord1.dimension[i] * 16 : coord1.dimension[i]; int c2 = coord2.isBlock[i] ? coord2.dimension[i] * 16 + 15 : coord2.dimension[i]; if (c1 > c2) { - c1 = coord1.isBlock[i] ? coord1.dimension[i] * 16 + 15 : coord1.dimension[i]; - c2 = coord2.isBlock[i] ? coord2.dimension[i] * 16 : coord2.dimension[i]; + c1 = coord1.isBlock[i] ? coord1.dimension[i] * 16 + 15 : coord1.dimension[i]; + c2 = coord2.isBlock[i] ? coord2.dimension[i] * 16 : coord2.dimension[i]; } coord1.dimension[i] = c1; coord2.dimension[i] = c2; @@ -428,7 +1194,7 @@ static void convertBlockToNodeCoordinates(NodeCoord &coord1, NodeCoord &coord2, } } -static void convertCenterToCornerCoordinates(NodeCoord &coord, NodeCoord &dimensions, int n) +void Mapper::convertCenterToCornerCoordinates(NodeCoord & coord, NodeCoord & dimensions, int n) { // This results in a slight bias to the negative side. // i.e.: 0,0:2x2 will be -1,-1 .. 0,0 and not 0,0 .. 1,1 @@ -445,7 +1211,7 @@ static void convertCenterToCornerCoordinates(NodeCoord &coord, NodeCoord &dimens } } -static void convertDimensionToCornerCoordinates(NodeCoord &coord1, NodeCoord &coord2, NodeCoord &dimensions, int n) +void Mapper::convertDimensionToCornerCoordinates(NodeCoord & coord1, NodeCoord & coord2, NodeCoord & dimensions, int n) { for (int i = 0; i < n; i++) { if (dimensions.dimension[i] < 0) @@ -455,7 +1221,7 @@ static void convertDimensionToCornerCoordinates(NodeCoord &coord1, NodeCoord &co } } -static void convertCornerToDimensionCoordinates(NodeCoord &coord1, NodeCoord &coord2, NodeCoord &dimensions, int n) +void Mapper::convertCornerToDimensionCoordinates(NodeCoord & coord1, NodeCoord & coord2, NodeCoord & dimensions, int n) { for (int i = 0; i < n; i++) { if (coord2.dimension[i] < coord1.dimension[i]) @@ -465,7 +1231,7 @@ static void convertCornerToDimensionCoordinates(NodeCoord &coord1, NodeCoord &co } } -static void convertPolarToCartesianCoordinates(NodeCoord &coord1, NodeCoord &coord2, double angle, double length) +void Mapper::convertPolarToCartesianCoordinates(NodeCoord & coord1, NodeCoord & coord2, double angle, double length) { angle *= M_PI / 180; double dxf = sin(angle) * length; @@ -478,17 +1244,17 @@ static void convertPolarToCartesianCoordinates(NodeCoord &coord1, NodeCoord &coo coord2.y() = coord1.y() + dy; } -static void convertCartesianToPolarCoordinates(NodeCoord &coord1, NodeCoord &coord2, double &angle, double &length) +void Mapper::convertCartesianToPolarCoordinates(NodeCoord & coord1, NodeCoord & coord2, double & angle, double & length) { int lx = coord2.x() - coord1.x(); lx += lx < 0 ? -1 : lx > 0 ? 1 : 0; int ly = coord2.y() - coord1.y(); ly += ly < 0 ? -1 : ly > 0 ? 1 : 0; - length = sqrt(lx*lx + ly*ly); + length = sqrt(lx*lx + ly * ly); angle = atan2(lx, ly) / M_PI * 180; } -static void orderCoordinateDimensions(NodeCoord &coord1, NodeCoord &coord2, int n) +void Mapper::orderCoordinateDimensions(NodeCoord & coord1, NodeCoord & coord2, int n) { for (int i = 0; i < n; i++) if (coord1.dimension[i] > coord2.dimension[i]) { @@ -498,19 +1264,7 @@ static void orderCoordinateDimensions(NodeCoord &coord1, NodeCoord &coord2, int } } - -// Parse the following geometry formats: -// x[++] -// (dimensions, and position) -// (if x and y are omitted, they default to -w/2 and -h/2) -// ,:, -// (2 corners of the area) -// ,:x -// (center of the area, and dimensions) -// [,:]++ -// (corner of the area, and dimensions) -// ,@+ -static bool parseGeometry(istream &is, NodeCoord &coord1, NodeCoord &coord2, NodeCoord &dimensions, bool &legacy, bool ¢ered, int n, FuzzyBool expectDimensions, int wildcard = 0) +bool Mapper::parseGeometry(istream & is, NodeCoord & coord1, NodeCoord & coord2, NodeCoord & dimensions, bool & legacy, bool & centered, int n, FuzzyBool expectDimensions, int wildcard) { std::streamoff pos; pos = is.tellg(); @@ -600,14 +1354,14 @@ static bool parseGeometry(istream &is, NodeCoord &coord1, NodeCoord &coord2, Nod is >> length; if (!validStreamAtEof(is)) { switch (safePeekStream(is)) { - case 'n' : - is.ignore(1); - world_relative = true; - break; - case 'p' : - is.ignore(1); - world_relative = false; - break; + case 'n': + is.ignore(1); + world_relative = true; + break; + case 'p': + is.ignore(1); + world_relative = false; + break; } } if (!validStreamAtWsOrEof(is)) @@ -651,7 +1405,7 @@ static bool parseGeometry(istream &is, NodeCoord &coord1, NodeCoord &coord2, Nod return false; } -static bool parseMapGeometry(istream &is, NodeCoord &coord1, NodeCoord &coord2, bool &legacy, FuzzyBool interpretAsCenter) +bool Mapper::parseMapGeometry(istream & is, NodeCoord & coord1, NodeCoord & coord2, bool & legacy, FuzzyBool interpretAsCenter) { NodeCoord dimensions; bool centered; @@ -678,847 +1432,3 @@ static bool parseMapGeometry(istream &is, NodeCoord &coord1, NodeCoord &coord2, return result; } - -int main(int argc, char *argv[]) -{ - if (argc) { - string argv0 = argv[0]; - size_t pos = argv0.find_last_of(PATH_SEPARATOR); - if (pos == string::npos) { - if (!argv0.empty()) - executableName = argv0; - else - executableName = DEFAULT_PROGRAM_NAME; - executablePath = ""; - - } - else { - executableName = argv0.substr(pos + 1); - executablePath = argv0.substr(0, pos); - } - } - - static struct option long_options[] = - { - {"help", no_argument, 0, 'h'}, - {"version", no_argument, 0, 'V'}, - {"input", required_argument, 0, 'i'}, - {"output", required_argument, 0, 'o'}, - {"colors", required_argument, 0, 'C'}, - {"heightmap-nodes", required_argument, 0, OPT_HEIGHTMAPNODESFILE}, - {"heightmap-colors", required_argument, 0, OPT_HEIGHTMAPCOLORSFILE}, - {"heightmap", optional_argument, 0, OPT_HEIGHTMAP}, - {"heightmap-yscale", required_argument, 0, OPT_HEIGHTMAPYSCALE}, - {"height-level-0", required_argument, 0, OPT_HEIGHT_LEVEL0}, - {"bgcolor", required_argument, 0, 'b'}, - {"blockcolor", required_argument, 0, OPT_BLOCKCOLOR}, - {"scalecolor", required_argument, 0, 's'}, - {"origincolor", required_argument, 0, 'r'}, - {"playercolor", required_argument, 0, 'p'}, - {"draworigin", no_argument, 0, 'R'}, - {"drawplayers", no_argument, 0, 'P'}, - {"drawscale", optional_argument, 0, 'S'}, - {"sidescale-interval", required_argument, 0, OPT_SCALEINTERVAL}, - {"drawheightscale", no_argument, 0, OPT_DRAWHEIGHTSCALE}, - {"heightscale-interval", required_argument, 0, OPT_SCALEINTERVAL}, - {"drawalpha", optional_argument, 0, 'e'}, - {"drawair", no_argument, 0, OPT_DRAWAIR}, - {"drawnodes", required_argument, 0, OPT_DRAWNODES}, - {"ignorenodes", required_argument, 0, OPT_DRAWNODES}, - {"drawpoint", required_argument, 0, OPT_DRAW_OBJECT}, - {"drawline", required_argument, 0, OPT_DRAW_OBJECT}, - {"drawcircle", required_argument, 0, OPT_DRAW_OBJECT}, - {"drawellipse", required_argument, 0, OPT_DRAW_OBJECT}, - {"drawrectangle", required_argument, 0, OPT_DRAW_OBJECT}, - {"drawarrow", required_argument, 0, OPT_DRAW_OBJECT}, - {"drawtext", required_argument, 0, OPT_DRAW_OBJECT}, - {"drawmappoint", required_argument, 0, OPT_DRAW_OBJECT}, - {"drawmapline", required_argument, 0, OPT_DRAW_OBJECT}, - {"drawmapcircle", required_argument, 0, OPT_DRAW_OBJECT}, - {"drawmapellipse", required_argument, 0, OPT_DRAW_OBJECT}, - {"drawmaprectangle", required_argument, 0, OPT_DRAW_OBJECT}, - {"drawmaparrow", required_argument, 0, OPT_DRAW_OBJECT}, - {"drawmaptext", required_argument, 0, OPT_DRAW_OBJECT}, - {"noshading", no_argument, 0, 'H'}, - {"geometry", required_argument, 0, 'g'}, - {"cornergeometry", required_argument, 0, 'g'}, - {"centergeometry", required_argument, 0, 'g'}, - {"geometrymode", required_argument, 0, 'G'}, - {"forcegeometry", no_argument, 0, 'G'}, - {"min-y", required_argument, 0, 'a'}, - {"max-y", required_argument, 0, 'c'}, - {"backend", required_argument, 0, 'd'}, - {"disable-blocklist-prefetch", optional_argument, 0, OPT_NO_BLOCKLIST_PREFETCH}, - {"database-format", required_argument, 0, OPT_DATABASE_FORMAT}, - {"prescan-world", required_argument, 0, OPT_PRESCAN_WORLD}, - {"sqlite-cacheworldrow", no_argument, 0, OPT_SQLITE_CACHEWORLDROW}, - {"sqlite3-limit-prescan-query-size", optional_argument, 0, OPT_SQLITE_LIMIT_PRESCAN_QUERY}, - {"tiles", required_argument, 0, 't'}, - {"tileorigin", required_argument, 0, 'T'}, - {"tilecenter", required_argument, 0, 'T'}, - {"tilebordercolor", required_argument, 0, 'B'}, - {"scalefactor", required_argument, 0, OPT_SCALEFACTOR}, - {"chunksize", required_argument, 0, OPT_CHUNKSIZE}, - {"silence-suggestions", required_argument, 0, OPT_SILENCE_SUGGESTIONS}, - {"verbose", optional_argument, 0, 'v'}, - {"verbose-search-colors", optional_argument, 0, OPT_VERBOSE_SEARCH_COLORS}, - {"progress", no_argument, 0, OPT_PROGRESS_INDICATOR}, - {NULL, 0, 0, 0} - }; - - string input; - string output; - bool heightMap = false; - bool loadHeightMapColorsFile = false; - string nodeColorsFile; - string heightMapColorsFile; - string heightMapNodesFile; - bool foundGeometrySpec = false; - bool setFixedOrShrinkGeometry = false; - CharEncodingConverter *charConvUTF8; - charConvUTF8 = CharEncodingConverter::createStandardConverter("UTF-8"); - - TileGenerator generator; - try { - int option_index = 0; - int c = 0; - while (1) { - c = getopt_long(argc, argv, "hi:o:", long_options, &option_index); - if (c == -1) { - if (input.empty() || output.empty()) { - std::cerr << "Input (world directory) or output (PNG filename) missing" << std::endl; - usage(); - return 0; - } - break; - } - switch (c) { - case 'h': - usage(); - return 0; - break; - case 'V': - cout << "Minetestmapper - Version-ID: " << PROJECT_VERSION_MAJOR << "." << PROJECT_VERSION_MINOR << std::endl; - return 0; - break; - case 'i': - input = optarg; - break; - case 'o': - output = optarg; - break; - case 'C': - nodeColorsFile = optarg; - break; - case OPT_HEIGHTMAPNODESFILE: - heightMapNodesFile = optarg; - break; - case OPT_HEIGHTMAPCOLORSFILE: - heightMapColorsFile = optarg; - break; - case 'b': - generator.setBgColor(Color(optarg, 0)); - break; - case OPT_NO_BLOCKLIST_PREFETCH: - if (optarg && *optarg) { - if (strlower(optarg) == "force") - generator.setGenerateNoPrefetch(2); - else { - std::cerr << "Invalid parameter to '" << long_options[option_index].name << "'; expected 'force' or nothing." << std::endl; - usage(); - exit(1); - } - } - else { - generator.setGenerateNoPrefetch(1); - } - break; - case OPT_DATABASE_FORMAT: { - std::string opt = strlower(optarg); - if (opt == "minetest-i64") - generator.setDBFormat(BlockPos::I64, false); - else if (opt == "freeminer-axyz") - generator.setDBFormat(BlockPos::AXYZ, false); - else if (opt == "mixed") - generator.setDBFormat(BlockPos::Unknown, false); - else if (opt == "query") - generator.setDBFormat(BlockPos::Unknown, true); - else { - std::cerr << "Invalid parameter to '" << long_options[option_index].name << "': '" << optarg << "'" << std::endl; - usage(); - exit(1); - } - } - break; - case OPT_PRESCAN_WORLD: { - std::string opt = strlower(optarg); - generator.setGenerateNoPrefetch(0); - if (opt == "disabled-force") - generator.setGenerateNoPrefetch(2); - else if (opt == "disabled") - generator.setGenerateNoPrefetch(1); - else if (opt == "auto") - generator.setScanEntireWorld(false); - else if (opt == "full") - generator.setScanEntireWorld(true); - else { - std::cerr << "Invalid parameter to '" << long_options[option_index].name << "': '" << optarg << "'" << std::endl; - usage(); - exit(1); - } - } - break; - case OPT_SQLITE_LIMIT_PRESCAN_QUERY: - if (!optarg || !*optarg) { - #ifdef USE_SQLITE3 - DBSQLite3::setLimitBlockListQuerySize(); - #endif - } - else { - if (!isdigit(optarg[0])) { - std::cerr << "Invalid parameter to '" << long_options[option_index].name << "': must be a positive number" << std::endl; - usage(); - exit(1); - } - #ifdef USE_SQLITE3 - int size = atoi(optarg); - DBSQLite3::setLimitBlockListQuerySize(size); - #endif - } - break; - case OPT_HEIGHTMAP: - generator.setHeightMap(true); - heightMap = true; - if (optarg && *optarg) { - loadHeightMapColorsFile = false; - std::string color = strlower(optarg); - if (color == "grey" || color == "gray") - generator.setHeightMapColor(Color(0, 0, 0), Color(255, 255, 255)); - else if (color == "black") - generator.setHeightMapColor(Color(0, 0, 0), Color(0, 0, 0)); - else if (color == "white") - generator.setHeightMapColor(Color(255, 255, 255), Color(255, 255, 255)); - else - generator.setHeightMapColor(Color(0, 0, 0), Color(color, 0)); - break; - } - else { - loadHeightMapColorsFile = true; - } - break; - case OPT_HEIGHTMAPYSCALE: - if (isdigit(optarg[0]) || ((optarg[0]=='-' || optarg[0]=='+') && isdigit(optarg[1]))) { - float scale = static_cast(atof(optarg)); - generator.setHeightMapYScale(scale); - } - else { - std::cerr << "Invalid parameter to '" << long_options[option_index].name << "': '" << optarg << "'" << std::endl; - usage(); - exit(1); - } - break; - case OPT_HEIGHT_LEVEL0: - if (isdigit(optarg[0]) || ((optarg[0]=='-' || optarg[0]=='+') && isdigit(optarg[1]))) { - int level = atoi(optarg); - generator.setSeaLevel(level); - } - else { - std::cerr << "Invalid parameter to '" << long_options[option_index].name << "': '" << optarg << "'" << std::endl; - usage(); - exit(1); - } - break; - case OPT_BLOCKCOLOR: - generator.setBlockDefaultColor(Color(optarg, 0)); - break; - case 's': - generator.setScaleColor(Color(optarg,0)); - break; - case 'r': - generator.setOriginColor(Color(optarg,1)); - break; - case 'p': - generator.setPlayerColor(Color(optarg,1)); - break; - case 'B': - generator.setTileBorderColor(Color(optarg,0)); - break; - case 'R': - generator.setDrawOrigin(true); - break; - case 'P': - generator.setDrawPlayers(true); - break; - case 'S': - if (optarg && *optarg) { - std::string opt = strlower(optarg); - if (opt == "left") - generator.setDrawScale(DRAWSCALE_LEFT); - else if (opt == "top") - generator.setDrawScale(DRAWSCALE_TOP); - else if (opt == "left,top") - generator.setDrawScale(DRAWSCALE_LEFT | DRAWSCALE_TOP); - else if (opt == "top,left") - generator.setDrawScale(DRAWSCALE_LEFT | DRAWSCALE_TOP); - else { - std::cerr << "Invalid parameter to '" << long_options[option_index].name - << "': '" << optarg << "' (expected: left,top)" << std::endl; - usage(); - exit(1); - } - } - else { - generator.setDrawScale(DRAWSCALE_LEFT | DRAWSCALE_TOP); - } - break; - case OPT_DRAWHEIGHTSCALE : - generator.setDrawHeightScale(DRAWHEIGHTSCALE_BOTTOM); - break; - case OPT_SCALEINTERVAL: { - istringstream arg; - arg.str(optarg); - int major; - int minor; - char sep; - arg >> major; - if (major < 0 || !isdigit(*optarg) || arg.fail()) { - std::cerr << "Invalid parameter to '" << long_options[option_index].name - << "': '" << optarg << "' (expected: [,]" << std::endl; - usage(); - exit(1); - } - arg >> std::ws >> sep >> std::ws; - if (!arg.fail()) { - if ((sep != ',' && sep != ':') || !isdigit(arg.peek())) { - std::cerr << "Invalid parameter to '" << long_options[option_index].name - << "': '" << optarg << "' (expected: [,]" << std::endl; - usage(); - exit(1); - } - arg >> minor; - if (minor < 0) { - std::cerr << "Invalid parameter to '" << long_options[option_index].name - << "': '" << optarg << "' (expected: [,]" << std::endl; - usage(); - exit(1); - } - } - else { - minor = 0; - } - if (minor && sep == ':') { - if (major % minor) { - std::cerr << long_options[option_index].name << ": Cannot divide major interval in " - << minor << " subintervals (not divisible)" << std::endl; - exit(1); - } - minor = major / minor; - } - if ((minor % major) == 0) - minor = 0; - if (long_options[option_index].name[0] == 's') { - generator.setSideScaleInterval(major, minor); - } - else if (long_options[option_index].name[0] == 'h') { - generator.setHeightScaleInterval(major, minor); - } - else { - std::cerr << "Internal error: option " << long_options[option_index].name << " not handled" << std::endl; - exit(1); - } - } - break; - case OPT_SILENCE_SUGGESTIONS: { - for (size_t i = 0; i < strlen(optarg); i++) { - optarg[i] = tolower(optarg[i]); - if (optarg[i] == ',') - optarg[i] = ' '; - } - std::istringstream iss(optarg); - std::string flag; - iss >> std::skipws >> flag; - while (!iss.fail()) { - if (flag == "all") { - generator.setSilenceSuggestion(SUGGESTION_ALL); - DBSQLite3::warnDatabaseLockDelay = false; - } - else if (flag == "prefetch") { - generator.setSilenceSuggestion(SUGGESTION_PREFETCH); - } - else if (flag == "sqlite3-lock") { - #ifdef USE_SQLITE3 - DBSQLite3::warnDatabaseLockDelay = false; - #endif - } - else { - std::cerr << "Invalid flag to '" << long_options[option_index].name << "': '" << flag << "'" << std::endl; - usage(); - exit(1); - } - iss >> flag; - } - } - break; - case 'v': - if (optarg && isdigit(optarg[0]) && optarg[1] == '\0') { - generator.verboseStatistics = optarg[0] - '0'; - generator.verboseCoordinates = optarg[0] - '0'; - } - else { - generator.verboseStatistics = 1; - generator.verboseCoordinates = 1; - } - break; - case OPT_VERBOSE_SEARCH_COLORS: - if (optarg && isdigit(optarg[0]) && optarg[1] == '\0') { - generator.verboseReadColors = optarg[0] - '0'; - } - else { - generator.verboseReadColors++; - } - break; - case 'e': - generator.setDrawAlpha(true); - if (!optarg || !*optarg) - PixelAttribute::setMixMode(PixelAttribute::AlphaMixAverage); - else if (string(optarg) == "cumulative" || strlower(optarg) == "nodarken") - // "nodarken" is supported for backwards compatibility - PixelAttribute::setMixMode(PixelAttribute::AlphaMixCumulative); - else if (string(optarg) == "darken" || strlower(optarg) == "cumulative-darken") - // "darken" is supported for backwards compatibility - PixelAttribute::setMixMode(PixelAttribute::AlphaMixCumulativeDarken); - else if (strlower(optarg) == "average") - PixelAttribute::setMixMode(PixelAttribute::AlphaMixAverage); - else if (strlower(optarg) == "none") - generator.setDrawAlpha(false); - else { - std::cerr << "Invalid parameter to '" << long_options[option_index].name << "': '" << optarg << "'" << std::endl; - usage(); - exit(1); - } - break; - case OPT_DRAWAIR: - generator.setDrawAir(true); - break; - case OPT_DRAWNODES: { - bool draw = long_options[option_index].name[0] == 'd'; - for (char *c = optarg; *c; c++) { - *c = tolower(*c); - if (*c == ',') *c = ' '; - } - istringstream iss(optarg); - string flag; - iss >> std::skipws >> flag; - while (!iss.fail()) { - bool enable = draw; - if (flag.substr(0,3) == "no-") { - flag = flag.substr(3); - enable = !enable; - } - if (flag == "") - (void) true; // Empty flag - ignore - else if (flag == "ignore") - generator.setDrawIgnore(enable); - else if (flag == "air") - generator.setDrawAir(enable); - else { - std::cerr << "Invalid " << long_options[option_index].name << " flag '" << flag << "'" << std::endl; - usage(); - exit(1); - } - iss >> flag; - } - } - break; - case 'H': - generator.setShading(false); - break; - case OPT_SQLITE_CACHEWORLDROW: - // This option is recognised for backward compatibility. - // Tests with a (large) world on SSD and on HDD showed a performance decrease - // on all map sizes with this option enabled. - // (Next: print a message when this option is used. - // Later: remove it completely) - break; - case OPT_PROGRESS_INDICATOR: - generator.enableProgressIndicator(); - break; - case 'a': { - istringstream iss; - iss.str(optarg); - int miny; - iss >> miny; - generator.setMinY(miny); - } - break; - case 'c': { - istringstream iss; - iss.str(optarg); - int maxy; - iss >> maxy; - generator.setMaxY(maxy); - } - break; - case OPT_CHUNKSIZE : { - istringstream iss; - iss.str(optarg); - int size; - iss >> size; - if (iss.fail() || size < 0) { - std::cerr << "Invalid chunk size (" << optarg << ")" << std::endl; - usage(); - exit(1); - } - generator.setChunkSize(size); - } - break; - case OPT_SCALEFACTOR: { - istringstream arg; - arg.str(optarg); - int one; - char colon; - int factor = 1; - arg >> one >> std::ws; - if (arg.fail() || one != 1) { - std::cerr << "Invalid scale factor specification (" << optarg << ") - expected: 1:" << std::endl; - exit(1); - } - if (!arg.eof()) { - arg >> colon >> factor >> std::ws; - if (arg.fail() || colon != ':' || factor<0 || !arg.eof()) { - std::cerr << "Invalid scale factor specification (" << optarg << ") - expected: 1:" << std::endl; - usage(); - exit(1); - } - if (factor != 1 && factor != 2 && factor != 4 && factor != 8 && factor != 16) { - std::cerr << "Scale factor must be 1:1, 1:2, 1:4, 1:8 or 1:16" << std::endl; - exit(1); - } - } - generator.setScaleFactor(factor); - } - break; - case 't': { - istringstream tilesize; - tilesize.str(strlower(optarg)); - if (tilesize.str() == "block") { - generator.setTileSize(BLOCK_SIZE, BLOCK_SIZE); - generator.setTileOrigin(TILECORNER_AT_WORLDCENTER, TILECORNER_AT_WORLDCENTER); - } - else if (tilesize.str() == "chunk") { - generator.setTileSize(TILESIZE_CHUNK, TILESIZE_CHUNK); - generator.setTileOrigin(TILECENTER_AT_CHUNKCENTER, TILECENTER_AT_CHUNKCENTER); - } - else { - int size, border; - char c; - tilesize >> size; - if (tilesize.fail() || size<0) { - std::cerr << "Invalid tile size specification (" << optarg << ")" << std::endl; - usage(); - exit(1); - } - generator.setTileSize(size, size); - tilesize >> c >> border; - if (!tilesize.fail()) { - if (c != '+' || border < 1) { - std::cerr << "Invalid tile border size specification (" << optarg << ")" << std::endl; - usage(); - exit(1); - } - generator.setTileBorderSize(border); - } - } - } - break; - case 'T': { - bool origin = long_options[option_index].name[4] == 'o'; - istringstream iss; - iss.str(strlower(optarg)); - NodeCoord coord; - if (iss.str() == "world") { - if (origin) - generator.setTileOrigin(TILECORNER_AT_WORLDCENTER, TILECORNER_AT_WORLDCENTER); - else - generator.setTileCenter(TILECENTER_AT_WORLDCENTER, TILECENTER_AT_WORLDCENTER); - } - else if (iss.str() == "map") { - if (origin) - generator.setTileOrigin(TILECORNER_AT_MAPCENTER, TILECORNER_AT_MAPCENTER); - else - generator.setTileCenter(TILECENTER_AT_MAPCENTER, TILECENTER_AT_MAPCENTER); - } - else { - bool result = true; - if (!parseCoordinates(iss, coord, 2, 0, ',')) { - iss.str(optarg); - result = parseCoordinates(iss, coord, 2, 0, ':'); - } - if (result) { - if (origin) { - convertBlockToNodeCoordinates(coord, 0, 2); - generator.setTileOrigin(coord.x(), coord.y()); - } - else { - convertBlockToNodeCoordinates(coord, 8, 2); - generator.setTileCenter(coord.x(), coord.y()); - } - } - else { - std::cerr << "Invalid " << long_options[option_index].name << " parameter (" << optarg << ")" << std::endl; - usage(); - exit(1); - } - } - } - break; - case 'G': - if (long_options[option_index].name[0] == 'f') { - // '--forcegeometry' - // Old behavior - for compatibility. - generator.setShrinkGeometry(false); - setFixedOrShrinkGeometry = true; - if (!foundGeometrySpec) - generator.setBlockGeometry(true); - } - else if (optarg && *optarg) { - for (char *c = optarg; *c; c++) { - *c = tolower(*c); - if (*c == ',') *c = ' '; - } - istringstream iss; - iss.str(optarg); - string flag; - iss >> std::skipws >> flag; - while (!iss.fail()) { - if (flag == "") - (void) true; // Empty flag - ignore - else if (flag == "pixel") - generator.setBlockGeometry(false); - else if (flag == "block") - generator.setBlockGeometry(true); - else if (flag == "fixed") - generator.setShrinkGeometry(false); - else if (flag == "shrink") - generator.setShrinkGeometry(true); - else { - std::cerr << "Invalid geometry mode flag '" << flag << "'" << std::endl; - usage(); - exit(1); - } - if (flag == "fixed" || flag == "shrink") - setFixedOrShrinkGeometry = true; - iss >> flag; - } - } - foundGeometrySpec = true; - break; - case 'g': { - istringstream iss; - iss.str(optarg); - NodeCoord coord1; - NodeCoord coord2; - bool legacy; - FuzzyBool center = FuzzyBool::Maybe; - if (long_options[option_index].name[0] == 'c' && long_options[option_index].name[1] == 'e') - center = FuzzyBool::Yes; - if (long_options[option_index].name[0] == 'c' && long_options[option_index].name[1] == 'o') - center = FuzzyBool::No; - if (!parseMapGeometry(iss, coord1, coord2, legacy, center)) { - std::cerr << "Invalid geometry specification '" << optarg << "'" << std::endl; - usage(); - exit(1); - } - // Set defaults - if (!foundGeometrySpec) { - if (long_options[option_index].name[0] == 'g' && legacy) { - // Compatibility when using the option 'geometry' - generator.setBlockGeometry(true); - generator.setShrinkGeometry(true); - } - else { - generator.setBlockGeometry(false); - generator.setShrinkGeometry(false); - } - setFixedOrShrinkGeometry = true; - } - if (!setFixedOrShrinkGeometry) { - // Special treatement is needed, because: - // - without any -[...]geometry option, default is shrink - // - with any -[...]geometry option, default is fixed - generator.setShrinkGeometry(false); - setFixedOrShrinkGeometry = true; - } - generator.setGeometry(coord1, coord2); - foundGeometrySpec = true; - } - break; - case OPT_DRAW_OBJECT: { - TileGenerator::DrawObject drawObject; - drawObject.world = long_options[option_index].name[4] != 'm'; - char object = long_options[option_index].name[4 + (drawObject.world ? 0 : 3)]; - switch (object) { - case 'p' : - drawObject.type = TileGenerator::DrawObject::Point; - break; - case 'l' : - drawObject.type = TileGenerator::DrawObject::Line; - break; - case 'r' : - drawObject.type = TileGenerator::DrawObject::Rectangle; - break; - case 'e' : - case 'c' : - drawObject.type = TileGenerator::DrawObject::Ellipse; - break; - case 'a' : - drawObject.type = TileGenerator::DrawObject::Line; - break; - case 't' : - drawObject.type = TileGenerator::DrawObject::Text; - break; - default : - std::cerr << "Internal error: unrecognised object (" - << long_options[option_index].name - << ")" << std::endl; - exit(1); - break; - } - - istringstream iss; - iss.str(optarg); - NodeCoord coord1; - NodeCoord coord2; - NodeCoord dimensions; - FuzzyBool needDimensions; - bool legacy; - bool centered; - - if (object == 'p' || object == 't') - needDimensions = FuzzyBool::No; - else - needDimensions = FuzzyBool::Yes; - if (!parseGeometry(iss, coord1, coord2, dimensions, legacy, centered, 2, needDimensions)) { - std::cerr << "Invalid drawing geometry specification for " - << long_options[option_index].name - << " '" << optarg << "'" << std::endl; - usage(); - exit(1); - } - bool haveCoord2 = coord2.dimension[0] != NodeCoord::Invalid - && coord2.dimension[1] != NodeCoord::Invalid; - bool haveDimensions = dimensions.dimension[0] != NodeCoord::Invalid - && dimensions.dimension[1] != NodeCoord::Invalid; - - if (object == 'p' || object == 't') { - for (int i = 0; i < 2; i++) - if (coord1.isBlock[i]) { - coord1.dimension[i] *= 16; - coord1.isBlock[i] = false; - } - drawObject.setCenter(coord1); - drawObject.setDimensions(NodeCoord(1,1,1)); - } - else { - if (haveDimensions) { - if (centered) - drawObject.setCenter(coord1); - else - drawObject.setCorner1(coord1); - drawObject.setDimensions(dimensions); - } - else if (haveCoord2) { - drawObject.setCorner1(coord1); - drawObject.setCorner2(coord2); - } - else { -#ifdef DEBUG - assert(!haveDimensions && !haveCoord2); -#else - break; -#endif - } - } - - string colorStr; - iss >> std::ws >> colorStr; - if (iss.fail()) { - std::cerr << "Missing color for " - << long_options[option_index].name - << " '" << optarg << "'" << std::endl; - usage(); - exit(1); - } - drawObject.color = colorStr; - - if (object == 't') { - iss >> std::ws; - std::string localizedText; - std::getline(iss, localizedText); - if (localizedText.empty() || iss.fail()) { - std::cerr << "Invalid or missing text for " - << long_options[option_index].name - << " '" << optarg << "'" << std::endl; - usage(); - exit(1); - } - drawObject.text = charConvUTF8->convert(localizedText); - } - - generator.drawObject(drawObject); - if (object == 'a') { - if (drawObject.haveCenter) { - std::cerr << "Arrow cannot use a centered dimension." - << " Specify at least one corner." << std::endl; - exit(1); - } - bool useDimensions = drawObject.haveDimensions; - - if (drawObject.haveDimensions) - convertDimensionToCornerCoordinates(drawObject.corner1, drawObject.corner2, drawObject.dimensions, 2); - double angle, length; - convertCartesianToPolarCoordinates(drawObject.corner1, drawObject.corner2, angle, length); - convertPolarToCartesianCoordinates(drawObject.corner1, drawObject.corner2, angle + DRAW_ARROW_ANGLE, DRAW_ARROW_LENGTH); - if (useDimensions) { - convertCornerToDimensionCoordinates(drawObject.corner1, drawObject.corner2, drawObject.dimensions, 2); - drawObject.haveDimensions = useDimensions; - } - generator.drawObject(drawObject); - convertPolarToCartesianCoordinates(drawObject.corner1, drawObject.corner2, angle - DRAW_ARROW_ANGLE, DRAW_ARROW_LENGTH); - if (useDimensions) { - convertCornerToDimensionCoordinates(drawObject.corner1, drawObject.corner2, drawObject.dimensions, 2); - drawObject.haveDimensions = useDimensions; - } - generator.drawObject(drawObject); - } - } - break; - case 'd': - generator.setBackend(strlower(optarg)); - break; - default: - exit(1); - } - } - } catch(std::runtime_error e) { - std::cout<<"Command-line error: "< +#include +#include "TileGenerator.h" +#include "CharEncodingConverter.h" +#include + +#define OPT_SQLITE_CACHEWORLDROW 0x81 +#define OPT_PROGRESS_INDICATOR 0x82 +#define OPT_DRAW_OBJECT 0x83 +#define OPT_BLOCKCOLOR 0x84 +#define OPT_DRAWAIR 0x85 +#define OPT_VERBOSE_SEARCH_COLORS 0x86 +#define OPT_CHUNKSIZE 0x87 +#define OPT_HEIGHTMAP 0x88 +#define OPT_HEIGHTMAPYSCALE 0x89 +#define OPT_HEIGHT_LEVEL0 0x8a +#define OPT_HEIGHTMAPNODESFILE 0x8b +#define OPT_HEIGHTMAPCOLORSFILE 0x8c +#define OPT_DRAWHEIGHTSCALE 0x8d +#define OPT_SCALEFACTOR 0x8e +#define OPT_SCALEINTERVAL 0x8f +#define OPT_NO_BLOCKLIST_PREFETCH 0x90 +#define OPT_DATABASE_FORMAT 0x91 +#define OPT_SILENCE_SUGGESTIONS 0x92 +#define OPT_PRESCAN_WORLD 0x93 +#define OPT_DRAWNODES 0x94 +#define OPT_SQLITE_LIMIT_PRESCAN_QUERY 0x95 + +#define DRAW_ARROW_LENGTH 10 +#define DRAW_ARROW_ANGLE 30 + +// Will be used in error messages if nothing better is available. +#define DEFAULT_PROGRAM_NAME "minetestmapper" + +class FuzzyBool { +private: + int m_value; + FuzzyBool(int i) : m_value(i) {} +public: + FuzzyBool() : m_value(0) {} + FuzzyBool(bool b) : m_value(b ? Yes.m_value : No.m_value) {} + static const FuzzyBool Yes; + static const FuzzyBool Maybe; + static const FuzzyBool No; + inline friend bool operator==(FuzzyBool f1, FuzzyBool f2) { return f1.m_value == f2.m_value; } + inline friend bool operator!=(FuzzyBool f1, FuzzyBool f2) { return f1.m_value != f2.m_value; } + inline friend bool operator>=(FuzzyBool f1, FuzzyBool f2) { return f1.m_value >= f2.m_value; } + inline friend bool operator<=(FuzzyBool f1, FuzzyBool f2) { return f1.m_value <= f2.m_value; } + inline friend bool operator<(FuzzyBool f1, FuzzyBool f2) { return f1.m_value < f2.m_value; } + inline friend bool operator>(FuzzyBool f1, FuzzyBool f2) { return f1.m_value < f2.m_value; } +}; + + +class Mapper +{ +public: + Mapper(const std::string &executableName, const std::string &executablePath); + int start(int argc, char *argv[]); + +private: + TileGenerator generator; + const std::string executableName; + const std::string executablePath; + + void usage(); + + std::string input; + std::string output; + bool heightMap = false; + bool loadHeightMapColorsFile = false; + std::string nodeColorsFile; + std::string heightMapColorsFile; + std::string heightMapNodesFile; + bool foundGeometrySpec = false; + bool setFixedOrShrinkGeometry = false; + CharEncodingConverter *charConvUTF8; + + const std::string nodeColorsDefaultFile = "colors.txt"; + const std::string heightMapNodesDefaultFile = "heightmap-nodes.txt"; + const std::string heightMapColorsDefaultFile = "heightmap-colors.txt"; + + static bool validStreamAtWsOrEof(std::istream & is) + { + // May need to peek before knowing it's EOF. + // However, peeking twice results in fail()... + return !is.fail() && (is.eof() || is.peek() == ' ' || is.eof() || is.peek() == '\t'); + } + + static bool validStreamAtEof(std::istream & is) + { + // May need to peek before knowing it's EOF. + // However, peeking twice at eof results in fail()... + return !is.fail() && (is.eof() || (is.peek(), is.eof())); + } + + static int safePeekStream(std::istream & is) + { + // Don't peek at EOF, else stream fails... + return is.eof() ? EOF : is.peek(); + } + + void parseDataFile(TileGenerator &generator, const std::string &input, std::string dataFile, std::string defaultFile, + void (TileGenerator::*parseFile)(const std::string &fileName)); + + // is: stream to read from + // coord: set to coordinate value that was read + // isBlockCoord: set to true if the coordinate read was a block coordinate + // wildcard: if non-zero, accept '*' as a coordinate, and return this value instead. + // (suggested values for 'wildcard': INT_MIN or INT_MAX) + // + // Accepted coordinate syntax: + // [+-]: node coordinate: node +/- n + // [+-]#: block coordinate: block +/- b (isBlockCoord will be set to true) + // [+-]#: node coordinate: node in block +/- + // [+-].: node coordinate: node +/- (b * 16 + n) + // As a special feature, double signs are also supported. E.g.: + // +-3 + // Which allows shell command-lines like the following + // ${width}x${height}+$xoffs+$yoffs + // (which otherwise require special measures to cope with xoffs or yoffs being negative...) + // Other uses of this feature are left as an excercise to the reader. + // Hint: --3.5 is *not* the same as 3.5 + static bool parseNodeCoordinate(std::istream &is, int &coord, bool &isBlockCoord, int wildcard); + + static bool parseCoordinates(std::istream &is, NodeCoord &coord, int n, int wildcard = 0, char separator = ','); + + static void convertBlockToNodeCoordinates(NodeCoord &coord, int offset, int n); + + static void convertBlockToNodeCoordinates(NodeCoord &coord1, NodeCoord &coord2, int n); + + static void convertCenterToCornerCoordinates(NodeCoord &coord, NodeCoord &dimensions, int n); + + static void convertDimensionToCornerCoordinates(NodeCoord &coord1, NodeCoord &coord2, NodeCoord &dimensions, int n); + + static void convertCornerToDimensionCoordinates(NodeCoord &coord1, NodeCoord &coord2, NodeCoord &dimensions, int n); + + static void convertPolarToCartesianCoordinates(NodeCoord &coord1, NodeCoord &coord2, double angle, double length); + + static void convertCartesianToPolarCoordinates(NodeCoord &coord1, NodeCoord &coord2, double &angle, double &length); + + static void orderCoordinateDimensions(NodeCoord &coord1, NodeCoord &coord2, int n); + + + // Parse the following geometry formats: + // x[++] + // (dimensions, and position) + // (if x and y are omitted, they default to -w/2 and -h/2) + // ,:, + // (2 corners of the area) + // ,:x + // (center of the area, and dimensions) + // [,:]++ + // (corner of the area, and dimensions) + // ,@+ + static bool parseGeometry(std::istream &is, NodeCoord &coord1, NodeCoord &coord2, NodeCoord &dimensions, bool &legacy, bool ¢ered, int n, FuzzyBool expectDimensions, int wildcard = 0); + + static bool parseMapGeometry(std::istream &is, NodeCoord &coord1, NodeCoord &coord2, bool &legacy, FuzzyBool interpretAsCenter); + +}; + + +