From 6b9d6c246302d9663d15a76a2479af3e65f765bb Mon Sep 17 00:00:00 2001 From: szdytom Date: Sat, 2 Aug 2025 18:57:05 +0800 Subject: [PATCH] feat: add deepwater generation pass and extend tile types for improved terrain detail Signed-off-by: szdytom --- tilemap/examples/biome_demo.cpp | 11 ++- tilemap/examples/bmp.h | 1 + tilemap/include/generation.h | 63 ++++++++++++++++- tilemap/include/tile.h | 4 +- tilemap/src/generation.cpp | 115 ++++++++++++++++++++++++++++++++ 5 files changed, 187 insertions(+), 7 deletions(-) diff --git a/tilemap/examples/biome_demo.cpp b/tilemap/examples/biome_demo.cpp index baf8b89..d42c79c 100644 --- a/tilemap/examples/biome_demo.cpp +++ b/tilemap/examples/biome_demo.cpp @@ -22,6 +22,8 @@ BmpColors::Color get_tile_color(istd::BaseTileType type) { return BmpColors::WATER; case istd::BaseTileType::Ice: return BmpColors::ICE; + case istd::BaseTileType::Deepwater: + return BmpColors::DEEPWATER; default: return BmpColors::Color(128, 128, 128); // Gray for unknown types } @@ -78,7 +80,9 @@ void generate_bmp(const istd::TileMap &tilemap, const std::string &filename) { // Print statistics about the generated map void print_statistics(const istd::TileMap &tilemap) { - int tile_counts[5] = {0}; // Count for each base tile type + int tile_counts[6] = { + 0 + }; // Count for each base tile type (now 6 types including Deepwater) const int chunks_per_side = tilemap.get_size(); const int tiles_per_chunk = istd::Chunk::size; @@ -95,13 +99,14 @@ void print_statistics(const istd::TileMap &tilemap) { } } - const char *tile_names[] = {"Land", "Mountain", "Sand", "Water", "Ice"}; + const char *tile_names[] + = {"Land", "Mountain", "Sand", "Water", "Ice", "Deepwater"}; int total_tiles = chunks_per_side * chunks_per_side * tiles_per_chunk * tiles_per_chunk; std::println("\nTile Statistics:"); std::println("================"); - for (int i = 0; i < 5; ++i) { + for (int i = 0; i < 6; ++i) { double percentage = (double)tile_counts[i] / total_tiles * 100.0; std::println( "{:>10}: {:>8} ({:.1f}%)", tile_names[i], tile_counts[i], percentage diff --git a/tilemap/examples/bmp.h b/tilemap/examples/bmp.h index 45cc807..f8c7cb1 100644 --- a/tilemap/examples/bmp.h +++ b/tilemap/examples/bmp.h @@ -234,6 +234,7 @@ constexpr Color MOUNTAIN(139, 69, 19); // Saddle brown constexpr Color SAND(244, 164, 96); // Sandy brown constexpr Color WATER(30, 144, 255); // Dodger blue constexpr Color ICE(176, 224, 230); // Powder blue +constexpr Color DEEPWATER(0, 0, 139); // Dark blue } // namespace BmpColors #endif // BMP_H diff --git a/tilemap/include/generation.h b/tilemap/include/generation.h index 66588d1..bc3a4c1 100644 --- a/tilemap/include/generation.h +++ b/tilemap/include/generation.h @@ -35,7 +35,8 @@ struct GenerationConfig { = 2; // Number of steps for mountain smoothing cellular automata std::uint32_t mountain_remove_threshold = 10; // Threshold for mountain removal - std::uint32_t fill_threshold = 10; // Fill holes smaller than this size + std::uint32_t fill_threshold = 10; // Fill holes smaller than this size + std::uint32_t deepwater_radius = 2; // Radius for deepwater generation }; class BiomeGenerationPass { @@ -264,6 +265,66 @@ private: * @param tilemap The tilemap to process */ void hole_fill_pass(TileMap &tilemap); + + /** + * @brief Generate deepwater tiles in ocean biomes + * @param tilemap The tilemap to process + */ + void deepwater_pass(TileMap &tilemap); +}; + +class DeepwaterGenerationPass { +private: + std::uint32_t deepwater_radius_; + +public: + /** + * @brief Construct a deepwater generation pass + * @param deepwater_radius Radius to check for water tiles around each water + * tile (default: 1) + */ + explicit DeepwaterGenerationPass(std::uint32_t deepwater_radius); + + /** + * @brief Generate deepwater tiles in ocean biomes + * @param tilemap The tilemap to process + */ + void operator()(TileMap &tilemap); + +private: + /** + * @brief Check if a tile position is within a certain radius and all tiles + * are water + * @param tilemap The tilemap to check + * @param center_pos Center position to check around + * @param radius Radius to check within + * @return True if all tiles within radius are water + */ + +private: + /** + * @brief Process an ocean sub-chunk to generate deepwater tiles + * @param tilemap The tilemap to process + * @param chunk_x Chunk X coordinate + * @param chunk_y Chunk Y coordinate + * @param sub_pos Sub-chunk position within the chunk + */ + void process_ocean_subchunk( + TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y, + SubChunkPos sub_pos + ); + + /** + * @brief Check if a tile position is within a certain radius and all tiles + * are water or deepwater + * @param tilemap The tilemap to check + * @param center_pos Center position to check around + * @param radius Radius to check within + * @return True if all tiles within radius are water or deepwater + */ + bool is_surrounded_by_water( + const TileMap &tilemap, TilePos center_pos, std::uint32_t radius + ) const; }; /** diff --git a/tilemap/include/tile.h b/tilemap/include/tile.h index 1f30d7d..225a770 100644 --- a/tilemap/include/tile.h +++ b/tilemap/include/tile.h @@ -12,14 +12,12 @@ enum class BaseTileType : std::uint8_t { Sand, Water, Ice, + Deepwater, _count }; enum class SurfaceTileType : std::uint8_t { Empty, - Wood, - Structure, // Indicates this tile is occupied by a player-built structure, - // should never be natually generated. _count }; diff --git a/tilemap/src/generation.cpp b/tilemap/src/generation.cpp index fba866f..a5817c7 100644 --- a/tilemap/src/generation.cpp +++ b/tilemap/src/generation.cpp @@ -492,6 +492,7 @@ void TerrainGenerator::operator()(TileMap &tilemap) { base_tile_type_pass(tilemap); smoothen_mountains_pass(tilemap); hole_fill_pass(tilemap); + deepwater_pass(tilemap); } void TerrainGenerator::biome_pass(TileMap &tilemap) { @@ -522,6 +523,120 @@ void TerrainGenerator::hole_fill_pass(TileMap &tilemap) { pass(tilemap); } +void TerrainGenerator::deepwater_pass(TileMap &tilemap) { + DeepwaterGenerationPass pass(config_.deepwater_radius); + pass(tilemap); +} + +DeepwaterGenerationPass::DeepwaterGenerationPass(std::uint32_t deepwater_radius) + : deepwater_radius_(deepwater_radius) {} + +void DeepwaterGenerationPass::operator()(TileMap &tilemap) { + std::uint8_t map_size = tilemap.get_size(); + + // Iterate through all sub-chunks to check biomes efficiently + 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); + + // Process each sub-chunk + for (std::uint8_t sub_x = 0; sub_x < Chunk::subchunk_count; + ++sub_x) { + for (std::uint8_t sub_y = 0; sub_y < Chunk::subchunk_count; + ++sub_y) { + SubChunkPos sub_pos(sub_x, sub_y); + BiomeType biome = chunk.get_biome(sub_pos); + const BiomeProperties &properties + = get_biome_properties(biome); + + // Only process ocean biomes + if (!properties.is_ocean) { + continue; + } + + // Process all tiles in this ocean sub-chunk + process_ocean_subchunk(tilemap, chunk_x, chunk_y, sub_pos); + } + } + } + } +} + +void DeepwaterGenerationPass::process_ocean_subchunk( + TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y, + SubChunkPos sub_pos +) { + // Get starting tile coordinates for this sub-chunk + auto [start_x, start_y] = subchunk_to_tile_start(sub_pos); + + // Process all tiles in this sub-chunk + for (std::uint8_t local_x = start_x; + local_x < start_x + Chunk::subchunk_size; ++local_x) { + for (std::uint8_t local_y = start_y; + local_y < start_y + Chunk::subchunk_size; ++local_y) { + TilePos pos{chunk_x, chunk_y, local_x, local_y}; + + // Get the tile at this position + const Tile &tile = tilemap.get_tile(pos); + + // Only process water tiles + if (tile.base != BaseTileType::Water) { + continue; + } + + // Check if this water tile is surrounded by water/deepwater within + // the specified radius + if (is_surrounded_by_water(tilemap, pos, deepwater_radius_)) { + // Replace water with deepwater + Tile new_tile = tile; + new_tile.base = BaseTileType::Deepwater; + tilemap.set_tile(pos, new_tile); + } + } + } +} + +bool DeepwaterGenerationPass::is_surrounded_by_water( + const TileMap &tilemap, TilePos center_pos, std::uint32_t radius +) const { + auto [center_global_x, center_global_y] = center_pos.to_global(); + std::uint8_t map_size = tilemap.get_size(); + std::uint32_t max_global_coord = map_size * Chunk::size; + + // Check all tiles within the radius + for (std::int32_t dx = -static_cast(radius); + dx <= static_cast(radius); ++dx) { + for (std::int32_t dy = -static_cast(radius); + dy <= static_cast(radius); ++dy) { + std::int32_t check_x + = static_cast(center_global_x) + dx; + std::int32_t check_y + = static_cast(center_global_y) + dy; + + // Check bounds + if (check_x < 0 || check_y < 0 + || check_x >= static_cast(max_global_coord) + || check_y >= static_cast(max_global_coord)) { + return false; // Out of bounds, consider as non-water + } + + // Convert back to TilePos and check if it's water + TilePos check_pos = TilePos::from_global( + static_cast(check_x), + static_cast(check_y) + ); + + const Tile &check_tile = tilemap.get_tile(check_pos); + if (check_tile.base != BaseTileType::Water + && check_tile.base != BaseTileType::Deepwater) { + return false; // Found non-water tile within radius + } + } + } + + return true; // All tiles within radius are water or deepwater +} + void map_generate(TileMap &tilemap, const GenerationConfig &config) { TerrainGenerator generator(config); generator(tilemap);