From 480eb7d816db5b1a8073d188f46e4d1e0b36bc53 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Thu, 28 Nov 2024 22:12:24 +0100 Subject: [PATCH] Mapgen: Fix biome Y calculation regression BiomeGen::getNextTransitionY(y) did not guarantee the condition (y < biome_y_min) of the next loop because the function may return the value (biome_y_min - 1). Hence, the biome was not updated until one Y coordinate after. --- src/mapgen/cavegen.cpp | 8 ++++---- src/mapgen/mapgen.cpp | 16 +++++++++++----- src/mapgen/mg_biome.cpp | 5 ++++- src/mapgen/mg_biome.h | 3 ++- src/unittest/test_mapgen.cpp | 7 +++---- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/mapgen/cavegen.cpp b/src/mapgen/cavegen.cpp index f422db67d..6bb841ab5 100644 --- a/src/mapgen/cavegen.cpp +++ b/src/mapgen/cavegen.cpp @@ -84,7 +84,7 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm, u16 depth_riverbed = biome->depth_riverbed; u16 nplaced = 0; - s16 biome_y_min = m_bmgn->getNextTransitionY(nmax.Y); + s16 biome_y_next = m_bmgn->getNextTransitionY(nmax.Y); // Don't excavate the overgenerated stone at nmax.Y + 1, // this creates a 'roof' over the tunnel, preventing light in @@ -94,13 +94,13 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm, index3d -= m_ystride, VoxelArea::add_y(em, vi, -1)) { // We need this check to make sure that biomes don't generate too far down - if (y < biome_y_min) { + if (y <= biome_y_next) { biome = m_bmgn->getBiomeAtIndex(index2d, v3s16(x, y, z)); - biome_y_min = m_bmgn->getNextTransitionY(y); + biome_y_next = m_bmgn->getNextTransitionY(y); if (x == nmin.X && z == nmin.Z && false) { dstream << "cavegen: biome at " << y << " is " << biome->name - << ", next at " << biome_y_min << std::endl; + << ", next at " << biome_y_next << std::endl; } } diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index dd416a3e4..d236132df 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -644,7 +644,7 @@ void MapgenBasic::generateBiomes() u16 depth_riverbed = 0; u32 vi = vm->m_area.index(x, node_max.Y, z); - s16 biome_y_min = biomegen->getNextTransitionY(node_max.Y); + s16 biome_y_next = biomegen->getNextTransitionY(node_max.Y); // Check node at base of mapchunk above, either a node of a previously // generated mapchunk or if not, a node of overgenerated base terrain. @@ -661,23 +661,29 @@ void MapgenBasic::generateBiomes() for (s16 y = node_max.Y; y >= node_min.Y; y--) { content_t c = vm->m_data[vi].getContent(); + const bool biome_outdated = !biome || y <= biome_y_next; // Biome is (re)calculated: // 1. At the surface of stone below air or water. // 2. At the surface of water below air. // 3. When stone or water is detected but biome has not yet been calculated. // 4. When stone or water is detected just below a biome's lower limit. bool is_stone_surface = (c == c_stone) && - (air_above || water_above || !biome || y < biome_y_min); // 1, 3, 4 + (air_above || water_above || biome_outdated); // 1, 3, 4 bool is_water_surface = (c == c_water_source || c == c_river_water_source) && - (air_above || !biome || y < biome_y_min); // 2, 3, 4 + (air_above || biome_outdated); // 2, 3, 4 if (is_stone_surface || is_water_surface) { - if (!biome || y < biome_y_min) { + if (biome_outdated) { // (Re)calculate biome biome = biomegen->getBiomeAtIndex(index, v3s16(x, y, z)); - biome_y_min = biomegen->getNextTransitionY(y); + biome_y_next = biomegen->getNextTransitionY(y); + + if (x == node_min.X && z == node_min.Z && false) { + dstream << "biomegen: biome at " << y << " is " << biome->name + << ", next at " << biome_y_next << std::endl; + } } // Add biome to biomemap at first stone surface detected diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp index 788bacede..a0bb6dee2 100644 --- a/src/mapgen/mg_biome.cpp +++ b/src/mapgen/mg_biome.cpp @@ -129,7 +129,10 @@ BiomeGenOriginal::BiomeGenOriginal(BiomeManager *biomemgr, for (size_t i = 0; i < m_bmgr->getNumObjects(); i++) { Biome *b = (Biome *)m_bmgr->getRaw(i); values.push_back(b->max_pos.Y); - values.push_back(b->min_pos.Y); + // We scan for biomes from high Y to low Y (top to bottom). Hence, + // biomes effectively transition at (min_pos.Y - 1). + if (b->min_pos.Y > -MAX_MAP_GENERATION_LIMIT) + values.push_back(b->min_pos.Y - 1); } std::sort(values.begin(), values.end(), std::greater<>()); diff --git a/src/mapgen/mg_biome.h b/src/mapgen/mg_biome.h index 637ec14b1..8adce5db6 100644 --- a/src/mapgen/mg_biome.h +++ b/src/mapgen/mg_biome.h @@ -197,7 +197,8 @@ private: Noise *noise_heat_blend; Noise *noise_humidity_blend; - // ordered descending + /// Y values at which biomes may transition. + /// This array may only be used for downwards scanning! std::vector m_transitions_y; }; diff --git a/src/unittest/test_mapgen.cpp b/src/unittest/test_mapgen.cpp index f72e85ba3..62bd7e54f 100644 --- a/src/unittest/test_mapgen.cpp +++ b/src/unittest/test_mapgen.cpp @@ -93,11 +93,10 @@ void TestMapgen::testBiomeGen(IGameDef *gamedef) const char *name; s16 next_y; } expected_biomes[] = { - { MAX_MAP_GENERATION_LIMIT, "deciduous_forest", 1 }, - // ^ FIXME: next_y should be 0 (min_pos.Y - 1) + { MAX_MAP_GENERATION_LIMIT, "deciduous_forest", 0 }, { 1, "deciduous_forest", 0 }, - { 0, "deciduous_forest_shore", -MAX_MAP_GENERATION_LIMIT }, - { -100, "deciduous_forest_shore", -MAX_MAP_GENERATION_LIMIT }, + { 0, "deciduous_forest_shore", S16_MIN }, + { -100, "deciduous_forest_shore", S16_MIN }, }; for (const auto expected : expected_biomes) { Biome *biome = biomegen->getBiomeAtIndex(