feat: add coal generation
Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
parent
1354d9c08a
commit
afc8d8fa1c
@ -4,6 +4,7 @@ cmake_minimum_required(VERSION 3.27)
|
||||
set(ISTD_TILEMAP_SRC
|
||||
src/pass/base_tile_type.cpp
|
||||
src/pass/biome.cpp
|
||||
src/pass/coal.cpp
|
||||
src/pass/deepwater.cpp
|
||||
src/pass/mineral_cluster.cpp
|
||||
src/pass/mountain_hole_fill.cpp
|
||||
|
@ -81,21 +81,7 @@ Configuration for terrain generation.
|
||||
```cpp
|
||||
struct GenerationConfig {
|
||||
Seed seed;
|
||||
|
||||
// Noise parameters
|
||||
double temperature_scale = 0.05;
|
||||
double humidity_scale = 0.05;
|
||||
double base_scale = 0.08;
|
||||
|
||||
int temperature_octaves = 3;
|
||||
int humidity_octaves = 3;
|
||||
int base_octaves = 3;
|
||||
|
||||
// Oil generation parameters
|
||||
double oil_density = 0.8; // Average oil fields per chunk
|
||||
std::uint32_t oil_cluster_min_size = 2; // Minimum tiles per cluster
|
||||
std::uint32_t oil_cluster_max_size = 6; // Maximum tiles per cluster
|
||||
double oil_biome_preference = 2.0; // Multiplier for preferred biomes
|
||||
// For more options, see generation.h
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.27)
|
||||
# Examples for the tilemap library
|
||||
# Each example is built as a separate executable
|
||||
|
||||
# Biome system demonstration
|
||||
# Tilemap system demonstration
|
||||
add_executable(tilemap_demo tilemap_demo.cpp)
|
||||
target_link_libraries(tilemap_demo PRIVATE istd_tilemap)
|
||||
target_include_directories(tilemap_demo PRIVATE ../include)
|
||||
|
@ -241,6 +241,7 @@ constexpr Color OIL(0, 0, 0); // Black
|
||||
constexpr Color HEMATITE(255, 0, 0); // Red
|
||||
constexpr Color TITANOMAGNETITE(128, 0, 128); // Purple
|
||||
constexpr Color GIBBSITE(255, 255, 0); // Yellow
|
||||
constexpr Color COAL(64, 64, 64); // Dark gray
|
||||
} // namespace BmpColors
|
||||
|
||||
#endif // BMP_H
|
||||
|
@ -19,6 +19,8 @@ BmpColors::Color get_tile_color(const istd::Tile &tile) {
|
||||
return BmpColors::TITANOMAGNETITE;
|
||||
case istd::SurfaceTileType::Gibbsite:
|
||||
return BmpColors::GIBBSITE;
|
||||
case istd::SurfaceTileType::Coal:
|
||||
return BmpColors::COAL;
|
||||
case istd::SurfaceTileType::Empty:
|
||||
default:
|
||||
break; // Fall through to base tile color
|
||||
@ -101,6 +103,7 @@ void print_statistics(const istd::TileMap &tilemap) {
|
||||
int hematite_count = 0; // Count hematite surface tiles
|
||||
int titanomagnetite_count = 0; // Count titanomagnetite surface tiles
|
||||
int gibbsite_count = 0; // Count gibbsite surface tiles
|
||||
int coal_count = 0; // Count coal surface tiles
|
||||
int mountain_edge_count = 0; // Count mountain edge tiles
|
||||
const int chunks_per_side = tilemap.get_size();
|
||||
const int tiles_per_chunk = istd::Chunk::size;
|
||||
@ -128,6 +131,9 @@ void print_statistics(const istd::TileMap &tilemap) {
|
||||
case istd::SurfaceTileType::Gibbsite:
|
||||
gibbsite_count++;
|
||||
break;
|
||||
case istd::SurfaceTileType::Coal:
|
||||
coal_count++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -195,6 +201,7 @@ void print_statistics(const istd::TileMap &tilemap) {
|
||||
print_mineral_stats("Hematite", hematite_count);
|
||||
print_mineral_stats("Titanomagnetite", titanomagnetite_count);
|
||||
print_mineral_stats("Gibbsite", gibbsite_count);
|
||||
print_mineral_stats("Coal", coal_count);
|
||||
|
||||
// Mountain edge statistics for mineral context
|
||||
int mountain_count = tile_counts[static_cast<int>(
|
||||
|
@ -47,16 +47,24 @@ struct GenerationConfig {
|
||||
// of 255)
|
||||
|
||||
// Mineral cluster generation parameters
|
||||
std::uint16_t hematite_density = 450; // ~1.8 per chunk (out of 255)
|
||||
std::uint16_t titanomagnetite_density = 300; // ~1.2 per chunk (out of 255)
|
||||
std::uint16_t gibbsite_density = 235; // ~0.9 per chunk (out of 255)
|
||||
std::uint16_t hematite_density = 450; // ~1.8 per chunk (n / 255)
|
||||
std::uint16_t titanomagnetite_density = 300; // ~1.2 per chunk (n / 255)
|
||||
std::uint16_t gibbsite_density = 235; // ~0.9 per chunk (n / 255)
|
||||
|
||||
std::uint8_t mineral_cluster_min_size = 2; // Minimum tiles per mineral
|
||||
// cluster
|
||||
std::uint8_t mineral_cluster_max_size = 5; // Maximum tiles per mineral
|
||||
// cluster
|
||||
std::uint8_t mineral_base_probe = 192; // Base probability for mineral
|
||||
std::uint8_t mineral_base_prob = 192; // Base probability for mineral
|
||||
// placement
|
||||
|
||||
// Coal generation parameters
|
||||
std::uint8_t coal_seeds_per_chunk = 3; // Number of initial coal seeds per
|
||||
// chunk
|
||||
std::uint8_t coal_evolution_steps = 6; // Number of cellular automata
|
||||
// evolution steps
|
||||
std::uint8_t coal_growth_base_prob = 21; // Base probability for coal
|
||||
// growth per neighbor (n / 255)
|
||||
};
|
||||
|
||||
// Terrain generator class that manages the generation process
|
||||
@ -126,6 +134,12 @@ private:
|
||||
* @param tilemap The tilemap to process
|
||||
*/
|
||||
void mineral_cluster_pass(TileMap &tilemap);
|
||||
|
||||
/**
|
||||
* @brief Generate coal deposits on suitable terrain
|
||||
* @param tilemap The tilemap to process
|
||||
*/
|
||||
void coal_pass(TileMap &tilemap);
|
||||
};
|
||||
/**
|
||||
* @brief Generate a tilemap using the new biome-based system
|
||||
|
86
tilemap/include/tilemap/pass/coal.h
Normal file
86
tilemap/include/tilemap/pass/coal.h
Normal file
@ -0,0 +1,86 @@
|
||||
#ifndef TILEMAP_PASS_COAL_H
|
||||
#define TILEMAP_PASS_COAL_H
|
||||
|
||||
#include "tilemap/generation.h"
|
||||
#include "tilemap/noise.h"
|
||||
|
||||
namespace istd {
|
||||
|
||||
/**
|
||||
* @brief Generates coal deposits on suitable terrain using cellular automata
|
||||
*/
|
||||
class CoalGenerationPass {
|
||||
private:
|
||||
const GenerationConfig &config_;
|
||||
Xoroshiro128PP rng_;
|
||||
DiscreteRandomNoise noise_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a coal generation pass
|
||||
* @param config Generation configuration parameters
|
||||
* @param rng Random number generator for coal placement
|
||||
* @param noise_rng Random number generator for noise-based operations
|
||||
*/
|
||||
CoalGenerationPass(
|
||||
const GenerationConfig &config, Xoroshiro128PP rng,
|
||||
Xoroshiro128PP noise_rng
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Generate coal deposits using cellular automata
|
||||
* @param tilemap The tilemap to process
|
||||
*/
|
||||
void operator()(TileMap &tilemap);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Generate initial coal seed points for a chunk
|
||||
* @param tilemap The tilemap to analyze
|
||||
* @param chunk_x Chunk X coordinate
|
||||
* @param chunk_y Chunk Y coordinate
|
||||
* @param seeds Output vector to fill with seed positions
|
||||
*/
|
||||
void chunk_coal_seeds(
|
||||
const TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y,
|
||||
std::vector<TilePos> &seeds
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Evolve coal deposits using cellular automata
|
||||
* @param tilemap The tilemap to modify
|
||||
* @param initial_seeds Initial coal seed positions
|
||||
*/
|
||||
void evolve_coal_deposits(
|
||||
TileMap &tilemap, const std::vector<TilePos> &initial_seeds
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Check if a tile is suitable for coal placement
|
||||
* @param tilemap The tilemap to check
|
||||
* @param pos Position to check
|
||||
* @return True if coal can be placed at this position
|
||||
*/
|
||||
bool is_suitable_for_coal(const TileMap &tilemap, TilePos pos) const;
|
||||
|
||||
/**
|
||||
* @brief Get the coal growth probability for a specific biome
|
||||
* @param biome The biome type
|
||||
* @return Growth probability multiplier (x / 255)
|
||||
*/
|
||||
std::uint8_t get_biome_coal_growth_probability(BiomeType biome) const;
|
||||
|
||||
/**
|
||||
* @brief Count coal neighbors around a position
|
||||
* @param tilemap The tilemap to check
|
||||
* @param pos Position to check around
|
||||
* @return Number of coal neighbors
|
||||
*/
|
||||
std::uint8_t count_coal_neighbors(
|
||||
const TileMap &tilemap, TilePos pos
|
||||
) const;
|
||||
};
|
||||
|
||||
} // namespace istd
|
||||
|
||||
#endif // TILEMAP_PASS_COAL_H
|
@ -20,6 +20,7 @@ enum class SurfaceTileType : std::uint8_t {
|
||||
Hematite,
|
||||
Titanomagnetite,
|
||||
Gibbsite,
|
||||
Coal,
|
||||
|
||||
// Player built structures
|
||||
// (not used in generation, but can be placed by player)
|
||||
|
@ -13,6 +13,7 @@ void TerrainGenerator::operator()(TileMap &tilemap) {
|
||||
deepwater_pass(tilemap);
|
||||
oil_pass(tilemap);
|
||||
mineral_cluster_pass(tilemap);
|
||||
coal_pass(tilemap);
|
||||
}
|
||||
|
||||
void map_generate(TileMap &tilemap, const GenerationConfig &config) {
|
||||
|
207
tilemap/src/pass/coal.cpp
Normal file
207
tilemap/src/pass/coal.cpp
Normal file
@ -0,0 +1,207 @@
|
||||
#include "tilemap/pass/coal.h"
|
||||
#include "tilemap/biome.h"
|
||||
#include "tilemap/chunk.h"
|
||||
#include "tilemap/generation.h"
|
||||
#include "tilemap/noise.h"
|
||||
#include "tilemap/xoroshiro.h"
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
|
||||
namespace istd {
|
||||
|
||||
CoalGenerationPass::CoalGenerationPass(
|
||||
const GenerationConfig &config, Xoroshiro128PP rng, Xoroshiro128PP noise_rng
|
||||
)
|
||||
: config_(config), rng_(rng), noise_(noise_rng) {}
|
||||
|
||||
void CoalGenerationPass::operator()(TileMap &tilemap) {
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
std::vector<TilePos> all_seeds;
|
||||
|
||||
// Generate coal seeds for each chunk
|
||||
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) {
|
||||
chunk_coal_seeds(tilemap, chunk_x, chunk_y, all_seeds);
|
||||
}
|
||||
}
|
||||
|
||||
// Evolve coal deposits using cellular automata
|
||||
evolve_coal_deposits(tilemap, all_seeds);
|
||||
}
|
||||
|
||||
void CoalGenerationPass::chunk_coal_seeds(
|
||||
const TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y,
|
||||
std::vector<TilePos> &seeds
|
||||
) {
|
||||
// Use a max heap to keep top N positions by noise value
|
||||
using PosNoisePair = std::pair<std::uint64_t, TilePos>;
|
||||
std::priority_queue<PosNoisePair> heap;
|
||||
|
||||
for (std::uint8_t local_x = 0; local_x < Chunk::size; ++local_x) {
|
||||
for (std::uint8_t local_y = 0; local_y < Chunk::size; ++local_y) {
|
||||
TilePos candidate{chunk_x, chunk_y, local_x, local_y};
|
||||
if (!is_suitable_for_coal(tilemap, candidate)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto [global_x, global_y] = candidate.to_global();
|
||||
auto noise_val = noise_.noise(global_x, global_y, 0x90);
|
||||
|
||||
heap.emplace(noise_val, candidate);
|
||||
if (heap.size() > config_.coal_seeds_per_chunk) {
|
||||
heap.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!heap.empty()) {
|
||||
seeds.push_back(heap.top().second);
|
||||
heap.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void CoalGenerationPass::evolve_coal_deposits(
|
||||
TileMap &tilemap, const std::vector<TilePos> &initial_seeds
|
||||
) {
|
||||
// Place initial seeds
|
||||
for (const auto &seed : initial_seeds) {
|
||||
Tile &tile = tilemap.get_tile(seed);
|
||||
if (tile.surface == SurfaceTileType::Empty) {
|
||||
tile.surface = SurfaceTileType::Coal;
|
||||
}
|
||||
}
|
||||
|
||||
// Evolve using cellular automata
|
||||
for (std::uint8_t step = 1; step <= config_.coal_evolution_steps; ++step) {
|
||||
std::vector<TilePos> new_coal_positions;
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
|
||||
// Iterate through all tiles
|
||||
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) {
|
||||
const Chunk &chunk = tilemap.get_chunk(chunk_x, chunk_y);
|
||||
|
||||
for (std::uint8_t local_x = 0; local_x < Chunk::size;
|
||||
++local_x) {
|
||||
for (std::uint8_t local_y = 0; local_y < Chunk::size;
|
||||
++local_y) {
|
||||
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
||||
const Tile &tile = tilemap.get_tile(pos);
|
||||
|
||||
// Skip if not suitable for coal
|
||||
if (!is_suitable_for_coal(tilemap, pos)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if already has coal
|
||||
if (tile.surface == SurfaceTileType::Coal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Count coal neighbors
|
||||
std::uint8_t coal_neighbors = count_coal_neighbors(
|
||||
tilemap, pos
|
||||
);
|
||||
|
||||
if (coal_neighbors > 0) {
|
||||
// Get biome for this position
|
||||
BiomeType biome = chunk.get_biome(local_x, local_y);
|
||||
auto biome_probability
|
||||
= get_biome_coal_growth_probability(biome);
|
||||
|
||||
// Base probability increases with more coal
|
||||
// neighbors
|
||||
std::uint32_t base_probability = coal_neighbors
|
||||
* config_.coal_growth_base_prob;
|
||||
std::uint8_t final_probability = std::clamp(
|
||||
base_probability * biome_probability / 255, 0u,
|
||||
255u
|
||||
);
|
||||
|
||||
// Use noise to decide whether to grow coal here
|
||||
auto [global_x, global_y] = pos.to_global();
|
||||
std::uint8_t sample = 0xFF
|
||||
& noise_.noise(global_x, global_y, step);
|
||||
|
||||
if (sample < final_probability) {
|
||||
new_coal_positions.push_back(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Place new coal
|
||||
for (const auto &pos : new_coal_positions) {
|
||||
Tile &tile = tilemap.get_tile(pos);
|
||||
if (tile.surface == SurfaceTileType::Empty) {
|
||||
tile.surface = SurfaceTileType::Coal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CoalGenerationPass::is_suitable_for_coal(
|
||||
const TileMap &tilemap, TilePos pos
|
||||
) const {
|
||||
const Tile &tile = tilemap.get_tile(pos);
|
||||
return (tile.base == BaseTileType::Sand || tile.base == BaseTileType::Land)
|
||||
&& tile.surface == SurfaceTileType::Empty;
|
||||
}
|
||||
|
||||
// Returns the coal growth probability for a given biome.
|
||||
// Higher values mean coal is more likely to spread in that biome.
|
||||
std::uint8_t CoalGenerationPass::get_biome_coal_growth_probability(
|
||||
BiomeType biome
|
||||
) const {
|
||||
switch (biome) {
|
||||
case BiomeType::Forest:
|
||||
return 255;
|
||||
case BiomeType::LukeOcean:
|
||||
return 204;
|
||||
case BiomeType::Savanna:
|
||||
return 153;
|
||||
case BiomeType::Plains:
|
||||
return 128;
|
||||
case BiomeType::SnowyPlains:
|
||||
case BiomeType::Ocean:
|
||||
return 102;
|
||||
case BiomeType::SnowyPeeks:
|
||||
return 77;
|
||||
case BiomeType::Desert:
|
||||
return 51;
|
||||
case BiomeType::FrozenOcean:
|
||||
return 26;
|
||||
default:
|
||||
std::unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
std::uint8_t CoalGenerationPass::count_coal_neighbors(
|
||||
const TileMap &tilemap, TilePos pos
|
||||
) const {
|
||||
std::uint8_t count = 0;
|
||||
auto neighbors = tilemap.get_neighbors(pos);
|
||||
|
||||
for (const auto neighbor_pos : neighbors) {
|
||||
const Tile &neighbor_tile = tilemap.get_tile(neighbor_pos);
|
||||
if (neighbor_tile.surface == SurfaceTileType::Coal) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void TerrainGenerator::coal_pass(TileMap &tilemap) {
|
||||
auto rng = master_rng_;
|
||||
master_rng_ = master_rng_.jump_96();
|
||||
auto noise_rng = master_rng_;
|
||||
master_rng_ = master_rng_.jump_96();
|
||||
CoalGenerationPass pass(config_, rng, noise_rng);
|
||||
pass(tilemap);
|
||||
}
|
||||
|
||||
} // namespace istd
|
@ -87,7 +87,7 @@ std::vector<TilePos> MineralClusterGenerationPass::generate_mineral_centers(
|
||||
mineral_type
|
||||
) // Use mineral type as seed variation
|
||||
);
|
||||
if (sample < config_.mineral_base_probe) {
|
||||
if (sample < config_.mineral_base_prob) {
|
||||
centers.push_back(candidate);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user