From 71ca41e171749799978f3473a052427c9b9f9c96 Mon Sep 17 00:00:00 2001 From: Cubitect Date: Sat, 20 Nov 2021 15:46:52 +0100 Subject: [PATCH] Readme and fixes 1) Updated readme for 1.18 API. 2) Nether biomes should be 2D and keep the legacy noise in 1.18. 3) Added wrapper for checkForBiomes() with the new generator. --- README.md | 191 +++++++++++++++++++++------------------------------ biome_tree.c | 37 ---------- finders.c | 123 +++++++++++++++++++++++++++------ finders.h | 62 +++++++++++++---- generator.c | 52 ++++++-------- generator.h | 7 +- layers.c | 90 ++++++++++-------------- layers.h | 25 +++---- 8 files changed, 304 insertions(+), 283 deletions(-) diff --git a/README.md b/README.md index 64c10b4..3c3929a 100644 --- a/README.md +++ b/README.md @@ -18,41 +18,42 @@ You should be familiar with the C programming language, also a basic understandi This section is meant to give you a quick starting point with small example programs if you want to use this library to find your own biome dependent features. + ### Biome Generator -Let's create a simple program called `find_jedge.c` which tests seeds for a Junge Edge biome at a predefined location. +Let's create a simple program called `find_biome_at.c` which tests seeds for a Mushroom Fields biome at a predefined location. ```C // check the biome at a block position -#include "finders.h" +#include "generator.h" #include int main() { - // Initialize a stack of biome layers that reflects the biome generation of - // Minecraft 1.17 - LayerStack g; - setupGenerator(&g, MC_1_17); + // Set up a biome generator that reflects the biome generation of + // Minecraft 1.18. + Generator g; + setupGenerator(&g, MC_1_18, 0); - // seeds are internally represented as unsigned 64-bit integers + // Seeds are internally represented as unsigned 64-bit integers. uint64_t seed; - Pos pos = {0,0}; // block position to be checked - for (seed = 0; ; seed++) { - // Go through the layers in the layer stack and initialize the seed - // dependent aspects of the generator. - applySeed(&g, seed); + // Apply the seed to the generator for the Overworld dismension. + applySeed(&g, 0, seed); - // To get the biome at single block position we can use getBiomeAtPos(). - int biomeID = getBiomeAtPos(&g, pos); - if (biomeID == jungle_edge) + // To get the biome at a single block position, we can use getBiomeAt(). + int scale = 1; // scale=1: block coordinates, scale=4: biome coordinates + int x = 0, y = 63, z = 0; + int biomeID = getBiomeAt(&g, scale, x, y, z); + if (biomeID == mushroom_fields) + { + printf("Seed %" PRId64 " has a Mushroom Fields biome at " + "block position (%d, %d).\n", (int64_t) seed, x, z); break; + } } - printf("Seed %" PRId64 " has a Junge Edge biome at block position " - "(%d, %d).\n", (int64_t) seed, pos.x, pos.z); - return 0; } ``` @@ -64,18 +65,24 @@ $ make libcubiomes ``` To compile, and link the cubiomes library you can use one of ``` -$ gcc find_jedge.c libcubiomes.a -fwrapv -lm # static -$ gcc find_jedge.c -L. -lcubiomes -fwrapv -lm # dynamic +$ gcc find_biome_at.c libcubiomes.a -fwrapv -lm # static +$ gcc find_biome_at.c -L. -lcubiomes -fwrapv -lm # dynamic ``` -Both commands assume that your source code is saved as `find_jedge.c` in the cubiomes working directory. If your makefile is configured to use pthreads you also may need to add the `-lpthread` option to the compiler. +Both commands assume that your source code is saved as `find_biome_at.c` in the cubiomes working directory. If your makefile is configured to use pthreads you also may need to add the `-lpthread` option to the compiler. The option `-fwrapv` enforces two's complement for signed integer overflow, which this library relies on. It is not strictly necessary for this example as the library should already be compiled with this flag, but it is good practice to prevent undefined behaviour. Running the program should output: ``` $ ./a.out -Seed 615 has a Junge Edge biome at block position (0, 0). +Seed 262 has a Junge Edge biome at block position (0, 0). ``` -We can also generate the biomes for a rectangular region using `genArea()` which also offers control over the entry layer, see the layer documentation for more information. +### Biome Generation in a Range + +We can also generate biomes for an area or volume using `genBiomes()`. This will utilize whatever optimizations are available for the generator and can be much faster than generating each position individually. (The layered generators for versions up to 1.17 will benefit significantly more from this than the noise-based ones.) + +Before we can generate an area or volume, we need to define the bounds with a `Range` structure and allocate the necessary buffer using `allocCache`. The `Range` is described by a scale, position and size and each cell inside the `Range` represents `scale` many blocks in the horizontal axes. The vertical direction is treated seperately and always follows the biome coordinate scaling of 1:4, with an exception when `scale == 1`, in which case the vertical scaling is also 1:1. + +The only supported values for `scale` are 1, 4, 16, 64, and for the Overworld also 256. For versions up to 1.17 the scale is matched to an appropriate biome layer and will influence the biomes that can generate. ```C // generate an image of the world @@ -84,34 +91,39 @@ We can also generate the biomes for a rectangular region using `genArea()` which int main() { + Generator g; + setupGenerator(&g, MC_1_18, LARGE_BIOMES); + + uint64_t seed = 123LL; + applySeed(&g, 0, seed); + + Range r; + // 1:16, a.k.a. horizontal chunk scaling + r.scale = 16; + // Define the position and size for a horizontal area: + r.x = -60, r.z = -60; // position (x,z) + r.sx = 120, r.sz = 120; // size (width,height) + // Set the vertical range as a plane near sea level at scale 1:4. + r.y = 15, r.sy = 1; + + // Allocate the necessary cache for this range. + int *biomeIds = allocCache(&g, r); + + // Generate the area inside biomeIds, indexed as: + // biomeIds[i_y*r.sx*r.sz + i_z*r.sx + i_x] + // where (i_x, i_y, i_z) is a position relative to the range cuboid. + genBiomes(&g, biomeIds, r); + + // Map the biomes to an image buffer, with 4 pixels per biome cell. + int pix4cell = 4; + int imgWidth = pix4cell*r.sx, imgHeight = pix4cell*r.sz; unsigned char biomeColors[256][3]; - - // Initialize a color map for biomes. initBiomeColors(biomeColors); - - // Initialize a stack of biome layers. - LayerStack g; - setupGenerator(&g, MC_1_17); - // Extract the desired layer. - Layer *layer = &g.layers[L_SHORE_16]; - - uint64_t seed = 1661454332289LL; - int areaX = -60, areaZ = -60; - unsigned int areaWidth = 120, areaHeight = 120; - unsigned int scale = 4; - unsigned int imgWidth = areaWidth*scale, imgHeight = areaHeight*scale; - - // Allocate a sufficient buffer for the biomes and for the image pixels. - int *biomeIds = allocCache(layer, areaWidth, areaHeight); unsigned char *rgb = (unsigned char *) malloc(3*imgWidth*imgHeight); + biomesToImage(rgb, biomeColors, biomeIds, r.sx, r.sz, pix4cell, 2); - // Apply the seed only for the required layers and generate the area. - setLayerSeed(layer, seed); - genArea(layer, biomeIds, areaX, areaZ, areaWidth, areaHeight); - - // Map the biomes to a color buffer and save to an image. - biomesToImage(rgb, biomeColors, biomeIds, areaWidth, areaHeight, scale, 2); - savePPM("biomes_at_layer.ppm", rgb, imgWidth, imgHeight); + // Save the RGB buffer to a PPM image file. + savePPM("map.ppm", rgb, imgWidth, imgHeight); // Clean up. free(biomeIds); @@ -122,58 +134,9 @@ int main() ``` -#### Layer Documentation - -There is a reference document for the generator layers which contains a summary for most generator layers and their function within the generation process (a little out of date, since 1.13). - - -#### Biome Filters - -Biome filters provide a way of generating an area, but only if that area contains certain biomes. Rather than generating an area first and then checking that it contains what we want, the requirements are tested during the generation process. This can be a dramatic speed up, particularly if we require several wildly different biomes. - -```C -// find seeds that have certain biomes near the origin -#include "finders.h" -#include - -int main() -{ - int mc = MC_1_17; - LayerStack g; - BiomeFilter filter; - - setupGenerator(&g, mc); - - // Define the required biomes. - int wanted[] = { - dark_forest, - ice_spikes, - mushroom_fields, - }; - filter = setupBiomeFilter(wanted, sizeof(wanted) / sizeof(int)); - - int x = -200, z = -200, w = 400, h = 400; - int entry = L_VORONOI_1; - int *area = allocCache(&g.layers[entry], w, h); - - printf("Searching...\n"); - uint64_t seed; - for (seed = 0; ; seed++) - if (checkForBiomes(&g, entry, area, seed, x, z, w, h, filter, 1) > 0) - break; - - printf("Seed %" PRId64 " has the required biomes in (%d, %d) - (%d, %d).\n", - (int64_t) seed, x, z, x+w, z+h); - - free(area); - return 0; -} -``` - - ### Structure Generation -The generation of structures can usually be regarded as a two stage process: generation attempts and biome checks. For most structures, Minecraft divides the world into a grid of regions (usually 32x32 chunks) and performs one generation attempt in each. We can use `getStructurePos` to get the position of such a generation attempt and then test whether a structure will actually generate there with `isViableStructurePos`, however, this is more expensive to compute (a few µsec rather than nsec). +The generation of structures can usually be regarded as a two stage process: generation attempts and biome checks. For most structures, Minecraft divides the world into a grid of regions (usually 32x32 chunks) and performs one generation attempt in each. We can use `getStructurePos` to get the position of such a generation attempt and then test whether a structure will actually generate there with `isViableStructurePos`, however, this is more expensive to compute (many µsec rather than nsec). ```C // find a seed with a certain structure at the origin chunk @@ -185,8 +148,8 @@ int main() int structType = Outpost; int mc = MC_1_17; - LayerStack g; - setupGenerator(&g, mc); + Generator g; + setupGenerator(&g, mc, 0); uint64_t lower48; for (lower48 = 0; ; lower48++) @@ -206,7 +169,8 @@ int main() for (upper16 = 0; upper16 < 0x10000; upper16++) { uint64_t seed = lower48 | (upper16 << 48); - if (isViableStructurePos(structType, mc, &g, seed, p.x, p.z)) + applySeed(&g, 0, seed); + if (isViableStructurePos(structType, &g, p.x, p.z)) { printf("Seed %" PRId64 " has a Pillager Outpost at (%d, %d).\n", (int64_t) seed, p.x, p.z); @@ -238,11 +202,11 @@ int check(uint64_t s48, void *data) int main() { int styp = Swamp_Hut; - int mc = MC_1_17; + int mc = MC_1_18; uint64_t basecnt = 0; uint64_t *bases = NULL; int threads = 8; - LayerStack g; + Generator g; StructureConfig sconf; getStructureConfig(styp, mc, &sconf); @@ -262,7 +226,7 @@ int main() exit(1); } - setupGenerator(&g, mc); + setupGenerator(&g, mc, 0); uint64_t i; for (i = 0; i < basecnt; i++) @@ -281,11 +245,12 @@ int main() for (high = 0; high < 0x10000; high++) { uint64_t seed = s48 | (high << 48); + applySeed(&g, 0, seed); - if (isViableStructurePos(styp, mc, &g, seed, pos[0].x, pos[0].z) && - isViableStructurePos(styp, mc, &g, seed, pos[1].x, pos[1].z) && - isViableStructurePos(styp, mc, &g, seed, pos[2].x, pos[2].z) && - isViableStructurePos(styp, mc, &g, seed, pos[3].x, pos[3].z)) + if (isViableStructurePos(styp, &g, pos[0].x, pos[0].z) && + isViableStructurePos(styp, &g, pos[1].x, pos[1].z) && + isViableStructurePos(styp, &g, pos[2].x, pos[2].z) && + isViableStructurePos(styp, &g, pos[3].x, pos[3].z)) { printf("%" PRId64 "\n", (int64_t) seed); } @@ -309,7 +274,7 @@ Strongholds as well as the world spawn point actually search until they find a s int main() { - int mc = MC_1_17; + int mc = MC_1_18; uint64_t seed = 3055141959546LL; // Only the first stronghold has a position which can be estimated @@ -322,17 +287,17 @@ int main() // The finders for the strongholds and spawn require that the seed is // applied to the generator beforehand. - LayerStack g; - setupGenerator(&g, mc); - applySeed(&g, seed); + Generator g; + setupGenerator(&g, mc, 0); + applySeed(&g, 0, seed); - pos = getSpawn(mc, &g, NULL, seed); + pos = getSpawn(&g); printf("Spawn: (%d, %d)\n", pos.x, pos.z); int i, N = 12; for (i = 1; i <= N; i++) { - if (nextStronghold(&sh, &g, NULL) <= 0) + if (nextStronghold(&sh, &g) <= 0) break; printf("Stronghold #%-3d: (%6d, %6d)\n", i, sh.pos.x, sh.pos.z); } diff --git a/biome_tree.c b/biome_tree.c index 974f42e..ed2cbd8 100644 --- a/biome_tree.c +++ b/biome_tree.c @@ -2249,43 +2249,6 @@ int p2overworld(const uint64_t np[6], uint64_t *dat) } -int p2nether(const uint64_t np[2], uint64_t *dat) -{ - (void) dat; - const int64_t ranges[5][4] = { - { 0, 0, 0, nether_wastes }, - { 4000, 0, 0, crimson_forest }, - { 0, -5000, 0, soul_sand_valley }, - { -5000, 0, 1750*1750, basalt_deltas }, - { 0, 5000, 3750*3750, warped_forest }, - }; - - uint64_t dmin = -1; - int i, id = 0; - - for (i = 0; i < 5; i++) - { - uint64_t a, b, q, j; - uint64_t ds; - - ds = ranges[i][2]; - for (j = 0; j < 2; j++) - { - a = +np[j] - (uint64_t) ranges[i][j]; - b = -np[j] + (uint64_t) ranges[i][j]; - q = (int64_t)a > 0 ? a : (int64_t)b > 0 ? b : 0; - ds += q * q; - } - if (ds < dmin) - { - dmin = ds; - id = i; - } - } - - return (int) ranges[id][3]; -} - #if __cplusplus } #endif diff --git a/finders.c b/finders.c index 6f1ebdb..b2f821c 100644 --- a/finders.c +++ b/finders.c @@ -2119,16 +2119,27 @@ uint64_t getHouseList(uint64_t worldSeed, int chunkX, int chunkZ, //============================================================================== -BiomeFilter setupBiomeFilter(const int *biomeList, int listLen) +BiomeFilter setupBiomeFilter( + const int *required, int requiredLen, + const int *excluded, int excludedLen) { BiomeFilter bf; int i, id; memset(&bf, 0, sizeof(bf)); - for (i = 0; i < listLen; i++) + for (i = 0; i < excludedLen; i++) { - id = biomeList[i]; + id = excluded[i]; + if (id < 128) + bf.biomeToExcl |= (1ULL << id); + else + bf.biomeToExclM |= (1ULL << (id-128)); + } + + for (i = 0; i < requiredLen; i++) + { + id = required[i]; if (id & ~0xbf) // i.e. not in ranges [0,64),[128,192) { fprintf(stderr, "setupBiomeFilter: biomeID=%d not supported.\n", id); @@ -2389,6 +2400,76 @@ BiomeFilter setupBiomeFilter(const int *biomeList, int listLen) } +int checkForBiomes( + Generator * g, + int * cache, + Range r, + int dim, + uint64_t seed, + BiomeFilter filter, + int approx, + int (*timeout)() + ) +{ + (void) timeout; + int i, j, ret; + + if (g->mc <= MC_1_17 && dim == 0) + { + Layer *entry = (Layer*) getLayerForScale(g, r.scale); + ret = checkForBiomesAtLayer(&g->ls, entry, cache, seed, + r.x, r.z, r.sx, r.sz, filter, approx); + if (ret == 0 && r.sy > 1 && cache) + { + for (i = 0; i < r.sy; i++) + { // overworld has no vertical noise: expanding 2D into 3D + for (j = 0; j < r.sx*r.sz; j++) + cache[i*r.sx*r.sz + j] = cache[j]; + } + } + return ret; + } + + // TODO: check optimization ideas... + // 1) excluded biomes can terminate noise generation early + // 2) set of biomes in the End might be determined by min,max heights + // 3) each biome in the 1.18 noise generator might have min.max biome + // parameter ranged + + int *ids; + if (cache) + ids = cache; + else + ids = allocCache(g, r); + + if (g->dim != dim || g->seed != seed) + { + applySeed(g, dim, seed); + } + + ret = !genBiomes(g, ids, r); + + if (ret) + { + uint64_t b = 0, bm = 0; + for (int i = 0; i < r.sx*r.sy*r.sz; i++) + { + int id = ids[i]; + if (id < 128) b |= (1ULL << id); + else bm |= (1ULL << (id-128)); + } + ret = !(((b & filter.riverToFind) ^ filter.riverToFind) || + ((bm & filter.riverToFindM) ^ filter.riverToFindM) || + (b & filter.biomeToExcl) || + (bm & filter.biomeToExclM)); + } + + if (ids != cache) + free(ids); + return ret; +} + + STRUCT(filter_data_t) { const BiomeFilter *bf; @@ -2676,10 +2757,10 @@ void restoreMap(filter_data_t *fd, Layer *l) } -int checkForBiomes( - LayerStack * g, - int layerID, - int * cache, +int checkForBiomesAtLayer( + LayerStack * g, + Layer * entry, + int * cache, uint64_t seed, int x, int z, @@ -2693,7 +2774,7 @@ int checkForBiomes( if (protoCheck) // TODO: protoCheck for 1.6- { - l = &g->layers[layerID]; + l = entry; int i, j; int bx = x * l->scale; @@ -2795,7 +2876,7 @@ L_HAS_PROTO_MUSHROOM: if (cache) ids = cache; else - ids = (int*) calloc(getMinLayerCacheSize(&l[layerID], w, h), sizeof(int)); + ids = (int*) calloc(getMinLayerCacheSize(entry, w, h), sizeof(int)); filter_data_t fd[9]; swapMap(fd+0, &filter, l+L_OCEAN_MIX_4, mapFilterOceanMix); @@ -2808,11 +2889,11 @@ L_HAS_PROTO_MUSHROOM: swapMap(fd+7, &filter, l+L_MUSHROOM_256, mapFilterMushroom); swapMap(fd+8, &filter, l+L_SPECIAL_1024, mapFilterSpecial); - setLayerSeed(&l[layerID], seed); - int ret = !l[layerID].getMap(&l[layerID], ids, x, z, w, h); + setLayerSeed(entry, seed); + int ret = !entry->getMap(entry, ids, x, z, w, h); if (ret) { - uint64_t required, b = 0, bm = 0; + uint64_t req, b = 0, bm = 0; unsigned int i; for (i = 0; i < w*h; i++) { @@ -2820,14 +2901,16 @@ L_HAS_PROTO_MUSHROOM: if (id < 128) b |= (1ULL << id); else bm |= (1ULL << (id-128)); } - required = filter.riverToFind; - required &= ~((1ULL << ocean) | (1ULL << deep_ocean)); - required |= filter.oceanToFind; - if ((b & required) ^ required) - ret = -1; - required = filter.riverToFindM; - if ((bm & required) ^ required) - ret = -1; + req = filter.riverToFind; + req &= ~((1ULL << ocean) | (1ULL << deep_ocean)); + req |= filter.oceanToFind; + if ((b & req) ^ req) + ret = 0; + req = filter.riverToFindM; + if ((bm & req) ^ req) + ret = 0; + if ((b & filter.biomeToExcl) || (bm & filter.biomeToExclM)) + ret = 0; } restoreMap(fd+8, l+L_SPECIAL_1024); diff --git a/finders.h b/finders.h index 99e947a..89db9ec 100644 --- a/finders.h +++ b/finders.h @@ -133,6 +133,9 @@ STRUCT(BiomeFilter) uint64_t oceanToFind; // all required ocean types int specialCnt; // number of special temperature categories required + + // excluded biomes that shall not be present + uint64_t biomeToExcl, biomeToExclM; }; STRUCT(StrongholdIter) @@ -283,7 +286,7 @@ int getStructureConfig(int structureType, int mc, StructureConfig *sconf); /* The library can be compiled to use a custom internal getter for structure * configurations. For this, the macro STRUCT_CONFIG_OVERRIDE should be defined - * as true and the function getStructureConfig_override() should be defined + * as true and the function getStructureConfig_override() should be defined * with a custom function body. However, note this is experimental and not all * structure configs may work. (Ideally only change structure salts.) */ @@ -616,29 +619,62 @@ uint64_t getHouseList(uint64_t worldSeed, int chunkX, int chunkZ, int *housesOut //============================================================================== -/* Creates a biome filter configuration from a given list of biomes. +/* Creates a biome filter configuration from a given list of required and + * excluded biomes. Biomes should not appear in both lists. Lists of length + * zero may be passed as null. */ -BiomeFilter setupBiomeFilter(const int *biomeList, int listLen); +BiomeFilter setupBiomeFilter( + const int *required, int requiredLen, + const int *excluded, int excludedLen + ); -/* Starts to generate the specified area and checks if all biomes in the filter - * are present. If so, the area will be fully generated inside the cache - * (if != NULL) up to the entry 'layerID', and the return value will be > 0. +/* Starts to generate the specified range and checks if the biomes meet the + * requirements of the biome filter. If so, the area will be fully generated + * inside the cache (if != NULL), and the return value will be > 0. * Otherwise, the contents of 'cache' is undefined and a value <= 0 is returned. - * More aggressive filtering can be enabled with 'protoCheck' which may yield + * More aggressive filtering can be enabled with 'approx' which may yield some * some false negatives in exchange for speed. * - * @g : generator (will be modified!) - * @layerID : layer enum of generation entry point + * The generator should be set up for the correct version, but the dimension + * and seed will be applied internally. This will modify the generator into a + * partially initialized state that is not valid to use outside this function + * without re-applying a seed. + * + * @g : biome generator + * @cache : working buffer and output (nullable) + * @r : range to be checked + * @dim : dimension (0:Overworld, -1:Nether, +1:End) + * @seed : world seed + * @filter : biome requirements to be met + * @approx : enables approximations with more aggressive filtering + * @timeout : occasional check for abort (nullable) + */ +int checkForBiomes( + Generator * g, + int * cache, + Range r, + int dim, + uint64_t seed, + BiomeFilter filter, + int approx, + int (*timeout)() + ); + +/* Specialization of checkForBiomes() for a LayerStack, i.e. the Overworld up + * to 1.17. + * + * @ls : layered generator (will be modified!) + * @entry : generation entry point (setLayerSeed() may be applied here) * @cache : working buffer, and output (if != NULL) * @seed : world seed * @x,z,w,h : requested area * @filter : biomes to be checked for * @protoCheck : enables more aggressive filtering when non-zero (MC >= 1.7) */ -int checkForBiomes( - LayerStack * g, - int layerID, - int * cache, +int checkForBiomesAtLayer( + LayerStack * ls, + Layer * entry, + int * cache, uint64_t seed, int x, int z, diff --git a/generator.c b/generator.c index de5a6ac..d0a4a35 100644 --- a/generator.c +++ b/generator.c @@ -62,7 +62,7 @@ int mapOceanMixMod(const Layer * l, int * out, int x, int z, int w, int h) void setupGenerator(Generator *g, int mc, uint32_t flags) { g->mc = mc; - g->dim = 0; + g->dim = 1000; // not initialized g->flags = flags; g->seed = 0; g->sha = 0; @@ -98,21 +98,20 @@ void applySeed(Generator *g, int dim, uint64_t seed) g->seed = seed; g->sha = 0; - if (g->mc <= MC_1_17) + if (dim == 0) { - if (dim == 0) + if (g->mc <= MC_1_17) setLayerSeed(g->entry ? g->entry : g->ls.entry_1, seed); - else if (dim == -1 && g->mc >= MC_1_16) - setNetherSeed(&g->nn, seed); - else if (dim == +1 && g->mc >= MC_1_9) - setEndSeed(&g->en, seed); - } - else - { - if (dim == 0 || dim == -1) - setBiomeSeed(&g->bn, seed, dim, g->flags & LARGE_BIOMES); else - setEndSeed(&g->en, seed); + setBiomeSeed(&g->bn, seed, g->flags & LARGE_BIOMES); + } + else if (dim == -1 && g->mc >= MC_1_16) + { + setNetherSeed(&g->nn, seed); + } + else if (dim == +1 && g->mc >= MC_1_9) + { + setEndSeed(&g->en, seed); } if (g->mc >= MC_1_15) { @@ -162,9 +161,9 @@ int genBiomes(const Generator *g, int *cache, Range r) int err = 1; int i, k; - if (g->mc <= MC_1_17) + if (g->dim == 0) { - if (g->dim == 0) + if (g->mc <= MC_1_17) { const Layer *entry = getLayerForScale(g, r.scale); if (!entry) return -1; @@ -177,25 +176,18 @@ int genBiomes(const Generator *g, int *cache, Range r) } return 0; } - else if (g->dim == -1) + else { - return genNetherScaled(&g->nn, cache, r, g->mc, g->sha); - } - else if (g->dim == +1) - { - return genEndScaled(&g->en, cache, r, g->mc, g->sha); + return genBiomeNoiseScaled(&g->bn, cache, r, g->mc, g->sha); } } - else + else if (g->dim == -1) { - if (g->dim == 0 || g->dim == -1) - { - return genBiomeNoiseScaled(&g->bn, cache, r, g->dim, g->mc, g->sha); - } - else if (g->dim == +1) - { - return genEndScaled(&g->en, cache, r, g->mc, g->sha); - } + return genNetherScaled(&g->nn, cache, r, g->mc, g->sha); + } + else if (g->dim == +1) + { + return genEndScaled(&g->en, cache, r, g->mc, g->sha); } return err; diff --git a/generator.h b/generator.h index f0a54e2..49478ee 100644 --- a/generator.h +++ b/generator.h @@ -21,12 +21,12 @@ STRUCT(Generator) LayerStack ls; Layer xlayer[5]; // buffer for custom entry layers @{1,4,16,64,256} Layer *entry; - NetherNoise nn; // MC 1.16 }; struct { // MC 1.18 BiomeNoise bn; }; }; + NetherNoise nn; // MC 1.16 EndNoise en; // MC 1.9 }; @@ -48,8 +48,7 @@ extern "C" void setupGenerator(Generator *g, int mc, uint32_t flags); /** - * Initializes the generator dimension using a given seed. - * + * Initializes the generator for a given dimension and seed. * dim=0: Overworld * dim=-1: Nether * dim=+1: End @@ -97,7 +96,7 @@ const Layer *getLayerForScale(const Generator *g, int scale); ///============================================================================= -/// Layered Biome Generation (interface up to 1.17) +/// Layered Biome Generation (old interface up to 1.17) ///============================================================================= /* Initialize an instance of a layered generator. */ diff --git a/layers.c b/layers.c index e7bbde7..dc739bb 100644 --- a/layers.c +++ b/layers.c @@ -492,7 +492,7 @@ double sampleSurfaceNoise(const SurfaceNoise *rnd, int x, int y, int z) //============================================================================== -// Nether (1.16-1.17) and End (1.9+) Biome Generation +// Nether (1.16+) and End (1.9+) Biome Generation //============================================================================== void setNetherSeed(NetherNoise *nn, uint64_t seed) @@ -516,6 +516,7 @@ int getNetherBiome(const NetherNoise *nn, int x, int y, int z, float *ndel) {-0.5, 0, 0.175*0.175, basalt_deltas }, }; + y = 0; float temp = sampleDoublePerlin(&nn->temperature, x, y, z); float humidity = sampleDoublePerlin(&nn->humidity, x, y, z); @@ -639,9 +640,6 @@ int mapNether2D(const NetherNoise *nn, int *out, int x, int z, int w, int h) int genNetherScaled(const NetherNoise *nn, int *out, Range r, int mc, uint64_t sha) { - if (mc >= MC_1_18) - return 1; // bad version - if (r.scale <= 0) r.scale = 4; if (r.sy == 0) r.sy = 1; @@ -1089,7 +1087,7 @@ int genEndScaled(const EndNoise *en, int *out, Range r, int mc, uint64_t sha) // Overworld and Nether Biome Generation 1.18 //============================================================================== -void setBiomeSeed(BiomeNoise *bn, uint64_t seed, int dim, int large) +void setBiomeSeed(BiomeNoise *bn, uint64_t seed, int large) { Xoroshiro pxr; int n = 0; @@ -1119,29 +1117,26 @@ void setBiomeSeed(BiomeNoise *bn, uint64_t seed, int dim, int large) n += xDoublePerlinInit(&bn->humidity, &pxr, bn->oct+n, amp_h, large ? -10 : -8, sizeof(amp_h)/sizeof(double)); - if (dim == 0) - { - double amp_c[] = {1, 1, 2, 2, 2, 1, 1, 1, 1}; - // md5 "minecraft:continentalness" or "minecraft:continentalness_large" - pxr.lo = xlo ^ (large ? 0x9a3f51a113fce8dc : 0x83886c9d0ae3a662); - pxr.hi = xhi ^ (large ? 0xee2dbd157e5dcdad : 0xafa638a61b42e8ad); - n += xDoublePerlinInit(&bn->continentalness, &pxr, bn->oct+n, - amp_c, large ? -11 : -9, sizeof(amp_c)/sizeof(double)); + double amp_c[] = {1, 1, 2, 2, 2, 1, 1, 1, 1}; + // md5 "minecraft:continentalness" or "minecraft:continentalness_large" + pxr.lo = xlo ^ (large ? 0x9a3f51a113fce8dc : 0x83886c9d0ae3a662); + pxr.hi = xhi ^ (large ? 0xee2dbd157e5dcdad : 0xafa638a61b42e8ad); + n += xDoublePerlinInit(&bn->continentalness, &pxr, bn->oct+n, + amp_c, large ? -11 : -9, sizeof(amp_c)/sizeof(double)); - double amp_e[] = {1, 1, 0, 1, 1}; - // md5 "minecraft:erosion" or "minecraft:erosion_large" - pxr.lo = xlo ^ (large ? 0x8c984b1f8702a951 : 0xd02491e6058f6fd8); - pxr.hi = xhi ^ (large ? 0xead7b1f92bae535f : 0x4792512c94c17a80); - n += xDoublePerlinInit(&bn->erosion, &pxr, bn->oct+n, - amp_e, large ? -11 : -9, sizeof(amp_e)/sizeof(double)); + double amp_e[] = {1, 1, 0, 1, 1}; + // md5 "minecraft:erosion" or "minecraft:erosion_large" + pxr.lo = xlo ^ (large ? 0x8c984b1f8702a951 : 0xd02491e6058f6fd8); + pxr.hi = xhi ^ (large ? 0xead7b1f92bae535f : 0x4792512c94c17a80); + n += xDoublePerlinInit(&bn->erosion, &pxr, bn->oct+n, + amp_e, large ? -11 : -9, sizeof(amp_e)/sizeof(double)); - double amp_w[] = {1, 2, 1, 0, 0, 0}; - // md5 "minecraft:ridge" - pxr.lo = xlo ^ 0xefc8ef4d36102b34; - pxr.hi = xhi ^ 0x1beeeb324a0f24ea; - n += xDoublePerlinInit(&bn->weirdness, &pxr, bn->oct+n, - amp_w, -7, sizeof(amp_w)/sizeof(double)); - } + double amp_w[] = {1, 2, 1, 0, 0, 0}; + // md5 "minecraft:ridge" + pxr.lo = xlo ^ 0xefc8ef4d36102b34; + pxr.hi = xhi ^ 0x1beeeb324a0f24ea; + n += xDoublePerlinInit(&bn->weirdness, &pxr, bn->oct+n, + amp_w, -7, sizeof(amp_w)/sizeof(double)); if ((size_t)n > sizeof(bn->oct) / sizeof(*bn->oct)) { @@ -1353,33 +1348,29 @@ extern "C" #endif int p2overworld(const uint64_t np[6], uint64_t *dat); -int p2nether(const uint64_t np[2], uint64_t *dat); #if __cplusplus } #endif /// Biome sampler for MC 1.18 -int sampleBiomeNoise(const BiomeNoise *bn, int x, int y, int z, int dim, uint64_t *dat) +int sampleBiomeNoise(const BiomeNoise *bn, int x, int y, int z, uint64_t *dat) { float t = 0, h = 0, c = 0, e = 0, d = 0, w = 0; double px = x + sampleDoublePerlin(&bn->shift, x, 0, z) * 4.0; double pz = z + sampleDoublePerlin(&bn->shift, z, x, 0) * 4.0; - if (dim == 0) - { - c = sampleDoublePerlin(&bn->continentalness, px, 0, pz); - e = sampleDoublePerlin(&bn->erosion, px, 0, pz); - w = sampleDoublePerlin(&bn->weirdness, px, 0, pz); + c = sampleDoublePerlin(&bn->continentalness, px, 0, pz); + e = sampleDoublePerlin(&bn->erosion, px, 0, pz); + w = sampleDoublePerlin(&bn->weirdness, px, 0, pz); - float np_param[] = { - c, e, -3.0F * ( fabsf( fabsf(w) - 0.6666667F ) - 0.33333334F ), w, - }; - double off = getSpline(bn->sp, np_param) + 0.015F; + float np_param[] = { + c, e, -3.0F * ( fabsf( fabsf(w) - 0.6666667F ) - 0.33333334F ), w, + }; + double off = getSpline(bn->sp, np_param) + 0.015F; - //double py = y + sampleDoublePerlin(&bn->shift, y, z, x) * 4.0; - d = 1.0 - (y << 2) / 128.0 - 83.0/160.0 + off; - } + //double py = y + sampleDoublePerlin(&bn->shift, y, z, x) * 4.0; + d = 1.0 - (y << 2) / 128.0 - 83.0/160.0 + off; t = sampleDoublePerlin(&bn->temperature, px, 0, pz); h = sampleDoublePerlin(&bn->humidity, px, 0, pz); @@ -1393,16 +1384,12 @@ int sampleBiomeNoise(const BiomeNoise *bn, int x, int y, int z, int dim, uint64_ (int64_t)(10000.0F*w), }; - int id = none; - if (dim == 0) - id = p2overworld((const uint64_t*)np, dat); - if (dim == -1) - id = p2nether((const uint64_t*)np, dat); + int id = p2overworld((const uint64_t*)np, dat); return id; } -static void genBiomeNoise3D(const BiomeNoise *bn, int *out, Range r, int dim, int opt) +static void genBiomeNoise3D(const BiomeNoise *bn, int *out, Range r, int opt) { uint64_t dat = 0; int i, j, k; @@ -1418,15 +1405,14 @@ static void genBiomeNoise3D(const BiomeNoise *bn, int *out, Range r, int dim, in for (i = 0; i < r.sx; i++) { int xi = (r.x+i)*scale + mid; - *p = sampleBiomeNoise(bn, xi, yk, zj, dim, opt ? &dat : NULL); + *p = sampleBiomeNoise(bn, xi, yk, zj, opt ? &dat : NULL); p++; } } } } -int genBiomeNoiseScaled(const BiomeNoise *bn, int *out, Range r, int dim, - int mc, uint64_t sha) +int genBiomeNoiseScaled(const BiomeNoise *bn, int *out, Range r, int mc, uint64_t sha) { if (mc <= MC_1_17) return 1; // bad version @@ -1444,7 +1430,7 @@ int genBiomeNoiseScaled(const BiomeNoise *bn, int *out, Range r, int dim, if (siz > 1) { // the source range is large enough that we can try optimizing src = out + siz; - genBiomeNoise3D(bn, src, s, dim, 0); + genBiomeNoise3D(bn, src, s, 0); } else { @@ -1467,7 +1453,7 @@ int genBiomeNoiseScaled(const BiomeNoise *bn, int *out, Range r, int dim, } else { - *p = sampleBiomeNoise(bn, x4, y4, z4, dim, 0); + *p = sampleBiomeNoise(bn, x4, y4, z4, 0); } p++; } @@ -1481,7 +1467,7 @@ int genBiomeNoiseScaled(const BiomeNoise *bn, int *out, Range r, int dim, // than 1:4, the accuracy becomes questionable anyway. Furthermore // situations that want to use a higher scale are usually better off // with a faster, if imperfect, result. - genBiomeNoise3D(bn, out, r, dim, r.scale > 4); + genBiomeNoise3D(bn, out, r, r.scale > 4); } return 0; } diff --git a/layers.h b/layers.h index e045088..a65058b 100644 --- a/layers.h +++ b/layers.h @@ -289,7 +289,7 @@ STRUCT(LayerStack) PerlinNoise oceanRnd; }; -// Nether biome generator 1.16-1.17 +// Nether biome generator 1.16+ STRUCT(NetherNoise) { // altitude and wierdness don't affect nether biomes // and the weight is a 5th noise parameter which is constant @@ -378,7 +378,6 @@ double sampleSurfaceNoise(const SurfaceNoise *rnd, int x, int y, int z); //============================================================================== /** - * Nether generation (1.16-1.17) * Nether biomes are 3D, and generated at scale 1:4. Use voronoiAccess3D() to * convert coordinates at 1:1 scale to their 1:4 access. Biome checks for * structures are generally done at y=0. @@ -429,25 +428,23 @@ int getSurfaceHeightEnd(int mc, uint64_t seed, int x, int z); int genEndScaled(const EndNoise *en, int *out, Range r, int mc, uint64_t sha); /** - * In 1.18 the Overworld and Nether use a new noise map system for the biome - * generation. The random number generation has also updated to a Xiroshiro128 - * algorithm. The scale is 1:4, and is sampled at each point individually as - * there is currently not much benefit from generating a volume as a whole. + * In 1.18 the Overworld uses a new noise map system for the biome generation. + * The random number generation has also updated to a Xiroshiro128 algorithm. + * The scale is 1:4, and is sampled at each point individually as there is + * currently not much benefit from generating a volume as a whole. * * The 1.18 End generation remains similar to 1.17 and does NOT use the * biome noise. */ void initBiomeNoise(BiomeNoise *bn, int mc); -void setBiomeSeed(BiomeNoise *bn, uint64_t seed, int dim, int large); -int sampleBiomeNoise(const BiomeNoise *bn, int x, int y, int z, int dim, - uint64_t *dat); +void setBiomeSeed(BiomeNoise *bn, uint64_t seed, int large); +int sampleBiomeNoise(const BiomeNoise *bn, int x, int y, int z, uint64_t *dat); /** - * The scaled biome noise generation applies for the Overworld and Nether for - * version 1.18. The 'sha' hash of the seed is only required for voronoi at - * scale 1:1. A scale of zero is interpreted as the default 1:4 scale. + * The scaled biome noise generation applies for the Overworld version 1.18. + * The 'sha' hash of the seed is only required for voronoi at scale 1:1. + * A scale of zero is interpreted as the default 1:4 scale. */ -int genBiomeNoiseScaled(const BiomeNoise *bn, int *out, Range r, int dim, - int mc, uint64_t sha); +int genBiomeNoiseScaled(const BiomeNoise *bn, int *out, Range r, int mc, uint64_t sha); //==============================================================================