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:
parent
c7a1beea82
commit
937091a40e
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user