feat: Refactor biome generation and terrain noise handling
Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
parent
0b245e0483
commit
1289e99fc3
@ -46,6 +46,7 @@ BraceWrapping:
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
BracedInitializerIndentWidth: 4
|
||||
BreakBeforeBinaryOperators: All
|
||||
BreakBeforeConceptDeclarations: true
|
||||
BreakBeforeBraces: Attach
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user