From 3af226cb0644e0a9b93a6892642470a308367da0 Mon Sep 17 00:00:00 2001
From: sfan5 <sfan5@live.de>
Date: Fri, 6 Sep 2024 11:30:10 +0200
Subject: [PATCH] Refactor "Cavegen y biome check"

---
 src/mapgen/cavegen.cpp  | 16 +++++-----------
 src/mapgen/mapgen.cpp   | 15 ++-------------
 src/mapgen/mg_biome.cpp | 35 ++++++++++++-----------------------
 src/mapgen/mg_biome.h   | 14 ++++++++++----
 4 files changed, 29 insertions(+), 51 deletions(-)

diff --git a/src/mapgen/cavegen.cpp b/src/mapgen/cavegen.cpp
index 47272142f..e6ab66980 100644
--- a/src/mapgen/cavegen.cpp
+++ b/src/mapgen/cavegen.cpp
@@ -82,8 +82,6 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm,
 	const v3s16 &em = vm->m_area.getExtent();
 	u32 index2d = 0;  // Biomemap index
 
-	s16 *biome_transitions = m_bmgn->getBiomeTransitions();
-
 	for (s16 z = nmin.Z; z <= nmax.Z; z++)
 	for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) {
 		bool column_is_open = false;  // Is column open to overground
@@ -101,8 +99,7 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm,
 		u16 depth_riverbed = biome->depth_riverbed;
 		u16 nplaced = 0;
 
-		int cur_biome_depth = 0;
-		s16 biome_y_min = biome_transitions[cur_biome_depth];
+		s16 biome_y_min = 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
@@ -114,15 +111,12 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm,
 			// We need this check to make sure that biomes don't generate too far down
 			if (y < biome_y_min) {
 				biome = m_bmgn->getBiomeAtIndex(index2d, v3s16(x, y, z));
+				biome_y_min = m_bmgn->getNextTransitionY(y);
 
-				// Finding the height of the next biome
-				// On first iteration this may loop a couple times after than it should just run once
-				while (y < biome_y_min) {
-					biome_y_min = biome_transitions[++cur_biome_depth];
+				if (x == nmin.X && z == nmin.Z && false) {
+					dstream << "cavegen: biome at " << y << " is " << biome->name
+						<< ", next at " << biome_y_min << std::endl;
 				}
-
-				/*if (x == nmin.X && z == nmin.Z)
-					printf("Cave: check @ %i -> %s -> again at %i\n", y, biome->name.c_str(), biome_y_min);*/
 			}
 
 			content_t c = vm->m_data[vi].getContent();
diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp
index 80ffebc9e..0b821e02e 100644
--- a/src/mapgen/mapgen.cpp
+++ b/src/mapgen/mapgen.cpp
@@ -649,8 +649,6 @@ void MapgenBasic::generateBiomes()
 
 	noise_filler_depth->perlinMap2D(node_min.X, node_min.Z);
 
-	s16 *biome_transitions = biomegen->getBiomeTransitions();
-
 	for (s16 z = node_min.Z; z <= node_max.Z; z++)
 	for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
 		Biome *biome = NULL;
@@ -661,8 +659,7 @@ void MapgenBasic::generateBiomes()
 		u16 depth_riverbed = 0;
 		u32 vi = vm->m_area.index(x, node_max.Y, z);
 
-		int cur_biome_depth = 0;
-		s16 biome_y_min = biome_transitions[cur_biome_depth];
+		s16 biome_y_min = 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.
@@ -695,15 +692,7 @@ void MapgenBasic::generateBiomes()
 				if (!biome || y < biome_y_min) {
 					// (Re)calculate biome
 					biome = biomegen->getBiomeAtIndex(index, v3s16(x, y, z));
-
-					// Finding the height of the next biome
-					// On first iteration this may loop a couple times after than it should just run once
-					while (y < biome_y_min) {
-						biome_y_min = biome_transitions[++cur_biome_depth];
-					}
-
-					/*if (x == node_min.X && z == node_min.Z)
-						printf("Map: check @ %i -> %s -> again at %i\n", y, biome->name.c_str(), biome_y_min);*/
+					biome_y_min = biomegen->getNextTransitionY(y);
 				}
 
 				// Add biome to biomemap at first stone surface detected
diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp
index 83ecbd691..b270a5413 100644
--- a/src/mapgen/mg_biome.cpp
+++ b/src/mapgen/mg_biome.cpp
@@ -149,47 +149,36 @@ BiomeGenOriginal::BiomeGenOriginal(BiomeManager *biomemgr,
 	// is disabled.
 	memset(biomemap, 0, sizeof(biome_t) * m_csize.X * m_csize.Z);
 
-	// Calculating the bounding position of each biome so we know when we might switch
-	// First gathering all heights where we might switch
-	std::vector<s16> temp_transition_heights;
-	temp_transition_heights.reserve(m_bmgr->getNumObjects() * 2);
+	// Calculate cache of Y transition points
+	std::vector<s16> values;
+	values.reserve(m_bmgr->getNumObjects() * 2);
 	for (size_t i = 0; i < m_bmgr->getNumObjects(); i++) {
 		Biome *b = (Biome *)m_bmgr->getRaw(i);
-		temp_transition_heights.push_back(b->max_pos.Y);
-		temp_transition_heights.push_back(b->min_pos.Y);
+		values.push_back(b->max_pos.Y);
+		values.push_back(b->min_pos.Y);
 	}
 
-	// Sorting the biome transition points
-	std::sort(temp_transition_heights.begin(), temp_transition_heights.end(), std::greater<int>());
+	std::sort(values.begin(), values.end(), std::greater<>());
+	values.erase(std::unique(values.begin(), values.end()), values.end());
 
-	// Getting rid of duplicate biome transition points
-	s16 last = temp_transition_heights[0];
-	size_t out_pos = 1;
-	for (size_t i = 1; i < temp_transition_heights.size(); i++){
-		if (temp_transition_heights[i] != last) {
-			last = temp_transition_heights[i];
-			temp_transition_heights[out_pos++] = last;
-		}
-	}
-
-	biome_transitions = new s16[out_pos];
-	memcpy(biome_transitions, temp_transition_heights.data(), sizeof(s16) * out_pos);
+	m_transitions_y = std::move(values);
 }
 
 BiomeGenOriginal::~BiomeGenOriginal()
 {
 	delete []biomemap;
 
-	delete []biome_transitions;
 	delete noise_heat;
 	delete noise_humidity;
 	delete noise_heat_blend;
 	delete noise_humidity_blend;
 }
 
-s16* BiomeGenOriginal::getBiomeTransitions() const
+s16 BiomeGenOriginal::getNextTransitionY(s16 y) const
 {
-	return biome_transitions;
+	// Find first value that is less than y using binary search
+	auto it = std::lower_bound(m_transitions_y.begin(), m_transitions_y.end(), y, std::greater_equal<>());
+	return (it == m_transitions_y.end()) ? S16_MIN : *it;
 }
 
 BiomeGen *BiomeGenOriginal::clone(BiomeManager *biomemgr) const
diff --git a/src/mapgen/mg_biome.h b/src/mapgen/mg_biome.h
index 567a0fe81..389b36ee9 100644
--- a/src/mapgen/mg_biome.h
+++ b/src/mapgen/mg_biome.h
@@ -128,11 +128,14 @@ public:
 	// Same as above, but uses a raw numeric index correlating to the (x,z) position.
 	virtual Biome *getBiomeAtIndex(size_t index, v3s16 pos) const = 0;
 
-	virtual s16 *getBiomeTransitions() const = 0;
+	// Returns the next lower y position at which the biome could change.
+	// You can use this to optimize calls to getBiomeAtIndex().
+	virtual s16 getNextTransitionY(s16 y) const {
+		return y == S16_MIN ? y : (y - 1);
+	};
 
 	// Result of calcBiomes bulk computation.
 	biome_t *biomemap = nullptr;
-	s16 *biome_transitions = nullptr;
 
 protected:
 	BiomeManager *m_bmgr = nullptr;
@@ -167,7 +170,7 @@ struct BiomeParamsOriginal : public BiomeParams {
 	NoiseParams np_humidity_blend;
 };
 
-class BiomeGenOriginal : public BiomeGen {
+class BiomeGenOriginal final : public BiomeGen {
 public:
 	BiomeGenOriginal(BiomeManager *biomemgr,
 		const BiomeParamsOriginal *params, v3s16 chunksize);
@@ -189,7 +192,7 @@ public:
 	Biome *getBiomeAtIndex(size_t index, v3s16 pos) const;
 
 	Biome *calcBiomeFromNoise(float heat, float humidity, v3s16 pos) const;
-	s16 *getBiomeTransitions() const;
+	s16 getNextTransitionY(s16 y) const;
 
 	float *heatmap;
 	float *humidmap;
@@ -201,6 +204,9 @@ private:
 	Noise *noise_humidity;
 	Noise *noise_heat_blend;
 	Noise *noise_humidity_blend;
+
+	// ordered descending
+	std::vector<s16> m_transitions_y;
 };