minetest-mapper-cpp/mapper.cpp

1519 lines
49 KiB
C++

/*
* =====================================================================
* Version: 1.0
* Created: 22.08.2012 15:15:54
* Author: Miroslav Bendík
* Company: LinuxOS.sk
* =====================================================================
*/
#include <cstdlib>
#include <getopt.h>
#include <iostream>
#include <map>
#include <string>
#include <cctype>
#include <sstream>
#include <cstring>
#include <stdexcept>
#include <fcntl.h>
#include <sys/types.h>
#include "porting.h"
#include "TileGenerator.h"
#include "PixelAttributes.h"
#include "util.h"
#include "db-sqlite3.h"
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)
{
// 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 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()));
}
static inline int safePeekStream(std::istream &is)
{
// Don't peek at EOF, else stream fails...
return is.eof() ? EOF : is.peek();
}
void usage()
{
const char *options_text = "[options]\n"
" -h/--help\n"
" -V/--version\n"
" -i/--input <world_path>\n"
" -o/--output <output_image.png>\n"
" --colors <file>\n"
" --heightmap-nodes <file>\n"
" --heightmap-colors[=<file>]\n"
" --height-level-0 <level>\n"
" --heightmap[=<color>]\n"
" --heightmap-yscale <scale>\n"
" --bgcolor <color>\n"
" --blockcolor <color>\n"
" --scalecolor <color>\n"
" --playercolor <color>\n"
" --origincolor <color>\n"
" --tilebordercolor <color>\n"
" --drawscale[=left,top]\n"
" --sidescale-interval <major>[[,:]<minor>]\n"
" --drawheightscale\n"
" --heightscale-interval <major>[[,:]<minor>]\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 \"<x>,<y> color\"\n"
" --draw[map]line \"<geometry> color\"\n"
" --draw[map]line \"<x>,<y> <angle> <length>[np] color\"\n"
" --draw[map]circle \"<geometry> color\"\n"
" --draw[map]ellipse \"<geometry> color\"\n"
" --draw[map]rectangle \"<geometry> color\"\n"
" --draw[map]arrow \"<x>,<y> <x>,<y> color\"\n"
" --draw[map]arrow \"<x>,<y> <angle> <length>[np] color\"\n"
" --draw[map]text \"<x>,<y> color text\"\n"
" --noshading\n"
" --min-y <y>\n"
" --max-y <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"
#if USE_SQLITE3
" --sqlite3-limit-prescan-query-size[=n]\n"
#endif
" --geometry <geometry>\n"
"\t(Warning: has a compatibility mode - see README.rst)\n"
" --cornergeometry <geometry>\n"
" --centergeometry <geometry>\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 <tilesize>[+<border>]|block|chunk\n"
" --tileorigin <x>,<y>|world|map\n"
" --tilecenter <x>,<y>|world|map\n"
" --scalefactor 1:<n>\n"
" --chunksize <size>\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 <color>[+-][wkrgbcmy]<n>, where n = 0.0..1.0 (or 1.00001 .. 255)\n"
"\t\t[+-][wkrgbcmy]<n> mixes in or out white, black, red, green, blue, cyan, magenta, yellow\n"
"Geometry formats:\n"
"\t<width>x<heigth>[+|-<xoffset>+|-<yoffset>] (dimensions and corner)\n"
"\t<xoffset>,<yoffset>+<width>+<height> (corner and dimensions)\n"
"\t<xcenter>,<ycenter>:<width>x<height> (center and dimensions)\n"
"\t<xcorner1>,<ycorner1>:<xcorner2>,<ycorner2> (corners of area)\n"
"\tOriginal/legacy format - see note under '--geometry' option:\n"
"\t<xoffset>:<yoffset>+<width>+<height> (corner and dimensions)\n"
"X and Y coordinate formats:\n"
"\t[+-]<n> (node +/- <n>)\n"
"\t[+-]<b>#[<n>] (node <n> in block +/- <b>)\n"
"\t[+-]<b>.[<n>] (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))
{
if (!dataFile.empty()) {
(generator.*parseFile)(dataFile);
return;
}
std::vector<std::string> 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";
if (FILE *file = fopen(minetestConf.c_str(), "r")) {
fclose(file);
colorPaths.push_back(minetestPath);
}
char *homedir;
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
#if MSDOS || __OS2__ || __NT__ || _WIN32 || DEBUG
// On windows, assume that argv[0] contains the full path location of minetestmapper.exe
// (i.e. where it is installed)
// On Unix, the path is usually absent from argv[0], and we don't want the behavior to
// depend on how it was invoked anyway.
// In DEBUG mode, do check the command-line path; so this code can at least be tested on
// Linux...
if (executablePath != "") {
size_t binpos = executablePath.find_last_of(PATH_SEPARATOR);
if (binpos != string::npos) {
string lastDir = executablePath.substr(binpos + 1);
lastDir = strlower(lastDir);
if (lastDir == "bin") {
colorPaths.push_back(executablePath.substr(0, binpos) + PATH_SEPARATOR + "colors");
colorPaths.push_back(executablePath.substr(0, binpos));
}
else {
colorPaths.push_back(executablePath);
colorPaths.push_back(executablePath + PATH_SEPARATOR + "colors");
}
}
else {
colorPaths.push_back(executablePath);
}
}
#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<std::string> fileNames;
fileNames.push_back(defaultFile);
for (std::vector<std::string>::iterator path = colorPaths.begin(); path != colorPaths.end(); path++) {
for (std::vector<std::string>::iterator name = fileNames.begin(); name != fileNames.end(); name++) {
if (path->empty())
dataFile = *name;
else
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
<< " 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()) {
}
}
}
}
ostringstream oss;
oss << "Failed to find or failed to open a " << defaultFile << " file.";
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:
// [+-]<n>: node coordinate: node +/- n
// [+-]<b>#: block coordinate: block +/- b (isBlockCoord will be set to true)
// [+-]<b>#<n>: node coordinate: node <n> in block +/- <b>
// [+-]<b>.<n>: 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 = safePeekStream(is);
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 (validStreamAtEof(is))
return true;
// Check if this is a block number, and so: if it has a node number.
c = safePeekStream(is);
if (c == '#' || c == '.') {
// coordinate read was a block number
is.ignore(1);
if (wildcard) {
return false; // wildcards are generic
}
else if (isdigit(safePeekStream(is))) {
// 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 convertCornerToDimensionCoordinates(NodeCoord &coord1, NodeCoord &coord2, NodeCoord &dimensions, int n)
{
for (int i = 0; i < n; i++) {
if (coord2.dimension[i] < coord1.dimension[i])
dimensions.dimension[i] = coord2.dimension[i] - coord1.dimension[i] - 1;
else
dimensions.dimension[i] = coord2.dimension[i] - coord1.dimension[i] + 1;
}
}
static void convertPolarToCartesianCoordinates(NodeCoord &coord1, NodeCoord &coord2, double angle, double length)
{
angle *= M_PI / 180;
double dxf = sin(angle) * length;
int dx = (dxf < 0 ? -1 : 1) * int(fabs(dxf + (dxf < 0 ? -0.5 : 0.5)));
dx -= dx < 0 ? -1 : dx > 0 ? +1 : 0;
double dyf = cos(angle) * length;
int dy = (dyf < 0 ? -1 : 1) * int(fabs(dyf + (dyf < 0 ? -0.5 : 0.5)));
dy -= dy < 0 ? -1 : dy > 0 ? +1 : 0;
coord2.x() = coord1.x() + dx;
coord2.y() = coord1.y() + dy;
}
static void 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);
angle = atan2(lx, ly) / M_PI * 180;
}
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:
// <w>x<h>[+<x>+<y>]
// (dimensions, and position)
// (if x and y are omitted, they default to -w/2 and -h/2)
// <x1>,<y1>:<x2>,<y2>
// (2 corners of the area)
// <x>,<y>:<w>x<h>
// (center of the area, and dimensions)
// <x>[,:]<y>+<w>+<h>
// (corner of the area, and dimensions)
// <x>,<y>@<angle>+<length>
static bool parseGeometry(istream &is, NodeCoord &coord1, NodeCoord &coord2, NodeCoord &dimensions, bool &legacy, bool &centered, int n, FuzzyBool expectDimensions, int wildcard = 0)
{
std::streamoff 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);
// <w>x<h>[+<x>+<y>]
if (validStreamAtEof(is)) {
centered = true;
for (int i = 0; i < n; i++) {
coord1.dimension[i] = 0;
coord1.isBlock[i] = false;
}
return validStreamAtWsOrEof(is);
}
else {
centered = false;
if (parseCoordinates(is, coord1, n, 0, '\0')) {
convertBlockToNodeCoordinates(coord1, 0, n);
return validStreamAtWsOrEof(is);
}
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 && validStreamAtWsOrEof(is))) {
// Just coordinates were specified
centered = false;
return validStreamAtWsOrEof(is);
}
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 (safePeekStream(is) == ':') {
is.ignore(1);
pos = is.tellg();
if (parseCoordinates(is, coord2, n, 0, ',')) {
// <x1>,<y1>:<x2>,<y2>
centered = false;
convertBlockToNodeCoordinates(coord1, coord2, n);
return validStreamAtWsOrEof(is);
}
is.clear();
is.seekg(pos);
if (parseCoordinates(is, dimensions, n, 0, 'x')) {
// <x>,<y>:<w>x<h>
// (x,y is the center of the area by default)
centered = true;
convertBlockToNodeCoordinates(coord1, 8, n);
convertBlockToNodeCoordinates(dimensions, 0, n);
return validStreamAtWsOrEof(is);
}
else {
return false;
}
}
else if (safePeekStream(is) == '@') {
// <x>,<y>@<angle>+<length>[np]
if (n != 2)
return false;
centered = false;
is.ignore(1);
double angle;
double length;
bool world_relative = false;
is >> angle;
if (safePeekStream(is) != '+' && safePeekStream(is) != '-')
return false;
if (safePeekStream(is) == '+')
is.ignore(1);
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;
}
}
if (!validStreamAtWsOrEof(is))
return false;
convertPolarToCartesianCoordinates(coord1, coord2, angle, length);
if (!world_relative) {
convertCornerToDimensionCoordinates(coord1, coord2, dimensions, n);
coord2.x() = NodeCoord::Invalid;
coord2.y() = NodeCoord::Invalid;
}
return true;
}
else {
// <x>,<y>+<w>+<h>
centered = false;
if (parseCoordinates(is, dimensions, n, 0, '\0')) {
convertBlockToNodeCoordinates(coord1, 0, n);
convertBlockToNodeCoordinates(dimensions, 0, n);
return validStreamAtWsOrEof(is);
}
else {
return false;
}
}
}
is.clear();
is.seekg(pos);
if (parseCoordinates(is, coord1, n, 0, ':')) {
// <x>:<y>+<w>+<h>
legacy = true;
centered = false;
if (parseCoordinates(is, dimensions, n, 0, '\0')) {
convertBlockToNodeCoordinates(coord1, 0, n);
convertBlockToNodeCoordinates(dimensions, 0, n);
return validStreamAtWsOrEof(is);
}
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 = 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: " << VERSION_MAJOR << "." << 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) {
#if 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);
}
#if 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<float>(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: <major>[,<minor>]" << 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: <major>[,<minor>]" << std::endl;
usage();
exit(1);
}
arg >> minor;
if (minor < 0) {
std::cerr << "Invalid parameter to '" << long_options[option_index].name
<< "': '" << optarg << "' (expected: <major>[,<minor>]" << 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") {
#if 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:<n>" << 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:<n>" << 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;
}