From cfdb3269fd2fcf423c6591e7526374daedf504ad Mon Sep 17 00:00:00 2001 From: Robert Cunningham Date: Mon, 16 Oct 2017 13:40:03 -0400 Subject: [PATCH 1/7] Added basic README. Put in some basic information about getting set up and installing dependencies, as well as nicely formatting the helptext. --- README.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..a6d4b711 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +## McMap by Zahl + +This is a utility to create maps of Minecraft worlds from an isometric perspective. It can be used to create timelapses with a script. See [here](http://www.minecraftforum.net/forums/mapping-and-modding-java-edition/minecraft-tools/1260548-mcmap-isometric-renders-ssp-smp-minecraft-1-3-1]) for more information. + +At the time of writing, it's compatible with Minecraft versions up to 1.9.X. + +To build, you need `make`, `g++`, and `libpng`. On Ubuntu 14.04, installing dependencies might look something like: + +`sudo apt-get install -y build-essential libpng12-dev` + +To build, run `make` in the base directory. + +For MSVC++, get "zlib compiled DLL" from http://www.zlib.net/ and read USAGE.txt +gcc from MinGW works with the static library of zlib; however, trying +static linking in MSVC++ gave me weird results and crashes (v2008) +on linux, static linking works too, of course, but shouldn't be needed + + +For help, run `mcmap` without arguments or with the `-h` flag. + +### Example usage + +`./mcmap ~/.minecraft/saves/World1` would render your entire singleplayer world in slot 1. + +`./mcmap -night -from -10 -10 -to 10 10 ~/.minecraft/saves/World1` would render the same world but at night, and only from chunk (-10 -10) to chunk (10 10) + +If you're using Windows, your singleplayer world in slot 1 is probably in `%%APPDATA%%\\.minecraft\\saves\\World1` instead. + +### Options +Format: `./mcmap [-from X Z -to X Z] [-night] [-cave] [-noise VAL] [...] WORLDPATH` + +- `-from X Z` sets the coordinate of the chunk to start rendering at +- `to X Z` sets the coordinate of the chunk to end rendering at +> Note: Currently you need both -from and -to to define +> bounds, otherwise the entire world will be rendered. +- `-cave` renders a map of all caves that have been explored by players +- `-blendcave` overlay caves over normal map; doesn't work with incremental + rendering (some parts will be hidden) +- `-night` renders the world at night using blocklight (torches) +- `-skylight` use skylight when rendering map (shadows below trees etc.) +> Note: using this with -night makes a difference +- `-noise VAL` adds some noise to certain blocks, reasonable values are 0-20 +- `-height VAL` maximum height at which blocks will be rendered +- `-min/max VAL` minimum/maximum Y index (height) of blocks to render +- `-file NAME` sets the output filename to `NAME`; default is `output.png` +- `-mem VAL` sets the amount of memory (in MiB) used for rendering. `mcmap` will use incremental rendering or disk caching to stick to this limit. Default is 1800. +- `-colors NAME` loads user defined colors from file `NAME` +- `-dumpcolors` creates a file which contains the default colors being used + for rendering. Can be used to modify them and then use `-colors` +- `-north -east -south -west` + controls which direction will point to the *top left* corner + it only makes sense to pass one of them; East is default +- `-blendall` always use blending mode for blocks +- `-hell` render the hell/nether dimension of the given world +- `-end` render the end dimension of the given world +- `-serverhell` force cropping of blocks at the top (use for nether servers) +- `-nowater` render map with water being clear (as if it were air) +- `-texture NAME` extract colors from png file `NAME`; eg. `terrain.png` +- `-biomes` apply biome colors to grass/leaves; requires that you run + Donkey Kong's biome extractor first on your world +- `-biomecolors PATH` load `grasscolor.png` and `foliagecolor.png` from `PATH` +- `-info NAME` Write information about map to file `NAME` You can choose the + format by using file extensions `.xml`, `.json` or `.txt` (default) +- `-split PATH` create tiled output (128x128 to 4096x4096) in given `PATH` +- `-marker c x z` place marker at x z with color c (r g b w) + +`WORLDPATH` is the path of the desired Minecraft world. From a7067fecec685af8ced089ce45942d519a4c283c Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 17 Oct 2017 14:16:08 -0400 Subject: [PATCH 2/7] Added a basic gitignore. --- .gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7796b83a --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +* +!*.* + +*.o +*.png +*.xml +*.json +*.txt + From cb3bf0244e7ce33ac2abdb2eaa05f930b08fe706 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 20 Oct 2017 11:08:09 -0400 Subject: [PATCH 3/7] Extracted option parsing into its own function. --- main.cpp | 564 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 312 insertions(+), 252 deletions(-) diff --git a/main.cpp b/main.cpp index f08a3c2c..1dcab203 100644 --- a/main.cpp +++ b/main.cpp @@ -66,6 +66,31 @@ static const inline int floorChunkX(const int val); static const inline int floorChunkZ(const int val); void printHelp(char *binary); +enum parseReturns { + Success, + Fail, + Return +}; + +struct parsedOptions { + parseReturns result; + + bool wholeworld; + char* filename; + char* outfile; + char* colorfile; + char* texturefile; + char* infoFile; + bool dumpColors; + bool infoOnly; + bool end; + char* biomepath; + uint64_t memlimit; + bool memlimitSet; +}; + +parsedOptions parseArgs(int argc, char** argv); + int main(int argc, char **argv) { // ########## command line parsing ########## @@ -73,233 +98,57 @@ int main(int argc, char **argv) printHelp(argv[0]); return 1; } - bool wholeworld = false; - char *filename = NULL, *outfile = NULL, *colorfile = NULL, *texturefile = NULL, *infoFile = NULL; - bool dumpColors = false, infoOnly = false, end = false; - char *biomepath = NULL; - uint64_t memlimit; + + parsedOptions ops = parseArgs(argc, argv); + + if (ops.result == Fail) { + return 1; + } else if (ops.result == Return) { + return 0; + } + ops.memlimitSet = false; + + if (sizeof(size_t) < 8) { - memlimit = 1500 * uint64_t(1024 * 1024); + ops.memlimit = 1500 * uint64_t(1024 * 1024); } else { - memlimit = 2000 * uint64_t(1024 * 1024); + ops.memlimit = 2000 * uint64_t(1024 * 1024); } - bool memlimitSet = false; + - // First, for the sake of backward compatibility, try to parse command line arguments the old way first - if (argc >= 7 - && isNumeric(argv[1]) && isNumeric(argv[2]) && isNumeric(argv[3]) && isNumeric(argv[4])) { // Specific area of world - g_FromChunkX = atoi(argv[1]); - g_FromChunkZ = atoi(argv[2]); - g_ToChunkX = atoi(argv[3]) + 1; - g_ToChunkZ = atoi(argv[4]) + 1; - g_MapsizeY = atoi(argv[5]); - filename = argv[6]; - if (argc > 7) { - g_Nightmode = (atoi(argv[7]) == 1); - g_Underground = (atoi(argv[7]) == 2); - } - } else if (argc == 3 && isNumeric(argv[2])) { // Whole world - old way - filename = argv[1]; - g_Nightmode = (atoi(argv[2]) == 1); - g_Underground = (atoi(argv[2]) == 2); - } else { // -- New command line parsing -- -# define MOREARGS(x) (argpos + (x) < argc) -# define NEXTARG argv[++argpos] -# define POLLARG(x) argv[argpos + (x)] - int argpos = 0; - while (MOREARGS(1)) { - const char *option = NEXTARG; - if (strcmp(option, "-from") == 0) { - if (!MOREARGS(2) || !isNumeric(POLLARG(1)) || !isNumeric(POLLARG(2))) { - printf("Error: %s needs two integer arguments, ie: %s -10 5\n", option, option); - return 1; - } - g_FromChunkX = atoi(NEXTARG); - g_FromChunkZ = atoi(NEXTARG); - } else if (strcmp(option, "-to") == 0) { - if (!MOREARGS(2) || !isNumeric(POLLARG(1)) || !isNumeric(POLLARG(2))) { - printf("Error: %s needs two integer arguments, ie: %s -5 20\n", option, option); - return 1; - } - g_ToChunkX = atoi(NEXTARG) + 1; - g_ToChunkZ = atoi(NEXTARG) + 1; - } else if (strcmp(option, "-night") == 0) { - g_Nightmode = true; - } else if (strcmp(option, "-cave") == 0 || strcmp(option, "-caves") == 0 || strcmp(option, "-underground") == 0) { - g_Underground = true; - } else if (strcmp(option, "-blendcave") == 0 || strcmp(option, "-blendcaves") == 0) { - g_BlendUnderground = true; - } else if (strcmp(option, "-skylight") == 0) { - g_Skylight = true; - } else if (strcmp(option, "-nether") == 0 || strcmp(option, "-hell") == 0 || strcmp(option, "-slip") == 0) { - g_Hell = true; - } else if (strcmp(option, "-end") == 0) { - end = true; - } else if (strcmp(option, "-nowater") == 0) { - g_NoWater = true; - } else if (strcmp(option, "-serverhell") == 0) { - g_ServerHell = true; - } else if (strcmp(option, "-biomes") == 0) { - g_UseBiomes = true; - } else if (strcmp(option, "-biomecolors") == 0) { - if (!MOREARGS(1)) { - printf("Error: %s needs path to grasscolor.png and foliagecolor.png, ie: %s ./subdir\n", option, option); - return 1; - } - g_UseBiomes = true; - biomepath = NEXTARG; - } else if (strcmp(option, "-png") == 0) { - // void - } else if (strcmp(option, "-blendall") == 0) { - g_BlendAll = true; - } else if (strcmp(option, "-noise") == 0 || strcmp(option, "-dither") == 0) { - if (!MOREARGS(1) || !isNumeric(POLLARG(1))) { - printf("Error: %s needs an integer argument, ie: %s 10\n", option, option); - return 1; - } - g_Noise = atoi(NEXTARG); - } else if (strcmp(option, "-height") == 0 || strcmp(option, "-max") == 0) { - if (!MOREARGS(1) || !isNumeric(POLLARG(1))) { - printf("Error: %s needs an integer argument, ie: %s 100\n", option, option); - return 1; - } - g_MapsizeY = atoi(NEXTARG); - if (strcmp(option, "-max") == 0) g_MapsizeY++; - } else if (strcmp(option, "-min") == 0) { - if (!MOREARGS(1) || !isNumeric(POLLARG(1))) { - printf("Error: %s needs an integer argument, ie: %s 50\n", option, option); - return 1; - } - g_MapminY = atoi(NEXTARG); - } else if (strcmp(option, "-mem") == 0) { - if (!MOREARGS(1) || !isNumeric(POLLARG(1)) || atoi(POLLARG(1)) <= 0) { - printf("Error: %s needs a positive integer argument, ie: %s 1000\n", option, option); - return 1; - } - memlimitSet = true; - memlimit = size_t (atoi(NEXTARG)) * size_t (1024 * 1024); - } else if (strcmp(option, "-file") == 0) { - if (!MOREARGS(1)) { - printf("Error: %s needs one argument, ie: %s myworld.bmp\n", option, option); - return 1; - } - outfile = NEXTARG; - } else if (strcmp(option, "-colors") == 0) { - if (!MOREARGS(1)) { - printf("Error: %s needs one argument, ie: %s colors.txt\n", option, option); - return 1; - } - colorfile = NEXTARG; - } else if (strcmp(option, "-texture") == 0) { - if (!MOREARGS(1)) { - printf("Error: %s needs one argument, ie: %s terrain.png\n", option, option); - return 1; - } - texturefile = NEXTARG; - } else if (strcmp(option, "-info") == 0) { - if (!MOREARGS(1)) { - printf("Error: %s needs one argument, ie: %s data.json\n", option, option); - return 1; - } - infoFile = NEXTARG; - } else if (strcmp(option, "-infoonly") == 0) { - infoOnly = true; - } else if (strcmp(option, "-dumpcolors") == 0) { - dumpColors = true; - } else if (strcmp(option, "-north") == 0) { - g_Orientation = North; - } else if (strcmp(option, "-south") == 0) { - g_Orientation = South; - } else if (strcmp(option, "-east") == 0) { - g_Orientation = East; - } else if (strcmp(option, "-west") == 0) { - g_Orientation = West; - } else if (strcmp(option, "-3") == 0) { - g_OffsetY = 3; - } else if (strcmp(option, "-split") == 0) { - if (!MOREARGS(1)) { - printf("Error: %s needs a path argument, ie: %s tiles/\n", option, option); - return 1; - } - g_TilePath = NEXTARG; - } else if (strcmp(option, "-help") == 0 || strcmp(option, "-h") == 0 || strcmp(option, "-?") == 0) { - printHelp(argv[0]); - return 0; - } else if (strcmp(option, "-marker") == 0) { - if (g_MarkerCount >= MAX_MARKERS) { - printf("Too many markers, ignoring additional ones\n"); - continue; - } - if (!MOREARGS(3) || !isNumeric(POLLARG(2)) || !isNumeric(POLLARG(3))) { - printf("Error: %s needs a char and two integer arguments, ie: %s r -15 240\n", option, option); - return 1; - } - Marker &marker = g_Markers[g_MarkerCount]; - switch (*NEXTARG) { - case 'r': - marker.color = 253; - break; - case 'g': - marker.color = 244; - break; - case 'b': - marker.color = 242; - break; - default: - marker.color = 35; - } - int x = atoi(NEXTARG); - int z = atoi(NEXTARG); - marker.chunkX = floorChunkX(x); - marker.chunkZ = floorChunkZ(z); - marker.offsetX = x - (marker.chunkX * CHUNKSIZE_X); - marker.offsetZ = z - (marker.chunkZ * CHUNKSIZE_Z); - g_MarkerCount++; - } else if (strcmp(option, "-mystcraftage") == 0) { - if (!MOREARGS(1)) { - printf("Error: %s needs an integer age number argument", option); - return 1; - } - g_MystCraftAge = atoi(NEXTARG); - } else { - filename = (char *) option; - } - } - wholeworld = (g_FromChunkX == UNDEFINED || g_ToChunkX == UNDEFINED); - } // ########## end of command line parsing ########## - if (g_Hell || g_ServerHell || end) g_UseBiomes = false; + if (g_Hell || g_ServerHell || ops.end) g_UseBiomes = false; printf("mcmap " VERSION " %dbit by Zahl\n", 8*sizeof(size_t)); - if (sizeof(size_t) < 8 && memlimit > 1800 * uint64_t(1024 * 1024)) { - memlimit = 1800 * uint64_t(1024 * 1024); + if (sizeof(size_t) < 8 && ops.memlimit > 1800 * uint64_t(1024 * 1024)) { + ops.memlimit = 1800 * uint64_t(1024 * 1024); } // Load colormap from file loadColors(); // Default base, in case some are missing in colors.txt (if used) // Load files from colors.txt - if (colorfile != NULL && fileExists(colorfile)) { - if (!loadColorsFromFile(colorfile)) { - printf("Error loading colors from %s: Opening failed.\n", colorfile); + if (ops.colorfile != NULL && fileExists(ops.colorfile)) { + if (!loadColorsFromFile(ops.colorfile)) { + printf("Error loading colors from %s: Opening failed.\n", ops.colorfile); return 1; } - } else if (colorfile != NULL) { - printf("Error loading colors from %s: File not found.\n", colorfile); + } else if (ops.colorfile != NULL) { + printf("Error loading colors from %s: File not found.\n", ops.colorfile); return 1; } // Extract colors from terrain.png - if (texturefile != NULL && fileExists(texturefile)) { - if (!extractColors(texturefile)) { - printf("Error extracting colors from %s: Opening failed (not a valid terrain png?).\n", texturefile); + if (ops.texturefile != NULL && fileExists(ops.texturefile)) { + if (!extractColors(ops.texturefile)) { + printf("Error extracting colors from %s: Opening failed (not a valid terrain png?).\n", ops.texturefile); return 1; } - } else if (texturefile != NULL) { - printf("Error loading colors from %s: File not found.\n", texturefile); + } else if (ops.texturefile != NULL) { + printf("Error loading colors from %s: File not found.\n", ops.texturefile); return 1; } // If colors should be dumped to file, exit afterwards - if (dumpColors) { + if (ops.dumpColors) { if (!dumpColorsToFile("defaultcolors.txt")) { printf("Could not dump colors to defaultcolors.txt, error opening file.\n"); return 1; @@ -308,43 +157,43 @@ int main(int argc, char **argv) return 0; } - if (filename == NULL) { + if (ops.filename == NULL) { printf("Error: No world given. Please add the path to your world to the command line.\n"); return 1; } - if (!isAlphaWorld(filename)) { + if (!isAlphaWorld(ops.filename)) { printf("Error: Given path does not contain a Minecraft world.\n"); return 1; } if (g_Hell) { - char *tmp = new char[strlen(filename) + 20]; - strcpy(tmp, filename); + char *tmp = new char[strlen(ops.filename) + 20]; + strcpy(tmp, ops.filename); strcat(tmp, "/DIM-1"); if (!dirExists(tmp)) { printf("Error: This world does not have a hell world yet. Build a portal first!\n"); return 1; } - filename = tmp; - } else if (end) { - char *tmp = new char[strlen(filename) + 20]; - strcpy(tmp, filename); + ops.filename = tmp; + } else if (ops.end) { + char *tmp = new char[strlen(ops.filename) + 20]; + strcpy(tmp, ops.filename); strcat(tmp, "/DIM1"); if (!dirExists(tmp)) { printf("Error: This world does not have an end-world yet. Find an ender portal first!\n"); return 1; } - filename = tmp; + ops.filename = tmp; } else if (g_MystCraftAge) { - char *tmp = new char[strlen(filename) + 20]; - sprintf(tmp, "%s/DIM_MYST%d", filename, g_MystCraftAge); + char *tmp = new char[strlen(ops.filename) + 20]; + sprintf(tmp, "%s/DIM_MYST%d", ops.filename, g_MystCraftAge); if (!dirExists(tmp)) { printf("Error: This world does not have Age %d!\n", g_MystCraftAge); return 1; } - filename = tmp; + ops.filename = tmp; } // Figure out whether this is the old save format or McRegion or Anvil - g_WorldFormat = getWorldFormat(filename); + g_WorldFormat = getWorldFormat(ops.filename); if (g_WorldFormat < 2) { if (g_MapsizeY > CHUNKSIZE_Y) { @@ -354,8 +203,8 @@ int main(int argc, char **argv) g_MapminY = CHUNKSIZE_Y; } } - if (wholeworld && !scanWorldDirectory(filename)) { - printf("Error accessing terrain at '%s'\n", filename); + if (ops.wholeworld && !scanWorldDirectory(ops.filename)) { + printf("Error accessing terrain at '%s'\n", ops.filename); return 1; } if (g_ToChunkX <= g_FromChunkX || g_ToChunkZ <= g_FromChunkZ) { @@ -377,25 +226,25 @@ int main(int argc, char **argv) g_TotalToChunkX = g_ToChunkX; g_TotalToChunkZ = g_ToChunkZ; // Don't allow ridiculously small values for big maps - if (memlimit && memlimit < 200000000 && memlimit < size_t(g_MapsizeX * g_MapsizeZ * 150000)) { + if (ops.memlimit && ops.memlimit < 200000000 && ops.memlimit < size_t(g_MapsizeX * g_MapsizeZ * 150000)) { printf("Need at least %d MiB of RAM to render a map of that size.\n", int(float(g_MapsizeX) * g_MapsizeZ * .15f + 1)); return 1; } // Load biomes if (g_UseBiomes) { - char *bpath = new char[strlen(filename) + 30]; - strcpy(bpath, filename); + char *bpath = new char[strlen(ops.filename) + 30]; + strcpy(bpath, ops.filename); strcat(bpath, "/biomes"); if (!dirExists(bpath)) { printf("Error loading biome information. '%s' does not exist.\n", bpath); return 1; } - if (biomepath == NULL) { - biomepath = bpath; + if (ops.biomepath == NULL) { + ops.biomepath = bpath; } - if (!loadBiomeColors(biomepath)) return 1; - biomepath = bpath; + if (!loadBiomeColors(ops.biomepath)) return 1; + ops.biomepath = bpath; } // Mem check @@ -403,35 +252,35 @@ int main(int argc, char **argv) uint64_t bitmapBytes = calcImageSize(g_ToChunkX - g_FromChunkX, g_ToChunkZ - g_FromChunkZ, g_MapsizeY, bitmapX, bitmapY, false); // Cropping int cropLeft = 0, cropRight = 0, cropTop = 0, cropBottom = 0; - if (wholeworld) { + if (ops.wholeworld) { calcBitmapOverdraw(cropLeft, cropRight, cropTop, cropBottom); bitmapX -= (cropLeft + cropRight); bitmapY -= (cropTop + cropBottom); bitmapBytes = uint64_t(bitmapX) * BYTESPERPIXEL * uint64_t(bitmapY); } - if (infoFile != NULL) { - writeInfoFile(infoFile, + if (ops.infoFile != NULL) { + writeInfoFile(ops.infoFile, -cropLeft, -cropTop, bitmapX, bitmapY); - infoFile = NULL; - if (infoOnly) exit(0); + ops.infoFile = NULL; + if (ops.infoOnly) exit(0); } bool splitImage = false; int numSplitsX = 0; int numSplitsZ = 0; - if (memlimit && memlimit < bitmapBytes + calcTerrainSize(g_ToChunkX - g_FromChunkX, g_ToChunkZ - g_FromChunkZ)) { + if (ops.memlimit && ops.memlimit < bitmapBytes + calcTerrainSize(g_ToChunkX - g_FromChunkX, g_ToChunkZ - g_FromChunkZ)) { // If we'd need more mem than allowed, we have to render groups of chunks... - if (memlimit < bitmapBytes + 220 * uint64_t(1024 * 1024)) { + if (ops.memlimit < bitmapBytes + 220 * uint64_t(1024 * 1024)) { // Warn about using incremental rendering if user didn't set limit manually - if (!memlimitSet && sizeof(size_t) > 4) { + if (!ops.memlimitSet && sizeof(size_t) > 4) { printf(" ***** PLEASE NOTE *****\n" "mcmap is using disk cached rendering as it has a default memory limit\n" "of %d MiB. If you want to use more memory to render (=faster) use\n" "the -mem switch followed by the amount of memory in MiB to use.\n" - "Start mcmap without any arguments to get more help.\n", int(memlimit / (1024 * 1024))); + "Start mcmap without any arguments to get more help.\n", int(ops.memlimit / (1024 * 1024))); } else { printf("Choosing disk caching strategy...\n"); } @@ -443,9 +292,9 @@ int main(int argc, char **argv) int subAreaX = ((g_TotalToChunkX - g_TotalFromChunkX) + (numSplitsX - 1)) / numSplitsX; int subAreaZ = ((g_TotalToChunkZ - g_TotalFromChunkZ) + (numSplitsZ - 1)) / numSplitsZ; int subBitmapX, subBitmapY; - if (splitImage && calcImageSize(subAreaX, subAreaZ, g_MapsizeY, subBitmapX, subBitmapY, true) + calcTerrainSize(subAreaX, subAreaZ) <= memlimit) { + if (splitImage && calcImageSize(subAreaX, subAreaZ, g_MapsizeY, subBitmapX, subBitmapY, true) + calcTerrainSize(subAreaX, subAreaZ) <= ops.memlimit) { break; // Found a suitable partitioning - } else if (!splitImage && bitmapBytes + calcTerrainSize(subAreaX, subAreaZ) <= memlimit) { + } else if (!splitImage && bitmapBytes + calcTerrainSize(subAreaX, subAreaZ) <= ops.memlimit) { break; // Found a suitable partitioning } // @@ -460,17 +309,17 @@ int main(int argc, char **argv) // Always same random seed, as this is only used for block noise, which should give the same result for the same input every time srand(1337); - if (outfile == NULL) { - outfile = (char *) "output.png"; + if (ops.outfile == NULL) { + ops.outfile = (char *) "output.png"; } // open output file only if not doing the tiled output FILE *fileHandle = NULL; if (g_TilePath == NULL) { - fileHandle = fopen(outfile, (splitImage ? "w+b" : "wb")); + fileHandle = fopen(ops.outfile, (splitImage ? "w+b" : "wb")); if (fileHandle == NULL) { - printf("Error opening '%s' for writing.\n", outfile); + printf("Error opening '%s' for writing.\n", ops.outfile); return 1; } @@ -542,13 +391,13 @@ int main(int argc, char **argv) } // Load world or part of world - if (numSplitsX == 0 && wholeworld && !loadEntireTerrain()) { - printf("Error loading terrain from '%s'\n", filename); + if (numSplitsX == 0 && ops.wholeworld && !loadEntireTerrain()) { + printf("Error loading terrain from '%s'\n", ops.filename); return 1; - } else if (numSplitsX != 0 || !wholeworld) { + } else if (numSplitsX != 0 || !ops.wholeworld) { int numberOfChunks; - if (!loadTerrain(filename, numberOfChunks)) { - printf("Error loading terrain from '%s'\n", filename); + if (!loadTerrain(ops.filename, numberOfChunks)) { + printf("Error loading terrain from '%s'\n", ops.filename); return 1; } if (splitImage && numberOfChunks == 0) { @@ -564,7 +413,7 @@ int main(int argc, char **argv) // Load biome data if requested if (g_UseBiomes) { - loadBiomeMap(biomepath); + loadBiomeMap(ops.biomepath); } // If underground mode, remove blocks that don't seem to belong to caves @@ -667,13 +516,13 @@ int main(int argc, char **argv) // Underground overlay mode if (g_BlendUnderground && !g_Underground) { // Load map data again, since block culling removed most of the blocks - if (numSplitsX == 0 && wholeworld && !loadEntireTerrain()) { - printf("Error loading terrain from '%s'\n", filename); + if (numSplitsX == 0 && ops.wholeworld && !loadEntireTerrain()) { + printf("Error loading terrain from '%s'\n", ops.filename); return 1; - } else if (numSplitsX != 0 || !wholeworld) { + } else if (numSplitsX != 0 || !ops.wholeworld) { int i; - if (!loadTerrain(filename, i)) { - printf("Error loading terrain from '%s'\n", filename); + if (!loadTerrain(ops.filename, i)) { + printf("Error loading terrain from '%s'\n", ops.filename); return 1; } } @@ -726,6 +575,217 @@ int main(int argc, char **argv) return 0; } +parsedOptions parseArgs(int argc, char** argv) { + bool wholeworld = false; + char *filename = NULL, *outfile = NULL, *colorfile = NULL, *texturefile = NULL, *infoFile = NULL; + bool dumpColors = false, infoOnly = false, end = false; + char *biomepath = NULL; + + int result = 0; + + parsedOptions out; + out.result = Fail; + + // First, for the sake of backward compatibility, try to parse command line arguments the old way first + if (argc >= 7 + && isNumeric(argv[1]) && isNumeric(argv[2]) && isNumeric(argv[3]) && isNumeric(argv[4])) { // Specific area of world + g_FromChunkX = atoi(argv[1]); + g_FromChunkZ = atoi(argv[2]); + g_ToChunkX = atoi(argv[3]) + 1; + g_ToChunkZ = atoi(argv[4]) + 1; + g_MapsizeY = atoi(argv[5]); + filename = argv[6]; + if (argc > 7) { + g_Nightmode = (atoi(argv[7]) == 1); + g_Underground = (atoi(argv[7]) == 2); + } + } else if (argc == 3 && isNumeric(argv[2])) { // Whole world - old way + filename = argv[1]; + g_Nightmode = (atoi(argv[2]) == 1); + g_Underground = (atoi(argv[2]) == 2); + } else { // -- New command line parsing -- +# define MOREARGS(x) (argpos + (x) < argc) +# define NEXTARG argv[++argpos] +# define POLLARG(x) argv[argpos + (x)] + int argpos = 0; + while (MOREARGS(1)) { + const char *option = NEXTARG; + if (strcmp(option, "-from") == 0) { + if (!MOREARGS(2) || !isNumeric(POLLARG(1)) || !isNumeric(POLLARG(2))) { + printf("Error: %s needs two integer arguments, ie: %s -10 5\n", option, option); + return out; + } + g_FromChunkX = atoi(NEXTARG); + g_FromChunkZ = atoi(NEXTARG); + } else if (strcmp(option, "-to") == 0) { + if (!MOREARGS(2) || !isNumeric(POLLARG(1)) || !isNumeric(POLLARG(2))) { + printf("Error: %s needs two integer arguments, ie: %s -5 20\n", option, option); + return out; + } + g_ToChunkX = atoi(NEXTARG) + 1; + g_ToChunkZ = atoi(NEXTARG) + 1; + } else if (strcmp(option, "-night") == 0) { + g_Nightmode = true; + } else if (strcmp(option, "-cave") == 0 || strcmp(option, "-caves") == 0 || strcmp(option, "-underground") == 0) { + g_Underground = true; + } else if (strcmp(option, "-blendcave") == 0 || strcmp(option, "-blendcaves") == 0) { + g_BlendUnderground = true; + } else if (strcmp(option, "-skylight") == 0) { + g_Skylight = true; + } else if (strcmp(option, "-nether") == 0 || strcmp(option, "-hell") == 0 || strcmp(option, "-slip") == 0) { + g_Hell = true; + } else if (strcmp(option, "-end") == 0) { + end = true; + } else if (strcmp(option, "-nowater") == 0) { + g_NoWater = true; + } else if (strcmp(option, "-serverhell") == 0) { + g_ServerHell = true; + } else if (strcmp(option, "-biomes") == 0) { + g_UseBiomes = true; + } else if (strcmp(option, "-biomecolors") == 0) { + if (!MOREARGS(1)) { + printf("Error: %s needs path to grasscolor.png and foliagecolor.png, ie: %s ./subdir\n", option, option); + return out; + } + g_UseBiomes = true; + biomepath = NEXTARG; + } else if (strcmp(option, "-png") == 0) { + // void + } else if (strcmp(option, "-blendall") == 0) { + g_BlendAll = true; + } else if (strcmp(option, "-noise") == 0 || strcmp(option, "-dither") == 0) { + if (!MOREARGS(1) || !isNumeric(POLLARG(1))) { + printf("Error: %s needs an integer argument, ie: %s 10\n", option, option); + return out; + } + g_Noise = atoi(NEXTARG); + } else if (strcmp(option, "-height") == 0 || strcmp(option, "-max") == 0) { + if (!MOREARGS(1) || !isNumeric(POLLARG(1))) { + printf("Error: %s needs an integer argument, ie: %s 100\n", option, option); + return out; + } + g_MapsizeY = atoi(NEXTARG); + if (strcmp(option, "-max") == 0) g_MapsizeY++; + } else if (strcmp(option, "-min") == 0) { + if (!MOREARGS(1) || !isNumeric(POLLARG(1))) { + printf("Error: %s needs an integer argument, ie: %s 50\n", option, option); + return out; + } + g_MapminY = atoi(NEXTARG); + } else if (strcmp(option, "-mem") == 0) { + if (!MOREARGS(1) || !isNumeric(POLLARG(1)) || atoi(POLLARG(1)) <= 0) { + printf("Error: %s needs a positive integer argument, ie: %s 1000\n", option, option); + return out; + } + out.memlimitSet = true; + out.memlimit = size_t (atoi(NEXTARG)) * size_t (1024 * 1024); + } else if (strcmp(option, "-file") == 0) { + if (!MOREARGS(1)) { + printf("Error: %s needs one argument, ie: %s myworld.bmp\n", option, option); + return out; + } + outfile = NEXTARG; + } else if (strcmp(option, "-colors") == 0) { + if (!MOREARGS(1)) { + printf("Error: %s needs one argument, ie: %s colors.txt\n", option, option); + return out; + } + colorfile = NEXTARG; + } else if (strcmp(option, "-texture") == 0) { + if (!MOREARGS(1)) { + printf("Error: %s needs one argument, ie: %s terrain.png\n", option, option); + return out; + } + texturefile = NEXTARG; + } else if (strcmp(option, "-info") == 0) { + if (!MOREARGS(1)) { + printf("Error: %s needs one argument, ie: %s data.json\n", option, option); + return out; + } + infoFile = NEXTARG; + } else if (strcmp(option, "-infoonly") == 0) { + infoOnly = true; + } else if (strcmp(option, "-dumpcolors") == 0) { + dumpColors = true; + } else if (strcmp(option, "-north") == 0) { + g_Orientation = North; + } else if (strcmp(option, "-south") == 0) { + g_Orientation = South; + } else if (strcmp(option, "-east") == 0) { + g_Orientation = East; + } else if (strcmp(option, "-west") == 0) { + g_Orientation = West; + } else if (strcmp(option, "-3") == 0) { + g_OffsetY = 3; + } else if (strcmp(option, "-split") == 0) { + if (!MOREARGS(1)) { + printf("Error: %s needs a path argument, ie: %s tiles/\n", option, option); + return out; + } + g_TilePath = NEXTARG; + } else if (strcmp(option, "-help") == 0 || strcmp(option, "-h") == 0 || strcmp(option, "-?") == 0) { + printHelp(argv[0]); + out.result = Return; + return out; + } else if (strcmp(option, "-marker") == 0) { + if (g_MarkerCount >= MAX_MARKERS) { + printf("Too many markers, ignoring additional ones\n"); + continue; + } + if (!MOREARGS(3) || !isNumeric(POLLARG(2)) || !isNumeric(POLLARG(3))) { + printf("Error: %s needs a char and two integer arguments, ie: %s r -15 240\n", option, option); + return out; + } + Marker &marker = g_Markers[g_MarkerCount]; + switch (*NEXTARG) { + case 'r': + marker.color = 253; + break; + case 'g': + marker.color = 244; + break; + case 'b': + marker.color = 242; + break; + default: + marker.color = 35; + } + int x = atoi(NEXTARG); + int z = atoi(NEXTARG); + marker.chunkX = floorChunkX(x); + marker.chunkZ = floorChunkZ(z); + marker.offsetX = x - (marker.chunkX * CHUNKSIZE_X); + marker.offsetZ = z - (marker.chunkZ * CHUNKSIZE_Z); + g_MarkerCount++; + } else if (strcmp(option, "-mystcraftage") == 0) { + if (!MOREARGS(1)) { + printf("Error: %s needs an integer age number argument", option); + return out; + } + g_MystCraftAge = atoi(NEXTARG); + } else { + filename = (char *) option; + } + } + wholeworld = (g_FromChunkX == UNDEFINED || g_ToChunkX == UNDEFINED); + } + + out.result = Success; + + out.wholeworld = wholeworld; + out.filename = filename; + out.outfile = outfile; + out.colorfile = colorfile; + out.texturefile = texturefile; + out.infoFile = infoFile; + out.dumpColors = dumpColors; + out.infoOnly = infoOnly; + out.end = end; + out.biomepath = biomepath; + + return out; +} + #ifdef _DEBUG static size_t gBlocksRemoved = 0; #endif From 5d513da08fbe421cdfcb4540492523db15e5b8b8 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 25 Oct 2017 14:02:23 -0400 Subject: [PATCH 4/7] Continue pulling out discreet functionality into functions. --- main.cpp | 730 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 390 insertions(+), 340 deletions(-) diff --git a/main.cpp b/main.cpp index 1dcab203..a7a57697 100644 --- a/main.cpp +++ b/main.cpp @@ -90,32 +90,33 @@ struct parsedOptions { }; parsedOptions parseArgs(int argc, char** argv); +int renderPartOfMap(bool&, int&, int&, int&, int&, int&, float*, parsedOptions&); + +void checkColorFile(parsedOptions&); +void checkTextureFile(parsedOptions&); +void checkDumpColors(parsedOptions&); +void loadFullWorldPath(parsedOptions&); +void useBiomes(parsedOptions& ops); +void setMapBounds(); +float* computeBrightnessLookup(); +FILE* determineFileHandle(parsedOptions&, bool, int, int); +void checkWorldDims(); +void setMapSize(); +void makeTilePath(); + int main(int argc, char **argv) { - // ########## command line parsing ########## - if (argc < 2) { - printHelp(argv[0]); - return 1; - } - parsedOptions ops = parseArgs(argc, argv); - if (ops.result == Fail) { - return 1; - } else if (ops.result == Return) { - return 0; - } ops.memlimitSet = false; - if (sizeof(size_t) < 8) { ops.memlimit = 1500 * uint64_t(1024 * 1024); } else { ops.memlimit = 2000 * uint64_t(1024 * 1024); } - // ########## end of command line parsing ########## if (g_Hell || g_ServerHell || ops.end) g_UseBiomes = false; @@ -128,103 +129,25 @@ int main(int argc, char **argv) // Load colormap from file loadColors(); // Default base, in case some are missing in colors.txt (if used) // Load files from colors.txt - if (ops.colorfile != NULL && fileExists(ops.colorfile)) { - if (!loadColorsFromFile(ops.colorfile)) { - printf("Error loading colors from %s: Opening failed.\n", ops.colorfile); - return 1; - } - } else if (ops.colorfile != NULL) { - printf("Error loading colors from %s: File not found.\n", ops.colorfile); - return 1; - } - // Extract colors from terrain.png - if (ops.texturefile != NULL && fileExists(ops.texturefile)) { - if (!extractColors(ops.texturefile)) { - printf("Error extracting colors from %s: Opening failed (not a valid terrain png?).\n", ops.texturefile); - return 1; - } - } else if (ops.texturefile != NULL) { - printf("Error loading colors from %s: File not found.\n", ops.texturefile); - return 1; - } - // If colors should be dumped to file, exit afterwards - if (ops.dumpColors) { - if (!dumpColorsToFile("defaultcolors.txt")) { - printf("Could not dump colors to defaultcolors.txt, error opening file.\n"); - return 1; - } - printf("Colors written to defaultcolors.txt\n"); - return 0; - } - if (ops.filename == NULL) { - printf("Error: No world given. Please add the path to your world to the command line.\n"); - return 1; - } - if (!isAlphaWorld(ops.filename)) { - printf("Error: Given path does not contain a Minecraft world.\n"); - return 1; - } - if (g_Hell) { - char *tmp = new char[strlen(ops.filename) + 20]; - strcpy(tmp, ops.filename); - strcat(tmp, "/DIM-1"); - if (!dirExists(tmp)) { - printf("Error: This world does not have a hell world yet. Build a portal first!\n"); - return 1; - } - ops.filename = tmp; - } else if (ops.end) { - char *tmp = new char[strlen(ops.filename) + 20]; - strcpy(tmp, ops.filename); - strcat(tmp, "/DIM1"); - if (!dirExists(tmp)) { - printf("Error: This world does not have an end-world yet. Find an ender portal first!\n"); - return 1; - } - ops.filename = tmp; - } else if (g_MystCraftAge) { - char *tmp = new char[strlen(ops.filename) + 20]; - sprintf(tmp, "%s/DIM_MYST%d", ops.filename, g_MystCraftAge); - if (!dirExists(tmp)) { - printf("Error: This world does not have Age %d!\n", g_MystCraftAge); - return 1; - } - ops.filename = tmp; - } + checkColorFile(ops); + checkTextureFile(ops); + checkDumpColors(ops); + + loadFullWorldPath(ops); + // Figure out whether this is the old save format or McRegion or Anvil g_WorldFormat = getWorldFormat(ops.filename); + setMapSize(); - if (g_WorldFormat < 2) { - if (g_MapsizeY > CHUNKSIZE_Y) { - g_MapsizeY = CHUNKSIZE_Y; - } - if (g_MapminY > CHUNKSIZE_Y) { - g_MapminY = CHUNKSIZE_Y; - } - } + checkWorldDims(); if (ops.wholeworld && !scanWorldDirectory(ops.filename)) { printf("Error accessing terrain at '%s'\n", ops.filename); return 1; } - if (g_ToChunkX <= g_FromChunkX || g_ToChunkZ <= g_FromChunkZ) { - printf("Nothing to render: -from X Z has to be <= -to X Z\n"); - return 1; - } - if (g_MapsizeY - g_MapminY < 1) { - printf("Nothing to render: -min Y has to be < -max/-height Y\n"); - return 1; - } - g_SectionMin = g_MapminY >> SECTION_Y_SHIFT; - g_SectionMax = (g_MapsizeY - 1) >> SECTION_Y_SHIFT; - g_MapsizeY -= g_MapminY; - printf("MinY: %d ... MaxY: %d ... MinSecY: %d ... MaxSecY: %d\n", g_MapminY, g_MapsizeY, g_SectionMin, g_SectionMax); - // Whole area to be rendered, in chunks - // If -mem is omitted or high enough, this won't be needed - g_TotalFromChunkX = g_FromChunkX; - g_TotalFromChunkZ = g_FromChunkZ; - g_TotalToChunkX = g_ToChunkX; - g_TotalToChunkZ = g_ToChunkZ; + + setMapBounds(); + // Don't allow ridiculously small values for big maps if (ops.memlimit && ops.memlimit < 200000000 && ops.memlimit < size_t(g_MapsizeX * g_MapsizeZ * 150000)) { printf("Need at least %d MiB of RAM to render a map of that size.\n", int(float(g_MapsizeX) * g_MapsizeZ * .15f + 1)); @@ -233,18 +156,7 @@ int main(int argc, char **argv) // Load biomes if (g_UseBiomes) { - char *bpath = new char[strlen(ops.filename) + 30]; - strcpy(bpath, ops.filename); - strcat(bpath, "/biomes"); - if (!dirExists(bpath)) { - printf("Error loading biome information. '%s' does not exist.\n", bpath); - return 1; - } - if (ops.biomepath == NULL) { - ops.biomepath = bpath; - } - if (!loadBiomeColors(ops.biomepath)) return 1; - ops.biomepath = bpath; + useBiomes(ops); } // Mem check @@ -314,22 +226,56 @@ int main(int argc, char **argv) } // open output file only if not doing the tiled output - FILE *fileHandle = NULL; + FILE *fileHandle = determineFileHandle(ops, splitImage, bitmapX, bitmapY); + // Precompute brightness adjustment factor + float *brightnessLookup = computeBrightnessLookup(); + + // Now here's the loop rendering all the required parts of the image. + // All the vars previously used to define bounds will be set on each loop, + // to create something like a virtual window inside the map. + while (!renderPartOfMap(splitImage, numSplitsX, numSplitsZ, cropLeft, cropRight, cropTop, brightnessLookup, ops)); + + // Drawing complete, now either just save the image or compose it if disk caching was used + // Saving + if (!splitImage) { + saveImage(); + } else { + if (!composeFinalImage()) { + printf("Aborted.\n"); + return 1; + } + } + if (fileHandle != NULL) fclose(fileHandle); + + printf("Job complete.\n"); + return 0; +} + +FILE* determineFileHandle(parsedOptions& ops, bool splitImage, int bitmapX, int bitmapY) { + FILE* fileHandle = NULL; if (g_TilePath == NULL) { fileHandle = fopen(ops.outfile, (splitImage ? "w+b" : "wb")); if (fileHandle == NULL) { printf("Error opening '%s' for writing.\n", ops.outfile); - return 1; + exit(1); } // This writes out the bitmap header and pre-allocates space if disk caching is used if (!createImage(fileHandle, bitmapX, bitmapY, splitImage)) { printf("Error allocating bitmap. Check if you have enough free disk space.\n"); - return 1; + exit(1); } } else { + makeTilePath(); // This would mean tiled output + createImageBuffer(bitmapX, bitmapY, splitImage); + } + return fileHandle; + +} + +void makeTilePath() { #ifdef _WIN32 mkdir(g_TilePath); #else @@ -337,254 +283,361 @@ int main(int argc, char **argv) #endif if (!dirExists(g_TilePath)) { printf("Error: '%s' does not exist.\n", g_TilePath); - return 1; + exit(1); } - createImageBuffer(bitmapX, bitmapY, splitImage); +} + +void checkColorFile(parsedOptions& ops) { + if (ops.colorfile != NULL && fileExists(ops.colorfile)) { + if (!loadColorsFromFile(ops.colorfile)) { + printf("Error loading colors from %s: Opening failed.\n", ops.colorfile); + exit(1); + } + } else if (ops.colorfile != NULL) { + printf("Error loading colors from %s: File not found.\n", ops.colorfile); + exit(1); + } +} + +void checkTextureFile(parsedOptions& ops) { + // Extract colors from terrain.png + if (ops.texturefile != NULL && fileExists(ops.texturefile)) { + if (!extractColors(ops.texturefile)) { + printf("Error extracting colors from %s: Opening failed (not a valid terrain png?).\n", ops.texturefile); + exit(1); + } + } else if (ops.texturefile != NULL) { + printf("Error loading colors from %s: File not found.\n", ops.texturefile); + exit(1); } +} - // Precompute brightness adjustment factor - float *brightnessLookup = new float[g_MapsizeY]; +void checkDumpColors(parsedOptions& ops) { + // If colors should be dumped to file, exit afterwards + if (ops.dumpColors) { + if (!dumpColorsToFile("defaultcolors.txt")) { + printf("Could not dump colors to defaultcolors.txt, error opening file.\n"); + exit(1); + } + printf("Colors written to defaultcolors.txt\n"); + } +} + +void loadFullWorldPath(parsedOptions& ops) { + if (ops.filename == NULL) { + printf("Error: No world given. Please add the path to your world to the command line.\n"); + exit(1); + } + if (!isAlphaWorld(ops.filename)) { + printf("Error: Given path does not contain a Minecraft world.\n"); + exit(1); + } + if (g_Hell) { + char *tmp = new char[strlen(ops.filename) + 20]; + strcpy(tmp, ops.filename); + strcat(tmp, "/DIM-1"); + if (!dirExists(tmp)) { + printf("Error: This world does not have a hell world yet. Build a portal first!\n"); + exit(1); + } + ops.filename = tmp; + } else if (ops.end) { + char *tmp = new char[strlen(ops.filename) + 20]; + strcpy(tmp, ops.filename); + strcat(tmp, "/DIM1"); + if (!dirExists(tmp)) { + printf("Error: This world does not have an end-world yet. Find an ender portal first!\n"); + exit(1); + } + ops.filename = tmp; + } else if (g_MystCraftAge) { + char *tmp = new char[strlen(ops.filename) + 20]; + sprintf(tmp, "%s/DIM_MYST%d", ops.filename, g_MystCraftAge); + if (!dirExists(tmp)) { + printf("Error: This world does not have Age %d!\n", g_MystCraftAge); + exit(1); + } + ops.filename = tmp; + } +} + +float* computeBrightnessLookup() { + float* brightnessLookup = new float[g_MapsizeY]; for (int y = 0; y < g_MapsizeY; ++y) { brightnessLookup[y] = ((100.0f / (1.0f + exp(- (1.3f * (float(y) * MIN(g_MapsizeY, 200) / g_MapsizeY) / 16.0f) + 6.0f))) - 91); // thx Donkey Kong } + return brightnessLookup; +} - // Now here's the loop rendering all the required parts of the image. - // All the vars previously used to define bounds will be set on each loop, - // to create something like a virtual window inside the map. - for (;;) { +void checkWorldDims() { + if (g_ToChunkX <= g_FromChunkX || g_ToChunkZ <= g_FromChunkZ) { + printf("Nothing to render: -from X Z has to be <= -to X Z\n"); + exit(1); + } + if (g_MapsizeY - g_MapminY < 1) { + printf("Nothing to render: -min Y has to be < -max/-height Y\n"); + exit(1); + } +} - int bitmapStartX = 3, bitmapStartY = 5; - if (numSplitsX) { // virtual window is set here - // Set current chunk bounds according to number of splits. returns true if everything has been rendered already - if (prepareNextArea(numSplitsX, numSplitsZ, bitmapStartX, bitmapStartY)) { - break; - } - // if image is split up, prepare memory block for next part - if (splitImage) { - bitmapStartX += 2; - const int sizex = (g_ToChunkX - g_FromChunkX) * CHUNKSIZE_X * 2 + (g_ToChunkZ - g_FromChunkZ) * CHUNKSIZE_Z * 2; - const int sizey = (int)g_MapsizeY * g_OffsetY + (g_ToChunkX - g_FromChunkX) * CHUNKSIZE_X + (g_ToChunkZ - g_FromChunkZ) * CHUNKSIZE_Z + 3; - if (sizex <= 0 || sizey <= 0) continue; // Don't know if this is right, might also be that the size calulation is plain wrong - int res = loadImagePart(bitmapStartX - cropLeft, bitmapStartY - cropTop, sizex, sizey); - if (res == -1) { - printf("Error loading partial image to render to.\n"); - return 1; - } else if (res == 1) continue; - } - } +void setMapBounds() { + g_SectionMin = g_MapminY >> SECTION_Y_SHIFT; + g_SectionMax = (g_MapsizeY - 1) >> SECTION_Y_SHIFT; + g_MapsizeY -= g_MapminY; + printf("MinY: %d ... MaxY: %d ... MinSecY: %d ... MaxSecY: %d\n", g_MapminY, g_MapsizeY, g_SectionMin, g_SectionMax); + // Whole area to be rendered, in chunks + // If -mem is omitted or high enough, this won't be needed + g_TotalFromChunkX = g_FromChunkX; + g_TotalFromChunkZ = g_FromChunkZ; + g_TotalToChunkX = g_ToChunkX; + g_TotalToChunkZ = g_ToChunkZ; +} - // More chunks are needed at the sides to get light and edge detection right at the edges - // This makes code below a bit messy, as most of the time the surrounding chunks are ignored - // By starting loops at CHUNKSIZE_X instead of 0. - ++g_ToChunkX; - ++g_ToChunkZ; - --g_FromChunkX; - --g_FromChunkZ; - - // For rotation, X and Z have to be swapped (East and West) - if (g_Orientation == North || g_Orientation == South) { - g_MapsizeZ = (g_ToChunkZ - g_FromChunkZ) * CHUNKSIZE_Z; - g_MapsizeX = (g_ToChunkX - g_FromChunkX) * CHUNKSIZE_X; - } else { - g_MapsizeX = (g_ToChunkZ - g_FromChunkZ) * CHUNKSIZE_Z; - g_MapsizeZ = (g_ToChunkX - g_FromChunkX) * CHUNKSIZE_X; +void useBiomes(parsedOptions& ops) { + char *bpath = new char[strlen(ops.filename) + 30]; + strcpy(bpath, ops.filename); + strcat(bpath, "/biomes"); + if (!dirExists(bpath)) { + printf("Error loading biome information. '%s' does not exist.\n", bpath); + exit(1); + } + if (ops.biomepath == NULL) { + ops.biomepath = bpath; + } + if (!loadBiomeColors(ops.biomepath)) exit(1); + ops.biomepath = bpath; +} + +void setMapSize() { + if (g_WorldFormat < 2) { + if (g_MapsizeY > CHUNKSIZE_Y) { + g_MapsizeY = CHUNKSIZE_Y; + } + if (g_MapminY > CHUNKSIZE_Y) { + g_MapminY = CHUNKSIZE_Y; } + } +} +int renderPartOfMap(bool& splitImage, int& numSplitsX, int& numSplitsZ, int& cropLeft, int& cropRight, int& cropTop, float* brightnessLookup, parsedOptions& ops) { + int bitmapStartX = 3, bitmapStartY = 5; + if (numSplitsX) { // virtual window is set here + // Set current chunk bounds according to number of splits. returns true if everything has been rendered already + if (prepareNextArea(numSplitsX, numSplitsZ, bitmapStartX, bitmapStartY)) { + return 2; + } + // if image is split up, prepare memory block for next part + if (splitImage) { + bitmapStartX += 2; + const int sizex = (g_ToChunkX - g_FromChunkX) * CHUNKSIZE_X * 2 + (g_ToChunkZ - g_FromChunkZ) * CHUNKSIZE_Z * 2; + const int sizey = (int)g_MapsizeY * g_OffsetY + (g_ToChunkX - g_FromChunkX) * CHUNKSIZE_X + (g_ToChunkZ - g_FromChunkZ) * CHUNKSIZE_Z + 3; + if (sizex <= 0 || sizey <= 0) return 0; // Don't know if this is right, might also be that the size calulation is plain wrong + int res = loadImagePart(bitmapStartX - cropLeft, bitmapStartY - cropTop, sizex, sizey); + if (res == -1) { + printf("Error loading partial image to render to.\n"); + exit(1); + } else if (res == 1) return 0; + } + } - // Load world or part of world - if (numSplitsX == 0 && ops.wholeworld && !loadEntireTerrain()) { + // More chunks are needed at the sides to get light and edge detection right at the edges + // This makes code below a bit messy, as most of the time the surrounding chunks are ignored + // By starting loops at CHUNKSIZE_X instead of 0. + ++g_ToChunkX; + ++g_ToChunkZ; + --g_FromChunkX; + --g_FromChunkZ; + + // For rotation, X and Z have to be swapped (East and West) + if (g_Orientation == North || g_Orientation == South) { + g_MapsizeZ = (g_ToChunkZ - g_FromChunkZ) * CHUNKSIZE_Z; + g_MapsizeX = (g_ToChunkX - g_FromChunkX) * CHUNKSIZE_X; + } else { + g_MapsizeX = (g_ToChunkZ - g_FromChunkZ) * CHUNKSIZE_Z; + g_MapsizeZ = (g_ToChunkX - g_FromChunkX) * CHUNKSIZE_X; + } + + // Load world or part of world + if (numSplitsX == 0 && ops.wholeworld && !loadEntireTerrain()) { + printf("Error loading terrain from '%s'\n", ops.filename); + exit(1); + } else if (numSplitsX != 0 || !ops.wholeworld) { + int numberOfChunks; + if (!loadTerrain(ops.filename, numberOfChunks)) { printf("Error loading terrain from '%s'\n", ops.filename); - return 1; - } else if (numSplitsX != 0 || !ops.wholeworld) { - int numberOfChunks; - if (!loadTerrain(ops.filename, numberOfChunks)) { - printf("Error loading terrain from '%s'\n", ops.filename); - return 1; - } - if (splitImage && numberOfChunks == 0) { - printf("Section is empty, skipping...\n"); - discardImagePart(); - continue; - } + exit(1); } - - if (g_WorldFormat == 2 && (g_Hell || g_ServerHell)) { - uncoverNether(); + if (splitImage && numberOfChunks == 0) { + printf("Section is empty, skipping...\n"); + discardImagePart(); + return 0; } + } - // Load biome data if requested - if (g_UseBiomes) { - loadBiomeMap(ops.biomepath); - } + if (g_WorldFormat == 2 && (g_Hell || g_ServerHell)) { + uncoverNether(); + } - // If underground mode, remove blocks that don't seem to belong to caves - if (g_Underground) { - undergroundMode(false); - } + // Load biome data if requested + if (g_UseBiomes) { + loadBiomeMap(ops.biomepath); + } - if (g_OffsetY == 2) { - optimizeTerrain2((numSplitsX == 0 ? cropLeft : 0), (numSplitsX == 0 ? cropRight : 0)); - } else { - optimizeTerrain3(); - } + // If underground mode, remove blocks that don't seem to belong to caves + if (g_Underground) { + undergroundMode(false); + } - // Finally, render terrain to file - printf("Drawing map...\n"); - for (size_t x = CHUNKSIZE_X; x < g_MapsizeX - CHUNKSIZE_X; ++x) { - printProgress(x - CHUNKSIZE_X, g_MapsizeX); - for (size_t z = CHUNKSIZE_Z; z < g_MapsizeZ - CHUNKSIZE_Z; ++z) { - // Biome colors - if (g_BiomeMap != NULL) { - uint16_t &offset = BIOMEAT(x,z); - // This is getting a bit stupid here, there should be a better solution than a dozen copy ops - memcpy(colors[GRASS], g_Grasscolor + offset * g_GrasscolorDepth, 3); - memcpy(colors[LEAVES], g_Leafcolor + offset * g_FoliageDepth, 3); - memcpy(colors[TALL_GRASS], g_TallGrasscolor + offset * g_GrasscolorDepth, 3); - memcpy(colors[PUMPKIN_STEM], g_TallGrasscolor + offset * g_GrasscolorDepth, 3); - memcpy(colors[MELON_STEM], g_TallGrasscolor + offset * g_GrasscolorDepth, 3); - memcpy(colors[VINES], g_Grasscolor + offset * g_GrasscolorDepth, 3); - memcpy(colors[LILYPAD], g_Grasscolor + offset * g_GrasscolorDepth, 3); - // Leaves: This is just an approximation to get different leaf colors at all - colors[PINELEAVES][PRED] = clamp(int32_t(colors[LEAVES][PRED]) - 17); - colors[PINELEAVES][PGREEN] = clamp(int32_t(colors[LEAVES][PGREEN]) - 12); - colors[PINELEAVES][PBLUE] = colors[LEAVES][PBLUE]; - int32_t avg = GETBRIGHTNESS(colors[LEAVES]); - colors[BIRCHLEAVES][PRED] = clamp(int32_t(colors[LEAVES][PRED]) + (avg - int32_t(colors[LEAVES][PRED])) / 2 + 15); - colors[BIRCHLEAVES][PGREEN] = clamp(int32_t(colors[LEAVES][PGREEN]) + (avg - int32_t(colors[LEAVES][PGREEN])) / 2 + 16); - colors[BIRCHLEAVES][PBLUE] = clamp(int32_t(colors[LEAVES][PBLUE]) + (avg - int32_t(colors[LEAVES][PBLUE])) / 2 + 15); - colors[JUNGLELEAVES][PRED] = clamp(int32_t(colors[LEAVES][PRED])); - colors[JUNGLELEAVES][PGREEN] = clamp(int32_t(colors[LEAVES][PGREEN]) + 18); - colors[JUNGLELEAVES][PBLUE] = colors[LEAVES][PBLUE]; + if (g_OffsetY == 2) { + optimizeTerrain2((numSplitsX == 0 ? cropLeft : 0), (numSplitsX == 0 ? cropRight : 0)); + } else { + optimizeTerrain3(); + } + + // Finally, render terrain to file + printf("Drawing map...\n"); + for (size_t x = CHUNKSIZE_X; x < g_MapsizeX - CHUNKSIZE_X; ++x) { + printProgress(x - CHUNKSIZE_X, g_MapsizeX); + for (size_t z = CHUNKSIZE_Z; z < g_MapsizeZ - CHUNKSIZE_Z; ++z) { + // Biome colors + if (g_BiomeMap != NULL) { + uint16_t &offset = BIOMEAT(x,z); + // This is getting a bit stupid here, there should be a better solution than a dozen copy ops + memcpy(colors[GRASS], g_Grasscolor + offset * g_GrasscolorDepth, 3); + memcpy(colors[LEAVES], g_Leafcolor + offset * g_FoliageDepth, 3); + memcpy(colors[TALL_GRASS], g_TallGrasscolor + offset * g_GrasscolorDepth, 3); + memcpy(colors[PUMPKIN_STEM], g_TallGrasscolor + offset * g_GrasscolorDepth, 3); + memcpy(colors[MELON_STEM], g_TallGrasscolor + offset * g_GrasscolorDepth, 3); + memcpy(colors[VINES], g_Grasscolor + offset * g_GrasscolorDepth, 3); + memcpy(colors[LILYPAD], g_Grasscolor + offset * g_GrasscolorDepth, 3); + // Leaves: This is just an approximation to get different leaf colors at all + colors[PINELEAVES][PRED] = clamp(int32_t(colors[LEAVES][PRED]) - 17); + colors[PINELEAVES][PGREEN] = clamp(int32_t(colors[LEAVES][PGREEN]) - 12); + colors[PINELEAVES][PBLUE] = colors[LEAVES][PBLUE]; + int32_t avg = GETBRIGHTNESS(colors[LEAVES]); + colors[BIRCHLEAVES][PRED] = clamp(int32_t(colors[LEAVES][PRED]) + (avg - int32_t(colors[LEAVES][PRED])) / 2 + 15); + colors[BIRCHLEAVES][PGREEN] = clamp(int32_t(colors[LEAVES][PGREEN]) + (avg - int32_t(colors[LEAVES][PGREEN])) / 2 + 16); + colors[BIRCHLEAVES][PBLUE] = clamp(int32_t(colors[LEAVES][PBLUE]) + (avg - int32_t(colors[LEAVES][PBLUE])) / 2 + 15); + colors[JUNGLELEAVES][PRED] = clamp(int32_t(colors[LEAVES][PRED])); + colors[JUNGLELEAVES][PGREEN] = clamp(int32_t(colors[LEAVES][PGREEN]) + 18); + colors[JUNGLELEAVES][PBLUE] = colors[LEAVES][PBLUE]; + } + // + const int bmpPosX = int((g_MapsizeZ - z - CHUNKSIZE_Z) * 2 + (x - CHUNKSIZE_X) * 2 + (splitImage ? -2 : bitmapStartX - cropLeft)); + int bmpPosY = int(g_MapsizeY * g_OffsetY + z + x - CHUNKSIZE_Z - CHUNKSIZE_X + (splitImage ? 0 : bitmapStartY - cropTop)) + 2 - (HEIGHTAT(x, z) & 0xFF) * g_OffsetY; + const int max = (HEIGHTAT(x, z) & 0xFF00) >> 8; + for (int y = uint8_t(HEIGHTAT(x, z)); y < max; ++y) { + bmpPosY -= g_OffsetY; + uint8_t &c = BLOCKAT(x, y, z); + if (c == AIR) { + return 0; } - // - const int bmpPosX = int((g_MapsizeZ - z - CHUNKSIZE_Z) * 2 + (x - CHUNKSIZE_X) * 2 + (splitImage ? -2 : bitmapStartX - cropLeft)); - int bmpPosY = int(g_MapsizeY * g_OffsetY + z + x - CHUNKSIZE_Z - CHUNKSIZE_X + (splitImage ? 0 : bitmapStartY - cropTop)) + 2 - (HEIGHTAT(x, z) & 0xFF) * g_OffsetY; - const int max = (HEIGHTAT(x, z) & 0xFF00) >> 8; - for (int y = uint8_t(HEIGHTAT(x, z)); y < max; ++y) { - bmpPosY -= g_OffsetY; - uint8_t &c = BLOCKAT(x, y, z); - if (c == AIR) { - continue; - } - //float col = float(y) * .78f - 91; - float brightnessAdjustment = brightnessLookup[y]; - if (g_BlendUnderground) { - brightnessAdjustment -= 168; - } - // we use light if... - if (g_Nightmode // nightmode is active, or - || (g_Skylight // skylight is used and - && (!BLOCK_AT_MAPEDGE(x, z)) // block is not edge of map (or if it is, has non-opaque block above) - )) { - int l = GETLIGHTAT(x, y, z); // find out how much light hits that block - if (l == 0 && y + 1 == g_MapsizeY) { - l = (g_Nightmode ? 3 : 15); // quickfix: assume maximum strength at highest level - } else { - const bool up = y + 1 < g_MapsizeY; - if (x + 1 < g_MapsizeX && (!up || BLOCKAT(x + 1, y + 1, z) == 0)) { - l = MAX(l, GETLIGHTAT(x + 1, y, z)); - if (x + 2 < g_MapsizeX) l = MAX(l, GETLIGHTAT(x + 2, y, z) - 1); - } - if (z + 1 < g_MapsizeZ && (!up || BLOCKAT(x, y + 1, z + 1) == 0)) { - l = MAX(l, GETLIGHTAT(x, y, z + 1)); - if (z + 2 < g_MapsizeZ) l = MAX(l, GETLIGHTAT(x, y, z + 2) - 1); - } - if (up) l = MAX(l, GETLIGHTAT(x, y + 1, z)); - //if (y + 2 < g_MapsizeY) l = MAX(l, GETLIGHTAT(x, y + 2, z) - 1); + //float col = float(y) * .78f - 91; + float brightnessAdjustment = brightnessLookup[y]; + if (g_BlendUnderground) { + brightnessAdjustment -= 168; + } + // we use light if... + if (g_Nightmode // nightmode is active, or + || (g_Skylight // skylight is used and + && (!BLOCK_AT_MAPEDGE(x, z)) // block is not edge of map (or if it is, has non-opaque block above) + )) { + int l = GETLIGHTAT(x, y, z); // find out how much light hits that block + if (l == 0 && y + 1 == g_MapsizeY) { + l = (g_Nightmode ? 3 : 15); // quickfix: assume maximum strength at highest level + } else { + const bool up = y + 1 < g_MapsizeY; + if (x + 1 < g_MapsizeX && (!up || BLOCKAT(x + 1, y + 1, z) == 0)) { + l = MAX(l, GETLIGHTAT(x + 1, y, z)); + if (x + 2 < g_MapsizeX) l = MAX(l, GETLIGHTAT(x + 2, y, z) - 1); } - if (!g_Skylight) { // Night - brightnessAdjustment -= (100 - l * 8); - } else { // Day - brightnessAdjustment -= (210 - l * 14); + if (z + 1 < g_MapsizeZ && (!up || BLOCKAT(x, y + 1, z + 1) == 0)) { + l = MAX(l, GETLIGHTAT(x, y, z + 1)); + if (z + 2 < g_MapsizeZ) l = MAX(l, GETLIGHTAT(x, y, z + 2) - 1); } + if (up) l = MAX(l, GETLIGHTAT(x, y + 1, z)); + //if (y + 2 < g_MapsizeY) l = MAX(l, GETLIGHTAT(x, y + 2, z) - 1); } - // Edge detection (this means where terrain goes 'down' and the side of the block is not visible) - uint8_t &b = BLOCKAT(x - 1, y - 1, z - 1); - if ((y && y + 1 < g_MapsizeY) // In bounds? - && BLOCKAT(x, y + 1, z) == AIR // Only if block above is air - && BLOCKAT(x - 1, y + 1, z - 1) == AIR // and block above and behind is air - && (b == AIR || b == c) // block behind (from pov) this one is same type or air - && (BLOCKAT(x - 1, y, z) == AIR || BLOCKAT(x, y, z - 1) == AIR)) { // block TL/TR from this one is air = edge - brightnessAdjustment += 13; + if (!g_Skylight) { // Night + brightnessAdjustment -= (100 - l * 8); + } else { // Day + brightnessAdjustment -= (210 - l * 14); } - setPixel(bmpPosX, bmpPosY, c, brightnessAdjustment); } + // Edge detection (this means where terrain goes 'down' and the side of the block is not visible) + uint8_t &b = BLOCKAT(x - 1, y - 1, z - 1); + if ((y && y + 1 < g_MapsizeY) // In bounds? + && BLOCKAT(x, y + 1, z) == AIR // Only if block above is air + && BLOCKAT(x - 1, y + 1, z - 1) == AIR // and block above and behind is air + && (b == AIR || b == c) // block behind (from pov) this one is same type or air + && (BLOCKAT(x - 1, y, z) == AIR || BLOCKAT(x, y, z - 1) == AIR)) { // block TL/TR from this one is air = edge + brightnessAdjustment += 13; + } + setPixel(bmpPosX, bmpPosY, c, brightnessAdjustment); } } - printProgress(10, 10); - // Bitmap creation complete - // unless using.... - // Underground overlay mode - if (g_BlendUnderground && !g_Underground) { - // Load map data again, since block culling removed most of the blocks - if (numSplitsX == 0 && ops.wholeworld && !loadEntireTerrain()) { + } + printProgress(10, 10); + // Bitmap creation complete + // unless using.... + // Underground overlay mode + if (g_BlendUnderground && !g_Underground) { + // Load map data again, since block culling removed most of the blocks + if (numSplitsX == 0 && ops.wholeworld && !loadEntireTerrain()) { + printf("Error loading terrain from '%s'\n", ops.filename); + exit(1); + } else if (numSplitsX != 0 || !ops.wholeworld) { + int i; + if (!loadTerrain(ops.filename, i)) { printf("Error loading terrain from '%s'\n", ops.filename); - return 1; - } else if (numSplitsX != 0 || !ops.wholeworld) { - int i; - if (!loadTerrain(ops.filename, i)) { - printf("Error loading terrain from '%s'\n", ops.filename); - return 1; - } - } - undergroundMode(true); - if (g_OffsetY == 2) { - optimizeTerrain2((numSplitsX == 0 ? cropLeft : 0), (numSplitsX == 0 ? cropRight : 0)); - } else { - optimizeTerrain3(); + exit(1); } - printf("Creating cave overlay...\n"); - for (size_t x = CHUNKSIZE_X; x < g_MapsizeX - CHUNKSIZE_X; ++x) { - printProgress(x - CHUNKSIZE_X, g_MapsizeX); - for (size_t z = CHUNKSIZE_Z; z < g_MapsizeZ - CHUNKSIZE_Z; ++z) { - const size_t bmpPosX = (g_MapsizeZ - z - CHUNKSIZE_Z) * 2 + (x - CHUNKSIZE_X) * 2 + (splitImage ? -2 : bitmapStartX) - cropLeft; - size_t bmpPosY = g_MapsizeY * g_OffsetY + z + x - CHUNKSIZE_Z - CHUNKSIZE_X + (splitImage ? 0 : bitmapStartY) - cropTop; - for (int y = 0; y < MIN(g_MapsizeY, 64); ++y) { - uint8_t &c = BLOCKAT(x, y, z); - if (c != AIR) { // If block is not air (colors[c][3] != 0) - blendPixel(bmpPosX, bmpPosY, c, float(y + 30) * .0048f); - } - bmpPosY -= g_OffsetY; + } + undergroundMode(true); + if (g_OffsetY == 2) { + optimizeTerrain2((numSplitsX == 0 ? cropLeft : 0), (numSplitsX == 0 ? cropRight : 0)); + } else { + optimizeTerrain3(); + } + printf("Creating cave overlay...\n"); + for (size_t x = CHUNKSIZE_X; x < g_MapsizeX - CHUNKSIZE_X; ++x) { + printProgress(x - CHUNKSIZE_X, g_MapsizeX); + for (size_t z = CHUNKSIZE_Z; z < g_MapsizeZ - CHUNKSIZE_Z; ++z) { + const size_t bmpPosX = (g_MapsizeZ - z - CHUNKSIZE_Z) * 2 + (x - CHUNKSIZE_X) * 2 + (splitImage ? -2 : bitmapStartX) - cropLeft; + size_t bmpPosY = g_MapsizeY * g_OffsetY + z + x - CHUNKSIZE_Z - CHUNKSIZE_X + (splitImage ? 0 : bitmapStartY) - cropTop; + for (int y = 0; y < MIN(g_MapsizeY, 64); ++y) { + uint8_t &c = BLOCKAT(x, y, z); + if (c != AIR) { // If block is not air (colors[c][3] != 0) + blendPixel(bmpPosX, bmpPosY, c, float(y + 30) * .0048f); } + bmpPosY -= g_OffsetY; } } - printProgress(10, 10); - } // End blend-underground - // If disk caching is used, save part to disk - if (splitImage && !saveImagePart()) { - printf("Error saving partially rendered image.\n"); - return 1; - } - // No incremental rendering at all, so quit the loop - if (numSplitsX == 0) { - break; } + printProgress(10, 10); + } // End blend-underground + // If disk caching is used, save part to disk + if (splitImage && !saveImagePart()) { + printf("Error saving partially rendered image.\n"); + exit(1); } - // Drawing complete, now either just save the image or compose it if disk caching was used - // Saving - if (!splitImage) { - saveImage(); - } else { - if (!composeFinalImage()) { - printf("Aborted.\n"); - return 1; - } + // No incremental rendering at all, so quit the loop + if (numSplitsX == 0) { + return 2; } - if (fileHandle != NULL) fclose(fileHandle); - - printf("Job complete.\n"); return 0; } - parsedOptions parseArgs(int argc, char** argv) { bool wholeworld = false; char *filename = NULL, *outfile = NULL, *colorfile = NULL, *texturefile = NULL, *infoFile = NULL; bool dumpColors = false, infoOnly = false, end = false; char *biomepath = NULL; - int result = 0; - parsedOptions out; - out.result = Fail; + + if (argc < 2) { + printHelp(argv[0]); + exit(1); + } // First, for the sake of backward compatibility, try to parse command line arguments the old way first if (argc >= 7 @@ -620,7 +673,7 @@ parsedOptions parseArgs(int argc, char** argv) { } else if (strcmp(option, "-to") == 0) { if (!MOREARGS(2) || !isNumeric(POLLARG(1)) || !isNumeric(POLLARG(2))) { printf("Error: %s needs two integer arguments, ie: %s -5 20\n", option, option); - return out; + exit(1); } g_ToChunkX = atoi(NEXTARG) + 1; g_ToChunkZ = atoi(NEXTARG) + 1; @@ -645,7 +698,7 @@ parsedOptions parseArgs(int argc, char** argv) { } else if (strcmp(option, "-biomecolors") == 0) { if (!MOREARGS(1)) { printf("Error: %s needs path to grasscolor.png and foliagecolor.png, ie: %s ./subdir\n", option, option); - return out; + exit(1); } g_UseBiomes = true; biomepath = NEXTARG; @@ -656,51 +709,51 @@ parsedOptions parseArgs(int argc, char** argv) { } else if (strcmp(option, "-noise") == 0 || strcmp(option, "-dither") == 0) { if (!MOREARGS(1) || !isNumeric(POLLARG(1))) { printf("Error: %s needs an integer argument, ie: %s 10\n", option, option); - return out; + exit(1); } g_Noise = atoi(NEXTARG); } else if (strcmp(option, "-height") == 0 || strcmp(option, "-max") == 0) { if (!MOREARGS(1) || !isNumeric(POLLARG(1))) { printf("Error: %s needs an integer argument, ie: %s 100\n", option, option); - return out; + exit(1); } g_MapsizeY = atoi(NEXTARG); if (strcmp(option, "-max") == 0) g_MapsizeY++; } else if (strcmp(option, "-min") == 0) { if (!MOREARGS(1) || !isNumeric(POLLARG(1))) { printf("Error: %s needs an integer argument, ie: %s 50\n", option, option); - return out; + exit(1); } g_MapminY = atoi(NEXTARG); } else if (strcmp(option, "-mem") == 0) { if (!MOREARGS(1) || !isNumeric(POLLARG(1)) || atoi(POLLARG(1)) <= 0) { printf("Error: %s needs a positive integer argument, ie: %s 1000\n", option, option); - return out; + exit(1); } out.memlimitSet = true; out.memlimit = size_t (atoi(NEXTARG)) * size_t (1024 * 1024); } else if (strcmp(option, "-file") == 0) { if (!MOREARGS(1)) { printf("Error: %s needs one argument, ie: %s myworld.bmp\n", option, option); - return out; + exit(1); } outfile = NEXTARG; } else if (strcmp(option, "-colors") == 0) { if (!MOREARGS(1)) { printf("Error: %s needs one argument, ie: %s colors.txt\n", option, option); - return out; + exit(1); } colorfile = NEXTARG; } else if (strcmp(option, "-texture") == 0) { if (!MOREARGS(1)) { printf("Error: %s needs one argument, ie: %s terrain.png\n", option, option); - return out; + exit(1); } texturefile = NEXTARG; } else if (strcmp(option, "-info") == 0) { if (!MOREARGS(1)) { printf("Error: %s needs one argument, ie: %s data.json\n", option, option); - return out; + exit(1); } infoFile = NEXTARG; } else if (strcmp(option, "-infoonly") == 0) { @@ -720,13 +773,12 @@ parsedOptions parseArgs(int argc, char** argv) { } else if (strcmp(option, "-split") == 0) { if (!MOREARGS(1)) { printf("Error: %s needs a path argument, ie: %s tiles/\n", option, option); - return out; + exit(1); } g_TilePath = NEXTARG; } else if (strcmp(option, "-help") == 0 || strcmp(option, "-h") == 0 || strcmp(option, "-?") == 0) { printHelp(argv[0]); - out.result = Return; - return out; + exit(1); } else if (strcmp(option, "-marker") == 0) { if (g_MarkerCount >= MAX_MARKERS) { printf("Too many markers, ignoring additional ones\n"); @@ -734,7 +786,7 @@ parsedOptions parseArgs(int argc, char** argv) { } if (!MOREARGS(3) || !isNumeric(POLLARG(2)) || !isNumeric(POLLARG(3))) { printf("Error: %s needs a char and two integer arguments, ie: %s r -15 240\n", option, option); - return out; + exit(1); } Marker &marker = g_Markers[g_MarkerCount]; switch (*NEXTARG) { @@ -759,8 +811,8 @@ parsedOptions parseArgs(int argc, char** argv) { g_MarkerCount++; } else if (strcmp(option, "-mystcraftage") == 0) { if (!MOREARGS(1)) { - printf("Error: %s needs an integer age number argument", option); - return out; + printf("Error: %s needs an integer age number argument", option); + exit(1); } g_MystCraftAge = atoi(NEXTARG); } else { @@ -770,8 +822,6 @@ parsedOptions parseArgs(int argc, char** argv) { wholeworld = (g_FromChunkX == UNDEFINED || g_ToChunkX == UNDEFINED); } - out.result = Success; - out.wholeworld = wholeworld; out.filename = filename; out.outfile = outfile; From 0802d399769f28dd24a8817227c144b7b10d809b Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 31 Oct 2017 09:06:09 -0400 Subject: [PATCH 5/7] Pull out one more method from main. --- main.cpp | 68 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/main.cpp b/main.cpp index a7a57697..b6be5b2e 100644 --- a/main.cpp +++ b/main.cpp @@ -103,6 +103,7 @@ FILE* determineFileHandle(parsedOptions&, bool, int, int); void checkWorldDims(); void setMapSize(); void makeTilePath(); +void splitUpRendering(int&, int&, bool, uint64_t, parsedOptions&); int main(int argc, char **argv) @@ -183,6 +184,41 @@ int main(int argc, char **argv) bool splitImage = false; int numSplitsX = 0; int numSplitsZ = 0; + splitUpRendering(numSplitsX, numSplitsZ, splitImage, bitmapBytes, ops); + // Always same random seed, as this is only used for block noise, which should give the same result for the same input every time + srand(1337); + + if (ops.outfile == NULL) { + ops.outfile = (char *) "output.png"; + } + + // open output file only if not doing the tiled output + FILE *fileHandle = determineFileHandle(ops, splitImage, bitmapX, bitmapY); + // Precompute brightness adjustment factor + float *brightnessLookup = computeBrightnessLookup(); + + // Now here's the loop rendering all the required parts of the image. + // All the vars previously used to define bounds will be set on each loop, + // to create something like a virtual window inside the map. + while (!renderPartOfMap(splitImage, numSplitsX, numSplitsZ, cropLeft, cropRight, cropTop, brightnessLookup, ops)); + + // Drawing complete, now either just save the image or compose it if disk caching was used + // Saving + if (!splitImage) { + saveImage(); + } else { + if (!composeFinalImage()) { + printf("Aborted.\n"); + return 1; + } + } + if (fileHandle != NULL) fclose(fileHandle); + + printf("Job complete.\n"); + return 0; +} + +void splitUpRendering(int& numSplitsX, int& numSplitsZ, bool splitImage, uint64_t bitmapBytes, parsedOptions& ops) { if (ops.memlimit && ops.memlimit < bitmapBytes + calcTerrainSize(g_ToChunkX - g_FromChunkX, g_ToChunkZ - g_FromChunkZ)) { // If we'd need more mem than allowed, we have to render groups of chunks... if (ops.memlimit < bitmapBytes + 220 * uint64_t(1024 * 1024)) { @@ -217,38 +253,6 @@ int main(int argc, char **argv) } } } - - // Always same random seed, as this is only used for block noise, which should give the same result for the same input every time - srand(1337); - - if (ops.outfile == NULL) { - ops.outfile = (char *) "output.png"; - } - - // open output file only if not doing the tiled output - FILE *fileHandle = determineFileHandle(ops, splitImage, bitmapX, bitmapY); - // Precompute brightness adjustment factor - float *brightnessLookup = computeBrightnessLookup(); - - // Now here's the loop rendering all the required parts of the image. - // All the vars previously used to define bounds will be set on each loop, - // to create something like a virtual window inside the map. - while (!renderPartOfMap(splitImage, numSplitsX, numSplitsZ, cropLeft, cropRight, cropTop, brightnessLookup, ops)); - - // Drawing complete, now either just save the image or compose it if disk caching was used - // Saving - if (!splitImage) { - saveImage(); - } else { - if (!composeFinalImage()) { - printf("Aborted.\n"); - return 1; - } - } - if (fileHandle != NULL) fclose(fileHandle); - - printf("Job complete.\n"); - return 0; } FILE* determineFileHandle(parsedOptions& ops, bool splitImage, int bitmapX, int bitmapY) { From 18b8cf10822c89ff8b901a2d7afa32c9f8092f38 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 31 Oct 2017 10:42:40 -0400 Subject: [PATCH 6/7] Fixes --- main.cpp | 74 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/main.cpp b/main.cpp index b6be5b2e..3e9b0b86 100644 --- a/main.cpp +++ b/main.cpp @@ -72,7 +72,7 @@ enum parseReturns { Return }; -struct parsedOptions { +struct options { parseReturns result; bool wholeworld; @@ -89,44 +89,31 @@ struct parsedOptions { bool memlimitSet; }; -parsedOptions parseArgs(int argc, char** argv); -int renderPartOfMap(bool&, int&, int&, int&, int&, int&, float*, parsedOptions&); +options parseArgs(int argc, char** argv); +int renderPartOfMap(bool&, int&, int&, int&, int&, int&, float*, options&); -void checkColorFile(parsedOptions&); -void checkTextureFile(parsedOptions&); -void checkDumpColors(parsedOptions&); -void loadFullWorldPath(parsedOptions&); -void useBiomes(parsedOptions& ops); +void checkColorFile(options&); +void checkTextureFile(options&); +void checkDumpColors(options&); +void loadFullWorldPath(options&); +void useBiomes(options& ops); void setMapBounds(); float* computeBrightnessLookup(); -FILE* determineFileHandle(parsedOptions&, bool, int, int); +FILE* determineFileHandle(options&, bool, int, int); void checkWorldDims(); void setMapSize(); void makeTilePath(); -void splitUpRendering(int&, int&, bool, uint64_t, parsedOptions&); +void splitUpRendering(int&, int&, bool&, uint64_t&, options&); int main(int argc, char **argv) { - parsedOptions ops = parseArgs(argc, argv); + options ops = parseArgs(argc, argv); - ops.memlimitSet = false; - - if (sizeof(size_t) < 8) { - ops.memlimit = 1500 * uint64_t(1024 * 1024); - } else { - ops.memlimit = 2000 * uint64_t(1024 * 1024); - } - - // ########## end of command line parsing ########## if (g_Hell || g_ServerHell || ops.end) g_UseBiomes = false; printf("mcmap " VERSION " %dbit by Zahl\n", 8*sizeof(size_t)); - if (sizeof(size_t) < 8 && ops.memlimit > 1800 * uint64_t(1024 * 1024)) { - ops.memlimit = 1800 * uint64_t(1024 * 1024); - } - // Load colormap from file loadColors(); // Default base, in case some are missing in colors.txt (if used) // Load files from colors.txt @@ -141,12 +128,13 @@ int main(int argc, char **argv) g_WorldFormat = getWorldFormat(ops.filename); setMapSize(); - checkWorldDims(); if (ops.wholeworld && !scanWorldDirectory(ops.filename)) { printf("Error accessing terrain at '%s'\n", ops.filename); return 1; } + checkWorldDims(); + setMapBounds(); // Don't allow ridiculously small values for big maps @@ -218,7 +206,7 @@ int main(int argc, char **argv) return 0; } -void splitUpRendering(int& numSplitsX, int& numSplitsZ, bool splitImage, uint64_t bitmapBytes, parsedOptions& ops) { +void splitUpRendering(int& numSplitsX, int& numSplitsZ, bool& splitImage, uint64_t& bitmapBytes, options& ops) { if (ops.memlimit && ops.memlimit < bitmapBytes + calcTerrainSize(g_ToChunkX - g_FromChunkX, g_ToChunkZ - g_FromChunkZ)) { // If we'd need more mem than allowed, we have to render groups of chunks... if (ops.memlimit < bitmapBytes + 220 * uint64_t(1024 * 1024)) { @@ -255,7 +243,7 @@ void splitUpRendering(int& numSplitsX, int& numSplitsZ, bool splitImage, uint64_ } } -FILE* determineFileHandle(parsedOptions& ops, bool splitImage, int bitmapX, int bitmapY) { +FILE* determineFileHandle(options& ops, bool splitImage, int bitmapX, int bitmapY) { FILE* fileHandle = NULL; if (g_TilePath == NULL) { fileHandle = fopen(ops.outfile, (splitImage ? "w+b" : "wb")); @@ -291,7 +279,7 @@ void makeTilePath() { } } -void checkColorFile(parsedOptions& ops) { +void checkColorFile(options& ops) { if (ops.colorfile != NULL && fileExists(ops.colorfile)) { if (!loadColorsFromFile(ops.colorfile)) { printf("Error loading colors from %s: Opening failed.\n", ops.colorfile); @@ -303,7 +291,7 @@ void checkColorFile(parsedOptions& ops) { } } -void checkTextureFile(parsedOptions& ops) { +void checkTextureFile(options& ops) { // Extract colors from terrain.png if (ops.texturefile != NULL && fileExists(ops.texturefile)) { if (!extractColors(ops.texturefile)) { @@ -316,7 +304,7 @@ void checkTextureFile(parsedOptions& ops) { } } -void checkDumpColors(parsedOptions& ops) { +void checkDumpColors(options& ops) { // If colors should be dumped to file, exit afterwards if (ops.dumpColors) { if (!dumpColorsToFile("defaultcolors.txt")) { @@ -324,10 +312,11 @@ void checkDumpColors(parsedOptions& ops) { exit(1); } printf("Colors written to defaultcolors.txt\n"); + exit(0); } } -void loadFullWorldPath(parsedOptions& ops) { +void loadFullWorldPath(options& ops) { if (ops.filename == NULL) { printf("Error: No world given. Please add the path to your world to the command line.\n"); exit(1); @@ -397,7 +386,7 @@ void setMapBounds() { g_TotalToChunkZ = g_ToChunkZ; } -void useBiomes(parsedOptions& ops) { +void useBiomes(options& ops) { char *bpath = new char[strlen(ops.filename) + 30]; strcpy(bpath, ops.filename); strcat(bpath, "/biomes"); @@ -422,12 +411,12 @@ void setMapSize() { } } } -int renderPartOfMap(bool& splitImage, int& numSplitsX, int& numSplitsZ, int& cropLeft, int& cropRight, int& cropTop, float* brightnessLookup, parsedOptions& ops) { +int renderPartOfMap(bool& splitImage, int& numSplitsX, int& numSplitsZ, int& cropLeft, int& cropRight, int& cropTop, float* brightnessLookup, options& ops) { int bitmapStartX = 3, bitmapStartY = 5; if (numSplitsX) { // virtual window is set here // Set current chunk bounds according to number of splits. returns true if everything has been rendered already if (prepareNextArea(numSplitsX, numSplitsZ, bitmapStartX, bitmapStartY)) { - return 2; + return 1; } // if image is split up, prepare memory block for next part if (splitImage) { @@ -630,19 +619,27 @@ int renderPartOfMap(bool& splitImage, int& numSplitsX, int& numSplitsZ, int& cro } return 0; } -parsedOptions parseArgs(int argc, char** argv) { +options parseArgs(int argc, char** argv) { bool wholeworld = false; char *filename = NULL, *outfile = NULL, *colorfile = NULL, *texturefile = NULL, *infoFile = NULL; bool dumpColors = false, infoOnly = false, end = false; char *biomepath = NULL; - parsedOptions out; + options out; if (argc < 2) { printHelp(argv[0]); exit(1); } + out.memlimitSet = false; + + if (sizeof(size_t) < 8) { + out.memlimit = 1500 * uint64_t(1024 * 1024); + } else { + out.memlimit = 2000 * uint64_t(1024 * 1024); + } + // First, for the sake of backward compatibility, try to parse command line arguments the old way first if (argc >= 7 && isNumeric(argv[1]) && isNumeric(argv[2]) && isNumeric(argv[3]) && isNumeric(argv[4])) { // Specific area of world @@ -826,6 +823,11 @@ parsedOptions parseArgs(int argc, char** argv) { wholeworld = (g_FromChunkX == UNDEFINED || g_ToChunkX == UNDEFINED); } + + if (sizeof(size_t) < 8 && out.memlimit > 1800 * uint64_t(1024 * 1024)) { + out.memlimit = 1800 * uint64_t(1024 * 1024); + } + out.wholeworld = wholeworld; out.filename = filename; out.outfile = outfile; From df207003ea6e574b52bd5b026a2398444bbbbfe1 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 31 Oct 2017 11:03:51 -0400 Subject: [PATCH 7/7] Bugfix --- main.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/main.cpp b/main.cpp index 3e9b0b86..db30e392 100644 --- a/main.cpp +++ b/main.cpp @@ -90,7 +90,7 @@ struct options { }; options parseArgs(int argc, char** argv); -int renderPartOfMap(bool&, int&, int&, int&, int&, int&, float*, options&); +bool renderPartOfMap(bool&, int&, int&, int&, int&, int&, float*, options&); void checkColorFile(options&); void checkTextureFile(options&); @@ -411,12 +411,12 @@ void setMapSize() { } } } -int renderPartOfMap(bool& splitImage, int& numSplitsX, int& numSplitsZ, int& cropLeft, int& cropRight, int& cropTop, float* brightnessLookup, options& ops) { +bool renderPartOfMap(bool& splitImage, int& numSplitsX, int& numSplitsZ, int& cropLeft, int& cropRight, int& cropTop, float* brightnessLookup, options& ops) { int bitmapStartX = 3, bitmapStartY = 5; if (numSplitsX) { // virtual window is set here // Set current chunk bounds according to number of splits. returns true if everything has been rendered already if (prepareNextArea(numSplitsX, numSplitsZ, bitmapStartX, bitmapStartY)) { - return 1; + return true; } // if image is split up, prepare memory block for next part if (splitImage) { @@ -428,7 +428,7 @@ int renderPartOfMap(bool& splitImage, int& numSplitsX, int& numSplitsZ, int& cro if (res == -1) { printf("Error loading partial image to render to.\n"); exit(1); - } else if (res == 1) return 0; + } else if (res == 1) return false; } } @@ -462,7 +462,7 @@ int renderPartOfMap(bool& splitImage, int& numSplitsX, int& numSplitsZ, int& cro if (splitImage && numberOfChunks == 0) { printf("Section is empty, skipping...\n"); discardImagePart(); - return 0; + return false; } } @@ -522,7 +522,7 @@ int renderPartOfMap(bool& splitImage, int& numSplitsX, int& numSplitsZ, int& cro bmpPosY -= g_OffsetY; uint8_t &c = BLOCKAT(x, y, z); if (c == AIR) { - return 0; + break; } //float col = float(y) * .78f - 91; float brightnessAdjustment = brightnessLookup[y]; @@ -615,9 +615,9 @@ int renderPartOfMap(bool& splitImage, int& numSplitsX, int& numSplitsZ, int& cro } // No incremental rendering at all, so quit the loop if (numSplitsX == 0) { - return 2; + return true; } - return 0; + return false; } options parseArgs(int argc, char** argv) { bool wholeworld = false;