/* * ===================================================================== * Version: 1.0 * Created: 22.08.2012 15:15:54 * Author: Miroslav Bendík * Company: LinuxOS.sk * ===================================================================== */ #include #include #include #include #include #include #include #include #include #include #include #include "TileGenerator.h" #include "PixelAttributes.h" using namespace std; #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 // Will be replaced with the actual name and location of the executable (if found) string executableName = "minetestmapper"; string installPrefix = INSTALL_PREFIX; string colorsDefaultName = "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; void usage() { const char *options_text = "[options]\n" " -h/--help\n" " -V/--version\n" " -i/--input \n" " -o/--output \n" " --colors \n" " --bgcolor \n" " --blockcolor \n" " --scalecolor \n" " --playercolor \n" " --origincolor \n" " --tilebordercolor \n" " --drawscale\n" " --drawplayers\n" " --draworigin\n" " --drawalpha[=cumulative|cumulative-darken|average|none]\n" " --drawair\n" " --draw[map]point \", color\"\n" " --draw[map]line \" color\"\n" " --draw[map]circle \" color\"\n" " --draw[map]ellipse \" color\"\n" " --draw[map]rectangle \" color\"\n" " --draw[map]text \", color text\"\n" " --noshading\n" " --min-y \n" " --max-y \n" " --backend <" USAGE_DATABASES ">\n" " --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" #if USE_SQLITE3 " --sqlite-cacheworldrow\n" #endif " --tiles [+]|block|chunk\n" " --tileorigin ,|world|map\n" " --tilecenter ,|world|map\n" " --chunksize \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" "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 parseColorsFile(TileGenerator &generator, const string &input, string colorsFile) { if (!colorsFile.empty()) { generator.parseColorsFile(colorsFile); return; } std::vector colorPaths; colorPaths.push_back(input); // Check if input/../.. looks like a valid minetest directory string minetestPath = input + PATH_SEPARATOR + ".." + PATH_SEPARATOR + ".."; string minetestConf = minetestPath + PATH_SEPARATOR + "minetest.conf"; int fd; if (0 <= (fd = open(minetestConf.c_str(), O_RDONLY))) { close(fd); colorPaths.push_back(minetestPath); } char *homedir; if ((homedir = getenv("HOME"))) { colorPaths.push_back(string(homedir) + PATH_SEPARATOR + ".minetest"); } if (!installPrefix.empty()) { #if PACKAGING_FLAT colorPaths.push_back(installPrefix); #else colorPaths.push_back(installPrefix + "/share/games/minetestmapper"); #endif } colorPaths.push_back(""); std::vector colorFileNames; colorFileNames.push_back(colorsDefaultName); for (std::vector::iterator path = colorPaths.begin(); path != colorPaths.end(); path++) { for (std::vector::iterator name = colorFileNames.begin(); name != colorFileNames.end(); name++) { if (path->empty()) colorsFile = *name; else colorsFile = *path + PATH_SEPARATOR + *name; try { generator.parseColorsFile(colorsFile); 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 << " 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; } return; } catch (std::runtime_error e) { // Ignore failure to locate colors file in standard location // (we have more places to search) if (path->empty()) { } } } } throw std::runtime_error("Failed to find or failed to open a colors.txt file."); } // 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) { char c; int i; char s; s = c = is.peek(); if (c == '*') { if (wildcard) { i = wildcard; is.ignore(1); } else { is >> coord; // Set stream status to failed } } else { wildcard = 0; // not processing a wildcard now if (s == '-' || s == '+') is.ignore(1); else s = '+'; is >> i; if (s == '-') i = -i; } if (is.fail()) return false; coord = i; isBlockCoord = false; if (is.eof()) return true; // Check if this is a block number, and so: if it has a node number. c = is.peek(); if (c == '#' || c == '.') { // coordinate read was a block number is.ignore(1); if (wildcard) { return false; // wildcards are generic } else if (isdigit(is.peek())) { // has a node number / offset is >> i; if (!is.fail()) { if (c == '.' && s == '-') { // Using '.', the node number has same sign as block number // Using '#', the node number is always positive // i.e. -1#1 is: node #1 in block -1 (i.e. node -16 + 1 = -15) // i.e. -1.1 is: 1 block and 1 node in negative direction (i.e. node 16 - 1 = -17) i = -i; } coord = coord * 16 + i; } } else { // No node number / offset isBlockCoord = true; } } return (!is.fail()); } static bool parseCoordinates(istream &is, NodeCoord &coord, int n, int wildcard = 0, char separator = ',') { bool result; result = true; NodeCoord tempCoord; for (int i = 0; result && i < n; i++) { if (i && separator) { char c; is >> c; if (c != separator) { result = false; break; } } result = parseNodeCoordinate(is, tempCoord.dimension[i], tempCoord.isBlock[i], wildcard); } if (result) coord = tempCoord; return result; } static void convertBlockToNodeCoordinates(NodeCoord &coord, int offset, int n) { for (int i = 0; i < n; i++) { if (coord.isBlock[i]) { coord.dimension[i] = coord.dimension[i] * 16 + offset; coord.isBlock[i] = false; } } } static void 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]; } coord1.dimension[i] = c1; coord2.dimension[i] = c2; coord1.isBlock[i] = false; coord2.isBlock[i] = false; } } static void 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 // The advantage is that e.g. 0#,0#:16x16 selects the 16x16 area that is block 0: // 0#,0#:16x16 -> 0,0:15,15 // With a bias to the positive side, that would be: // 0#,0#:16x16 -> 1,1:16,16 // Which is counter-intuitive by itself (IMHO :-) for (int i = 0; i < n; i++) { if (dimensions.dimension[i] < 0) coord.dimension[i] += -dimensions.dimension[i] / 2; else coord.dimension[i] -= dimensions.dimension[i] / 2; } } static void convertDimensionToCornerCoordinates(NodeCoord &coord1, NodeCoord &coord2, NodeCoord &dimensions, int n) { for (int i = 0; i < n; i++) { if (dimensions.dimension[i] < 0) coord2.dimension[i] = coord1.dimension[i] + dimensions.dimension[i] + 1; else coord2.dimension[i] = coord1.dimension[i] + dimensions.dimension[i] - 1; } } static void orderCoordinateDimensions(NodeCoord &coord1, NodeCoord &coord2, int n) { for (int i = 0; i < n; i++) if (coord1.dimension[i] > coord2.dimension[i]) { int temp = coord1.dimension[i]; coord1.dimension[i] = coord2.dimension[i]; coord2.dimension[i] = temp; } } // 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) { int pos; pos = is.tellg(); legacy = false; for (int i = 0; i < n; i++) { coord1.dimension[i] = NodeCoord::Invalid; coord2.dimension[i] = NodeCoord::Invalid; dimensions.dimension[i] = NodeCoord::Invalid; } if (expectDimensions >= FuzzyBool::Maybe && parseCoordinates(is, dimensions, n, 0, 'x')) { convertBlockToNodeCoordinates(dimensions, 0, n); // x[++] if (is.eof()) { centered = true; for (int i = 0; i < n; i++) { coord1.dimension[i] = 0; coord1.isBlock[i] = false; } return (is.eof() || is.peek() == ' ' || is.peek() == '\t'); } else { centered = false; if (parseCoordinates(is, coord1, n, 0, '\0')) { convertBlockToNodeCoordinates(coord1, 0, n); return (is.eof() || is.peek() == ' ' || is.peek() == '\t'); } else return false; } } is.clear(); is.seekg(pos); if (wildcard) { coord1.x = coord1.y = coord1.z = 0; } if (parseCoordinates(is, coord1, n, wildcard, ',')) { if (expectDimensions == FuzzyBool::No || (expectDimensions == FuzzyBool::Maybe && (is.eof() || is.peek() == ' ' || is.peek() == '\t'))) { // Just coordinates were specified centered = false; return (is.eof() || is.peek() == ' ' || is.peek() == '\t'); } else if (wildcard && (coord1.x == wildcard || coord1.y == wildcard || coord1.z == wildcard)) { // wildcards are only allowed for plain coordinates (i.e. no dimensions) return false; } else if (is.peek() == ':') { is.ignore(1); pos = is.tellg(); if (parseCoordinates(is, coord2, n, 0, ',')) { // ,:, centered = false; convertBlockToNodeCoordinates(coord1, coord2, n); return (is.eof() || is.peek() == ' ' || is.peek() == '\t'); } is.clear(); is.seekg(pos); if (parseCoordinates(is, dimensions, n, 0, 'x')) { // ,:x // (x,y is the center of the area by default) centered = true; convertBlockToNodeCoordinates(coord1, 8, n); convertBlockToNodeCoordinates(dimensions, 0, n); return (is.eof() || is.peek() == ' ' || is.peek() == '\t'); } else { return false; } } else { // ,++ centered = false; if (parseCoordinates(is, dimensions, n, 0, '\0')) { convertBlockToNodeCoordinates(coord1, 0, n); convertBlockToNodeCoordinates(dimensions, 0, n); return (is.eof() || is.peek() == ' ' || is.peek() == '\t'); } else { return false; } } } is.clear(); is.seekg(pos); if (parseCoordinates(is, coord1, n, 0, ':')) { // :++ legacy = true; centered = false; if (parseCoordinates(is, dimensions, n, 0, '\0')) { convertBlockToNodeCoordinates(coord1, 0, n); convertBlockToNodeCoordinates(dimensions, 0, n); return (is.eof() || is.peek() == ' ' || is.peek() == '\t'); } return false; } return false; } static bool parseMapGeometry(istream &is, NodeCoord &coord1, NodeCoord &coord2, bool &legacy, FuzzyBool interpretAsCenter) { NodeCoord dimensions; bool centered; bool result = parseGeometry(is, coord1, coord2, dimensions, legacy, centered, 2, FuzzyBool::Yes); if (result) { 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 (!haveCoord2 && haveDimensions) { // Convert coord1 + dimensions to coord1 + coord2. // First, if coord1 must be interpreted as center of the area, adjust it to be a corner if ((centered && interpretAsCenter == FuzzyBool::Maybe) || interpretAsCenter == FuzzyBool::Yes) convertCenterToCornerCoordinates(coord1, dimensions, 2); convertDimensionToCornerCoordinates(coord1, coord2, dimensions, 2); } else if (!haveCoord2 || haveDimensions) { return false; } orderCoordinateDimensions(coord1, coord2, 2); } 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 = argv0.substr(pos + 1); } } 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'}, {"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", no_argument, 0, 'S'}, {"drawalpha", optional_argument, 0, 'e'}, {"drawair", no_argument, 0, OPT_DRAWAIR}, {"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}, {"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}, {"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'}, {"sqlite-cacheworldrow", no_argument, 0, OPT_SQLITE_CACHEWORLDROW}, {"tiles", required_argument, 0, 't'}, {"tileorigin", required_argument, 0, 'T'}, {"tilecenter", required_argument, 0, 'T'}, {"tilebordercolor", required_argument, 0, 'B'}, {"chunksize", required_argument, 0, OPT_CHUNKSIZE}, {"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; string colorsFile; bool foundGeometrySpec = false; bool setFixedOrShrinkGeometry = false; 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: " << VERSION_MAJOR << "." << VERSION_MINOR << std::endl; return 0; break; case 'i': input = optarg; break; case 'o': output = optarg; break; case 'C': colorsFile = optarg; break; case 'b': generator.setBgColor(Color(optarg, 0)); 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': generator.setDrawScale(true); break; case 'v': if (optarg && isdigit(optarg[0]) && optarg[1] == '\0') { if (optarg[0] == '0') generator.verboseStatistics = false; else generator.verboseStatistics = true; generator.verboseCoordinates = optarg[0] - '0'; } else { generator.verboseStatistics = true; 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" || string(optarg) == "nodarken") // "nodarken" is supported for backwards compatibility PixelAttribute::setMixMode(PixelAttribute::AlphaMixCumulative); else if (string(optarg) == "darken" || string(optarg) == "cumulative-darken") // "darken" is supported for backwards compatibility PixelAttribute::setMixMode(PixelAttribute::AlphaMixCumulativeDarken); else if (string(optarg) == "average") PixelAttribute::setMixMode(PixelAttribute::AlphaMixAverage); else if (string(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 'H': generator.setShading(false); break; case OPT_SQLITE_CACHEWORLDROW: generator.setSqliteCacheWorldRow(true); 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 't': { istringstream tilesize; tilesize.str(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(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++) if (*c == ',') *c = ' '; istringstream iss; iss.str(optarg); iss >> std::skipws; string flag; while (!iss.eof() && !iss.fail()) { iss >> flag; 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; } if (iss.fail()) { // Don't know when / if this could happen... std::cerr << "Error parsing geometry mode flags" << std::endl; usage(); exit(1); } } 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 '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 << "Invalid color specification for " << long_options[option_index].name << " '" << optarg << "'" << std::endl; usage(); exit(1); } drawObject.color = colorStr; if (object == 't') { iss >> std::ws; std::getline(iss, drawObject.text); if (drawObject.text.empty() || iss.fail()) { std::cerr << "Invalid or missing text for " << long_options[option_index].name << " '" << optarg << "'" << std::endl; usage(); exit(1); } } generator.drawObject(drawObject); } break; case 'd': generator.setBackend(optarg); break; default: exit(1); } } } catch(std::runtime_error e) { std::cout<<"Command-line error: "<