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 SplitEmptyFunction: false
SplitEmptyRecord: false SplitEmptyRecord: false
SplitEmptyNamespace: false SplitEmptyNamespace: false
BracedInitializerIndentWidth: 4
BreakBeforeBinaryOperators: All BreakBeforeBinaryOperators: All
BreakBeforeConceptDeclarations: true BreakBeforeConceptDeclarations: true
BreakBeforeBraces: Attach BreakBeforeBraces: Attach

View File

@ -8,41 +8,43 @@ namespace istd {
// Biome types based on temperature and humidity // Biome types based on temperature and humidity
enum class BiomeType : std::uint8_t { enum class BiomeType : std::uint8_t {
Desert = 0, // Hot & Dry SnowyPeeks = 0, // Cold & Dry
Savanna = 1, // Hot & Moderate SnowyPlains = 1, // Cold & Moderate
TropicalRainforest = 2, // Hot & Wet FrozenOcean = 2, // Cold & Wet
Grassland = 3, // Temperate & Dry Plains = 3, // Temperate & Dry
DeciduousForest = 4, // Temperate & Moderate Forest = 4, // Temperate & Moderate
TemperateRainforest = 5, // Temperate & Wet Ocean = 5, // Temperate & Wet
Tundra = 6, // Cold & Dry Desert = 6, // Hot & Dry
Taiga = 7, // Cold & Moderate Savanna = 7, // Hot & Moderate
FrozenOcean = 8 // Cold & Wet 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 // Biome properties for terrain generation
struct BiomeProperties { 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 // Biome name for debugging
std::string_view name; 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 // 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) {} 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 // Get the starting tile coordinates for a sub-chunk
constexpr std::pair<std::uint8_t, std::uint8_t> subchunk_to_tile_start( constexpr std::pair<std::uint8_t, std::uint8_t> subchunk_to_tile_start(
const SubChunkPos &pos const SubChunkPos &pos

View File

@ -14,17 +14,25 @@ namespace istd {
struct GenerationConfig { struct GenerationConfig {
std::uint64_t seed = 0; // Seed for random generation std::uint64_t seed = 0; // Seed for random generation
// Climate noise parameters // Noise parameters
double temperature_scale = 0.005; // Scale for temperature noise double temperature_scale = 0.005; // Scale for temperature noise
double humidity_scale = 0.007; // Scale for humidity 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 // Terrain generator class that manages the generation process
class TerrainGenerator { class TerrainGenerator {
private: private:
GenerationConfig config_; 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 base_noise_; // For base terrain generation
PerlinNoise surface_noise_; // For surface feature generation
PerlinNoise temperature_noise_; // For temperature PerlinNoise temperature_noise_; // For temperature
PerlinNoise humidity_noise_; // For humidity PerlinNoise humidity_noise_; // For humidity
@ -91,19 +99,6 @@ private:
BaseTileType determine_base_type( BaseTileType determine_base_type(
double noise_value, const BiomeProperties &properties double noise_value, const BiomeProperties &properties
) const; ) 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 { enum class SurfaceTileType : std::uint8_t {
Empty, Empty,
Wood, Wood,
Snow,
Structure, // Indicates this tile is occupied by a player-built structure, Structure, // Indicates this tile is occupied by a player-built structure,
// should never be natually generated. // should never be natually generated.
_count _count

View File

@ -1,159 +1,88 @@
#include "biome.h" #include "biome.h"
#include <algorithm>
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <algorithm>
namespace istd { namespace istd {
// Biome properties lookup table // Biome properties lookup table
constexpr std::array<BiomeProperties, 9> biome_properties = {{ constexpr BiomeProperties biome_properties[] = {
// Desert: Hot & Dry // Snowy Peeks (Cold & Dry)
{ {
.water_threshold = 0.1, .name = "Snowy Peeks",
.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"
},
// Savanna: Hot & 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"
},
// TropicalRainforest: Hot & 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"
},
// Grassland: 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"
},
// DeciduousForest: 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"
},
// TemperateRainforest: 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"
},
// Tundra: Cold & 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"
},
// Taiga: Cold & 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"
},
// FrozenOcean: Cold & 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"
}
}};
const BiomeProperties& get_biome_properties(BiomeType biome) { .water_threshold = .05,
.ice_threshold = .15,
.sand_threshold = .1,
.land_threshold = .2,
},
// Snowy Plains (Cold & Moderate)
{
.name = "Snowy Plains",
.water_threshold = .05,
.ice_threshold = .25,
.sand_threshold = .1,
.land_threshold = .4,
},
// Frozen Ocean (Cold & Wet)
{
.name = "Frozen Ocean",
.water_threshold = .3,
.ice_threshold = .4,
.sand_threshold = .25,
.land_threshold = .05,
},
// Plains (Temperate & Dry)
{
.name = "Plains",
.water_threshold = .1,
.ice_threshold = .0,
.sand_threshold = .05,
.land_threshold = .65,
},
// Forest (Temperate & Moderate)
{
.name = "Forest",
.water_threshold = .2,
.ice_threshold = .0,
.sand_threshold = .1,
.land_threshold = .5,
},
// Ocean (Temperate & Wet)
{
.name = "Ocean",
.water_threshold = .7,
.ice_threshold = .0,
.sand_threshold = .2,
.land_threshold = .1,
},
// Desert (Hot & Dry)
{
.name = "Desert",
.water_threshold = .0,
.ice_threshold = .0,
.sand_threshold = .75,
.land_threshold = .05,
},
// Savanna (Hot & Moderate)
{
.name = "Savanna",
.water_threshold = .2,
.ice_threshold = .0,
.sand_threshold = .1,
.land_threshold = .5,
},
// Luke Ocean (Hot & Wet)
{
.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)]; 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); temperature = std::clamp(temperature, 0.0, 1.0);
humidity = std::clamp(humidity, 0.0, 1.0); humidity = std::clamp(humidity, 0.0, 1.0);
// Determine temperature category BiomeTemperature temp_category;
int temp_category;
if (temperature < 0.33) { if (temperature < 0.33) {
temp_category = 0; // Cold temp_category = BiomeTemperature::Cold;
} else if (temperature < 0.67) { } else if (temperature < 0.66) {
temp_category = 1; // Temperate temp_category = BiomeTemperature::Temperate;
} else { } else {
temp_category = 2; // Hot temp_category = BiomeTemperature::Hot;
} }
// Determine humidity category BiomeHumidity humidity_category;
int humidity_category;
if (humidity < 0.33) { if (humidity < 0.33) {
humidity_category = 0; // Dry humidity_category = BiomeHumidity::Dry;
} else if (humidity < 0.67) { } else if (humidity < 0.66) {
humidity_category = 1; // Moderate humidity_category = BiomeHumidity::Moderate;
} else { } else {
humidity_category = 2; // Wet humidity_category = BiomeHumidity::Wet;
} }
// Map to biome type (3x3 grid) int mat_x = static_cast<int>(temp_category);
static constexpr BiomeType biome_matrix[3][3] = { int mat_y = static_cast<int>(humidity_category);
// Cold row int index = mat_x * 3 + mat_y; // 3 humidity categories
{BiomeType::Tundra, BiomeType::Taiga, BiomeType::FrozenOcean}, return static_cast<BiomeType>(index);
// Temperate row
{BiomeType::Grassland, BiomeType::DeciduousForest, BiomeType::TemperateRainforest},
// Hot row
{BiomeType::Desert, BiomeType::Savanna, BiomeType::TropicalRainforest}
};
return biome_matrix[temp_category][humidity_category];
} }
} // namespace istd } // namespace istd

View File

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