diff options
42 files changed, 1150 insertions, 175 deletions
diff --git a/.gitignore b/.gitignore index ea4ccdd59..caceee4a8 100644 --- a/.gitignore +++ b/.gitignore @@ -91,13 +91,15 @@ src/AllFiles.lst *.opendb *.idb -# cmake output folders +# cmake output folders and files ZERO_CHECK.dir/ Debug/ DebugProfile/ Release/ ReleaseProfile/ *.dir/ +CPackConfig.cmake +CPackSourceConfig.cmake # APIDump-generated status files: Server/cuberite_api.lua diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b5abb362..17644e86c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,7 @@ set(SELF_TEST OFF CACHE BOOL "") # This has to be done before any flags have been set up. if(${BUILD_TOOLS}) message("Building tools") + add_subdirectory(Tools/GrownBiomeGenVisualiser/) add_subdirectory(Tools/MCADefrag/) add_subdirectory(Tools/ProtoProxy/) endif() diff --git a/Tools/GrownBiomeGenVisualiser/.gitignore b/Tools/GrownBiomeGenVisualiser/.gitignore new file mode 100644 index 000000000..c336f9028 --- /dev/null +++ b/Tools/GrownBiomeGenVisualiser/.gitignore @@ -0,0 +1,2 @@ +# Ignore the output images: +*.ppm diff --git a/Tools/GrownBiomeGenVisualiser/CMakeLists.txt b/Tools/GrownBiomeGenVisualiser/CMakeLists.txt new file mode 100644 index 000000000..0669275cb --- /dev/null +++ b/Tools/GrownBiomeGenVisualiser/CMakeLists.txt @@ -0,0 +1,104 @@ + +cmake_minimum_required (VERSION 2.6) + +project (GrownBiomeGenVisualiser) + +# Without this, the MSVC variable isn't defined for MSVC builds ( https://www.cmake.org/pipermail/cmake/2011-November/047130.html ) +enable_language(CXX C) + +include(../../SetFlags.cmake) +set_flags() +set_lib_flags() +enable_profile() + +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + add_flags_cxx("-Wno-error=sign-conversion -Wno-error=conversion -Wno-error=shorten-64-to-32") + add_flags_cxx("-Wno-error=old-style-cast") + if ("${CLANG_VERSION}" VERSION_GREATER 3.5) + add_flags_cxx("-Wno-error=keyword-macro") + endif() +endif() + +# Set include paths to the used libraries: +include_directories("../../lib") +include_directories("../../src") + + +function(flatten_files arg1) + set(res "") + foreach(f ${${arg1}}) + get_filename_component(f ${f} ABSOLUTE) + list(APPEND res ${f}) + endforeach() + set(${arg1} "${res}" PARENT_SCOPE) +endfunction() + + +# Include the libraries: + +set_exe_flags() + +# Include the shared files: +set(SHARED_SRC + ../../src/StringUtils.cpp + ../../src/Logger.cpp + ../../src/Noise/Noise.cpp + ../../src/BiomeDef.cpp +) +set(SHARED_HDR + ../../src/StringUtils.h +) + +flatten_files(SHARED_SRC) +flatten_files(SHARED_HDR) +source_group("Shared" FILES ${SHARED_SRC} ${SHARED_HDR}) + +set(SHARED_OSS_SRC + ../../src/OSSupport/CriticalSection.cpp + ../../src/OSSupport/Event.cpp + ../../src/OSSupport/File.cpp + ../../src/OSSupport/IsThread.cpp + ../../src/OSSupport/StackTrace.cpp +) + +set(SHARED_OSS_HDR + ../../src/OSSupport/CriticalSection.h + ../../src/OSSupport/Event.h + ../../src/OSSupport/File.h + ../../src/OSSupport/IsThread.h + ../../src/OSSupport/StackTrace.h +) + +if(WIN32) + list (APPEND SHARED_OSS_SRC ../../src/StackWalker.cpp) + list (APPEND SHARED_OSS_HDR ../../src/StackWalker.h) +endif() + +flatten_files(SHARED_OSS_SRC) +flatten_files(SHARED_OSS_HDR) + +source_group("Shared\\OSSupport" FILES ${SHARED_OSS_SRC} ${SHARED_OSS_HDR}) + + + +# Include the main source files: +set(SOURCES + GrownBiomeGenVisualiser.cpp + Globals.cpp +) +set(HEADERS + Globals.h +) + +source_group("" FILES ${SOURCES} ${HEADERS}) + +add_executable(GrownBiomeGenVisualiser + ${SOURCES} + ${HEADERS} + ${SHARED_SRC} + ${SHARED_HDR} + ${SHARED_OSS_SRC} + ${SHARED_OSS_HDR} +) + +set_target_properties(GrownBiomeGenVisualiser PROPERTIES FOLDER Tools) diff --git a/Tools/GrownBiomeGenVisualiser/Globals.cpp b/Tools/GrownBiomeGenVisualiser/Globals.cpp new file mode 100644 index 000000000..13c6ae709 --- /dev/null +++ b/Tools/GrownBiomeGenVisualiser/Globals.cpp @@ -0,0 +1,10 @@ + +// Globals.cpp + +// This file is used for precompiled header generation in MSVC environments + +#include "Globals.h" + + + + diff --git a/Tools/GrownBiomeGenVisualiser/Globals.h b/Tools/GrownBiomeGenVisualiser/Globals.h new file mode 100644 index 000000000..3fcd2f60c --- /dev/null +++ b/Tools/GrownBiomeGenVisualiser/Globals.h @@ -0,0 +1,262 @@ + +// Globals.h + +// This file gets included from every module in the project, so that global symbols may be introduced easily +// Also used for precompiled header generation in MSVC environments + + + + + +// Compiler-dependent stuff: +#if defined(_MSC_VER) + // MSVC produces warning C4481 on the override keyword usage, so disable the warning altogether + #pragma warning(disable:4481) + + // Disable some warnings that we don't care about: + #pragma warning(disable:4100) + + #define OBSOLETE __declspec(deprecated) + + // No alignment needed in MSVC + #define ALIGN_8 + #define ALIGN_16 + + #define FORMATSTRING(formatIndex, va_argsIndex) + + // MSVC has its own custom version of zu format + #define SIZE_T_FMT "%Iu" + #define SIZE_T_FMT_PRECISION(x) "%" #x "Iu" + #define SIZE_T_FMT_HEX "%Ix" + + #define NORETURN __declspec(noreturn) + +#elif defined(__GNUC__) + + // TODO: Can GCC explicitly mark classes as abstract (no instances can be created)? + #define abstract + + // override is part of c++11 + #if __cplusplus < 201103L + #define override + #endif + + #define OBSOLETE __attribute__((deprecated)) + + #define ALIGN_8 __attribute__((aligned(8))) + #define ALIGN_16 __attribute__((aligned(16))) + + // Some portability macros :) + #define stricmp strcasecmp + + #define FORMATSTRING(formatIndex, va_argsIndex) __attribute__((format (printf, formatIndex, va_argsIndex))) + + #if defined(_WIN32) + // We're compiling on MinGW, which uses an old MSVCRT library that has no support for size_t printfing. + // We need direct size formats: + #if defined(_WIN64) + #define SIZE_T_FMT "%I64u" + #define SIZE_T_FMT_PRECISION(x) "%" #x "I64u" + #define SIZE_T_FMT_HEX "%I64x" + #else + #define SIZE_T_FMT "%u" + #define SIZE_T_FMT_PRECISION(x) "%" #x "u" + #define SIZE_T_FMT_HEX "%x" + #endif + #else + // We're compiling on Linux, so we can use libc's size_t printf format: + #define SIZE_T_FMT "%zu" + #define SIZE_T_FMT_PRECISION(x) "%" #x "zu" + #define SIZE_T_FMT_HEX "%zx" + #endif + + #define NORETURN __attribute((__noreturn__)) +#else + + #error "You are using an unsupported compiler, you might need to #define some stuff here for your compiler" + + /* + // Copy and uncomment this into another #elif section based on your compiler identification + + // Explicitly mark classes as abstract (no instances can be created) + #define abstract + + // Mark virtual methods as overriding (forcing them to have a virtual function of the same signature in the base class) + #define override + + // Mark functions as obsolete, so that their usage results in a compile-time warning + #define OBSOLETE + + // Mark types / variables for alignment. Do the platforms need it? + #define ALIGN_8 + #define ALIGN_16 + */ + + #define FORMATSTRING(formatIndex,va_argsIndex) __attribute__((format (printf, formatIndex, va_argsIndex))) + +#endif + + + + + +// Integral types with predefined sizes: +typedef long long Int64; +typedef int Int32; +typedef short Int16; + +typedef unsigned long long UInt64; +typedef unsigned int UInt32; +typedef unsigned short UInt16; + +typedef unsigned char Byte; + + + + + +// A macro to disallow the copy constructor and operator= functions +// This should be used in the private: declarations for any class that shouldn't allow copying itself +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName &); \ + void operator=(const TypeName &) + +// A macro that is used to mark unused function parameters, to avoid pedantic warnings in gcc +#define UNUSED(X) (void)(X) + + + + +// OS-dependent stuff: +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + #include <Windows.h> + #include <winsock2.h> + #include <ws2tcpip.h> + + // Windows SDK defines min and max macros, messing up with our std::min and std::max usage + #undef min + #undef max + + // Windows SDK defines GetFreeSpace as a constant, probably a Win16 API remnant + #ifdef GetFreeSpace + #undef GetFreeSpace + #endif // GetFreeSpace + + #define SocketError WSAGetLastError() +#else + #include <sys/types.h> + #include <sys/stat.h> // for mkdir + #include <sys/time.h> + #include <sys/socket.h> + #include <netinet/in.h> + #include <arpa/inet.h> + #include <netdb.h> + #include <time.h> + #include <dirent.h> + #include <errno.h> + #include <iostream> + #include <unistd.h> + + #include <cstdio> + #include <cstring> + #include <pthread.h> + #include <semaphore.h> + #include <errno.h> + #include <fcntl.h> + + typedef int SOCKET; + enum + { + INVALID_SOCKET = -1, + }; + #define closesocket close + #define SocketError errno +#if !defined(ANDROID_NDK) + #include <tr1/memory> +#endif +#endif + +#if !defined(ANDROID_NDK) + #define USE_SQUIRREL +#endif + +#if defined(ANDROID_NDK) + #define FILE_IO_PREFIX "/sdcard/mcserver/" +#else + #define FILE_IO_PREFIX "" +#endif + + + + + +// CRT stuff: +#include <assert.h> +#include <stdio.h> +#include <math.h> +#include <stdarg.h> +#include <time.h> + + + + + +// STL stuff: +#include <vector> +#include <list> +#include <deque> +#include <string> +#include <map> +#include <algorithm> +#include <memory> + + + + + +// Common headers (without macros): +#include "StringUtils.h" + + + + + +// Common definitions: + +/// Evaluates to the number of elements in an array (compile-time!) +#define ARRAYCOUNT(X) (sizeof(X) / sizeof(*(X))) + +/// Allows arithmetic expressions like "32 KiB" (but consider using parenthesis around it, "(32 KiB)" ) +#define KiB * 1024 +#define MiB * 1024 * 1024 + +/// Faster than (int)floorf((float)x / (float)div) +#define FAST_FLOOR_DIV( x, div ) ( (x) < 0 ? (((int)x / div) - 1) : ((int)x / div) ) + +// Own version of assert() that writes failed assertions to the log for review +#ifdef NDEBUG + #define ASSERT(x) ((void)0) +#else + #define ASSERT assert +#endif + +// Pretty much the same as ASSERT() but stays in Release builds +#define VERIFY( x ) ( !!(x) || ( LOGERROR("Verification failed: %s, file %s, line %i", #x, __FILE__, __LINE__ ), exit(1), 0 ) ) + + + + + +/// A generic interface used mainly in ForEach() functions +template <typename Type> class cItemCallback +{ +public: + /// Called for each item in the internal list; return true to stop the loop, or false to continue enumerating + virtual bool Item(Type * a_Type) = 0; + virtual ~cItemCallback() {} +} ; + + + + diff --git a/Tools/GrownBiomeGenVisualiser/GrownBiomeGenVisualiser.cpp b/Tools/GrownBiomeGenVisualiser/GrownBiomeGenVisualiser.cpp new file mode 100644 index 000000000..531e3edd9 --- /dev/null +++ b/Tools/GrownBiomeGenVisualiser/GrownBiomeGenVisualiser.cpp @@ -0,0 +1,456 @@ + +// GrownBiomeGenVisualiser.cpp + +// Implements the main app entrypoint + +#include "Globals.h" +#include <iostream> +#include <fstream> +#include <random> +#define PROT_INT_BUFFER_SIZE (130 * 130) +#include "Generating/ProtIntGen.h" + + + + + +typedef int Color[3]; // Color is an array of 3 ints + + + + +// Forward declarations, needed for GCC and Clang: +void log(const char * a_Fmt, ...) FORMATSTRING(1, 2); +void outputBitmapFile( + const AString & a_FileName, + unsigned a_ImageSizeX, unsigned a_ImageSizeY, + const int * a_ColorIndices, + unsigned a_IndicesSizeX, unsigned a_IndicesSizeY, + const Color * a_Colors, + size_t a_NumColors +); +void initializeBiomeColors(void); +void generateZoomLevels(int a_Seed); +void generateSmoothLevels(int a_Seed); +void generateExamples(int a_Seed); + + + + + + +/** Color palette used for algorithm examples. +No relevance to biomes whatsoever. */ +static const Color spectrumColors[] = +{ + {0, 0, 0}, + {255, 0, 0}, + {0, 255, 0}, + {0, 0, 255}, + {255, 255, 0}, + {255, 0, 255}, + {0, 255, 255}, +}; + + + + + +/** Color palette used for displaying biome groups. */ +static const Color biomeGroupColors[] = +{ + /* bgOcean */ {0x00, 0x00, 0x70}, + /* bgDesert */ {0xfa, 0x94, 0x18}, + /* bgTemperate */ {0x05, 0x66, 0x21}, + /* bgMountains */ {0x60, 0x60, 0x60}, + /* bgIce */ {0xa0, 0xa0, 0xff}, +}; + + + + + +/** Color palette used for outputting biome images. +Initialized from biomeColorMap[] in initializeBiomeColors(). */ +static Color biomeColors[255]; + + + + +/** Map of biome -> color, used to initialize biomeColorMap[]. */ +static const struct +{ + EMCSBiome biome; + Color color; +} +biomeColorMap[] = +{ + { biOcean, { 0x00, 0x00, 0x70 }, }, + { biPlains, { 0x8d, 0xb3, 0x60 }, }, + { biDesert, { 0xfa, 0x94, 0x18 }, }, + { biExtremeHills, { 0x60, 0x60, 0x60 }, }, + { biForest, { 0x05, 0x66, 0x21 }, }, + { biTaiga, { 0x0b, 0x66, 0x59 }, }, + { biSwampland, { 0x2f, 0xff, 0xda }, }, + { biRiver, { 0x30, 0x30, 0xaf }, }, + { biHell, { 0x7f, 0x00, 0x00 }, }, + { biSky, { 0x00, 0x7f, 0xff }, }, + { biFrozenOcean, { 0xa0, 0xa0, 0xdf }, }, + { biFrozenRiver, { 0xa0, 0xa0, 0xff }, }, + { biIcePlains, { 0xff, 0xff, 0xff }, }, + { biIceMountains, { 0xa0, 0xa0, 0xa0 }, }, + { biMushroomIsland, { 0xff, 0x00, 0xff }, }, + { biMushroomShore, { 0xa0, 0x00, 0xff }, }, + { biBeach, { 0xfa, 0xde, 0x55 }, }, + { biDesertHills, { 0xd2, 0x5f, 0x12 }, }, + { biForestHills, { 0x22, 0x55, 0x1c }, }, + { biTaigaHills, { 0x16, 0x39, 0x33 }, }, + { biExtremeHillsEdge, { 0x7f, 0x8f, 0x7f }, }, + { biJungle, { 0x53, 0x7b, 0x09 }, }, + { biJungleHills, { 0x2c, 0x42, 0x05 }, }, + + { biJungleEdge, { 0x62, 0x8b, 0x17 }, }, + { biDeepOcean, { 0x00, 0x00, 0x30 }, }, + { biStoneBeach, { 0xa2, 0xa2, 0x84 }, }, + { biColdBeach, { 0xfa, 0xf0, 0xc0 }, }, + { biBirchForest, { 0x30, 0x74, 0x44 }, }, + { biBirchForestHills, { 0x1f, 0x5f, 0x32 }, }, + { biRoofedForest, { 0x40, 0x51, 0x1a }, }, + { biColdTaiga, { 0x31, 0x55, 0x4a }, }, + { biColdTaigaHills, { 0x59, 0x7d, 0x72 }, }, + { biMegaTaiga, { 0x59, 0x66, 0x51 }, }, + { biMegaTaigaHills, { 0x59, 0x66, 0x59 }, }, + { biExtremeHillsPlus, { 0x50, 0x70, 0x50 }, }, + { biSavanna, { 0xbd, 0xb2, 0x5f }, }, + { biSavannaPlateau, { 0xa7, 0x9d, 0x64 }, }, + { biMesa, { 0xd9, 0x45, 0x15 }, }, + { biMesaPlateauF, { 0xb0, 0x97, 0x65 }, }, + { biMesaPlateau, { 0xca, 0x8c, 0x65 }, }, + + // M variants: + { biSunflowerPlains, { 0xb5, 0xdb, 0x88 }, }, + { biDesertM, { 0xff, 0xbc, 0x40 }, }, + { biExtremeHillsM, { 0x88, 0x88, 0x88 }, }, + { biFlowerForest, { 0x2d, 0x8e, 0x49 }, }, + { biTaigaM, { 0x33, 0x8e, 0x81 }, }, + { biSwamplandM, { 0x07, 0xf9, 0xb2 }, }, + { biIcePlainsSpikes, { 0xb4, 0xdc, 0xdc }, }, + { biJungleM, { 0x7b, 0xa3, 0x31 }, }, + { biJungleEdgeM, { 0x62, 0x8b, 0x17 }, }, + { biBirchForestM, { 0x58, 0x9c, 0x6c }, }, + { biBirchForestHillsM, { 0x47, 0x87, 0x5a }, }, + { biRoofedForestM, { 0x68, 0x79, 0x42 }, }, + { biColdTaigaM, { 0x24, 0x3f, 0x36 }, }, + { biMegaSpruceTaiga, { 0x45, 0x4f, 0x3e }, }, + { biMegaSpruceTaigaHills, { 0x45, 0x4f, 0x4e }, }, + { biExtremeHillsPlusM, { 0x78, 0x98, 0x78 }, }, + { biSavannaM, { 0xe5, 0xda, 0x87 }, }, + { biSavannaPlateauM, { 0xa7, 0x9d, 0x74 }, }, + { biMesaBryce, { 0xff, 0x6d, 0x3d }, }, + { biMesaPlateauFM, { 0xd8, 0xbf, 0x8d }, }, + { biMesaPlateauM, { 0xf2, 0xb4, 0x8d }, }, +}; + + + + + +void log(const char * a_Fmt, ...) +{ + AString buf; + va_list args; + va_start(args, a_Fmt); + AppendVPrintf(buf, a_Fmt, args); + va_end(args); + std::cout << buf << std::endl << std::flush; +} + + + + + +void outputBitmapFile( + const AString & a_FileName, + unsigned a_ImageSizeX, unsigned a_ImageSizeY, + const int * a_ColorIndices, + unsigned a_IndicesSizeX, unsigned a_IndicesSizeY, + const Color * a_Colors, + size_t a_NumColors +) +{ + std::ofstream f(a_FileName, std::ios::out | std::ios::binary); + if (!f.good()) + { + log("Cannot open file %s for writing. Skipping.", a_FileName.c_str()); + return; + } + f << "P3\r\n" << a_ImageSizeX << " " << a_ImageSizeY << "\r\n255\r\n"; + unsigned oldIndY = 0; + for (unsigned y = 0; y < a_ImageSizeY; y++) + { + unsigned indY = y * a_IndicesSizeY / a_ImageSizeY; + if (oldIndY != indY) + { + // Output a horizontal divider line: + for (unsigned x = 0; x < a_ImageSizeX; x++) + { + f << "128 128 128 "; + } + f << "\r\n"; + oldIndY = indY; + continue; + } + unsigned oldIndX = 0; + for (unsigned x = 0; x < a_ImageSizeX; x++) + { + unsigned indX = x * a_IndicesSizeX / a_ImageSizeX; + if (indX == oldIndX) + { + auto & color = a_Colors[a_ColorIndices[indX + a_IndicesSizeX * indY]]; + f << color[0] << " " << color[1] << " " << color[2] << " "; + } + else + { + // vertical divider line: + f << "128 128 128 "; + } + oldIndX = indX; + } + f << "\r\n"; + } +} + + + + + +/** Initializes biomeColors[] using the biomeColorMap[]. */ +void initializeBiomeColors(void) +{ + // Initialize all colors to red, so that anything unassigned is visible: + for (size_t i = 0; i < ARRAYCOUNT(biomeColors); i++) + { + auto & color = biomeColors[i]; + color[0] = 0; + color[1] = 0xff; + color[2] = 0; + } + + // Initialize per-biome: + for(size_t i = 0; i < ARRAYCOUNT(biomeColorMap); i++) + { + auto & dst = biomeColors[biomeColorMap[i].biome]; + const auto & src = biomeColorMap[i].color; + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + } +} + + + + + +/** Generates a series of images showing the "zoom" effect of the IntGen zoom operation. +Each image is the same size, the integer arrays are scaled to fit the image. */ +void generateZoomLevels(int a_Seed) +{ + log("Generating zoom levels..."); + const unsigned NumImages = 7; ///< Number of images to generate. The more images, the larger they will be + const unsigned maxArrSize = (1 << NumImages) + 1; + for (unsigned i = 1; i <= NumImages; i++) + { + unsigned arrSize = (1 << i) + 1; // Dimensions of the actually generated array + ASSERT(arrSize <= maxArrSize); + int workspace[maxArrSize * maxArrSize]; // Workspace for the generated array + + // Chain the zoom operation as many times as the image number: + std::shared_ptr<cProtIntGen> gen = std::make_shared<cProtIntGenChoice>(a_Seed, static_cast<int>(ARRAYCOUNT(spectrumColors) + 1)); + for (unsigned j = 1; j < i; j++) + { + gen = std::make_shared<cProtIntGenZoom>(a_Seed + static_cast<int>(j), gen); + } + gen->GetInts(0, 0, arrSize, arrSize, workspace); + + // Output to a bitmap file: + AString fnam = Printf("zoomedgrown_%u.pbm", i); + outputBitmapFile(fnam, 257, 257, workspace, arrSize, arrSize, spectrumColors, ARRAYCOUNT(spectrumColors)); + log(" zoom level %u complete", i); + } // for i - Image +} + + + + + +void generateSmoothLevels(int a_Seed) +{ + log("Generating smooth levels..."); + const unsigned NumImages = 7; ///< Number of images to generate. The more images, the larger they will be + const unsigned maxArrSize = 65; ///< Size of the underlying generated array + + // Initialize the underlying generator: + std::shared_ptr<cProtIntGen> underlyingGen = std::make_shared<cProtIntGenChoice>(a_Seed, static_cast<int>(ARRAYCOUNT(spectrumColors) + 1)); + for (int j = 1; j < 4; j++) + { + underlyingGen = std::make_shared<cProtIntGenZoom>(a_Seed + j, underlyingGen); + } + + // Generate smooth levels: + for (unsigned i = 1; i <= NumImages; i++) + { + unsigned arrSize = maxArrSize - 2 * i; // Dimensions of the actually generated array + int workspace[maxArrSize * maxArrSize]; // Workspace for the generated array + + // Chain the zoom operation as many times as the image number: + std::shared_ptr<cProtIntGen> gen = underlyingGen; + for (unsigned j = 1; j < i; j++) + { + gen = std::make_shared<cProtIntGenSmooth>(a_Seed, gen); + } + gen->GetInts(static_cast<int>(i), static_cast<int>(i), arrSize, arrSize, workspace); + + // Output to a bitmap file: + AString fnam = Printf("smoothedgrown_%u.ppm", i); + outputBitmapFile(fnam, 257, 257, workspace, arrSize, arrSize, spectrumColors, ARRAYCOUNT(spectrumColors)); + log(" smooth level %u complete", i); + } // for i - Image +} + + + + + +void generateExamples(int a_Seed) +{ + log("Generating examples"); + + const int maxArrSize = 65; + const int inArrSize = 24; + const int imgSize = 256; + + // Create the inputs: + auto in1 = + std::make_shared<cProtIntGenZoom >(a_Seed + 1, + std::make_shared<cProtIntGenAddIslands >(a_Seed + 2000, 200, + std::make_shared<cProtIntGenSetRandomly >(a_Seed + 9, 300, bgOcean, + std::make_shared<cProtIntGenZoom >(a_Seed + 2, + std::make_shared<cProtIntGenLandOcean >(a_Seed + 1, 30 + ))))); + auto in2 = + std::make_shared<cProtIntGenZoom >(a_Seed + 1, + std::make_shared<cProtIntGenBiomeGroupEdges>(in1 + )); + auto in3 = + std::make_shared<cProtIntGenZoom >(a_Seed + 1, + std::make_shared<cProtIntGenZoom >(a_Seed + 2, + std::make_shared<cProtIntGenBiomes >(a_Seed + 3000, in2 + ))); + auto inAlt = + std::make_shared<cProtIntGenZoom >(a_Seed, + std::make_shared<cProtIntGenLandOcean >(a_Seed, 30 + )); + auto inRiver = std::make_shared<cProtIntGenRiver>(a_Seed, in2); + int workspace[maxArrSize * maxArrSize]; + in1->GetInts(0, 0, inArrSize, inArrSize, workspace); + outputBitmapFile("grownexample_in1.ppm", imgSize, imgSize, workspace, inArrSize, inArrSize, biomeGroupColors, ARRAYCOUNT(biomeGroupColors)); + log(" Created example input 1"); + in2->GetInts(0, 0, inArrSize, inArrSize, workspace); + outputBitmapFile("grownexample_in2.ppm", imgSize, imgSize, workspace, inArrSize, inArrSize, biomeGroupColors, ARRAYCOUNT(biomeGroupColors)); + log(" Created example input 2"); + in3->GetInts(0, 0, inArrSize, inArrSize, workspace); + outputBitmapFile("grownexample_in3.ppm", imgSize, imgSize, workspace, inArrSize, inArrSize, biomeColors, ARRAYCOUNT(biomeColors)); + log(" Created example input 3"); + inAlt->GetInts(0, 0, inArrSize, inArrSize, workspace); + outputBitmapFile("grownexample_in_alt.ppm", imgSize, imgSize, workspace, inArrSize, inArrSize, biomeGroupColors, ARRAYCOUNT(biomeGroupColors)); + log(" Created example input alt"); + inRiver->GetInts(0, 0, inArrSize, inArrSize, workspace); + outputBitmapFile("grownexample_in_river.ppm", imgSize, imgSize, workspace, inArrSize, inArrSize, biomeColors, ARRAYCOUNT(biomeColors)); + log(" Created example input river"); + + // Shortcuts for colormaps used for the outputs: + struct ColorMap + { + const Color * colors; + size_t count; + }; + static const ColorMap cmGroups = { biomeGroupColors, ARRAYCOUNT(biomeGroupColors) }; + static const ColorMap cmBiomes = { biomeColors, ARRAYCOUNT(biomeColors) }; + + // Create the result generators: + struct + { + const char * name; + unsigned size; + int offset; + const ColorMap & colormap; + std::shared_ptr<cProtIntGen> gen; + } + gens[] = + { + {"add_islands", inArrSize, 0, cmGroups, std::make_shared<cProtIntGenAddIslands> (a_Seed, 400, in1)}, + {"alt_biomes", inArrSize, 0, cmBiomes, std::make_shared<cProtIntGenAlternateBiomes>(a_Seed, inAlt, in3)}, + {"beaches", inArrSize - 2, 1, cmBiomes, std::make_shared<cProtIntGenBeaches> (in3)}, + {"biome_edges", inArrSize - 2, 1, cmBiomes, std::make_shared<cProtIntGenBiomeEdges> (a_Seed, in3)}, + {"biomes", inArrSize, 0, cmBiomes, std::make_shared<cProtIntGenBiomes> (a_Seed, in2)}, + {"grp_edges", inArrSize - 2, 0, cmGroups, std::make_shared<cProtIntGenBiomeGroupEdges>(in1)}, + {"m_biomes", inArrSize, 0, cmBiomes, std::make_shared<cProtIntGenMBiomes> (a_Seed, inAlt, in3)}, + {"mix_river", inArrSize, 0, cmBiomes, std::make_shared<cProtIntGenMixRivers> (in3, inRiver)}, + {"river", inArrSize - 2, 1, cmBiomes, inRiver}, + {"set_rnd", inArrSize, 0, cmBiomes, std::make_shared<cProtIntGenSetRandomly> (a_Seed, 500, bgOcean, in3)}, + {"smooth", inArrSize - 2, 1, cmBiomes, std::make_shared<cProtIntGenSmooth> (a_Seed, in3)}, + {"zoom", inArrSize * 2 - 1, 0, cmBiomes, std::make_shared<cProtIntGenZoom> (a_Seed, in3)}, + }; + + // Create the outputs: + for (const auto & gen: gens) + { + gen.gen->GetInts(gen.offset, gen.offset, gen.size, gen.size, workspace); + AString fnam = Printf("grownexample_%s.ppm", gen.name); + outputBitmapFile(fnam, 256, 256, workspace, gen.size, gen.size, gen.colormap.colors, gen.colormap.count); + log(" Created example \"%s\"", gen.name); + } // for gen - gens[] + + log("Examples generated"); +} + + + + + +int main(int argc, char ** argv) +{ + log("GrownBiomeGenVisualiser starting"); + + // Parse the seed from the command line, if present: + int seed; + if (argc > 1) + { + if (!StringToInteger(argv[1], seed)) + { + log("Cannot parse seed from \"%s\", bailing out.", argv[1]); + return 1; + } + } + else + { + // Get a random seed: + std::random_device rd; + seed = static_cast<int>(rd()); + } + + log("Seed = %d", seed); + + initializeBiomeColors(); + + generateZoomLevels(seed); + generateSmoothLevels(seed); + generateExamples(seed); + + log("GrownBiomeGenVisualiser finished"); + return 0; +} + + + + diff --git a/Tools/GrownBiomeGenVisualiser/README.md b/Tools/GrownBiomeGenVisualiser/README.md new file mode 100644 index 000000000..d8f6919d6 --- /dev/null +++ b/Tools/GrownBiomeGenVisualiser/README.md @@ -0,0 +1,4 @@ +GrownBiomeGenVisualiser +---- + +This project aims to provide the visualisation of "Grown" biome generator used in Cuberite. It uses the generator to generate several bitmaps showcasing the generator; these images are then used in the generator documentation ($/docs/Generator.html) diff --git a/docs/Generator.html b/docs/Generator.html index c852d8bf0..89dff3502 100644 --- a/docs/Generator.html +++ b/docs/Generator.html @@ -4,9 +4,7 @@ </head> <body> <h1>Generating terrain in Cuberite</h1> -<p>This article explains the principles behind the terrain generator in Cuberite. It is not strictly -specific to Cuberite, though, it can be viewed as a generic guide to various terrain-generating algorithms, -with specific implementation notes regarding Cuberite.</p> +<p>This article explains the principles behind the terrain generator in Cuberite. It is not strictly specific to Cuberite, though, it can be viewed as a generic guide to various terrain-generating algorithms, with specific implementation notes regarding Cuberite.</p> <p>Contents: <ul> @@ -15,7 +13,15 @@ with specific implementation notes regarding Cuberite.</p> <li><a href="#reversingflow">Reversing the flow</a></li> <li><a href="#composablegen">The ComposableGenerator pipeline</a></li> <li><a href="#coherentnoise">Using coherent noise</a></li> -<li><a href="#biomegen">Generating biomes</a></li> +<li><a href="#biomegen">Generating biomes</a> +<ul> + <li><a href="#biome.voronoi">Voronoi</a></li> + <li><a href="#biome.distortedvoronoi">DistortedVoronoi</a></li> + <li><a href="#biome.multistepmap">MultiStepMap</a></li> + <li><a href="#biome.twolevel">TwoLevel</a></li> + <li><a href="#biome.grown">Grown</a></li> +</ul> +</li> <li><a href="#heightgen">Terrain height</a></li> <li><a href="#compositiongen">Terrain composition</a></li> <li><a href="#finishgen">Finishers</a></li> @@ -28,17 +34,9 @@ with specific implementation notes regarding Cuberite.</p> <hr /> <a name="preface"><h2>Preface: How it's done in real life</h2></a> -<p>The nature has many complicated geological, physical and biological processes working on all scales from -microscopic to planet-wide scale, that have shaped the terrain into what we see today. The tectonic plates -collide, push mountain ranges up and ocean trenches down. Erosion dulls the sharp shapes. Plantlife takes -over to further change the overall look of the world.</p> +<p>The nature has many complicated geological, physical and biological processes working on all scales from microscopic to planet-wide scale, that have shaped the terrain into what we see today. The tectonic plates collide, push mountain ranges up and ocean trenches down. Erosion dulls the sharp shapes. Plantlife takes over to further change the overall look of the world.</p> -<p>Generally speaking, the processes take what's there and change it. Unlike computer generating, which -usually creates a finished terrain from scratch, or maybe with only a few iterations. It would be unfeasible -for software to emulate all the natural processes in enough detail to provide world generation for a game, -mainly because in the nature everything interacts with everything. If a mountain range rises, it changes the -way that the precipitation is carried by the wind to the lands beyond the mountains, thus changing the -erosion rate there and the vegetation type. </p> +<p>Generally speaking, the processes take what's there and change it. Unlike computer generating, which usually creates a finished terrain from scratch, or maybe with only a few iterations. It would be unfeasible for software to emulate all the natural processes in enough detail to provide world generation for a game, mainly because in the nature everything interacts with everything. If a mountain range rises, it changes the way that the precipitation is carried by the wind to the lands beyond the mountains, thus changing the erosion rate there and the vegetation type. </p> <hr /> @@ -46,15 +44,9 @@ erosion rate there and the vegetation type. </p> <a name="expectedprops"><h2>Expected properties</h2></a> <p>For a MineCraft-like game terrain generator we need the generator to have several properties: <ul> -<li>The generator must be able to generate terrain in small chunks. This means it must be possible to -generate each of the chunks separately, without dependencies on the neighboring chunks. Note that this -doesn't mean chunks cannot coordinate together, it means that "a tree in one chunk cannot ask if there's -a building in the neighbor chunk", simply because the neighbor chunk may not be generated yet.</li> -<li>The generated chunk needs to be the same if re-generated. This property is not exactly required, but it -makes available several techniques that wouldn't be possible otherwise.</li> -<li>The generator needs to be reasonably fast. For a server application this means at least some 20 chunks -per second for chunks close to each other, and 5 chunks per second for distant chunks. The reason for this -distinction will be discussed later.</li> +<li>The generator must be able to generate terrain in small chunks. This means it must be possible to generate each of the chunks separately, without dependencies on the neighboring chunks. Note that this doesn't mean chunks cannot coordinate together, it means that "a tree in one chunk cannot ask if there's a building in the neighbor chunk", simply because the neighbor chunk may not be generated yet.</li> +<li>The generated chunk needs to be the same if re-generated. This property is not exactly required, but it makes available several techniques that wouldn't be possible otherwise.</li> +<li>The generator needs to be reasonably fast. For a server application this means at least some 20 chunks per second for chunks close to each other, and 5 chunks per second for distant chunks. The reason for this distinction will be discussed later.</li> </ul> </p> @@ -62,28 +54,15 @@ distinction will be discussed later.</li> <hr /> <a name="reversingflow"><h2>Reversing the flow</h2></a> -<p>As already mentioned, the nature works basically by generating raw terrain composition, then "applying" -erosion, vegetation and finally this leads to biomes being formed. Let's now try a somewhat inverse -approach: First generate biomes, then fit them with appropriate terrain, and finally cover in vegetation -and all the other stuff.</p> +<p>As already mentioned, the nature works basically by generating raw terrain composition, then "applying" erosion, vegetation and finally this leads to biomes being formed. Let's now try a somewhat inverse approach: First generate biomes, then fit them with appropriate terrain, and finally cover in vegetation and all the other stuff.</p> -<p>Splitting the parts like this suddenly makes it possible to create a generator with the required -properties. We can generate a reasonable biome map chunk-wise, independently of all the other data. Once we -have the biomes, we can compose the terrain for the chunk by using the biome data for the chunk, and -possibly even for neighboring chunks. Note that we're not breaking the first property, the biomes can be -generated separately so a neighboring chunk's biome map can be generated without the need for the entire -neighboring chunk to be present. Similarly, once we have the terrain composition for a chunk, we can -generate all the vegetation and structures in it, and those can again use the terrain composition in -neighboring chunks.</p> +<p>Splitting the parts like this suddenly makes it possible to create a generator with the required properties. We can generate a reasonable biome map chunk-wise, independently of all the other data. Once we have the biomes, we can compose the terrain for the chunk by using the biome data for the chunk, and possibly even for neighboring chunks. Note that we're not breaking the first property, the biomes can be generated separately so a neighboring chunk's biome map can be generated without the need for the entire neighboring chunk to be present. Similarly, once we have the terrain composition for a chunk, we can generate all the vegetation and structures in it, and those can again use the terrain composition in neighboring chunks.</p> <hr /> <a name="composablegen"><h2>The ComposableGenerator pipeline</h2></a> -<p>This leads us directly to the main pipeline that is used for generating terrain in Cuberite. For -technical reasons, the terrain composition step is further subdivided into Height generation and Composition -generation, and the structures are really called Finishers. For each chunk the generator generates, in this -sequence: +<p>This leads us directly to the main pipeline that is used for generating terrain in Cuberite. For technical reasons, the terrain composition step is further subdivided into Height generation and Composition generation, and the structures are really called Finishers. For each chunk the generator generates, in this sequence: <ul> <li>Biomes</li> <li>Terrain height</li> @@ -96,144 +75,91 @@ sequence: <img src="img/terrainheight.jpg" /> <img src="img/terraincomposition.jpg" /> <img src="img/finishers.jpg" /> -<p>The beautiful thing about this is that the individual components can be changed independently. You can -have 5 biome generators and 3 height generators and you can let the users mix'n'match. -</p> +<p>The beautiful thing about this is that the individual components can be changed independently. You can have 5 biome generators and 3 height generators and you can let the users mix'n'match.</p> +<p>This pipeline had been used in Cuberite for about a year, before we realized that is has a flaw: There is no way for it to generate overhangs. We tried to implement a Finisher that would actually carve overhangs into the terrain; this approach has several problems, most severe one of those is that tree and village generation becomes unbelievably difficult - those finishers need to know the basic terrain composition of the neighboring chunks in order to generate, and the composition would be different after the overhangs are carved. So we need to come up with a better way, something that directly generates the overhangs at latest by the Terrain composition stage.</p> +<p>Luckily we have just the thing. Instead of generating a 2D heightmap, we generate a 3D "density map" - we decide about each block in the chunk being generated, whether it is a solid block or an air block. The following pictures try to illustrate this in one less dimension - the heightmap is a 1D function and the density map is a 2D function:</p> +<img src="img/heightmap.jpg" /> +<img src="img/densitymap.jpg" /> + +<p>This way we can have generators that produce overhangs and yet allow finishers that need the entire composition of the neighboring chunks. However, we pay the price for this in performance, because a 3D noise for the density map needs order of magnitude more CPU cycles than 2D noise for heightmap. Also the RAM usage is increased because instead of storing 16 * 16 height values we need to store 16 * 256 * 16 density values. </p> <hr /> <a name="coherentnoise"><h2>Using coherent noise for the generation</h2></a> -<p>For a great tutorial on coherent noise, see the <a href="http://libnoise.sourceforge.net/">LibNoise -documentation</a>.</p> +<p>For a great tutorial on coherent noise, see the <a href="http://libnoise.sourceforge.net/">LibNoise documentation</a>.</p> <p>Coherent noise is a type of noise that has three important properties that we can use to our advantage: <ul> -<li>The noise is smooth</li> -<li>The noise is algorithmically generated, which means that the same data is generated when the same -parameters are given to the noise functions.</li> +<li>The noise is smooth - small change in the input coord produces only a small change in the output value</li> +<li>The noise is algorithmically generated, which means that the same data is generated when the same inputs are given to the noise functions.</li> <li>The noise can be seamlessly extended in any direction</li> </ul></p> -<p>We'll be mostly using Perlin noise in this article. It is the easiest one to visualise and use and is one -of the most useful kinds of coherent noises. Here's an example of a Perlin noise generated in 2 dimensions:</p> +<p>We'll be mostly using Perlin noise in this article. It is the easiest one to visualise and use and is one of the most useful kinds of coherent noises. Here's an example of a Perlin noise generated in 2 dimensions:</p> <img src="img/perlin.jpg" /> <p>It comes only naturally that such a 2D noise can be used as a terrain height map directly:</p> <img src="img/perlinheightmap.jpg" /> -<p>However, this is not the only use for this noise, and 2 dimensions is not the limit - this noise can be -generated for any number of dimensions.</p> +<p>However, this is not the only use for this noise, and 2 dimensions is not the limit - this noise can be generated for any number of dimensions.</p> <hr /> <a name="biomegen"><h2>Generating biomes</h2></a> -<p>The easiest way to generate biomes is to not generate them at all - simply assign a single constant biome -to everywhere. And indeed there are times when this kind of "generator" is useful - for the MineCraft's Flat -world type, or for testing purposes, or for tematic maps. In Cuberite, this is exactly what the Constant -biome generator does.</p> - -<p>Of course, there are more interesting test scenarios for which multiple biomes must be generated as easy -as possible. For these special needs, there's a CheckerBoard biome generator. As the name suggests, it -generates a grid of alternating biomes.</p> - -<h3>Voronoi diagram</h3> -<p>Those two generators were more of a technicality, we need to make something more interesting if we're -going for a natural look. The Voronoi generator is the first step towards such a change. Recall that a -<a href="https://en.wikipedia.org/wiki/Voronoi_diagram">Voronoi diagram</a> is a construct that creates a -set of areas where each point in an area is closer to the appropriate seed of the area than the seeds of any -other area:</p> +<p>The easiest way to generate biomes is to not generate them at all - simply assign a single constant biome to everywhere. And indeed there are times when this kind of "generator" is useful - for the MineCraft's Flat world type, or for testing purposes, or for tematic maps. In Cuberite, this is exactly what the Constant biome generator does.</p> + +<p>Of course, there are more interesting test scenarios for which multiple biomes must be generated as easy as possible. For these special needs, there's a CheckerBoard biome generator. As the name suggests, it generates a grid of alternating biomes.</p> + +<a name="biome.voronoi"><h3>Voronoi diagram</h3></a> +<p>Those two generators were more of a technicality, we need to make something more interesting if we're going for a natural look. The Voronoi generator is the first step towards such a change. Recall that a <a href="https://en.wikipedia.org/wiki/Voronoi_diagram">Voronoi diagram</a> is a construct that creates a set of areas where each point in an area is closer to the appropriate seed of the area than the seeds of any other area:</p> <img src="img/voronoi.png" /> -<p>To generate biomes using this approach, you select random "seeds", assign a biome to each one, and then -for each "column" of the world you find the seed that is the nearest to that column, and use that seed's -biome.</p> +<p>To generate biomes using this approach, you select random "seeds", assign a biome to each one, and then for each "column" of the world you find the seed that is the nearest to that column, and use that seed's biome.</p> -<p>The overall shape of a Voronoi diagram is governed by the placement of the seeds. In extreme cases, a -seed could affect the entire diagram, which is what we don't want - we need our locality, so that we can -generate a chunk's worth of biome data. We also don't want the too much irregular diagrams that are produced -when the seeds are in small clusters. We need our seeds to come in random, yet somewhat uniform fashion.</p> +<p>The overall shape of a Voronoi diagram is governed by the placement of the seeds. In extreme cases, a seed could affect the entire diagram, which is what we don't want - we need our locality, so that we can generate a chunk's worth of biome data. We also don't want the too much irregular diagrams that are produced when the seeds are in small clusters. We need our seeds to come in random, yet somewhat uniform fashion.</p> -<p>Luckily, we have just the tool: Grid with jitter. Originally used in antialiasing techniques, they can be -successfully applied as a source of the seeds for a Voronoi diagram. Simply take a regular 2D grid of seeds -with the grid distance being N, and move each seed along the X and Y axis by a random distance, usually in -the range [-N / 2, +N / 2]:</p> +<p>Luckily, we have just the tool: Grid with jitter. Originally used in antialiasing techniques, they can be successfully applied as a source of the seeds for a Voronoi diagram. Simply take a regular 2D grid of seeds with the grid distance being N, and move each seed along the X and Y axis by a random distance, usually in the range [-N / 2, +N / 2]:</p> <img src="img/jittergrid.jpg" /> -<p>Such a grid is the ideal seed source for a Voronoi biome generator, because not -only are the Voronoi cells "reasonable", but the seed placement's effect on the diagram is localized - each -pixel in the diagram depends on at most 4 x 4 seeds around it. In the following picture, the seed for the -requested point (blue) must be within the indicated circle. Even the second-nearest seed, which we will need -later, is inside that circle.</p> +<p>Such a grid is the ideal seed source for a Voronoi biome generator, because not only are the Voronoi cells "reasonable", but the seed placement's effect on the diagram is localized - each pixel in the diagram depends on at most 4 x 4 seeds around it. In the following picture, the seed for the requested point (blue) must be within the indicated circle. Even the second-nearest seed, which we will need later, is inside that circle.</p> <img src="img/jittergridlocality.jpg" /> -<p>Calculating the jitter for each cell can be done easily by using a 2D Perlin noise for each coord. We -calculate the noise's value at [X, Z], which gives us a number in the range [-1; 1]. We then multiply the -number by N / 2, this gives us the required range of [-N / 2, +N / 2]. Adding this number to the X coord -gives us the seed's X position. We use another Perlin noise and the same calculation for the Z coord of the -seed.</p> +<p>Calculating the jitter for each cell can be done easily by using a 2D Perlin noise for each coord. We calculate the noise's value at [X, Z], which gives us a number in the range [-1; 1]. We then multiply the number by N / 2, this gives us the required range of [-N / 2, +N / 2]. Adding this number to the X coord gives us the seed's X position. We use another Perlin noise and the same calculation for the Z coord of the seed.</p> -<p>Here's an example of a biome map generated using the Voronoi + jitter grid, as implemented by the Voronoi -biome generator in Cuberite:</p> +<p>Here's an example of a biome map generated using the Voronoi + jitter grid, as implemented by the Voronoi biome generator in Cuberite:</p> <img src="img/voronoijitterbiomes.png" /> -<h3>Distorted Voronoi</h3> -<p>The biomes are starting to look interesting, but now they have straight-line borders, which looks rather -weird and the players will most likely notice very soon. We need to somehow distort the borders to make them -look more natural. By far the easiest way to achieve that is to use a little trick: When the generator is -asked for the biome at column [X, Z], instead of calculating the Voronoi biome for column [X, Z], we first -calculate a random offset for each coord, and add it to the coordinates. So the generator actually responds -with the biome for [X + rndX, Z + rndZ].</p> - -<p>In order to keep the property that generating for the second time gives us the same result, we need the -"random offset" to be replicatable - same output for the same input. This is where we use yet another Perlin -noise - just like with the jitter for the Voronoi grid, we add a value from a separate noise to each -coordinate before sending the coordinates down to the Voronoi generator:</p> +<a name="biome.distortedvoronoi"><h3>Distorted Voronoi</h3></a> +<p>The biomes are starting to look interesting, but now they have straight-line borders, which looks rather weird and the players will most likely notice very soon. We need to somehow distort the borders to make them look more natural. By far the easiest way to achieve that is to use a little trick: When the generator is asked for the biome at column [X, Z], instead of calculating the Voronoi biome for column [X, Z], we first calculate a random offset for each coord, and add it to the coordinates. So the generator actually responds with the biome for [X + rndX, Z + rndZ].</p> + +<p>In order to keep the property that generating for the second time gives us the same result, we need the "random offset" to be replicatable - same output for the same input. This is where we use yet another Perlin noise - just like with the jitter for the Voronoi grid, we add a value from a separate noise to each coordinate before sending the coordinates down to the Voronoi generator:</p> <code> DistortedVoronoiBiome(X, Z) := VoronoiBiome(X + PerlinX(X, Z), Z + PerlinZ(X, Z)) </code> -<p>The following image shows the effects of the change, as generated by Cuberite's DistortedVoronoi biome -generator. It is actually using the very same Voronoi map as the previous image, the only change has been -the addition of the distortion:</p> +<p>The following image shows the effects of the change, as generated by Cuberite's DistortedVoronoi biome generator. It is actually using the very same Voronoi map as the previous image, the only change has been the addition of the distortion:</p> <img src="img/distortedvoronoibiomes.png" /> -<p>As you can see, this already looks reasonable enough, it could be considered natural biomes, if it -weren't for several drawbacks: +<p>As you can see, this already looks reasonable enough, it could be considered natural biomes, if it weren't for several drawbacks: <ul> <li>There's no way to limit the neighbors. A desert biome can neighbor a tundra biome. </li> -<li>All the biomes are considered equal. There's no way to make oceans larger. A mushroom biome is -generated right next to other land biomes.</li> +<li>All the biomes are considered equal. There's no way to make oceans larger. A mushroom biome is generated right next to other land biomes.</li> </ul></p> -<h3>Adding relativity</h3> -<p>Our next goal is to remove the first defect of the distorted Voronoi generator: unrelated biomes -generating next to each other. It is highly unlikely to find a jungle biome next to a desert biome, so we -want to have as few of those borders as possible. We could further improve on the selection of -biome-to-seed in the Voronoi generator. Or we can try a completely different idea altogether.</p> - -<p>Recall how we talked about the nature, where the biomes are formed by the specific conditions of a place. -What if we could make a similar dependency, but without the terrain? It turns out this is possible rather -easily - instead of depending on the terrain, we choose two completely artificial measures. Let's call them -Temperature and Humidity. If we knew the temperature of the place, we know what set of biomes are possible -for such temperatures - we won't place deserts in the cold and tundra in the hot anymore. Similarly, the -humidity will help us sort out the desert vs jungle issue. But how do we get a temperature and humidity? -Once again, the Perlin noise comes to the rescue. We can use a simple 2D Perlin noise as the temperature -map, and another one as the humidity map.</p> - -<p>What we need next is a decision of what biome to generate in certain temperature and humidity -combinations. The fastest way for a computer is to have a 2D array, where the temperature is one dimension -and humidity the other, and the values in the array specify the biome to generate:</p> +<a name="biome.multistepmap"><h3>MultiStepMap</h3></a> +<p>Our next goal is to remove the first defect of the distorted Voronoi generator: unrelated biomes generating next to each other. You are highly unlikely to find a jungle biome next to a desert biome in the real world, so we want to have as few of those borders as possible in our generator, too. We could further improve on the selection of biome-to-seed in the Voronoi generator. Or we can try a completely different idea altogether.</p> + +<p>Recall how we talked about the nature, where the biomes are formed by the specific conditions of a place. What if we could make a similar dependency, but without the terrain? It turns out this is possible rather easily - instead of depending on the terrain, we choose two completely artificial measures. Let's call them Temperature and Humidity. If we knew the temperature of the place, we know what set of biomes are possible for such temperatures - we won't place deserts in the cold and tundra in the hot anymore. Similarly, the humidity will help us sort out the desert vs jungle issue. But how do we get a temperature and humidity? Once again, the Perlin noise comes to the rescue. We can use a simple 2D Perlin noise as the temperature map, and another one as the humidity map.</p> + +<p>What we need next is a decision of what biome to generate in certain temperature and humidity combinations. The fastest way for a computer is to have a 2D array, where the temperature is one dimension and humidity the other, and the values in the array specify the biome to generate:</p> <img src="img/temperaturehumiditydecisionsimple.jpg" /> -<p>We can even "misuse" the above diagram to include the hill variants of the biomes and have those hills -neighbor each other properly, simply by declaring some of the decision diagram's parts as hills:</p> +<p>We can even "misuse" the above diagram to include the hill variants of the biomes and have those hills neighbor each other properly, simply by declaring some of the decision diagram's parts as hills:</p> <img src="img/temperaturehumiditydecisionhills.jpg" /> -<p>The problem with this approach is that there are biomes that should not depend on temperature or -humidity, they generate across all of their values. Biomes like Oceans, Rivers and Mushroom. We could -either add them somewhere into the decision diagram, or we can make the generator use a multi-step decision: +<p>The problem with this approach is that there are biomes that should not depend on temperature or humidity, they generate across all of their values. Biomes like Oceans, Rivers and Mushroom. We could either add them somewhere into the decision diagram, or we can make the generator use a multi-step decision: <ul> <li>Decide whether the point is in the ocean, land or mushroom</li> <li>If it's land, decide if it's real land or river.</li> @@ -241,68 +167,272 @@ either add them somewhere into the decision diagram, or we can make the generato </ul> </p> -<p>This is the approach implemented in Cuberite's MultiStepMap biome generator. It generates biome maps like -this:</p> +<p>This is the approach implemented in Cuberite's MultiStepMap biome generator. It generates biome maps like this:</p> <img src="img/multistepmapbiomes.png" /> -<p>To decide whether the point is in the ocean, land or mushroom, the generator first chooses seeds in a grid -that will be later fed to a DistortedVoronoi algorithm, the seeds get the "ocean" and "land" values. Then it -considers all the "ocean" seeds that are surrounded by 8 other "ocean" seeds and turns a random few of them -into "mushroom". This special seed processing makes the mushroom biomes mostly surrounded by ocean. The -following image shows an example seeds grid that the generator might consider, only the two framed cells are -allowed to change into mushroom. L = land, O = ocean:</p> +<p>To decide whether the point is in the ocean, land or mushroom, the generator first chooses seeds in a grid that will be later fed to a DistortedVoronoi algorithm, the seeds get the "ocean" and "land" values. Then it considers all the "ocean" seeds that are surrounded by 8 other "ocean" seeds and turns a random few of them into "mushroom". This special seed processing makes the mushroom biomes mostly surrounded by ocean. The following image shows an example seeds grid that the generator might consider, only the two framed cells are allowed to change into mushroom. L = land, O = ocean:</p> <img src="img/multistepmapgrid.jpg" /> -<p>Next, the generator calculates the DistortedVoronoi for the seeds. For the areas that are calculated as -mushroom, the distance to the nearest-seed is used to further shrink the mushroom biome and then to -distinguish between mushroom and mushroom-shore (image depicts a Voronoi cell for illustration purposes, it -works similarly with DistortedVoronoi). O = ocean, M = mushroom, MS = mushroom shore:</p> +<p>Next, the generator calculates the DistortedVoronoi for the seeds. For the areas that are calculated as mushroom, the distance to the nearest-seed is used to further shrink the mushroom biome and then to distinguish between mushroom and mushroom-shore (image depicts a Voronoi cell for illustration purposes, it works similarly with DistortedVoronoi). O = ocean, M = mushroom, MS = mushroom shore:</p> <img src="img/multistepmapdistance.jpg" /> <a name="perlinrivers"> -<p>The rivers are added only to the areas that have been previously marked as land. A simple 2D Perlin noise -is used as the base, where its value is between 0 and a configured threshold value, a river is created. This -creates the rivers in a closed-loop-like shapes, occasionally splitting two branches off:</p> +<p>The rivers are added only to the areas that have been previously marked as land. A simple 2D Perlin noise is used as the base, where its value is between 0 and a configured threshold value, a river is created. This creates the rivers in a closed-loop-like shapes, occasionally splitting two branches off:</p> <img src="img/perlinrivers1.jpg" /> <img src="img/perlinrivers2.jpg" /> <img src="img/perlinrivers3.jpg" /> </a> -<p>For the leftover land biomes, the two Perlin noises, representing temperature and humidity, are used to -generate the biomes, as described earlier. Additionally, the temperature map is used to turn the Ocean biome -into FrozenOcean, and the River biome into FrozenRiver, wherever the temperature drops below a threshold.</p> - -<h3>Two-level Voronoi</h3> -<p>The 1.7 MineCraft update brought a completely new terrain generation, which has sparked renewed interest -in the biome generation. A new, potentially simpler way of generating biomes was found, the two-level -DistortedVoronoi generator.</p> - -<p>The main idea behind it all is that we create large areas of similar biomes. There are several groups of -related biomes that can be generated near each other: Desert biomes, Ice biomes, Forest biomes, Mesa biomes. -Technically, the Ocean biomes were added as yet another group, so that the oceans will generate in -approximately the size of the larger areas, too.</p> - -<p>For each column a DistortedVoronoi is used to select, which large area to use. This in turn results in -the list of biomes from which to choose. Another DistortedVoronoi, this time with a smaller grid size, is -used to select one biome out of that list. Additionally, the smaller DistortedVoronoi calculates not only -the nearest seed's distance, but also the distance to the second-nearest seed; the ratio between these two -is used as an indicator whether the column is in the "inside" or on the "outskirt" of the smaller Voronoi -cell. This allows us to give certain biomes an "edge" biome - the Mushroom biome has a MushroomShore edge, -the ExtremeHills biome have an ExtremeHillsEdge biome on the edge, etc.</p> - -<p>The images below illustrate the process with regular Voronoi diagrams, for clarity purposes. The real -generator uses distortion before querying the small areas.</p> +<p>For the leftover land biomes, the two Perlin noises, representing temperature and humidity, are used to generate the biomes, as described earlier. Additionally, the temperature map is used to turn the Ocean biome into FrozenOcean, and the River biome into FrozenRiver, wherever the temperature drops below a threshold.</p> + +<a name="biome.twolevel"><h3>TwoLevel</h3></a> +<p>The 1.7 MineCraft update brought a completely new terrain generation, which has sparked renewed interest in the biome generation. A new, potentially simpler way of generating biomes was found, the two-level DistortedVoronoi generator.</p> + +<p>The main idea behind it all is that we create large areas of similar biomes. There are several groups of related biomes that can be generated near each other: Desert biomes, Ice biomes, Forest biomes, Mesa biomes. Technically, the Ocean biomes were added as yet another group, so that the oceans will generate in approximately the size of the larger areas, too.</p> + +<p>For each column a DistortedVoronoi is used to select, which large area to use. This in turn results in the list of biomes from which to choose. Another DistortedVoronoi, this time with a smaller grid size, is used to select one biome out of that list. Additionally, the smaller DistortedVoronoi calculates not only the nearest seed's distance, but also the distance to the second-nearest seed; the ratio between these two is used as an indicator whether the column is in the "inside" or on the "outskirt" of the smaller Voronoi cell. This allows us to give certain biomes an "edge" biome - the Mushroom biome has a MushroomShore edge, the ExtremeHills biome have an ExtremeHillsEdge biome on the edge, etc.</p> + +<p>The images below illustrate the process with regular Voronoi diagrams, for clarity purposes. The real generator uses distortion before querying the small areas.</p> <img src="img/twolevellargeareas.jpg" /><br /> <img src="img/twolevelsmallgrid.jpg" /><br /> <img src="img/twolevelsmallareas.jpg" /><br /> -<p>The following image shows an example output of a TwoLevel biome generator in Cuberite:</p> +<p>The following image shows an example output of a TwoLevel biome generator in Cuberite. Note how the mushroom biomes (violet) have mushroom shores (pink) on their edges.</p> <img src="img/twolevelbiomes.png" /> <p>Note that rivers are currently not implemented in this generator in Cuberite, but they could be added using the same approach as in MultiStepMap - by using a thresholded 2D Perlin noise.</p> + +<a name="biome.grown"><h3>Grown biomes</h3></a> +<p>This generator uses a completely new approach to biome generation. Internally, it uses 2D arrays of integers of varying sizes, and defines a few operations on those arrays. At various points in the generator's pipeline, the integers are interpreted as having a different meaning. At the first stage, they diffentiate between ocean and land. Later on they are interpreted as biome groups - ocean biomes, dry biomes, temperate biomes, mountain biomes or ice biomes. In the final stages they represent individual biomes, each number in the array representing the biome of a single-block-wide column in the world. Still, most of the operations are agnostic of this interpretation, they only "see numbers".</p> +<p>At the core of the generator is the <b>"Zoom"</b> operation, that enlarges the array almost twice in size (N -> 2*N - 1). For each 2x2 neighboring numbers in the original array it produces a 3x3 array, where the corner values inherit from their corner counterparts of the original array, and the values in the middle get chosen randomly from their appropriate neighbors:</p> +<table><tr> +<td><table border="1" cellspacing="0" cellpadding="2"><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table></td> +<td>--- zooom --></td> +<td><table border="1" cellspacing="0" cellpadding="2"> + <tr><td align="center">a</td><td align="center">a or b</td><td align="center">b</td></tr> + <tr><td align="center">a or c</td><td align="center">a or b or c or d</td><td align="center">b or d</td></tr> + <tr><td align="center">c</td><td align="center">c or d</td><td align="center">d</td></tr> +</table></td> +</tr></table> + +<style> + .h1 { background-color: #cff; } + .hh { background-color: #fcf; } + .hv { background-color: #ffc; } + .ho { background-color: #fff; } + .hr { background-color: #ddd; } + .bgr { width: 1em; height: 1em; background-color: red; } + .bgg { width: 1em; height: 1em; background-color: green; } + .bgb { width: 1em; height: 1em; background-color: blue; } + .bgc { width: 1em; height: 1em; background-color: cyan; } + .bgy { width: 1em; height: 1em; background-color: yellow; } +</style> + +<p>This scheme is repeated for larger arrays accordingly, for example (highlighted values are the ones directly copied from the source array):</p> +<table> +<tr> +<td><table border="1" cellspacing="0" cellpadding="2"> + <tr><td>0</td><td>0</td><td>1</td><td>1</td></tr> + <tr><td>0</td><td>1</td><td>0</td><td>1</td></tr> + <tr><td>1</td><td>1</td><td>0</td><td>0</td></tr> + <tr><td>0</td><td>1</td><td>1</td><td>0</td></tr> +</table></td> +<td>--- Zooom --></td> +<td><table border="1" cellspacing="0" cellpadding="2"> + <tr><td class="h1">0</td><td>0</td><td class="h1">0</td><td>1</td><td class="h1">1</td><td>1</td><td class="h1">1</td></tr> + <tr><td> 0</td><td>1</td><td> 0</td><td>1</td><td> 0</td><td>1</td><td> 1</td></tr> + <tr><td class="h1">0</td><td>0</td><td class="h1">1</td><td>1</td><td class="h1">0</td><td>0</td><td class="h1">1</td></tr> + <tr><td> 0</td><td>1</td><td> 1</td><td>1</td><td> 0</td><td>0</td><td> 1</td></tr> + <tr><td class="h1">1</td><td>1</td><td class="h1">1</td><td>0</td><td class="h1">0</td><td>0</td><td class="h1">0</td></tr> + <tr><td> 1</td><td>1</td><td> 1</td><td>1</td><td> 0</td><td>0</td><td> 0</td></tr> + <tr><td class="h1">0</td><td>0</td><td class="h1">1</td><td>1</td><td class="h1">1</td><td>1</td><td class="h1">0</td></tr> +</table></td> +</tr></table> +<p>The basic idea is that we're having a low-resolution image of the "land" and we're zooming in; in each zoom iteration we're adding random details - the randomly chosen numbers. This becomes apparent when we enlarge each image to the same dimensions:</p> +<img src="img/zoomedgrown_1.png"/> +<img src="img/zoomedgrown_2.png"/> +<img src="img/zoomedgrown_3.png"/> +<img src="img/zoomedgrown_4.png"/> +<img src="img/zoomedgrown_5.png"/> +<img src="img/zoomedgrown_6.png"/> +<img src="img/zoomedgrown_7.png"/> +<p>As you can see, the areas take a nice random-looking shape, but the edges are a little bit too noisy. There's where the second most important operation comes in: the <b>"Smooth"</b> slightly reduces the array size (N -> N - 2), losing the values on the edge of the array, and for the internal numbers it considers their 4 neighbors. If both the horizontal neighbors are the same and the vertical neighbors are the same (but not necessarily the same as the horizontal ones), the value is set randomly to either the horizontal or the vertical neihbors' value. If both the horizontal neighbors are the same, the value is set to the value of those neighbors, otherwise if both the vertical neighbors are the same, the value is set to the value of those neighbors. In all the rest cases, the value is kept at its original.</p> +<table><tr> +<td><table border="1" cellspacing="0" cellpadding="2"> + <tr><td align="center"></td><td align="center">a</td><td align="center"></td></tr> + <tr><td align="center">b</td><td align="center">X</td><td align="center">c</td></tr> + <tr><td align="center"></td><td align="center">d</td><td align="center"></td></tr> +</table></td> +<td><table border="1" cellspacing="0" cellpadding="2"> + <tr><th align="center">Condition</th><th>X becomes</th></tr> + <tr><td align="center">(a == d) && (b == c)</td><td align="center">a or b (random)</td></tr> + <tr><td align="center">a == d</td><td align="center">a</td></tr> + <tr><td align="center">b == c</td><td align="center">b</td></tr> + <tr><td align="center">otherwise</td><td align="center">X (unchanged)</td></tr> +</table></td> +</tr></table> +<p>The following examples show how the decisions work:</p> +<table border="1" cellspacing="0" cellpadding="4px"> +<tr><th>Input</th><th>Output</th><th>Notes</th></tr> +<tr> +<td><table> + <tr><td/> <td class="bgb"> </td><td/></tr> + <tr><td class="bgr"> </td><td class="bgg"> </td><td class="bgy"> </td></tr> + <tr><td/> <td class="bgr"> </td><td/></tr> +</table></td> +<td align="center"><table><tr><td class="bgg"> </td></tr></table></td> +<td>Neither pair of neighbors are the same, so the value is left intact</td> +</tr> +<tr> +<td><table> + <tr><td/> <td class="bgb"> </td><td/></tr> + <tr><td class="bgr"> </td><td class="bgg"> </td><td class="bgr"> </td></tr> + <tr><td/> <td class="bgc"> </td><td/></tr> +</table></td> +<td align="center"><table><tr><td class="bgr"> </td></tr></table></td> +<td>Horizontal neighbors are the same, so the value is copied from them</td> +</tr> +<tr> +<td><table> + <tr><td/> <td class="bgb"> </td><td/></tr> + <tr><td class="bgr"> </td><td class="bgg"> </td><td class="bgy"> </td></tr> + <tr><td/> <td class="bgb"> </td><td/></tr> +</table></td> +<td align="center"><table><tr><td class="bgb"> </td></tr></table></td> +<td>Vertical neighbors are the same, so the value is copied from them</td> +</tr> +<tr> +<td><table> + <tr><td/> <td class="bgb"> </td><td/></tr> + <tr><td class="bgr"> </td><td class="bgg"> </td><td class="bgr"> </td></tr> + <tr><td/> <td class="bgb"> </td><td/></tr> +</table></td> +<td align="center"><table><tr><td class="bgr"> </td></tr></table></td> +<td>Each pair of neighbors are the same, so the value is chosen from one of the pairs randomly</td> +</tr> +</table> +<p>This decision is repeated for each value in the array, for example:</p> +<table> +<tr> +<td><table border="1" cellspacing="0" cellpadding="2"> + <tr><td>0</td><td> 0</td><td> 0</td><td> 1</td><td> 1</td><td> 1</td><td>1</td></tr> + <tr><td>0</td><td class="h1">1</td><td class="h1">0</td><td class="h1">1</td><td class="h1">0</td><td class="h1">1</td><td>1</td></tr> + <tr><td>0</td><td class="h1">0</td><td class="h1">1</td><td class="h1">1</td><td class="h1">0</td><td class="h1">0</td><td>1</td></tr> + <tr><td>0</td><td class="h1">1</td><td class="h1">1</td><td class="h1">1</td><td class="h1">0</td><td class="h1">0</td><td>1</td></tr> + <tr><td>1</td><td class="h1">1</td><td class="h1">1</td><td class="h1">0</td><td class="h1">0</td><td class="h1">0</td><td>0</td></tr> + <tr><td>1</td><td class="h1">1</td><td class="h1">1</td><td class="h1">1</td><td class="h1">0</td><td class="h1">0</td><td>0</td></tr> + <tr><td>0</td><td> 0</td><td> 1</td><td> 1</td><td> 1</td><td> 1</td><td>0</td></tr> +</table></td> +<td>--- Smooth --></td> +<td><table border="1" cellspacing="0" cellpadding="2"> + <tr><td class="hr">0</td><td class="hh">1</td><td class="hr">0</td><td class="hh">1</td><td class="ho">1</td></tr> + <tr><td class="hv">1</td><td class="ho">1</td><td class="hv">1</td><td class="hv">0</td><td class="ho">0</td></tr> + <tr><td class="ho">1</td><td class="hh">1</td><td class="ho">1</td><td class="hv">0</td><td class="hv">0</td></tr> + <tr><td class="hh">1</td><td class="hv">1</td><td class="hv">1</td><td class="hh">0</td><td class="hh">0</td></tr> + <tr><td class="hh">1</td><td class="hh">1</td><td class="ho">1</td><td class="ho">0</td><td class="hh">0</td></tr> +</table></td> +</tr> +<tr><td>Highlighted area is processed into output</td> +<td/> +<td> +<span class="ho">Original value kept</span><br/> +<span class="hr">Value forced by both horizontal and vertical neighbors, random</span><br/> +<span class="hh">Value forced by horizontal neighbors</span><br/> +<span class="hv">Value forced by vertical neighbors</span> +</td></table> +<p>The following example shows multiple successive Smooth operations performed on the same data set over and over again:</p> +<img src="img/smoothedgrown_1.png"/> +<img src="img/smoothedgrown_2.png"/> +<img src="img/smoothedgrown_3.png"/> +<img src="img/smoothedgrown_4.png"/> +<img src="img/smoothedgrown_5.png"/> +<img src="img/smoothedgrown_6.png"/> +<img src="img/smoothedgrown_7.png"/> +<p>As you can see, the smoothing operation doesn't make much difference after its first pass, so it usually isn't used more than once after each zoom.</p> +<p>One important thing to note is that both the Zoom and Smooth operations only output the numbers already present in the array, they don't create new numbers. This is important because it allows the late stages of the generator to grow indepent biomes next to each other without them "bleeding" into different biomes on their edges.</p> +<p>The Grown generator uses several more supplementary operations, such as "AddIslands", "ReplaceRandomly", "River", "Beaches" and more. There isn't anything too special to those, they perform mostly trivial operations, manipulating the numbers in some way; the main power of the generator lies in the zoom and smooth operations. Perhaps noteworthy is the generation of rivers: it starts with the regular bitmap (only 0 and 1 used), zooms in and smooths for a while and then performs edge detection - a river biome is set in pixels whose neighbors are different, and no change applied when the neighbors are the same. Among other things, this means that there are actually two chains of array operations, and their results are combined together in the "MixRivers" operation.</p> +<p>The following table summarizes the operations, visually:</p> +<table border="1" cellpadding="2" cellspacing="1"> +<tr><th>Operation</th><th>Input 1</th><th>Input 2</th><th>Output</th><th>Notes</th></tr> +<tr> + <td>AddIslands</td> + <td><img src="img/grownexample_in1.png"/></td> + <td>-</td> + <td><img src="img/grownexample_add_islands.png"/></td> + <td>Adds a configurable (by percentage) amount of islands to ocean biome group.</td> +</tr> +<tr> + <td>AlternateBiomes</td> + <td><img src="img/grownexample_in3.png"/></td> + <td><img src="img/grownexample_in_alt.png"/></td> + <td><img src="img/grownexample_alt_biomes.png"/></td> + <td>Mostly copies the first input, but where the second input has non-ocean biome group, turns the first input's biomes into their alternatives.</td> +</tr> +<tr> + <td>Beaches</td> + <td><img src="img/grownexample_in3.png"/></td> + <td>-</td> + <td><img src="img/grownexample_beaches.png"/></td> + <td>Any biome neighboring an ocean is turned into a corresponding beach.</td> +</tr> +<tr> + <td>BiomeEdges</td> + <td><img src="img/grownexample_in3.png"/></td> + <td>-</td> + <td><img src="img/grownexample_biome_edges.png"/></td> + <td>If the neighbors of a biome are incompatible (such as desert vs ice plains, or jungle vs anything etc.), turns the biome into a corresponding neutral biome (plains, jungle-edge etc.)</td> +</tr> +<tr> + <td>Biomes</td> + <td><img src="img/grownexample_in2.png"/></td> + <td>-</td> + <td><img src="img/grownexample_biomes.png"/></td> + <td>Input is interpreted as biome groups, for each point a random biome corresponding to the group is chosen for the output.</td> +</tr> +<tr> + <td>BiomeGroupEdges</td> + <td><img src="img/grownexample_in1.png"/></td> + <td>-</td> + <td><img src="img/grownexample_grp_edges.png"/></td> + <td>Converts biome groups on an edge between two incompatible groups (such as desert and ice) into a neutral one (temperate).</td> +</tr> +<tr> + <td>MBiomes</td> + <td><img src="img/grownexample_in3.png"/></td> + <td><img src="img/grownexample_in_alt.png"/></td> + <td><img src="img/grownexample_m_biomes.png"/></td> + <td>Where the second input is zero, copies the first input's biomes; where the second input is nonzero, converts first input's biomes into their M variants. </td> +</tr> +<tr> + <td>MixRivers</td> + <td><img src="img/grownexample_in3.png"/></td> + <td><img src="img/grownexample_in_river.png"/></td> + <td><img src="img/grownexample_mix_river.png"/></td> + <td>Copies first input's biomes into the output, unless there's a river biome in the second input and a land biome in the first input - then it sets a river biome in the output instead.</td> +</tr> +<tr> + <td>River</td> + <td><img src="img/grownexample_in2.png"/></td> + <td>-</td> + <td><img src="img/grownexample_river.png"/></td> + <td>Somewhat of an edge detector - wherever the input has a different biome neighbors, sets a river biome; otherwise sets an ocean biome.</td> +</tr> +<tr> + <td>SetRandomly</td> + <td><img src="img/grownexample_in3.png"/></td> + <td>-</td> + <td><img src="img/grownexample_set_rnd.png"/></td> + <td>Randomly sets points to a specified biome. The amount of changed points is settable as a percentage.</td> +</tr> +</table> + +<p>Of further note is the existence of two sets of the IntGen classes, representing the individual operations. There are the cProtIntGen class descendants, which are used for prototyping the connections between the operations - it's easy to just chain several operations after each other and they automatically use the correct array dimensions. However, it is possible to further optimize the calculations by moving the array dimensions into template parameters (so that they are, in fact, constant from the code's point of view, and so highly optimizable). This is what the cIntGen class descendants do. Unfortunately, this optimization makes it difficult to change the operation chain - when a new operation is added or removed in the chain, the array sizes for the rest of the chain change and they all have to be updated manually. So the optimal strategy was to use the cProtIntGen classes to find out the best-looking combination of operations, and once the combination was found, to rewrite it using cIntGen classes for performance. +</p> + + <hr /> <a name="heightgen"><h2>Terrain height</h2></a> @@ -508,11 +638,15 @@ spring:</p> <p>Cuberite uses an approximation of the above curves to choose the height at which to generate the spring.</p> -<!-- <h3>Caves</h3> <p>Caves are definitely one of the main things people notice about MineCraft terrain. There are quite a lot -of different algorithms available to generate terrain with caves. ---> +of different algorithms available to generate terrain with caves, each with different results. Cuberite currently implements three finishers that generate caves:</p> +<ul> +<li>MarbleCaves</li> +<li>DualRidgeCaves</li> +<li>WormNestCaves</li> +</ul> +<p></p> <hr /> diff --git a/docs/img/densitymap.jpg b/docs/img/densitymap.jpg Binary files differnew file mode 100644 index 000000000..a7a7b3f36 --- /dev/null +++ b/docs/img/densitymap.jpg diff --git a/docs/img/grownexample_add_islands.png b/docs/img/grownexample_add_islands.png Binary files differnew file mode 100644 index 000000000..f69faaaf1 --- /dev/null +++ b/docs/img/grownexample_add_islands.png diff --git a/docs/img/grownexample_alt_biomes.png b/docs/img/grownexample_alt_biomes.png Binary files differnew file mode 100644 index 000000000..866d774e2 --- /dev/null +++ b/docs/img/grownexample_alt_biomes.png diff --git a/docs/img/grownexample_beaches.png b/docs/img/grownexample_beaches.png Binary files differnew file mode 100644 index 000000000..a84fb0eff --- /dev/null +++ b/docs/img/grownexample_beaches.png diff --git a/docs/img/grownexample_biome_edges.png b/docs/img/grownexample_biome_edges.png Binary files differnew file mode 100644 index 000000000..58de63aef --- /dev/null +++ b/docs/img/grownexample_biome_edges.png diff --git a/docs/img/grownexample_biomes.png b/docs/img/grownexample_biomes.png Binary files differnew file mode 100644 index 000000000..ecd8af29b --- /dev/null +++ b/docs/img/grownexample_biomes.png diff --git a/docs/img/grownexample_grp_edges.png b/docs/img/grownexample_grp_edges.png Binary files differnew file mode 100644 index 000000000..2ac32b9a6 --- /dev/null +++ b/docs/img/grownexample_grp_edges.png diff --git a/docs/img/grownexample_in1.png b/docs/img/grownexample_in1.png Binary files differnew file mode 100644 index 000000000..2238886ab --- /dev/null +++ b/docs/img/grownexample_in1.png diff --git a/docs/img/grownexample_in2.png b/docs/img/grownexample_in2.png Binary files differnew file mode 100644 index 000000000..9ef9f6ae2 --- /dev/null +++ b/docs/img/grownexample_in2.png diff --git a/docs/img/grownexample_in3.png b/docs/img/grownexample_in3.png Binary files differnew file mode 100644 index 000000000..95d6608b5 --- /dev/null +++ b/docs/img/grownexample_in3.png diff --git a/docs/img/grownexample_in_alt.png b/docs/img/grownexample_in_alt.png Binary files differnew file mode 100644 index 000000000..59979ed62 --- /dev/null +++ b/docs/img/grownexample_in_alt.png diff --git a/docs/img/grownexample_in_river.png b/docs/img/grownexample_in_river.png Binary files differnew file mode 100644 index 000000000..58556369d --- /dev/null +++ b/docs/img/grownexample_in_river.png diff --git a/docs/img/grownexample_m_biomes.png b/docs/img/grownexample_m_biomes.png Binary files differnew file mode 100644 index 000000000..c3d7079ae --- /dev/null +++ b/docs/img/grownexample_m_biomes.png diff --git a/docs/img/grownexample_mix_river.png b/docs/img/grownexample_mix_river.png Binary files differnew file mode 100644 index 000000000..81899a8c7 --- /dev/null +++ b/docs/img/grownexample_mix_river.png diff --git a/docs/img/grownexample_river.png b/docs/img/grownexample_river.png Binary files differnew file mode 100644 index 000000000..cb07f44dd --- /dev/null +++ b/docs/img/grownexample_river.png diff --git a/docs/img/grownexample_set_rnd.png b/docs/img/grownexample_set_rnd.png Binary files differnew file mode 100644 index 000000000..5b7a2d254 --- /dev/null +++ b/docs/img/grownexample_set_rnd.png diff --git a/docs/img/grownexample_smooth.png b/docs/img/grownexample_smooth.png Binary files differnew file mode 100644 index 000000000..bfd43f6ef --- /dev/null +++ b/docs/img/grownexample_smooth.png diff --git a/docs/img/grownexample_zoom.png b/docs/img/grownexample_zoom.png Binary files differnew file mode 100644 index 000000000..7afffe50b --- /dev/null +++ b/docs/img/grownexample_zoom.png diff --git a/docs/img/heightmap.jpg b/docs/img/heightmap.jpg Binary files differnew file mode 100644 index 000000000..c7eb5c865 --- /dev/null +++ b/docs/img/heightmap.jpg diff --git a/docs/img/smoothedgrown_1.png b/docs/img/smoothedgrown_1.png Binary files differnew file mode 100644 index 000000000..16e563f96 --- /dev/null +++ b/docs/img/smoothedgrown_1.png diff --git a/docs/img/smoothedgrown_2.png b/docs/img/smoothedgrown_2.png Binary files differnew file mode 100644 index 000000000..2d97cfb4e --- /dev/null +++ b/docs/img/smoothedgrown_2.png diff --git a/docs/img/smoothedgrown_3.png b/docs/img/smoothedgrown_3.png Binary files differnew file mode 100644 index 000000000..2d4d13f49 --- /dev/null +++ b/docs/img/smoothedgrown_3.png diff --git a/docs/img/smoothedgrown_4.png b/docs/img/smoothedgrown_4.png Binary files differnew file mode 100644 index 000000000..d52a34bfe --- /dev/null +++ b/docs/img/smoothedgrown_4.png diff --git a/docs/img/smoothedgrown_5.png b/docs/img/smoothedgrown_5.png Binary files differnew file mode 100644 index 000000000..ae14d9847 --- /dev/null +++ b/docs/img/smoothedgrown_5.png diff --git a/docs/img/smoothedgrown_6.png b/docs/img/smoothedgrown_6.png Binary files differnew file mode 100644 index 000000000..0a7f17595 --- /dev/null +++ b/docs/img/smoothedgrown_6.png diff --git a/docs/img/smoothedgrown_7.png b/docs/img/smoothedgrown_7.png Binary files differnew file mode 100644 index 000000000..4351d6881 --- /dev/null +++ b/docs/img/smoothedgrown_7.png diff --git a/docs/img/zoomedgrown_1.png b/docs/img/zoomedgrown_1.png Binary files differnew file mode 100644 index 000000000..c73326b0e --- /dev/null +++ b/docs/img/zoomedgrown_1.png diff --git a/docs/img/zoomedgrown_2.png b/docs/img/zoomedgrown_2.png Binary files differnew file mode 100644 index 000000000..45fa6427f --- /dev/null +++ b/docs/img/zoomedgrown_2.png diff --git a/docs/img/zoomedgrown_3.png b/docs/img/zoomedgrown_3.png Binary files differnew file mode 100644 index 000000000..3c9d89759 --- /dev/null +++ b/docs/img/zoomedgrown_3.png diff --git a/docs/img/zoomedgrown_4.png b/docs/img/zoomedgrown_4.png Binary files differnew file mode 100644 index 000000000..221a5fc76 --- /dev/null +++ b/docs/img/zoomedgrown_4.png diff --git a/docs/img/zoomedgrown_5.png b/docs/img/zoomedgrown_5.png Binary files differnew file mode 100644 index 000000000..3881532ca --- /dev/null +++ b/docs/img/zoomedgrown_5.png diff --git a/docs/img/zoomedgrown_6.png b/docs/img/zoomedgrown_6.png Binary files differnew file mode 100644 index 000000000..cc03d2150 --- /dev/null +++ b/docs/img/zoomedgrown_6.png diff --git a/docs/img/zoomedgrown_7.png b/docs/img/zoomedgrown_7.png Binary files differnew file mode 100644 index 000000000..7a9f43aac --- /dev/null +++ b/docs/img/zoomedgrown_7.png |