feat: refactor terrain generation into modular pass-based architecture with biome and base tile type generation passes

Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
方而静 2025-08-02 10:01:14 +08:00
parent c7a1beea82
commit 937091a40e
Signed by: szTom
GPG Key ID: 072D999D60C6473C
3 changed files with 283 additions and 95 deletions

View File

@ -7,7 +7,8 @@ The tilemap library provides a flexible system for generating and managing tile-
- **TileMap**: The main map container holding chunks of tiles
- **Chunk**: 64x64 tile containers with biome information
- **Tile**: Individual map tiles with base and surface types
- **TerrainGenerator**: Procedural terrain generation system
- **TerrainGenerator**: Pass-based procedural terrain generation system
- **Generation Passes**: Modular generation components (biome, base terrain)
- **Biome System**: Climate-based terrain variation
## Core Classes
@ -41,7 +42,7 @@ Each chunk contains 64×64 tiles and sub-chunk biome information.
```cpp
struct Chunk {
static constexpr uint8_t size = 64; // Tiles per side
static constexpr uint8_t subchunk_size = 4; // Tiles per sub-chunk side
static constexpr uint8_t subchunk_size = /*default value*/; // Tiles per sub-chunk side
static constexpr uint8_t subchunk_count = size / subchunk_size; // Sub-chunks per side
Tile tiles[size][size]; // 64x64 tile grid
@ -108,6 +109,8 @@ std::pair<std::uint8_t, std::uint8_t> subchunk_to_tile_start(
## Terrain Generation
The terrain generation system has been refactored into a modular pass-based architecture, providing better separation of concerns and more flexible generation control.
### GenerationConfig
Configuration parameters for terrain generation.
@ -117,19 +120,19 @@ struct GenerationConfig {
Seed seed; // 128-bit seed for random generation
// Temperature noise parameters
double temperature_scale = 0.005; // Scale for temperature noise
int temperature_octaves = 3; // Number of octaves for temperature noise
double temperature_persistence = 0.4; // Persistence for temperature noise
double temperature_scale = /*default value*/; // Scale for temperature noise
int temperature_octaves = /*default value*/; // Number of octaves for temperature noise
double temperature_persistence = /*default value*/; // Persistence for temperature noise
// Humidity noise parameters
double humidity_scale = 0.005; // Scale for humidity noise
int humidity_octaves = 3; // Number of octaves for humidity noise
double humidity_persistence = 0.4; // Persistence for humidity noise
double humidity_scale = /*default value*/; // Scale for humidity noise
int humidity_octaves = /*default value*/; // Number of octaves for humidity noise
double humidity_persistence = /*default value*/; // Persistence for humidity noise
// Base terrain noise parameters
double base_scale = 0.08; // Scale for base terrain noise
int base_octaves = 3; // Number of octaves for base terrain noise
double base_persistence = 0.5; // Persistence for base terrain noise
double base_scale = /*default value*/; // Scale for base terrain noise
int base_octaves = /*default value*/; // Number of octaves for base terrain noise
double base_persistence = /*default value*/; // Persistence for base terrain noise
};
```
@ -146,21 +149,88 @@ struct GenerationConfig {
- `base_octaves`: Number of noise octaves for base terrain
- `base_persistence`: How much each octave contributes to base terrain noise (0.0-1.0)
### Generation Passes
The generation system is organized into distinct passes, each responsible for a specific aspect of terrain generation.
#### BiomeGenerationPass
Generates biome data for all sub-chunks based on temperature and humidity noise.
```cpp
class BiomeGenerationPass {
public:
BiomeGenerationPass(
const GenerationConfig& config,
Xoroshiro128PP r1,
Xoroshiro128PP r2
);
void operator()(TileMap& tilemap);
private:
std::pair<double, double> get_climate(double global_x, double global_y) const;
};
```
**Key Features:**
- **Climate Generation**: Uses separate noise generators for temperature and humidity
- **Sub-chunk Resolution**: Assigns biomes to 16×16 sub-chunks for efficient generation
- **Climate Mapping**: Maps noise values to temperature/humidity ranges
- **Biome Determination**: Uses climate values to determine appropriate biomes
#### BaseTileTypeGenerationPass
Generates base terrain types for all tiles based on their sub-chunk biomes.
```cpp
class BaseTileTypeGenerationPass {
public:
BaseTileTypeGenerationPass(const GenerationConfig& config, Xoroshiro128PP rng);
void operator()(TileMap& tilemap);
void generate_chunk(TileMap& tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y);
void generate_subchunk(
TileMap& tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y,
SubChunkPos sub_pos, BiomeType biome
);
private:
BaseTileType determine_base_type(
double noise_value, const BiomeProperties& properties
) const;
};
```
**Key Features:**
- **Biome-aware Generation**: Uses biome properties to control terrain type ratios
- **Hierarchical Processing**: Processes chunks, then sub-chunks, then individual tiles
- **Noise-based Distribution**: Uses calibrated noise for balanced terrain distribution
- **Tile-level Detail**: Generates terrain at individual tile resolution
### TerrainGenerator
Main class for procedural terrain generation.
Main orchestrator class that manages the generation process using multiple passes.
```cpp
class TerrainGenerator {
public:
explicit TerrainGenerator(const GenerationConfig& config);
void generate_map(TileMap& tilemap);
void operator()(TileMap& tilemap);
private:
void biome_pass(TileMap& tilemap);
void base_tile_type_pass(TileMap& tilemap);
};
```
**Generation Flow:**
1. **Biome Pass**: Generate climate data and assign biomes to sub-chunks
2. **Base Tile Type Pass**: Generate base terrain types based on biomes and noise
### Generation Function
Convenience function for map generation.
Convenience function for complete map generation.
```cpp
void map_generate(TileMap& tilemap, const GenerationConfig& config);
@ -309,12 +379,12 @@ istd::GenerationConfig config;
config.seed = istd::Seed::from_string("hello_world"); // 128-bit seed from string
// Temperature noise settings
config.temperature_scale = 0.005;
config.temperature_scale = 0.05;
config.temperature_octaves = 3;
config.temperature_persistence = 0.4;
// Humidity noise settings
config.humidity_scale = 0.005;
config.humidity_scale = 0.05;
config.humidity_octaves = 3;
config.humidity_persistence = 0.4;
@ -327,14 +397,44 @@ config.base_persistence = 0.5;
istd::map_generate(tilemap, config);
// Access tiles
for (int chunk_y = 0; chunk_y < Chunk::subchunk_count; ++chunk_y) {
for (int chunk_x = 0; chunk_x < Chunk::subchunk_count; ++chunk_x) {
for (int chunk_y = 0; chunk_y < tilemap.get_size(); ++chunk_y) {
for (int chunk_x = 0; chunk_x < tilemap.get_size(); ++chunk_x) {
const auto& chunk = tilemap.get_chunk(chunk_x, chunk_y);
// Process chunk tiles...
}
}
```
### Advanced Generation with Custom Passes
```cpp
#include "tilemap.h"
#include "generation.h"
// Create map and config
istd::TileMap tilemap(2);
istd::GenerationConfig config;
config.seed = istd::Seed::from_string("custom_world");
// Use TerrainGenerator for step-by-step control
istd::TerrainGenerator generator(config);
// Generate terrain (runs both biome and base tile passes)
generator(tilemap);
// Access biome data
const auto& chunk = tilemap.get_chunk(0, 0);
for (int sub_y = 0; sub_y < istd::Chunk::subchunk_count; ++sub_y) {
for (int sub_x = 0; sub_x < istd::Chunk::subchunk_count; ++sub_x) {
istd::SubChunkPos pos(sub_x, sub_y);
istd::BiomeType biome = chunk.get_biome(pos);
const auto& props = istd::get_biome_properties(biome);
// Process biome...
}
}
```
```
### Seed Usage Examples
```cpp
@ -427,10 +527,24 @@ istd::Seed seed = istd::Seed::from_string("consistent_world");
istd::GenerationConfig config;
config.seed = seed;
TerrainGenerator generator(config);
generator.generate_map(tilemap); // Uses calibrated uniform noise with Xoroshiro128++
// TerrainGenerator handles pass coordination and RNG management
istd::TerrainGenerator generator(config);
generator(tilemap); // Uses calibrated uniform noise with Xoroshiro128++
// Or use the convenience function
istd::map_generate(tilemap, config);
```
### Pass-based Architecture Benefits
The new pass-based system provides:
1. **Separation of Concerns**: Each pass handles a specific aspect of generation
2. **RNG Independence**: Each pass uses independent random number generators
3. **Reproducible Results**: Same seed produces identical results across passes
4. **Extensibility**: Easy to add new passes or modify existing ones
5. **Performance**: Efficient memory access patterns and reduced redundant calculations
## Thread Safety
The library is not inherently thread-safe. External synchronization is required for concurrent access to TileMap objects.

View File

@ -28,34 +28,62 @@ struct GenerationConfig {
double base_persistence = 0.5; // Persistence for base terrain noise
};
// Terrain generator class that manages the generation process
class TerrainGenerator {
class BiomeGenerationPass {
private:
GenerationConfig config_;
const GenerationConfig &config_;
UniformPerlinNoise base_noise_; // For base terrain
UniformPerlinNoise temperature_noise_; // For temperature
UniformPerlinNoise humidity_noise_; // For humidity
UniformPerlinNoise temperature_noise_;
UniformPerlinNoise humidity_noise_;
public:
/**
* @brief Construct a terrain generator with the given configuration
* @param config Generation configuration
* @brief Construct a biome generation pass
* @param config Generation configuration parameters
* @param r1 Random number generator for temperature noise
* @param r2 Random number generator for humidity noise
*/
explicit TerrainGenerator(const GenerationConfig &config);
BiomeGenerationPass(
const GenerationConfig &config, Xoroshiro128PP r1, Xoroshiro128PP r2
);
/**
* @brief Generate terrain for the entire tilemap
* @param tilemap The tilemap to generate into
* @brief Generate biomes for the entire tilemap
* @param tilemap The tilemap to generate biomes into
*/
void generate_map(TileMap &tilemap);
void operator()(TileMap &tilemap);
private:
/**
* @brief Generate biome data for all chunks
* @param tilemap The tilemap to generate biomes into
* @brief Get climate values at a global position
* @param global_x Global X coordinate
* @param global_y Global Y coordinate
* @return Pair of (temperature, humidity) in range [0,1]
*/
void generate_biomes(TileMap &tilemap);
std::pair<double, double> get_climate(
double global_x, double global_y
) const;
};
class BaseTileTypeGenerationPass {
private:
const GenerationConfig &config_;
UniformPerlinNoise base_noise_;
public:
/**
* @brief Construct a base tile type generation pass
* @param config Generation configuration parameters
* @param rng Random number generator for base terrain noise
*/
BaseTileTypeGenerationPass(
const GenerationConfig &config, Xoroshiro128PP rng
);
/**
* @brief Generate base tile types for the entire tilemap
* @param tilemap The tilemap to generate base types into
*/
void operator()(TileMap &tilemap);
/**
* @brief Generate terrain for a single chunk
@ -80,16 +108,6 @@ private:
SubChunkPos sub_pos, BiomeType biome
);
/**
* @brief Get climate values at a global position
* @param global_x Global X coordinate
* @param global_y Global Y coordinate
* @return Pair of (temperature, humidity) in range [0,1]
*/
std::pair<double, double> get_climate(
double global_x, double global_y
) const;
/**
* @brief Determine base terrain type based on noise value and biome
* properties
@ -102,6 +120,39 @@ private:
) const;
};
// Terrain generator class that manages the generation process
class TerrainGenerator {
private:
GenerationConfig config_;
Xoroshiro128PP master_rng_;
public:
/**
* @brief Construct a terrain generator with the given configuration
* @param config Generation configuration
*/
explicit TerrainGenerator(const GenerationConfig &config);
/**
* @brief Generate terrain for the entire tilemap
* @param tilemap The tilemap to generate into
*/
void operator()(TileMap &tilemap);
private:
/**
* @brief Generate biome data for all chunks
* @param tilemap The tilemap to generate biomes into
*/
void biome_pass(TileMap &tilemap);
/**
* @brief Generate base tile types for all chunks
* @param tilemap The tilemap to generate base types into
*/
void base_tile_type_pass(TileMap &tilemap);
};
/**
* @brief Generate a tilemap using the new biome-based system
* @param tilemap The tilemap to generate into

View File

@ -6,44 +6,21 @@
namespace istd {
TerrainGenerator::TerrainGenerator(const GenerationConfig &config)
: config_(config) {
Xoroshiro128PP rng{config.seed};
base_noise_ = UniformPerlinNoise(rng);
rng = rng.jump_96();
temperature_noise_ = UniformPerlinNoise(rng);
rng = rng.jump_96();
humidity_noise_ = UniformPerlinNoise(rng);
base_noise_.calibrate(
config.base_scale, config.base_octaves, config.base_persistence
);
BiomeGenerationPass::BiomeGenerationPass(
const GenerationConfig &config, Xoroshiro128PP r1, Xoroshiro128PP r2
)
: config_(config), temperature_noise_(r1), humidity_noise_(r2) {
temperature_noise_.calibrate(
config.temperature_scale, config.temperature_octaves,
config.temperature_persistence
);
humidity_noise_.calibrate(
config.humidity_scale, config.humidity_octaves,
config.humidity_persistence
);
}
void TerrainGenerator::generate_map(TileMap &tilemap) {
// First, generate biome data for all chunks
generate_biomes(tilemap);
// Then generate terrain for each chunk
std::uint8_t map_size = tilemap.get_size();
for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) {
for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) {
generate_chunk(tilemap, chunk_x, chunk_y);
}
}
}
void TerrainGenerator::generate_biomes(TileMap &tilemap) {
void BiomeGenerationPass::operator()(TileMap &tilemap) {
std::uint8_t map_size = tilemap.get_size();
// Generate biomes for each sub-chunk
@ -77,7 +54,43 @@ void TerrainGenerator::generate_biomes(TileMap &tilemap) {
}
}
void TerrainGenerator::generate_chunk(
std::pair<double, double> BiomeGenerationPass::get_climate(
double global_x, double global_y
) const {
// Generate temperature noise (0-1 range)
double temperature = temperature_noise_.uniform_noise(
global_x * config_.temperature_scale,
global_y * config_.temperature_scale
);
// Generate humidity noise (0-1 range)
double humidity = humidity_noise_.uniform_noise(
global_x * config_.humidity_scale, global_y * config_.humidity_scale
);
return {temperature, humidity};
}
BaseTileTypeGenerationPass::BaseTileTypeGenerationPass(
const GenerationConfig &config, Xoroshiro128PP rng
)
: config_(config), base_noise_(rng) {
base_noise_.calibrate(
config.base_scale, config.base_octaves, config.base_persistence
);
}
void BaseTileTypeGenerationPass::operator()(TileMap &tilemap) {
// Generate base tile types for each chunk
std::uint8_t map_size = tilemap.get_size();
for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) {
for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) {
generate_chunk(tilemap, chunk_x, chunk_y);
}
}
}
void BaseTileTypeGenerationPass::generate_chunk(
TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y
) {
const Chunk &chunk = tilemap.get_chunk(chunk_x, chunk_y);
@ -92,7 +105,7 @@ void TerrainGenerator::generate_chunk(
}
}
void TerrainGenerator::generate_subchunk(
void BaseTileTypeGenerationPass::generate_subchunk(
TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y,
SubChunkPos sub_pos, BiomeType biome
) {
@ -130,24 +143,7 @@ void TerrainGenerator::generate_subchunk(
}
}
std::pair<double, double> TerrainGenerator::get_climate(
double global_x, double global_y
) const {
// Generate temperature noise (0-1 range)
double temperature = temperature_noise_.uniform_noise(
global_x * config_.temperature_scale,
global_y * config_.temperature_scale
);
// Generate humidity noise (0-1 range)
double humidity = humidity_noise_.uniform_noise(
global_x * config_.humidity_scale, global_y * config_.humidity_scale
);
return {temperature, humidity};
}
BaseTileType TerrainGenerator::determine_base_type(
BaseTileType BaseTileTypeGenerationPass::determine_base_type(
double noise_value, const BiomeProperties &properties
) const {
const std::pair<BaseTileType, double> ratios[] = {
@ -168,10 +164,37 @@ BaseTileType TerrainGenerator::determine_base_type(
std::unreachable();
}
// Legacy function for backward compatibility
TerrainGenerator::TerrainGenerator(const GenerationConfig &config)
: config_(config), master_rng_(config.seed) {}
void TerrainGenerator::biome_pass(TileMap &tilemap) {
// Create two RNGs for temperature and humidity noise
Xoroshiro128PP temp_rng = master_rng_;
master_rng_ = master_rng_.jump_96();
Xoroshiro128PP humidity_rng = master_rng_;
master_rng_ = master_rng_.jump_96();
BiomeGenerationPass biome_pass(config_, temp_rng, humidity_rng);
biome_pass(tilemap);
}
void TerrainGenerator::operator()(TileMap &tilemap) {
// First, generate biome data for all chunks
biome_pass(tilemap);
// Then, generate base tile types based on biomes
base_tile_type_pass(tilemap);
}
void TerrainGenerator::base_tile_type_pass(TileMap &tilemap) {
BaseTileTypeGenerationPass pass(config_, master_rng_);
master_rng_ = master_rng_.jump_96();
pass(tilemap);
}
void map_generate(TileMap &tilemap, const GenerationConfig &config) {
TerrainGenerator generator(config);
generator.generate_map(tilemap);
generator(tilemap);
}
} // namespace istd