From afc8d8fa1c28ff139944fed8fbfd318ad6cd1983 Mon Sep 17 00:00:00 2001 From: szdytom Date: Sun, 3 Aug 2025 21:21:37 +0800 Subject: [PATCH] feat: add coal generation Signed-off-by: szdytom --- tilemap/CMakeLists.txt | 1 + tilemap/docs/api.md | 26 +--- tilemap/examples/CMakeLists.txt | 2 +- tilemap/examples/bmp.h | 1 + tilemap/examples/tilemap_demo.cpp | 7 + tilemap/include/tilemap/generation.h | 22 ++- tilemap/include/tilemap/pass/coal.h | 86 +++++++++++ tilemap/include/tilemap/tile.h | 1 + tilemap/src/generation.cpp | 1 + tilemap/src/pass/coal.cpp | 207 +++++++++++++++++++++++++++ tilemap/src/pass/mineral_cluster.cpp | 2 +- 11 files changed, 330 insertions(+), 26 deletions(-) create mode 100644 tilemap/include/tilemap/pass/coal.h create mode 100644 tilemap/src/pass/coal.cpp diff --git a/tilemap/CMakeLists.txt b/tilemap/CMakeLists.txt index b79dbd6..04f7e6b 100644 --- a/tilemap/CMakeLists.txt +++ b/tilemap/CMakeLists.txt @@ -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 diff --git a/tilemap/docs/api.md b/tilemap/docs/api.md index 7db3e51..171b0f3 100644 --- a/tilemap/docs/api.md +++ b/tilemap/docs/api.md @@ -17,7 +17,7 @@ public: std::uint8_t get_size() const; Chunk& get_chunk(std::uint8_t chunk_x, std::uint8_t chunk_y); - + Tile& get_tile(TilePos pos); const Tile& get_tile(TilePos pos) const; void set_tile(TilePos pos, const Tile& tile); @@ -31,10 +31,10 @@ public: ```cpp struct Chunk { static constexpr uint8_t size = 64; - + Tile tiles[size][size]; BiomeType biome[16][16]; // Sub-chunk biomes - + BiomeType& get_biome(SubChunkPos pos); }; ``` @@ -62,7 +62,7 @@ struct Tile { struct TilePos { uint8_t chunk_x, chunk_y; // Chunk coordinates uint8_t local_x, local_y; // Tile within chunk (0-63) - + std::pair to_global() const; static TilePos from_global(std::uint16_t global_x, std::uint16_t global_y); }; @@ -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 }; ``` @@ -110,7 +96,7 @@ void map_generate(TileMap& tilemap, const GenerationConfig& config); ```cpp struct Seed { std::uint64_t s[2]; - + static Seed from_string(const char* str); static Seed device_random(); }; diff --git a/tilemap/examples/CMakeLists.txt b/tilemap/examples/CMakeLists.txt index 000a61b..daf9938 100644 --- a/tilemap/examples/CMakeLists.txt +++ b/tilemap/examples/CMakeLists.txt @@ -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) diff --git a/tilemap/examples/bmp.h b/tilemap/examples/bmp.h index f1468e3..0220e8b 100644 --- a/tilemap/examples/bmp.h +++ b/tilemap/examples/bmp.h @@ -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 diff --git a/tilemap/examples/tilemap_demo.cpp b/tilemap/examples/tilemap_demo.cpp index a7685c3..69b36d9 100644 --- a/tilemap/examples/tilemap_demo.cpp +++ b/tilemap/examples/tilemap_demo.cpp @@ -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( diff --git a/tilemap/include/tilemap/generation.h b/tilemap/include/tilemap/generation.h index c835cae..0df1be7 100644 --- a/tilemap/include/tilemap/generation.h +++ b/tilemap/include/tilemap/generation.h @@ -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 diff --git a/tilemap/include/tilemap/pass/coal.h b/tilemap/include/tilemap/pass/coal.h new file mode 100644 index 0000000..58bc57a --- /dev/null +++ b/tilemap/include/tilemap/pass/coal.h @@ -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 &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 &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 diff --git a/tilemap/include/tilemap/tile.h b/tilemap/include/tilemap/tile.h index 21ca86d..e579f4c 100644 --- a/tilemap/include/tilemap/tile.h +++ b/tilemap/include/tilemap/tile.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) diff --git a/tilemap/src/generation.cpp b/tilemap/src/generation.cpp index a890859..260649d 100644 --- a/tilemap/src/generation.cpp +++ b/tilemap/src/generation.cpp @@ -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) { diff --git a/tilemap/src/pass/coal.cpp b/tilemap/src/pass/coal.cpp new file mode 100644 index 0000000..ccf6a89 --- /dev/null +++ b/tilemap/src/pass/coal.cpp @@ -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 +#include +#include + +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 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 &seeds +) { + // Use a max heap to keep top N positions by noise value + using PosNoisePair = std::pair; + std::priority_queue 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 &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 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 diff --git a/tilemap/src/pass/mineral_cluster.cpp b/tilemap/src/pass/mineral_cluster.cpp index 2abdce7..dd5e62a 100644 --- a/tilemap/src/pass/mineral_cluster.cpp +++ b/tilemap/src/pass/mineral_cluster.cpp @@ -87,7 +87,7 @@ std::vector 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); } }