439 lines
12 KiB
C++
439 lines
12 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 <sstream>
|
|
#include <stdexcept>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include "TileGenerator.h"
|
|
#include "ZlibDecompressor.h"
|
|
|
|
using namespace std;
|
|
|
|
#define OPT_SQLITE_CACHEWORLDROW 0x81
|
|
#define OPT_PROGRESS_INDICATOR 0x82
|
|
|
|
void usage()
|
|
{
|
|
const char *usage_text = "minetestmapper [options]\n"
|
|
" -h/--help\n"
|
|
" -i/--input <world_path>\n"
|
|
" -o/--output <output_image.png>\n"
|
|
" --colors <file>\n"
|
|
" --bgcolor <color>\n"
|
|
" --scalecolor <color>\n"
|
|
" --playercolor <color>\n"
|
|
" --origincolor <color>\n"
|
|
" --tilebordercolor <color>\n"
|
|
" --drawscale\n"
|
|
" --drawplayers\n"
|
|
" --draworigin\n"
|
|
" --drawalpha\n"
|
|
" --noshading\n"
|
|
" --min-y <y>\n"
|
|
" --max-y <y>\n"
|
|
" --backend <sqlite3/leveldb>\n"
|
|
" --geometry <geometry>\n"
|
|
" --cornergeometry <geometry>\n"
|
|
" --centergeometry <geometry>\n"
|
|
" --geometrymode pixel,block,fixed,shrink\n"
|
|
" --sqlite-cacheworldrow\n"
|
|
" --tiles <tilesize>[+<border>]\n"
|
|
" --tileorigin <x>,<y>|center-world|center-map\n"
|
|
" --verbose[=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"
|
|
"\t<width>x<heigth>[+|-<xoffset>+|-<yoffset>]\n"
|
|
"\t<xoffset>:<yoffset>+<width>+<height>\n";
|
|
std::cout << usage_text;
|
|
}
|
|
|
|
void parseColorsFile(TileGenerator &generator, const string &input, string colorsFile) {
|
|
if (!colorsFile.empty()) {
|
|
generator.parseColorsFile(colorsFile);
|
|
}
|
|
else {
|
|
bool colorsFound = false;
|
|
char *homedir;
|
|
colorsFile = input + PATH_SEPARATOR + "colors.txt";
|
|
|
|
try {
|
|
generator.parseColorsFile(colorsFile);
|
|
colorsFound = true;
|
|
} catch (std::runtime_error e) {
|
|
// Ignore failure to locate world-specific colors file
|
|
}
|
|
|
|
if (!colorsFound) {
|
|
// Check if '../..' seems like a valid minetest directory
|
|
string file = input + PATH_SEPARATOR + ".." + PATH_SEPARATOR + ".." + PATH_SEPARATOR + "minetest.conf";
|
|
int fd;
|
|
if (0 <= (fd = open(file.c_str(), O_RDONLY))) {
|
|
close(fd);
|
|
colorsFile = input + PATH_SEPARATOR + ".." + PATH_SEPARATOR + ".." + PATH_SEPARATOR + "colors.txt";
|
|
try {
|
|
generator.parseColorsFile(colorsFile);
|
|
colorsFound = true;
|
|
} catch (std::runtime_error e) {
|
|
// Ignore failure to locate world-specific colors file
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!colorsFound && (homedir = getenv("HOME"))) {
|
|
colorsFile = string(homedir) + PATH_SEPARATOR + ".minetest" + PATH_SEPARATOR + "colors.txt";
|
|
try {
|
|
generator.parseColorsFile(colorsFile);
|
|
colorsFound = true;
|
|
} catch (std::runtime_error e) {
|
|
// Ignore failure to locate user private colors file
|
|
}
|
|
}
|
|
|
|
// TODO: look for system-wide colors file (?) (e.g. /usr/share/games/minetest/colors.txt)
|
|
// (location should be subject to a build-time configuration of the installation directory)
|
|
|
|
if (!colorsFound) {
|
|
try {
|
|
generator.parseColorsFile("colors.txt");
|
|
// I hope this is not obnoxious to windows users ?
|
|
cerr << "Warning: Using colors.txt 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;
|
|
} catch(std::runtime_error e) {
|
|
throw std::runtime_error("Failed to find or failed to open a colors.txt file.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
static struct option long_options[] =
|
|
{
|
|
{"help", no_argument, 0, 'h'},
|
|
{"input", required_argument, 0, 'i'},
|
|
{"output", required_argument, 0, 'o'},
|
|
{"colors", required_argument, 0, 'C'},
|
|
{"bgcolor", required_argument, 0, 'b'},
|
|
{"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", no_argument, 0, 'e'},
|
|
{"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'},
|
|
{"tilebordercolor", required_argument, 0, 'B'},
|
|
{"verbose", optional_argument, 0, 'v'},
|
|
{"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 'i':
|
|
input = optarg;
|
|
break;
|
|
case 'o':
|
|
output = optarg;
|
|
break;
|
|
case 'C':
|
|
colorsFile = optarg;
|
|
break;
|
|
case 'b':
|
|
generator.setBgColor(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 'e':
|
|
generator.setDrawAlpha(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 't': {
|
|
istringstream tilesize;
|
|
tilesize.str(optarg);
|
|
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': {
|
|
istringstream origin;
|
|
origin.str(optarg);
|
|
int x, y;
|
|
char c;
|
|
origin >> x >> c >> y;
|
|
if (origin.fail() || (c != ':' && c != ',')) {
|
|
if (string("center-world") == optarg)
|
|
generator.setTileOrigin(TILECENTER_IS_WORLDCENTER, TILECENTER_IS_WORLDCENTER);
|
|
else if (string("center-map") == optarg)
|
|
generator.setTileOrigin(TILECENTER_IS_MAPCENTER, TILECENTER_IS_MAPCENTER);
|
|
else {
|
|
std::cerr << "Invalid tile origin specification (" << optarg << ")" << std::endl;
|
|
usage();
|
|
exit(1);
|
|
}
|
|
}
|
|
else {
|
|
generator.setTileOrigin(x, y);
|
|
}
|
|
}
|
|
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 {
|
|
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': {
|
|
// Set defaults
|
|
if (!foundGeometrySpec) {
|
|
if (long_options[option_index].name[0] == 'g') {
|
|
// 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;
|
|
}
|
|
|
|
istringstream iss;
|
|
iss.str(optarg);
|
|
int p1, p2, p3, p4;
|
|
char c;
|
|
iss >> p1 >> c >> p2;
|
|
if (!iss.fail() && c == 'x' && iss.eof()) {
|
|
p3 = -(p1 / 2);
|
|
p4 = -(p2 / 2);
|
|
}
|
|
else {
|
|
char s3, s4;
|
|
iss >> s3 >> p3 >> s4 >> p4;
|
|
// accept +-23 as well (for ease of use)
|
|
if ((s3 != '+' && s3 != '-') || (s4 != '+' && s4 != '-'))
|
|
c = 0; // Causes an 'invalid geometry' message
|
|
if (s3 == '-') p3 = -p3;
|
|
if (s4 == '-') p4 = -p4;
|
|
if (long_options[option_index].name[0] == 'c'
|
|
&& long_options[option_index].name[1] == 'e') {
|
|
// option 'centergeometry'
|
|
p3 -= p1 / 2;
|
|
p4 -= p2 / 2;
|
|
}
|
|
}
|
|
if (iss.fail() || (c != ':' && c != 'x')) {
|
|
std::cerr << "Invalid geometry specification '" << optarg << "'" << std::endl;
|
|
usage();
|
|
exit(1);
|
|
}
|
|
if ((c == ':' && (p3 < 1 || p4 < 1))
|
|
|| (c == 'x' && (p1 < 1 || p2 < 1))) {
|
|
std::cerr << "Invalid geometry (width and/or heigth is zero or negative)" << std::endl;
|
|
usage();
|
|
exit(1);
|
|
}
|
|
if (c == ':')
|
|
generator.setGeometry(p1, p2, p3, p4);
|
|
if (c == 'x')
|
|
generator.setGeometry(p3, p4, p1, p2);
|
|
foundGeometrySpec = true;
|
|
}
|
|
break;
|
|
case 'd':
|
|
generator.setBackend(optarg);
|
|
break;
|
|
default:
|
|
exit(1);
|
|
}
|
|
}
|
|
} catch(std::runtime_error e) {
|
|
std::cout<<"Command-line error: "<<e.what()<<std::endl;
|
|
return 1;
|
|
}
|
|
|
|
try {
|
|
parseColorsFile(generator, input, colorsFile);
|
|
generator.generate(input, output);
|
|
} catch(std::runtime_error e) {
|
|
std::cout<<"Exception: "<<e.what()<<std::endl;
|
|
return 1;
|
|
} catch(ZlibDecompressor::DecompressError e) {
|
|
std::cout<<"Block decompression failure: "<<e.message<<std::endl;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|