feat: add deepwater generation pass and extend tile types for improved terrain detail

Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
方而静 2025-08-02 18:57:05 +08:00
parent 2133af991a
commit 6b9d6c2463
Signed by: szTom
GPG Key ID: 072D999D60C6473C
5 changed files with 187 additions and 7 deletions

View File

@ -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

View File

@ -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

View File

@ -36,6 +36,7 @@ struct GenerationConfig {
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 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;
};
/**

View File

@ -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
};

View File

@ -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<std::int32_t>(radius);
dx <= static_cast<std::int32_t>(radius); ++dx) {
for (std::int32_t dy = -static_cast<std::int32_t>(radius);
dy <= static_cast<std::int32_t>(radius); ++dy) {
std::int32_t check_x
= static_cast<std::int32_t>(center_global_x) + dx;
std::int32_t check_y
= static_cast<std::int32_t>(center_global_y) + dy;
// Check bounds
if (check_x < 0 || check_y < 0
|| check_x >= static_cast<std::int32_t>(max_global_coord)
|| check_y >= static_cast<std::int32_t>(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<std::uint16_t>(check_x),
static_cast<std::uint16_t>(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);