feat: Refactor biome generation and terrain noise handling

Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
方而静 2025-08-01 18:47:55 +08:00
parent 0b245e0483
commit 1289e99fc3
Signed by: szTom
GPG Key ID: 072D999D60C6473C
6 changed files with 166 additions and 285 deletions

View File

@ -46,6 +46,7 @@ BraceWrapping:
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BracedInitializerIndentWidth: 4
BreakBeforeBinaryOperators: All
BreakBeforeConceptDeclarations: true
BreakBeforeBraces: Attach

View File

@ -8,41 +8,43 @@ namespace istd {
// Biome types based on temperature and humidity
enum class BiomeType : std::uint8_t {
Desert = 0, // Hot & Dry
Savanna = 1, // Hot & Moderate
TropicalRainforest = 2, // Hot & Wet
Grassland = 3, // Temperate & Dry
DeciduousForest = 4, // Temperate & Moderate
TemperateRainforest = 5, // Temperate & Wet
Tundra = 6, // Cold & Dry
Taiga = 7, // Cold & Moderate
FrozenOcean = 8 // Cold & Wet
SnowyPeeks = 0, // Cold & Dry
SnowyPlains = 1, // Cold & Moderate
FrozenOcean = 2, // Cold & Wet
Plains = 3, // Temperate & Dry
Forest = 4, // Temperate & Moderate
Ocean = 5, // Temperate & Wet
Desert = 6, // Hot & Dry
Savanna = 7, // Hot & Moderate
LukeOcean = 8, // Hot & Wet
};
enum class BiomeTemperature : std::uint8_t {
Cold = 0,
Temperate = 1,
Hot = 2,
};
enum class BiomeHumidity : std::uint8_t {
Dry = 0,
Moderate = 1,
Wet = 2,
};
// Biome properties for terrain generation
struct BiomeProperties {
// Base terrain thresholds (0.0 - 1.0)
double water_threshold;
double mountain_threshold;
double sand_threshold;
double ice_threshold;
// Surface coverage thresholds (0.0 - 1.0)
double wood_threshold;
double snow_threshold;
// Noise parameters for base terrain
double base_scale;
int base_octaves;
double base_persistence;
// Noise parameters for surface features
double surface_scale;
int surface_octaves;
double surface_persistence;
// Biome name for debugging
std::string_view name;
// Base terrain thresholds (0.0 - 1.0)
double water_threshold;
double ice_threshold;
double sand_threshold;
double land_threshold;
// Noise parameters for base terrain
int base_octaves = 3;
double base_persistence = 0.5;
};
// Get biome properties for terrain generation
@ -59,13 +61,6 @@ struct SubChunkPos {
constexpr SubChunkPos(std::uint8_t x, std::uint8_t y): sub_x(x), sub_y(y) {}
};
// Convert local tile coordinates to sub-chunk position
constexpr SubChunkPos tile_to_subchunk(
std::uint8_t local_x, std::uint8_t local_y
) {
return SubChunkPos(local_x / 16, local_y / 16);
}
// Get the starting tile coordinates for a sub-chunk
constexpr std::pair<std::uint8_t, std::uint8_t> subchunk_to_tile_start(
const SubChunkPos &pos

View File

@ -14,17 +14,25 @@ namespace istd {
struct GenerationConfig {
std::uint64_t seed = 0; // Seed for random generation
// Climate noise parameters
// Noise parameters
double temperature_scale = 0.005; // Scale for temperature noise
double humidity_scale = 0.007; // Scale for humidity noise
double base_scale = 0.08; // Scale for base terrain noise
};
// Terrain generator class that manages the generation process
class TerrainGenerator {
private:
GenerationConfig config_;
// Just some random numbers to mask the seeds
static constexpr std::uint64_t base_seed_mask = 0x06'a9'cb'b1'b3'96'f3'50;
static constexpr std::uint64_t temperature_seed_mask
= 0x79'c8'a7'a1'09'99'd0'e3;
static constexpr std::uint64_t humidity_seed_mask
= 0x5e'10'be'e4'd2'6f'34'c2;
PerlinNoise base_noise_; // For base terrain generation
PerlinNoise surface_noise_; // For surface feature generation
PerlinNoise temperature_noise_; // For temperature
PerlinNoise humidity_noise_; // For humidity
@ -91,19 +99,6 @@ private:
BaseTileType determine_base_type(
double noise_value, const BiomeProperties &properties
) const;
/**
* @brief Determine surface feature type based on noise value and biome
* properties
* @param noise_value Surface feature noise value [0,1]
* @param properties Biome properties to use
* @param base_type The base terrain type (affects surface placement)
* @return The appropriate surface tile type
*/
SurfaceTileType determine_surface_type(
double noise_value, const BiomeProperties &properties,
BaseTileType base_type
) const;
};
/**

View File

@ -18,7 +18,6 @@ enum class BaseTileType : std::uint8_t {
enum class SurfaceTileType : std::uint8_t {
Empty,
Wood,
Snow,
Structure, // Indicates this tile is occupied by a player-built structure,
// should never be natually generated.
_count

View File

@ -1,157 +1,86 @@
#include "biome.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <algorithm>
namespace istd {
// Biome properties lookup table
constexpr std::array<BiomeProperties, 9> biome_properties = {{
// Desert: Hot & Dry
constexpr BiomeProperties biome_properties[] = {
// Snowy Peeks (Cold & Dry)
{
.water_threshold = 0.1,
.mountain_threshold = 0.85,
.sand_threshold = 0.8,
.ice_threshold = 0.0, // No ice in desert
.wood_threshold = 0.05, // Very sparse vegetation
.snow_threshold = 0.0, // No snow in desert
.base_scale = 0.03,
.base_octaves = 3,
.base_persistence = 0.4,
.surface_scale = 0.05,
.surface_octaves = 2,
.surface_persistence = 0.3,
.name = "Desert"
.name = "Snowy Peeks",
.water_threshold = .05,
.ice_threshold = .15,
.sand_threshold = .1,
.land_threshold = .2,
},
// Savanna: Hot & Moderate
// Snowy Plains (Cold & Moderate)
{
.water_threshold = 0.15,
.mountain_threshold = 0.8,
.sand_threshold = 0.3,
.ice_threshold = 0.0,
.wood_threshold = 0.25, // Scattered trees
.snow_threshold = 0.0,
.base_scale = 0.025,
.base_octaves = 4,
.base_persistence = 0.5,
.surface_scale = 0.04,
.surface_octaves = 3,
.surface_persistence = 0.4,
.name = "Savanna"
.name = "Snowy Plains",
.water_threshold = .05,
.ice_threshold = .25,
.sand_threshold = .1,
.land_threshold = .4,
},
// TropicalRainforest: Hot & Wet
// Frozen Ocean (Cold & Wet)
{
.water_threshold = 0.25,
.mountain_threshold = 0.85,
.sand_threshold = 0.1,
.ice_threshold = 0.0,
.wood_threshold = 0.7, // Dense forest
.snow_threshold = 0.0,
.base_scale = 0.02,
.base_octaves = 5,
.base_persistence = 0.6,
.surface_scale = 0.03,
.surface_octaves = 4,
.surface_persistence = 0.5,
.name = "Tropical Rainforest"
.name = "Frozen Ocean",
.water_threshold = .3,
.ice_threshold = .4,
.sand_threshold = .25,
.land_threshold = .05,
},
// Grassland: Temperate & Dry
// Plains (Temperate & Dry)
{
.water_threshold = 0.2,
.mountain_threshold = 0.8,
.sand_threshold = 0.15,
.ice_threshold = 0.0,
.wood_threshold = 0.15, // Sparse trees
.snow_threshold = 0.05, // Occasional snow
.base_scale = 0.035,
.base_octaves = 3,
.base_persistence = 0.45,
.surface_scale = 0.06,
.surface_octaves = 2,
.surface_persistence = 0.35,
.name = "Grassland"
.name = "Plains",
.water_threshold = .1,
.ice_threshold = .0,
.sand_threshold = .05,
.land_threshold = .65,
},
// DeciduousForest: Temperate & Moderate
// Forest (Temperate & Moderate)
{
.water_threshold = 0.3,
.mountain_threshold = 0.82,
.sand_threshold = 0.05,
.ice_threshold = 0.0,
.wood_threshold = 0.6, // Dense deciduous forest
.snow_threshold = 0.1, // Some snow coverage
.base_scale = 0.025,
.base_octaves = 4,
.base_persistence = 0.55,
.surface_scale = 0.04,
.surface_octaves = 3,
.surface_persistence = 0.45,
.name = "Deciduous Forest"
.name = "Forest",
.water_threshold = .2,
.ice_threshold = .0,
.sand_threshold = .1,
.land_threshold = .5,
},
// TemperateRainforest: Temperate & Wet
// Ocean (Temperate & Wet)
{
.water_threshold = 0.35,
.mountain_threshold = 0.85,
.sand_threshold = 0.05,
.ice_threshold = 0.0,
.wood_threshold = 0.8, // Very dense forest
.snow_threshold = 0.15, // More snow
.base_scale = 0.02,
.base_octaves = 5,
.base_persistence = 0.6,
.surface_scale = 0.03,
.surface_octaves = 4,
.surface_persistence = 0.5,
.name = "Temperate Rainforest"
.name = "Ocean",
.water_threshold = .7,
.ice_threshold = .0,
.sand_threshold = .2,
.land_threshold = .1,
},
// Tundra: Cold & Dry
// Desert (Hot & Dry)
{
.water_threshold = 0.15,
.mountain_threshold = 0.75,
.sand_threshold = 0.05,
.ice_threshold = 0.3, // Lots of ice
.wood_threshold = 0.05, // Very sparse vegetation
.snow_threshold = 0.4, // Lots of snow
.base_scale = 0.04,
.base_octaves = 2,
.base_persistence = 0.3,
.surface_scale = 0.08,
.surface_octaves = 2,
.surface_persistence = 0.25,
.name = "Tundra"
.name = "Desert",
.water_threshold = .0,
.ice_threshold = .0,
.sand_threshold = .75,
.land_threshold = .05,
},
// Taiga: Cold & Moderate
// Savanna (Hot & Moderate)
{
.water_threshold = 0.25,
.mountain_threshold = 0.8,
.sand_threshold = 0.02,
.ice_threshold = 0.15,
.wood_threshold = 0.5, // Coniferous forest
.snow_threshold = 0.6, // Heavy snow coverage
.base_scale = 0.03,
.base_octaves = 4,
.base_persistence = 0.5,
.surface_scale = 0.05,
.surface_octaves = 3,
.surface_persistence = 0.4,
.name = "Taiga"
.name = "Savanna",
.water_threshold = .2,
.ice_threshold = .0,
.sand_threshold = .1,
.land_threshold = .5,
},
// FrozenOcean: Cold & Wet
// Luke Ocean (Hot & Wet)
{
.water_threshold = 0.4,
.mountain_threshold = 0.85,
.sand_threshold = 0.02,
.ice_threshold = 0.6, // Mostly ice
.wood_threshold = 0.1, // Very sparse trees
.snow_threshold = 0.8, // Almost all snow
.base_scale = 0.025,
.base_octaves = 3,
.base_persistence = 0.4,
.surface_scale = 0.04,
.surface_octaves = 2,
.surface_persistence = 0.3,
.name = "Frozen Ocean"
}
}};
.name = "Luke Ocean",
.water_threshold = .8,
.ice_threshold = .0,
.sand_threshold = .2,
.land_threshold = .0,
},
};
const BiomeProperties &get_biome_properties(BiomeType biome) {
return biome_properties[static_cast<std::uint8_t>(biome)];
@ -162,37 +91,28 @@ BiomeType determine_biome(double temperature, double humidity) {
temperature = std::clamp(temperature, 0.0, 1.0);
humidity = std::clamp(humidity, 0.0, 1.0);
// Determine temperature category
int temp_category;
BiomeTemperature temp_category;
if (temperature < 0.33) {
temp_category = 0; // Cold
} else if (temperature < 0.67) {
temp_category = 1; // Temperate
temp_category = BiomeTemperature::Cold;
} else if (temperature < 0.66) {
temp_category = BiomeTemperature::Temperate;
} else {
temp_category = 2; // Hot
temp_category = BiomeTemperature::Hot;
}
// Determine humidity category
int humidity_category;
BiomeHumidity humidity_category;
if (humidity < 0.33) {
humidity_category = 0; // Dry
} else if (humidity < 0.67) {
humidity_category = 1; // Moderate
humidity_category = BiomeHumidity::Dry;
} else if (humidity < 0.66) {
humidity_category = BiomeHumidity::Moderate;
} else {
humidity_category = 2; // Wet
humidity_category = BiomeHumidity::Wet;
}
// Map to biome type (3x3 grid)
static constexpr BiomeType biome_matrix[3][3] = {
// Cold row
{BiomeType::Tundra, BiomeType::Taiga, BiomeType::FrozenOcean},
// Temperate row
{BiomeType::Grassland, BiomeType::DeciduousForest, BiomeType::TemperateRainforest},
// Hot row
{BiomeType::Desert, BiomeType::Savanna, BiomeType::TropicalRainforest}
};
return biome_matrix[temp_category][humidity_category];
int mat_x = static_cast<int>(temp_category);
int mat_y = static_cast<int>(humidity_category);
int index = mat_x * 3 + mat_y; // 3 humidity categories
return static_cast<BiomeType>(index);
}
} // namespace istd

View File

@ -2,16 +2,15 @@
#include "biome.h"
#include <cmath>
#include <random>
#include <utility>
namespace istd {
TerrainGenerator::TerrainGenerator(const GenerationConfig &config)
: config_(config)
, base_noise_(config.seed)
, surface_noise_(config.seed + 500) // Different seed for surface features
, temperature_noise_(config.seed + 1000) // Different seed for temperature
, humidity_noise_(config.seed + 2000) // Different seed for humidity
{}
, base_noise_(config.seed ^ base_seed_mask)
, temperature_noise_(config.seed ^ temperature_seed_mask)
, humidity_noise_(config.seed ^ humidity_seed_mask) {}
void TerrainGenerator::generate_map(TileMap &tilemap) {
// First, generate biome data for all chunks
@ -93,31 +92,18 @@ void TerrainGenerator::generate_subchunk(
// Generate base terrain noise value
double base_noise_value = base_noise_.octave_noise(
global_x * properties.base_scale,
global_y * properties.base_scale, properties.base_octaves,
properties.base_persistence
);
// Generate surface feature noise value
double surface_noise_value = surface_noise_.octave_noise(
global_x * properties.surface_scale,
global_y * properties.surface_scale, properties.surface_octaves,
properties.surface_persistence
global_x * config_.base_scale, global_y * config_.base_scale,
properties.base_octaves, properties.base_persistence
);
// Determine base terrain type
BaseTileType base_type
= determine_base_type(base_noise_value, properties);
// Determine surface feature type
SurfaceTileType surface_type = determine_surface_type(
surface_noise_value, properties, base_type
);
// Create tile with base and surface components
Tile tile;
tile.base = base_type;
tile.surface = surface_type;
tile.surface = SurfaceTileType::Empty;
// Set the tile
TilePos pos{chunk_x, chunk_y, local_x, local_y};
@ -147,37 +133,22 @@ std::pair<double, double> TerrainGenerator::get_climate(
BaseTileType TerrainGenerator::determine_base_type(
double noise_value, const BiomeProperties &properties
) const {
if (noise_value < properties.water_threshold) {
return BaseTileType::Water;
} else if (noise_value < properties.sand_threshold) {
return BaseTileType::Sand;
} else if (noise_value < properties.mountain_threshold) {
return BaseTileType::Mountain;
} else if (properties.ice_threshold > 0.0
&& noise_value < properties.ice_threshold) {
return BaseTileType::Ice;
} else {
return BaseTileType::Land;
const std::pair<BaseTileType, double> thresholds[] = {
{BaseTileType::Water, properties.water_threshold},
{BaseTileType::Ice, properties.ice_threshold },
{BaseTileType::Sand, properties.sand_threshold },
{BaseTileType::Land, properties.land_threshold },
{BaseTileType::Mountain, 1.0 },
};
for (const auto &[type, threshold] : thresholds) {
if (noise_value < threshold) {
return type;
}
noise_value -= threshold; // Adjust noise value for next type
}
SurfaceTileType TerrainGenerator::determine_surface_type(
double noise_value, const BiomeProperties &properties,
BaseTileType base_type
) const {
// Don't place surface features on water or ice
if (base_type == BaseTileType::Water || base_type == BaseTileType::Ice) {
return SurfaceTileType::Empty;
}
// Check for surface features based on thresholds
if (noise_value < properties.wood_threshold) {
return SurfaceTileType::Wood;
} else if (noise_value < properties.snow_threshold) {
return SurfaceTileType::Snow;
} else {
return SurfaceTileType::Empty;
}
std::unreachable();
}
// Legacy function for backward compatibility