From 0b245e0483a423f544dfee5158be524b49967174 Mon Sep 17 00:00:00 2001 From: szdytom Date: Fri, 1 Aug 2025 15:32:36 +0800 Subject: [PATCH] feat: Add dual noise terrain generation demo - Introduced a new example `dual_noise_demo.cpp` to showcase the base and surface generation system. - Updated biome properties to include ice thresholds and surface feature parameters. - Enhanced chunk structure to support biomes for sub-chunks. - Refactored terrain generation logic to separate base terrain and surface feature generation. - Improved biome determination logic to include new biomes and their properties. - Updated tile representation to use enums for base and surface tile types. - Added detailed analysis of generated terrain and sample tile outputs in the demo. Signed-off-by: szdytom --- .github/copilot-instructions.md | 2 + BIOME_SYSTEM_GUIDE.md | 155 ---------- TILEMAP_USAGE.md | 92 ------ tilemap/examples/CMakeLists.txt | 20 +- tilemap/examples/advanced_biome_demo.cpp | 299 ------------------- tilemap/examples/advanced_demo.cpp | 175 ------------ tilemap/examples/basic_demo.cpp | 90 ------ tilemap/examples/biome_demo.cpp | 349 ++++++++++------------- tilemap/examples/dual_noise_demo.cpp | 164 +++++++++++ tilemap/include/biome.h | 29 +- tilemap/include/chunk.h | 18 +- tilemap/include/generation.h | 48 ++-- tilemap/include/tile.h | 56 ++-- tilemap/src/biome.cpp | 309 ++++++++++++-------- tilemap/src/generation.cpp | 94 ++++-- 15 files changed, 660 insertions(+), 1240 deletions(-) create mode 100644 .github/copilot-instructions.md delete mode 100644 BIOME_SYSTEM_GUIDE.md delete mode 100644 TILEMAP_USAGE.md delete mode 100644 tilemap/examples/advanced_biome_demo.cpp delete mode 100644 tilemap/examples/advanced_demo.cpp delete mode 100644 tilemap/examples/basic_demo.cpp create mode 100644 tilemap/examples/dual_noise_demo.cpp diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..9da8c94 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,2 @@ ++ Always use English for code comments. ++ Don't create getters or setters if they are not necessary. diff --git a/BIOME_SYSTEM_GUIDE.md b/BIOME_SYSTEM_GUIDE.md deleted file mode 100644 index ce5cea4..0000000 --- a/BIOME_SYSTEM_GUIDE.md +++ /dev/null @@ -1,155 +0,0 @@ -# 生物群系系统使用指南 - -## 概述 - -新的生物群系系统基于温度和湿度参数来决定地形生成,提供了更加真实和多样化的地图生成体验。 - -## 核心特性 - -### 1. 生物群系类型 -系统支持9种不同的生物群系,基于温度(冷/温带/热)和湿度(干燥/适中/潮湿)的组合: - -| 温度\湿度 | 干燥 | 适中 | 潮湿 | -|----------|------|------|------| -| **冷** | 苔原(Tundra) | 针叶林(Taiga) | 寒带雨林(ColdRainforest) | -| **温带** | 草原(Grassland) | 落叶林(DeciduousForest) | 温带雨林(TemperateRainforest) | -| **热** | 沙漠(Desert) | 热带草原(Savanna) | 热带雨林(TropicalRainforest) | - -### 2. 子区块系统 -- 每个64×64的区块被划分为4×4个子区块 -- 每个子区块大小为16×16瓦片 -- 每个子区块具有一致的生物群系 -- 生物群系基于子区块中心位置的气候值确定 - -### 3. 地形生成参数 -每个生物群系都有独特的地形生成参数: -- **水域阈值**: 决定水体分布 -- **沙地阈值**: 决定沙地区域 -- **森林阈值**: 决定树木覆盖 -- **山地阈值**: 决定山脉分布 -- **噪声参数**: 控制地形的细节程度 - -## 使用方法 - -### 基本使用 - -```cpp -#include "generation.h" -#include "tilemap.h" - -// 创建地图 -istd::TileMap tilemap(10); - -// 配置生成参数 -istd::GenerationConfig config; -config.seed = 12345; -config.temperature_scale = 0.005; // 温度变化尺度 -config.humidity_scale = 0.007; // 湿度变化尺度 - -// 生成地图 -istd::map_generate(tilemap, config); -``` - -### 使用TerrainGenerator类 - -```cpp -// 创建生成器实例 -istd::TerrainGenerator generator(config); - -// 生成地图 -generator.generate_map(tilemap); -// 生物群系数据会在生成完成后自动清理 -``` - -### 获取生物群系信息 - -```cpp -// 获取特定位置的气候值 -double global_x = chunk_x * 64 + sub_x * 16 + 8; -double global_y = chunk_y * 64 + sub_y * 16 + 8; - -// 使用噪声获取气候 -istd::PerlinNoise temp_noise(seed + 1000); -istd::PerlinNoise humidity_noise(seed + 2000); - -double temperature = temp_noise.octave_noise( - global_x * temperature_scale, global_y * temperature_scale, 3, 0.5); -double humidity = humidity_noise.octave_noise( - global_x * humidity_scale, global_y * humidity_scale, 3, 0.5); - -// 确定生物群系 -istd::BiomeType biome = istd::determine_biome(temperature, humidity); - -// 获取生物群系属性 -const istd::BiomeProperties& props = istd::get_biome_properties(biome); -``` - -## 坐标系统 - -### SubChunkPos结构 -```cpp -istd::SubChunkPos sub_pos(2, 1); // 子区块(2,1) -``` - -### 坐标转换 -```cpp -// 瓦片坐标转子区块坐标 -istd::SubChunkPos sub_pos = istd::tile_to_subchunk(local_x, local_y); - -// 子区块坐标转瓦片起始坐标 -auto [start_x, start_y] = istd::subchunk_to_tile_start(sub_pos); -``` - -## 配置参数详解 - -### GenerationConfig参数 - -- **seed**: 随机种子,控制整体地图布局 -- **temperature_scale**: 温度噪声的缩放因子 - - 较小值 (0.001-0.005): 大规模气候区域 - - 较大值 (0.01-0.02): 小规模气候变化 -- **humidity_scale**: 湿度噪声的缩放因子 - - 建议比temperature_scale稍大,产生不同的气候模式 - -### 生物群系属性示例 - -```cpp -// 沙漠生物群系 -{ - .water_threshold = 0.1, // 很少水体 - .sand_threshold = 0.7, // 大量沙地 - .wood_threshold = 0.85, // 很少植被 - .mountain_threshold = 0.9, // 适中山地 - .scale = 0.03, // 地形噪声尺度 - .octaves = 3, // 噪声层数 - .persistence = 0.4, // 噪声持续性 - .name = "Desert" -} -``` - -## 最佳实践 - -### 1. 气候尺度设置 -- 对于大陆级地图: `temperature_scale = 0.001-0.003` -- 对于区域级地图: `temperature_scale = 0.005-0.01` -- 对于局部地图: `temperature_scale = 0.01-0.02` - -### 2. 生物群系多样性 -- 使用不同的种子可以产生完全不同的气候分布 -- 调整温度和湿度尺度的比例可以改变生物群系的形状和分布 - -### 3. 性能考虑 -- 生物群系数据仅在生成过程中存储,生成完成后自动释放 -- 大地图建议分块生成以控制内存使用 - -## 演示程序 - -- `biome_demo`: 基础生物群系演示 -- `advanced_biome_demo`: 高级生物群系分析和可视化 -- `tilemap_demo`: 传统兼容模式演示 - -运行示例: -```bash -./build/tilemap/biome_demo -./build/tilemap/advanced_biome_demo -``` diff --git a/TILEMAP_USAGE.md b/TILEMAP_USAGE.md deleted file mode 100644 index 91aa462..0000000 --- a/TILEMAP_USAGE.md +++ /dev/null @@ -1,92 +0,0 @@ -# Perlin Noise Tilemap Generator - -这个项目实现了一个基于Perlin噪声的C++地图生成系统,可以生成包含不同地形类型的n×n区块地图。 - -## 功能特性 - -- **Perlin噪声生成**: 使用高质量的Perlin噪声算法生成自然的地形 -- **区块系统**: 支持最大100×100区块的大型地图 -- **TilePos坐标系统**: 使用chunk_x, chunk_y, local_x, local_y的四元坐标系统 -- **多种地形类型**: 支持空地、山地、森林、沙地、水域五种地形 -- **可配置生成**: 提供种子、缩放、八度等可调参数 - -## 使用示例 - -```cpp -#include "generation.h" -#include "tilemap.h" - -int main() { - // 创建一个10×10区块的地图 - istd::TileMap tilemap(10); - - // 配置生成参数 - istd::GenerationConfig config; - config.seed = 12345; - config.scale = 0.02; - config.octaves = 4; - config.persistence = 0.5; - - // 生成地图 - istd::map_generate(tilemap, config); - - // 访问特定位置的瓦片 - istd::TilePos pos{5, 5, 32, 32}; // 区块(5,5)的本地坐标(32,32) - istd::Tile tile = tilemap.get_tile(pos); - - // 修改瓦片 - istd::Tile mountain = istd::Tile::from_name("mountain"); - tilemap.set_tile(pos, mountain); - - return 0; -} -``` - -## 地形类型 - -- **空地** (`empty`): 类型0,显示为空格' ' -- **山地** (`mountain`): 类型1,显示为'^' -- **森林** (`wood`): 类型2,显示为'T' -- **沙地** (`sand`): 类型3,显示为'.' -- **水域** (`water`): 类型4,显示为'~' - -## 坐标系统 - -使用`TilePos`结构体表示地图中的位置: -- `chunk_x`, `chunk_y`: 区块坐标 (0-99) -- `local_x`, `local_y`: 区块内本地坐标 (0-63) - -每个区块包含64×64个瓦片。 - -## 构建和运行 - -```bash -# 构建项目 -cmake -S . -B build && cmake --build build - -# 运行基础演示 -./build/tilemap/tilemap_demo - -# 运行高级演示 -./build/tilemap/tilemap_advanced_demo -``` - -## API参考 - -### TileMap类 -- `TileMap(uint8_t size)`: 构造指定大小的地图 -- `get_tile(const TilePos& pos)`: 获取指定位置的瓦片 -- `set_tile(const TilePos& pos, const Tile& tile)`: 设置指定位置的瓦片 -- `get_chunk(uint8_t x, uint8_t y)`: 获取指定区块 - -### GenerationConfig结构体 -- `seed`: 随机种子 -- `scale`: 噪声坐标缩放 -- `octaves`: 噪声八度数量 -- `persistence`: 八度持续性 -- `*_threshold`: 各地形类型的噪声阈值 - -### PerlinNoise类 -- `PerlinNoise(uint64_t seed)`: 构造噪声生成器 -- `noise(double x, double y)`: 生成2D噪声值 -- `octave_noise(...)`: 生成多八度噪声值 diff --git a/tilemap/examples/CMakeLists.txt b/tilemap/examples/CMakeLists.txt index 00596d5..ed70802 100644 --- a/tilemap/examples/CMakeLists.txt +++ b/tilemap/examples/CMakeLists.txt @@ -3,22 +3,12 @@ cmake_minimum_required(VERSION 3.27) # Examples for the tilemap library # Each example is built as a separate executable -# Basic demonstration of tilemap generation -add_executable(basic_demo basic_demo.cpp) -target_link_libraries(basic_demo PRIVATE istd_tilemap) -target_include_directories(basic_demo PRIVATE ../include) +# Dual-noise terrain generation demonstration +add_executable(dual_noise_demo dual_noise_demo.cpp) +target_link_libraries(dual_noise_demo PRIVATE istd_tilemap) +target_include_directories(dual_noise_demo PRIVATE ../include) -# Advanced tilemap features demonstration -add_executable(advanced_demo advanced_demo.cpp) -target_link_libraries(advanced_demo PRIVATE istd_tilemap) -target_include_directories(advanced_demo PRIVATE ../include) - -# Basic biome system demonstration +# Biome system demonstration add_executable(biome_demo biome_demo.cpp) target_link_libraries(biome_demo PRIVATE istd_tilemap) target_include_directories(biome_demo PRIVATE ../include) - -# Advanced biome analysis and visualization -add_executable(advanced_biome_demo advanced_biome_demo.cpp) -target_link_libraries(advanced_biome_demo PRIVATE istd_tilemap) -target_include_directories(advanced_biome_demo PRIVATE ../include) diff --git a/tilemap/examples/advanced_biome_demo.cpp b/tilemap/examples/advanced_biome_demo.cpp deleted file mode 100644 index 08699f6..0000000 --- a/tilemap/examples/advanced_biome_demo.cpp +++ /dev/null @@ -1,299 +0,0 @@ -#include "biome.h" -#include "generation.h" -#include "tile.h" -#include "tilemap.h" -#include -#include -#include - -// Helper function to get tile character for display -char get_tile_char(const istd::Tile &tile) { - switch (tile.type) { - case 0: - return ' '; // empty - case 1: - return '^'; // mountain - case 2: - return 'T'; // wood - case 3: - return '.'; // sand - case 4: - return '~'; // water - default: - return '?'; - } -} - -// Helper function to get biome character for display -char get_biome_char(istd::BiomeType biome) { - switch (biome) { - case istd::BiomeType::Desert: - return 'D'; - case istd::BiomeType::Savanna: - return 'S'; - case istd::BiomeType::TropicalRainforest: - return 'R'; - case istd::BiomeType::Grassland: - return 'G'; - case istd::BiomeType::DeciduousForest: - return 'F'; - case istd::BiomeType::TemperateRainforest: - return 'M'; - case istd::BiomeType::Tundra: - return 'U'; - case istd::BiomeType::Taiga: - return 'A'; - case istd::BiomeType::ColdRainforest: - return 'C'; - default: - return '?'; - } -} - -// Function to create biome map visualization -void show_biome_map( - std::uint8_t map_size, const istd::GenerationConfig &config -) { - std::cout << "=== Biome Map Visualization ===" << std::endl; - - // Create noise generators for climate - istd::PerlinNoise temp_noise(config.seed + 1000); - istd::PerlinNoise humidity_noise(config.seed + 2000); - - // Generate biome map for visualization - for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) { - for (std::uint8_t sub_y = 0; sub_y < 4; ++sub_y) { - for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) { - for (std::uint8_t sub_x = 0; sub_x < 4; ++sub_x) { - // Calculate global position for this sub-chunk's center - double global_x = static_cast( - chunk_x * istd::Chunk::size + sub_x * 16 + 8 - ); - double global_y = static_cast( - chunk_y * istd::Chunk::size + sub_y * 16 + 8 - ); - - // Get climate values - double temperature = temp_noise.octave_noise( - global_x * config.temperature_scale, - global_y * config.temperature_scale, 3, 0.5 - ); - - double humidity = humidity_noise.octave_noise( - global_x * config.humidity_scale, - global_y * config.humidity_scale, 3, 0.5 - ); - - istd::BiomeType biome - = istd::determine_biome(temperature, humidity); - std::cout << get_biome_char(biome); - } - } - std::cout << std::endl; - } - } - - std::cout << std::endl << "Biome Legend:" << std::endl; - std::cout << "D=Desert, S=Savanna, R=TropicalRainforest, G=Grassland" - << std::endl; - std::cout << "F=DeciduousForest, M=TemperateRainforest, U=Tundra, A=Taiga, " - "C=ColdRainforest" - << std::endl; - std::cout << std::endl; -} - -// Function to analyze terrain distribution in different biomes -void analyze_biome_terrain( - const istd::TileMap &tilemap, std::uint8_t map_size, - const istd::GenerationConfig &config -) { - std::cout << "=== Biome Terrain Analysis ===" << std::endl; - - // Count tiles for each biome - std::map> biome_tile_counts; - - // Create noise generators for climate - istd::PerlinNoise temp_noise(config.seed + 1000); - istd::PerlinNoise humidity_noise(config.seed + 2000); - - // Sample terrain from different sub-chunks - for (std::uint8_t chunk_y = 0; chunk_y < map_size; - chunk_y += 2) { // Sample every other chunk - for (std::uint8_t chunk_x = 0; chunk_x < map_size; chunk_x += 2) { - for (std::uint8_t sub_y = 0; sub_y < 4; - sub_y += 2) { // Sample every other sub-chunk - for (std::uint8_t sub_x = 0; sub_x < 4; sub_x += 2) { - // Determine biome for this sub-chunk - double global_x = static_cast( - chunk_x * istd::Chunk::size + sub_x * 16 + 8 - ); - double global_y = static_cast( - chunk_y * istd::Chunk::size + sub_y * 16 + 8 - ); - - double temperature = temp_noise.octave_noise( - global_x * config.temperature_scale, - global_y * config.temperature_scale, 3, 0.5 - ); - - double humidity = humidity_noise.octave_noise( - global_x * config.humidity_scale, - global_y * config.humidity_scale, 3, 0.5 - ); - - istd::BiomeType biome - = istd::determine_biome(temperature, humidity); - - // Sample 8x8 area from center of sub-chunk - auto [start_x, start_y] = istd::subchunk_to_tile_start( - istd::SubChunkPos(sub_x, sub_y) - ); - for (std::uint8_t y = start_y + 4; y < start_y + 12; ++y) { - for (std::uint8_t x = start_x + 4; x < start_x + 12; - ++x) { - istd::TilePos pos{chunk_x, chunk_y, x, y}; - istd::Tile tile = tilemap.get_tile(pos); - biome_tile_counts[biome][tile.type]++; - } - } - } - } - } - } - - // Display results - for (const auto &[biome, tile_counts] : biome_tile_counts) { - const auto &props = istd::get_biome_properties(biome); - std::cout << props.name << ":" << std::endl; - - int total = 0; - for (const auto &[tile_type, count] : tile_counts) { - total += count; - } - - for (const auto &[tile_type, count] : tile_counts) { - double percentage = (static_cast(count) / total) * 100.0; - std::cout << " " << get_tile_char(istd::Tile{tile_type}) << ": " - << std::fixed << std::setprecision(1) << percentage << "%" - << std::endl; - } - std::cout << std::endl; - } -} - -int main() { - try { - std::cout << "=== Advanced Biome System Demo ===" << std::endl; - - // Create a larger map to show more biome diversity - std::uint8_t map_size = 8; - istd::TileMap tilemap(map_size); - - // Configure generation for more diverse biomes - istd::GenerationConfig config; - config.seed = 1337; - config.temperature_scale = 0.01; // More variation in temperature - config.humidity_scale = 0.012; // More variation in humidity - - std::cout << "Generating " << static_cast(map_size) << "x" - << static_cast(map_size) - << " chunk map with diverse biomes..." << std::endl; - - // Show biome distribution before generating terrain - show_biome_map(map_size, config); - - // Generate the terrain - istd::map_generate(tilemap, config); - std::cout << "Terrain generation complete!" << std::endl << std::endl; - - // Analyze terrain distribution in different biomes - analyze_biome_terrain(tilemap, map_size, config); - - // Show terrain samples from different biomes - std::cout << "=== Terrain Samples by Biome ===" << std::endl; - std::cout - << "Legend: ' '=empty, '^'=mountain, 'T'=wood, '.'=sand, '~'=water" - << std::endl - << std::endl; - - // Sample specific locations with known biomes - struct BiomeSample { - std::uint8_t chunk_x, chunk_y, sub_x, sub_y; - const char *expected_biome; - }; - - std::vector samples = { - {1, 1, 1, 1, "Top-left region" }, - {6, 1, 2, 1, "Top-right region" }, - {1, 6, 1, 2, "Bottom-left region" }, - {6, 6, 2, 2, "Bottom-right region"}, - {3, 3, 1, 1, "Center region" } - }; - - // Create noise generators for biome determination - istd::PerlinNoise temp_noise(config.seed + 1000); - istd::PerlinNoise humidity_noise(config.seed + 2000); - - for (const auto &sample : samples) { - // Determine biome for this sample - double global_x = static_cast( - sample.chunk_x * istd::Chunk::size + sample.sub_x * 16 + 8 - ); - double global_y = static_cast( - sample.chunk_y * istd::Chunk::size + sample.sub_y * 16 + 8 - ); - - double temperature = temp_noise.octave_noise( - global_x * config.temperature_scale, - global_y * config.temperature_scale, 3, 0.5 - ); - - double humidity = humidity_noise.octave_noise( - global_x * config.humidity_scale, - global_y * config.humidity_scale, 3, 0.5 - ); - - istd::BiomeType biome - = istd::determine_biome(temperature, humidity); - const auto &props = istd::get_biome_properties(biome); - - std::cout << sample.expected_biome << " - " << props.name - << " (T:" << std::fixed << std::setprecision(2) - << temperature << " H:" << humidity << "):" << std::endl; - - // Show 10x6 terrain sample - auto [start_x, start_y] = istd::subchunk_to_tile_start( - istd::SubChunkPos(sample.sub_x, sample.sub_y) - ); - for (std::uint8_t y = start_y + 3; y < start_y + 9; ++y) { - for (std::uint8_t x = start_x + 3; x < start_x + 13; ++x) { - istd::TilePos pos{sample.chunk_x, sample.chunk_y, x, y}; - istd::Tile tile = tilemap.get_tile(pos); - std::cout << get_tile_char(tile); - } - std::cout << std::endl; - } - std::cout << std::endl; - } - - // Show statistics - std::cout << "=== Map Statistics ===" << std::endl; - std::cout << "- Map size: " << static_cast(map_size) << "x" - << static_cast(map_size) << " chunks" << std::endl; - std::cout << "- Total sub-chunks: " - << static_cast(map_size) * static_cast(map_size) - * 16 - << std::endl; - std::cout << "- Climate scales: T=" << config.temperature_scale - << ", H=" << config.humidity_scale << std::endl; - std::cout << "- Each sub-chunk represents a 16x16 tile area with " - "consistent biome" - << std::endl; - - } catch (const std::exception &e) { - std::cerr << "Error: " << e.what() << std::endl; - return 1; - } - - return 0; -} diff --git a/tilemap/examples/advanced_demo.cpp b/tilemap/examples/advanced_demo.cpp deleted file mode 100644 index dba9590..0000000 --- a/tilemap/examples/advanced_demo.cpp +++ /dev/null @@ -1,175 +0,0 @@ -#include "generation.h" -#include "tile.h" -#include "tilemap.h" -#include -#include -#include - -// Helper function to get tile character for display -char get_tile_char(const istd::Tile &tile) { - switch (tile.type) { - case 0: - return ' '; // empty - case 1: - return '^'; // mountain - case 2: - return 'T'; // wood - case 3: - return '.'; // sand - case 4: - return '~'; // water - default: - return '?'; - } -} - -// Function to count tile types in a region -std::map count_tiles( - const istd::TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y, - std::uint8_t start_x, std::uint8_t start_y, std::uint8_t end_x, - std::uint8_t end_y -) { - std::map counts; - - for (std::uint8_t y = start_y; y < end_y; ++y) { - for (std::uint8_t x = start_x; x < end_x; ++x) { - istd::TilePos pos{chunk_x, chunk_y, x, y}; - istd::Tile tile = tilemap.get_tile(pos); - counts[tile.type]++; - } - } - - return counts; -} - -int main() { - try { - // Test with a larger map (8x8 chunks) - std::uint8_t map_size = 8; - istd::TileMap tilemap(map_size); - - std::cout << "=== Perlin Noise Tilemap Generation Demo ===" - << std::endl; - std::cout << "Generating " << static_cast(map_size) << "x" - << static_cast(map_size) << " chunk tilemap..." - << std::endl; - - // Configure generation parameters for more interesting terrain - istd::GenerationConfig config; - config.seed = 42; - config.scale = 0.02; // Smaller scale for larger features - config.octaves = 5; // More octaves for detail - config.persistence = 0.5; - - // Better balanced thresholds - config.water_threshold = 0.3; - config.sand_threshold = 0.45; - config.wood_threshold = 0.7; - config.mountain_threshold = 0.85; - - // Generate the map - istd::map_generate(tilemap, config); - - std::cout << "Map generated successfully!" << std::endl; - std::cout - << "Legend: ' '=empty, '^'=mountain, 'T'=wood, '.'=sand, '~'=water" - << std::endl - << std::endl; - - // Display multiple sample areas - std::cout << "Sample areas from different chunks:" << std::endl; - - // Show 4 corners of the map - std::vector> sample_chunks = { - {0, 0 }, - {map_size - 1, 0 }, - {0, map_size - 1}, - {map_size - 1, map_size - 1} - }; - - for (const auto &chunk_pos : sample_chunks) { - std::cout << "Chunk (" << static_cast(chunk_pos.first) << "," - << static_cast(chunk_pos.second) - << ") - top-left 20x10:" << std::endl; - - const istd::Chunk &chunk - = tilemap.get_chunk(chunk_pos.first, chunk_pos.second); - - // Display 20x10 area from this chunk - for (int y = 0; y < 10; ++y) { - for (int x = 0; x < 20; ++x) { - std::cout << get_tile_char(chunk.tiles[y][x]); - } - std::cout << std::endl; - } - - // Count tile distribution in this 20x10 area - auto counts = count_tiles( - tilemap, chunk_pos.first, chunk_pos.second, 0, 0, 20, 10 - ); - std::cout << "Distribution: "; - for (const auto &pair : counts) { - std::cout << get_tile_char(istd::Tile{pair.first}) << ":" - << pair.second << " "; - } - std::cout << std::endl << std::endl; - } - - // Overall statistics - std::cout << "=== Overall Map Statistics ===" << std::endl; - std::cout << "- Map size: " << static_cast(map_size) << "x" - << static_cast(map_size) << " chunks" << std::endl; - std::cout << "- Chunk size: " << static_cast(istd::Chunk::size) - << "x" << static_cast(istd::Chunk::size) << " tiles" - << std::endl; - std::cout << "- Total tiles: " - << static_cast(map_size) * static_cast(map_size) - * static_cast(istd::Chunk::size) - * static_cast(istd::Chunk::size) - << std::endl; - std::cout << "- Total chunks: " - << static_cast(map_size) * static_cast(map_size) - << std::endl; - - // Test TilePos functionality - std::cout << std::endl << "=== TilePos Testing ===" << std::endl; - std::vector test_positions = { - {0, 0, 0, 0}, // Top-left corner - {map_size - 1, map_size - 1, istd::Chunk::size - 1, - istd::Chunk::size - 1 }, // Bottom-right corner - {map_size / 2, map_size / 2, istd::Chunk::size / 2, - istd::Chunk::size / 2 }, // Center - }; - - for (const auto &pos : test_positions) { - istd::Tile tile = tilemap.get_tile(pos); - std::cout << "Tile at chunk(" << static_cast(pos.chunk_x) - << "," << static_cast(pos.chunk_y) << ") local(" - << static_cast(pos.local_x) << "," - << static_cast(pos.local_y) - << "): " << get_tile_char(tile) << " (type " - << static_cast(tile.type) << ")" << std::endl; - } - - // Test tile modification - std::cout << std::endl << "=== Tile Modification Test ===" << std::endl; - istd::TilePos modify_pos{1, 1, 30, 30}; - istd::Tile original_tile = tilemap.get_tile(modify_pos); - std::cout << "Original tile: " << get_tile_char(original_tile) - << std::endl; - - // Change it to mountain - istd::Tile mountain_tile = istd::Tile::from_name("mountain"); - tilemap.set_tile(modify_pos, mountain_tile); - - istd::Tile modified_tile = tilemap.get_tile(modify_pos); - std::cout << "Modified tile: " << get_tile_char(modified_tile) - << std::endl; - - } catch (const std::exception &e) { - std::cerr << "Error: " << e.what() << std::endl; - return 1; - } - - return 0; -} diff --git a/tilemap/examples/basic_demo.cpp b/tilemap/examples/basic_demo.cpp deleted file mode 100644 index 109862f..0000000 --- a/tilemap/examples/basic_demo.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "generation.h" -#include "tile.h" -#include "tilemap.h" -#include - -// Helper function to get tile character for display -char get_tile_char(const istd::Tile &tile) { - switch (tile.type) { - case 0: - return ' '; // empty - case 1: - return '^'; // mountain - case 2: - return 'T'; // wood - case 3: - return '.'; // sand - case 4: - return '~'; // water - default: - return '?'; - } -} - -int main() { - try { - std::cout << "=== Perlin Noise Tilemap Generator Demo ===" << std::endl; - - // Create a 6x6 chunk tilemap - std::uint8_t map_size = 6; - istd::TileMap tilemap(map_size); - - // Configure generation parameters - istd::GenerationConfig config; - config.seed = 2024; - config.scale = 0.03; - config.octaves = 4; - config.persistence = 0.6; - - // Generate the map using Perlin noise - std::cout << "Generating " << static_cast(map_size) << "x" - << static_cast(map_size) - << " chunk map with Perlin noise..." << std::endl; - istd::map_generate(tilemap, config); - std::cout << "Generation complete!" << std::endl << std::endl; - - // Display a sample area - std::cout << "Sample from center chunk (3,3):" << std::endl; - std::cout - << "Legend: ' '=empty, '^'=mountain, 'T'=wood, '.'=sand, '~'=water" - << std::endl; - - const istd::Chunk ¢er_chunk = tilemap.get_chunk(3, 3); - for (int y = 20; y < 44; ++y) { - for (int x = 20; x < 44; ++x) { - std::cout << get_tile_char(center_chunk.tiles[y][x]); - } - std::cout << std::endl; - } - - // Demonstrate TilePos usage - std::cout << std::endl << "TilePos demonstration:" << std::endl; - istd::TilePos test_pos{2, 3, 15, 25}; - istd::Tile original = tilemap.get_tile(test_pos); - std::cout << "Tile at chunk(2,3) local(15,25): " - << get_tile_char(original) << std::endl; - - // Modify the tile - istd::Tile water = istd::Tile::from_name("water"); - tilemap.set_tile(test_pos, water); - istd::Tile modified = tilemap.get_tile(test_pos); - std::cout << "After setting to water: " << get_tile_char(modified) - << std::endl; - - std::cout << std::endl << "Map Statistics:" << std::endl; - std::cout << "- Total chunks: " << static_cast(map_size * map_size) - << std::endl; - std::cout << "- Total tiles: " - << static_cast(map_size * map_size * 64 * 64) - << std::endl; - std::cout << "- Memory usage: ~" - << static_cast(map_size * map_size * 64 * 64) << " bytes" - << std::endl; - - } catch (const std::exception &e) { - std::cerr << "Error: " << e.what() << std::endl; - return 1; - } - - return 0; -} diff --git a/tilemap/examples/biome_demo.cpp b/tilemap/examples/biome_demo.cpp index 7b7d150..f77bd42 100644 --- a/tilemap/examples/biome_demo.cpp +++ b/tilemap/examples/biome_demo.cpp @@ -1,226 +1,187 @@ #include "biome.h" #include "generation.h" -#include "tile.h" #include "tilemap.h" #include #include -#include -// Helper function to get tile character for display -char get_tile_char(const istd::Tile &tile) { - switch (tile.type) { - case 0: - return ' '; // empty - case 1: - return '^'; // mountain - case 2: - return 'T'; // wood - case 3: - return '.'; // sand - case 4: - return '~'; // water - default: - return '?'; - } -} +using namespace istd; -// Helper function to get biome character for display -char get_biome_char(istd::BiomeType biome) { +// Function to get character representation for biome visualization +char get_biome_char(BiomeType biome) { switch (biome) { - case istd::BiomeType::Desert: + case BiomeType::Desert: return 'D'; - case istd::BiomeType::Savanna: + case BiomeType::Savanna: return 'S'; - case istd::BiomeType::TropicalRainforest: - return 'R'; - case istd::BiomeType::Grassland: + case BiomeType::TropicalRainforest: + return 'T'; + case BiomeType::Grassland: return 'G'; - case istd::BiomeType::DeciduousForest: + case BiomeType::DeciduousForest: return 'F'; - case istd::BiomeType::TemperateRainforest: - return 'M'; - case istd::BiomeType::Tundra: + case BiomeType::TemperateRainforest: + return 'R'; + case BiomeType::Tundra: return 'U'; - case istd::BiomeType::Taiga: + case BiomeType::Taiga: return 'A'; - case istd::BiomeType::ColdRainforest: - return 'C'; - default: - return '?'; + case BiomeType::FrozenOcean: + return 'O'; } + return '?'; } -// Function to analyze biome distribution in a chunk -void analyze_chunk_biomes( - const istd::TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y -) { - std::cout << "Analyzing chunk (" << static_cast(chunk_x) << "," - << static_cast(chunk_y) - << ") biome distribution:" << std::endl; - - // Create a temporary generator to get climate data - istd::GenerationConfig config; - config.seed = 12345; - istd::TerrainGenerator temp_generator(config); - - // Count biomes in each sub-chunk - std::map biome_counts; - - for (std::uint8_t sub_y = 0; sub_y < 4; ++sub_y) { - for (std::uint8_t sub_x = 0; sub_x < 4; ++sub_x) { - // Calculate global position for this sub-chunk's center - double global_x = static_cast( - chunk_x * istd::Chunk::size + sub_x * 16 + 8 - ); - double global_y = static_cast( - chunk_y * istd::Chunk::size + sub_y * 16 + 8 - ); - - // Get climate values (we need to recreate this logic since the - // generator's method is private) - istd::PerlinNoise temp_noise(config.seed + 1000); - istd::PerlinNoise humidity_noise(config.seed + 2000); - - double temperature = temp_noise.octave_noise( - global_x * config.temperature_scale, - global_y * config.temperature_scale, 3, 0.5 - ); - - double humidity = humidity_noise.octave_noise( - global_x * config.humidity_scale, - global_y * config.humidity_scale, 3, 0.5 - ); - - istd::BiomeType biome - = istd::determine_biome(temperature, humidity); - biome_counts[biome]++; - - std::cout << get_biome_char(biome); - if (sub_x == 3) { - std::cout << std::endl; - } - } +// Function to get biome name as string +const char *get_biome_name(BiomeType biome) { + switch (biome) { + case BiomeType::Desert: + return "Desert"; + case BiomeType::Savanna: + return "Savanna"; + case BiomeType::TropicalRainforest: + return "Tropical Rainforest"; + case BiomeType::Grassland: + return "Grassland"; + case BiomeType::DeciduousForest: + return "Deciduous Forest"; + case BiomeType::TemperateRainforest: + return "Temperate Rainforest"; + case BiomeType::Tundra: + return "Tundra"; + case BiomeType::Taiga: + return "Taiga"; + case BiomeType::FrozenOcean: + return "Frozen Ocean"; } - - std::cout << std::endl << "Biome legend:" << std::endl; - std::cout << "D=Desert, S=Savanna, R=TropicalRainforest, G=Grassland" - << std::endl; - std::cout << "F=DeciduousForest, M=TemperateRainforest, U=Tundra, A=Taiga, " - "C=ColdRainforest" - << std::endl; - - std::cout << std::endl << "Sub-chunk counts:" << std::endl; - for (const auto &[biome, count] : biome_counts) { - const auto &props = istd::get_biome_properties(biome); - std::cout << "- " << props.name << ": " << count << " sub-chunks" - << std::endl; - } - std::cout << std::endl; -} - -// Function to show terrain sample from a specific sub-chunk -void show_subchunk_terrain( - const istd::TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y, - std::uint8_t sub_x, std::uint8_t sub_y -) { - std::cout << "Terrain sample from chunk(" << static_cast(chunk_x) - << "," << static_cast(chunk_y) << ") sub-chunk(" - << static_cast(sub_x) << "," << static_cast(sub_y) - << "):" << std::endl; - - auto [start_x, start_y] - = istd::subchunk_to_tile_start(istd::SubChunkPos(sub_x, sub_y)); - - // Show 8x8 sample from the center of the sub-chunk - std::uint8_t sample_start_x = start_x + 4; - std::uint8_t sample_start_y = start_y + 4; - - for (std::uint8_t y = sample_start_y; y < sample_start_y + 8; ++y) { - for (std::uint8_t x = sample_start_x; x < sample_start_x + 8; ++x) { - istd::TilePos pos{chunk_x, chunk_y, x, y}; - istd::Tile tile = tilemap.get_tile(pos); - std::cout << get_tile_char(tile); - } - std::cout << std::endl; - } - std::cout << std::endl; + return "Unknown"; } int main() { - try { - std::cout << "=== Biome-Based Terrain Generation Demo ===" << std::endl; + std::cout << "=== Biome System Demo ===" << std::endl; + std::cout << "This demo shows biome distribution based on temperature and " + "humidity" + << std::endl; + std::cout << "Legend:" << std::endl; + std::cout << " D = Desert S = Savanna T = Tropical" + << std::endl; + std::cout << " G = Grassland F = Deciduous R = Temp.Rain" + << std::endl; + std::cout << " U = Tundra A = Taiga O = Frozen" + << std::endl; + std::cout << std::endl; - // Create a 6x6 chunk tilemap - std::uint8_t map_size = 6; - istd::TileMap tilemap(map_size); + // Create a tilemap for biome demonstration + constexpr std::uint8_t map_size = 3; // 3x3 chunks for compact display + TileMap tilemap(map_size); - // Configure generation with biome system - istd::GenerationConfig config; - config.seed = 42; - config.temperature_scale = 0.003; // Larger climate features - config.humidity_scale = 0.004; + // Configure generation with good biome variety + GenerationConfig config; + config.seed = 98765; + config.temperature_scale = 0.003; // Larger temperature zones + config.humidity_scale = 0.004; // Larger humidity zones - std::cout << "Generating " << static_cast(map_size) << "x" - << static_cast(map_size) - << " chunk map with biome system..." << std::endl; + std::cout << "Generating " << static_cast(map_size) << "x" + << static_cast(map_size) << " chunks..." << std::endl; - // Generate the map using the new biome-based system - istd::map_generate(tilemap, config); + // Generate the map + map_generate(tilemap, config); - std::cout << "Generation complete!" << std::endl << std::endl; + std::cout << "Generation complete!" << std::endl << std::endl; - // Analyze biome distribution in a few chunks - std::cout << "=== Biome Analysis ===" << std::endl; - analyze_chunk_biomes(tilemap, 1, 1); - analyze_chunk_biomes(tilemap, 4, 4); + // Show biome properties + std::cout << "=== Biome Properties ===" << std::endl; + const BiomeType all_biomes[] + = {BiomeType::Desert, + BiomeType::Savanna, + BiomeType::TropicalRainforest, + BiomeType::Grassland, + BiomeType::DeciduousForest, + BiomeType::TemperateRainforest, + BiomeType::Tundra, + BiomeType::Taiga, + BiomeType::FrozenOcean}; - // Show terrain samples from different sub-chunks - std::cout << "=== Terrain Samples ===" << std::endl; - std::cout - << "Legend: ' '=empty, '^'=mountain, 'T'=wood, '.'=sand, '~'=water" - << std::endl - << std::endl; - - show_subchunk_terrain(tilemap, 1, 1, 0, 0); // Top-left sub-chunk - show_subchunk_terrain(tilemap, 1, 1, 3, 3); // Bottom-right sub-chunk - show_subchunk_terrain(tilemap, 4, 4, 1, 2); // Different chunk - - // Show overall statistics - std::cout << "=== Map Statistics ===" << std::endl; - std::cout << "- Map size: " << static_cast(map_size) << "x" - << static_cast(map_size) << " chunks" << std::endl; - std::cout << "- Sub-chunks per chunk: 4x4 (16 total)" << std::endl; - std::cout << "- Tiles per sub-chunk: 16x16 (256 total)" << std::endl; - std::cout << "- Total sub-chunks: " - << static_cast(map_size) * static_cast(map_size) - * 16 - << std::endl; - std::cout << "- Total tiles: " - << static_cast(map_size) * static_cast(map_size) - * static_cast(istd::Chunk::size) - * static_cast(istd::Chunk::size) - << std::endl; - - // Test coordinate conversion functions - std::cout << std::endl << "=== Coordinate System Test ===" << std::endl; - - // Test tile to sub-chunk conversion - istd::SubChunkPos sub_pos = istd::tile_to_subchunk(25, 40); - std::cout << "Tile (25,40) is in sub-chunk (" - << static_cast(sub_pos.sub_x) << "," - << static_cast(sub_pos.sub_y) << ")" << std::endl; - - // Test sub-chunk to tile conversion - auto [tile_start_x, tile_start_y] - = istd::subchunk_to_tile_start(istd::SubChunkPos(2, 1)); - std::cout << "Sub-chunk (2,1) starts at tile (" - << static_cast(tile_start_x) << "," - << static_cast(tile_start_y) << ")" << std::endl; - - } catch (const std::exception &e) { - std::cerr << "Error: " << e.what() << std::endl; - return 1; + for (BiomeType biome : all_biomes) { + const BiomeProperties &props = get_biome_properties(biome); + std::cout << get_biome_name(biome) << " (" << get_biome_char(biome) + << "):" << std::endl; + std::cout << " Base terrain - Scale: " << props.base_scale + << " Octaves: " << props.base_octaves + << " Persistence: " << props.base_persistence << std::endl; + std::cout << " Surface features - Scale: " << props.surface_scale + << " Octaves: " << props.surface_octaves + << " Persistence: " << props.surface_persistence << std::endl; + std::cout << " Thresholds - Water: " << props.water_threshold + << " Sand: " << props.sand_threshold + << " Mountain: " << props.mountain_threshold << std::endl; + std::cout << " Features - Wood: " << props.wood_threshold + << " Snow: " << props.snow_threshold << std::endl; + std::cout << std::endl; } + // Sample some actual generated tiles to show the system working + std::cout << "=== Sample Generated Tiles ===" << std::endl; + for (int i = 0; i < 9; ++i) { + std::uint8_t chunk_x = i % map_size; + std::uint8_t chunk_y = i / map_size; + std::uint8_t local_x = 32; // Middle of chunk + std::uint8_t local_y = 32; + + TilePos pos{chunk_x, chunk_y, local_x, local_y}; + Tile tile = tilemap.get_tile(pos); + + std::cout << "Chunk (" << static_cast(chunk_x) << "," + << static_cast(chunk_y) << "): "; + + // Convert base and surface types to readable strings + const char *base_names[] = {"Land", "Mountain", "Sand", "Water", "Ice"}; + const char *surface_names[] = {"Empty", "Wood", "Snow"}; + + std::cout << "Base=" << base_names[static_cast(tile.base)] + << " Surface=" + << surface_names[static_cast(tile.surface)] << std::endl; + } + + // Show climate zones demonstration + std::cout << std::endl << "=== Climate Zone Demonstration ===" << std::endl; + std::cout + << "Temperature/Humidity grid (each position shows resulting biome):" + << std::endl; + std::cout << "Humidity →" << std::endl; + + for (int temp = 0; temp < 3; ++temp) { + if (temp == 1) { + std::cout << "T "; + } else { + std::cout << "e "; + } + + for (int humid = 0; humid < 3; ++humid) { + double temperature + = static_cast(temp) / 2.0; // 0.0, 0.5, 1.0 + double humidity = static_cast(humid) / 2.0; + + BiomeType biome = determine_biome(temperature, humidity); + std::cout << get_biome_char(biome) << " "; + } + + if (temp == 1) { + std::cout << " ← Temperature"; + } + std::cout << std::endl; + + if (temp == 0) { + std::cout << "m "; + } else if (temp == 2) { + std::cout << "p "; + } else { + std::cout << " "; + } + } + + std::cout << std::endl; + std::cout << "Where: Cold(top) → Hot(bottom), Dry(left) → Wet(right)" + << std::endl; + return 0; } diff --git a/tilemap/examples/dual_noise_demo.cpp b/tilemap/examples/dual_noise_demo.cpp new file mode 100644 index 0000000..b550009 --- /dev/null +++ b/tilemap/examples/dual_noise_demo.cpp @@ -0,0 +1,164 @@ +#include "generation.h" +#include "tilemap.h" +#include +#include + +using namespace istd; + +// Function to get character representation of base tile type +char get_base_char(BaseTileType base_type) { + switch (base_type) { + case BaseTileType::Land: + return '.'; + case BaseTileType::Mountain: + return '^'; + case BaseTileType::Sand: + return '~'; + case BaseTileType::Water: + return 'W'; + case BaseTileType::Ice: + return 'I'; + } + return '?'; +} + +// Function to get character representation of surface tile type +char get_surface_char(SurfaceTileType surface_type) { + switch (surface_type) { + case SurfaceTileType::Empty: + return ' '; + case SurfaceTileType::Wood: + return 'T'; + case SurfaceTileType::Snow: + return 'S'; + case SurfaceTileType::Structure: + return 'H'; + } + return '?'; +} + +// Function to get combined display character for a tile +char get_tile_char(const Tile &tile) { + // Surface features take priority for display + if (tile.surface != SurfaceTileType::Empty) { + return get_surface_char(tile.surface); + } + return get_base_char(tile.base); +} + +int main() { + std::cout << "=== Dual-Noise Terrain Generation Demo ===" << std::endl; + std::cout << "This demo shows the new base + surface generation system" + << std::endl; + std::cout << "Legend:" << std::endl; + std::cout + << " . = Land ^ = Mountain ~ = Sand W = Water I = Ice" + << std::endl; + std::cout << " T = Trees S = Snow H = Structure" << std::endl; + std::cout << std::endl; + + // Create a small tilemap for demonstration + constexpr std::uint8_t map_size = 4; // 4x4 chunks + TileMap tilemap(map_size); + + // Configure generation with different seeds for variety + GenerationConfig config; + config.seed = 12345; + config.temperature_scale = 0.008; // Slightly larger temperature variation + config.humidity_scale = 0.006; // Slightly larger humidity variation + + std::cout << "Generating " << static_cast(map_size) << "x" + << static_cast(map_size) << " chunks (" + << static_cast(map_size * Chunk::size) << "x" + << static_cast(map_size * Chunk::size) << " tiles)..." + << std::endl; + + // Generate the map + map_generate(tilemap, config); + + std::cout << "Generation complete!" << std::endl << std::endl; + + // Display the entire map + std::cout << "Complete Map:" << std::endl; + for (std::uint8_t global_y = 0; global_y < map_size * Chunk::size; + ++global_y) { + for (std::uint8_t global_x = 0; global_x < map_size * Chunk::size; + ++global_x) { + // Convert global coordinates to chunk and local coordinates + std::uint8_t chunk_x = global_x / Chunk::size; + std::uint8_t chunk_y = global_y / Chunk::size; + std::uint8_t local_x = global_x % Chunk::size; + std::uint8_t local_y = global_y % Chunk::size; + + TilePos pos{chunk_x, chunk_y, local_x, local_y}; + Tile tile = tilemap.get_tile(pos); + std::cout << get_tile_char(tile); + } + std::cout << std::endl; + } + + std::cout << std::endl; + + // Show detailed analysis for each chunk + std::cout << "=== Chunk-by-Chunk Analysis ===" << std::endl; + for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) { + for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) { + std::cout << "Chunk (" << static_cast(chunk_x) << "," + << static_cast(chunk_y) << "):" << std::endl; + + // Count tile types in this chunk + int base_counts[5] = {0}; // Land, Mountain, Sand, Water, Ice + int surface_counts[4] = {0}; // Empty, Wood, Snow, Structure + + for (std::uint8_t local_y = 0; local_y < Chunk::size; ++local_y) { + for (std::uint8_t local_x = 0; local_x < Chunk::size; + ++local_x) { + TilePos pos{chunk_x, chunk_y, local_x, local_y}; + Tile tile = tilemap.get_tile(pos); + + base_counts[static_cast(tile.base)]++; + surface_counts[static_cast(tile.surface)]++; + } + } + + std::cout << " Base terrain: Land=" << base_counts[0] + << " Mountain=" << base_counts[1] + << " Sand=" << base_counts[2] + << " Water=" << base_counts[3] + << " Ice=" << base_counts[4] << std::endl; + + std::cout << " Surface features: Empty=" << surface_counts[0] + << " Wood=" << surface_counts[1] + << " Snow=" << surface_counts[2] + << " Structure=" << surface_counts[3] << std::endl; + } + } + + std::cout << std::endl; + std::cout << "=== Sample Detailed Tiles ===" << std::endl; + + // Show some individual tile details + std::vector sample_positions = { + {0, 0, 10, 10}, + {1, 1, 30, 30}, + {2, 2, 50, 50}, + {3, 3, 60, 60} + }; + + for (const auto &pos : sample_positions) { + if (pos.chunk_x < map_size && pos.chunk_y < map_size + && pos.local_x < Chunk::size && pos.local_y < Chunk::size) { + Tile tile = tilemap.get_tile(pos); + std::cout << "Tile at chunk(" << static_cast(pos.chunk_x) + << "," << static_cast(pos.chunk_y) << ") local(" + << static_cast(pos.local_x) << "," + << static_cast(pos.local_y) << "): "; + + std::cout << "Base=" << get_base_char(tile.base) + << " Surface=" << get_surface_char(tile.surface) + << " Display=" << get_tile_char(tile) << std::endl; + } + } + + return 0; +} diff --git a/tilemap/include/biome.h b/tilemap/include/biome.h index 26cbc40..3b88539 100644 --- a/tilemap/include/biome.h +++ b/tilemap/include/biome.h @@ -1,5 +1,5 @@ -#ifndef ISTD_TILEMAP_BIOME_H -#define ISTD_TILEMAP_BIOME_H +#ifndef TILEMAP_BIOME_H +#define TILEMAP_BIOME_H #include #include @@ -16,21 +16,30 @@ enum class BiomeType : std::uint8_t { TemperateRainforest = 5, // Temperate & Wet Tundra = 6, // Cold & Dry Taiga = 7, // Cold & Moderate - ColdRainforest = 8 // Cold & Wet + FrozenOcean = 8 // Cold & Wet }; // Biome properties for terrain generation struct BiomeProperties { - // Terrain thresholds (0.0 - 1.0) + // Base terrain thresholds (0.0 - 1.0) double water_threshold; - double sand_threshold; - double wood_threshold; double mountain_threshold; + double sand_threshold; + double ice_threshold; - // Noise parameters - double scale; - int octaves; - double persistence; + // Surface coverage thresholds (0.0 - 1.0) + double wood_threshold; + double snow_threshold; + + // Noise parameters for base terrain + double base_scale; + int base_octaves; + double base_persistence; + + // Noise parameters for surface features + double surface_scale; + int surface_octaves; + double surface_persistence; // Biome name for debugging std::string_view name; diff --git a/tilemap/include/chunk.h b/tilemap/include/chunk.h index 5f5882a..b81b4b9 100644 --- a/tilemap/include/chunk.h +++ b/tilemap/include/chunk.h @@ -4,6 +4,10 @@ #include namespace istd { + +// Forward declaration +enum class BiomeType : std::uint8_t; + // Represents the position of a tile in the map, using chunk and local // coordinates struct TilePos { @@ -14,8 +18,20 @@ struct TilePos { }; struct Chunk { + // Size of a chunk in tiles (64 x 64) static constexpr uint8_t size = 64; - Tile tiles[size][size]; // 64x64 array of tile types + + // Each sub-chunk is 16x16 tiles + static constexpr uint8_t subchunk_size = 16; + + // Number of sub-chunks in each dimension + static constexpr uint8_t subchunk_count = size / subchunk_size; + + // 64x64 array of tile types + Tile tiles[size][size]; + + // 4x4 array of biomes for sub-chunks + BiomeType biome[subchunk_count][subchunk_count]; }; } // namespace istd diff --git a/tilemap/include/generation.h b/tilemap/include/generation.h index 97a6eb6..96e1bec 100644 --- a/tilemap/include/generation.h +++ b/tilemap/include/generation.h @@ -17,30 +17,16 @@ struct GenerationConfig { // Climate noise parameters double temperature_scale = 0.005; // Scale for temperature noise double humidity_scale = 0.007; // Scale for humidity noise - - // Base terrain parameters (used as fallback) - double scale = 0.02; - int octaves = 4; - double persistence = 0.5; - - // Legacy thresholds (for compatibility) - double water_threshold = 0.3; - double sand_threshold = 0.4; - double wood_threshold = 0.7; - double mountain_threshold = 0.8; }; // Terrain generator class that manages the generation process class TerrainGenerator { private: GenerationConfig config_; - PerlinNoise terrain_noise_; - PerlinNoise temperature_noise_; - PerlinNoise humidity_noise_; - - // Biome data for current generation (discarded after completion) - std::vector, 4>>> - chunk_biomes_; + PerlinNoise base_noise_; // For base terrain generation + PerlinNoise surface_noise_; // For surface feature generation + PerlinNoise temperature_noise_; // For temperature + PerlinNoise humidity_noise_; // For humidity public: /** @@ -58,9 +44,9 @@ public: private: /** * @brief Generate biome data for all chunks - * @param map_size Number of chunks per side + * @param tilemap The tilemap to generate biomes into */ - void generate_biomes(std::uint8_t map_size); + void generate_biomes(TileMap &tilemap); /** * @brief Generate terrain for a single chunk @@ -96,14 +82,28 @@ private: ) const; /** - * @brief Determine tile type based on noise value and biome properties - * @param noise_value Terrain noise value [0,1] + * @brief Determine base terrain type based on noise value and biome + * properties + * @param noise_value Base terrain noise value [0,1] * @param properties Biome properties to use - * @return The appropriate tile type + * @return The appropriate base tile type */ - Tile determine_tile_type( + BaseTileType determine_base_type( double noise_value, const BiomeProperties &properties ) const; + + /** + * @brief Determine surface feature type based on noise value and biome + * properties + * @param noise_value Surface feature noise value [0,1] + * @param properties Biome properties to use + * @param base_type The base terrain type (affects surface placement) + * @return The appropriate surface tile type + */ + SurfaceTileType determine_surface_type( + double noise_value, const BiomeProperties &properties, + BaseTileType base_type + ) const; }; /** diff --git a/tilemap/include/tile.h b/tilemap/include/tile.h index d38c29f..bfda0d0 100644 --- a/tilemap/include/tile.h +++ b/tilemap/include/tile.h @@ -6,40 +6,36 @@ namespace istd { -// Array of tile types -constexpr const char *_tiles_types[] = { - "empty", "mountain", "wood", "sand", "water", +enum class BaseTileType : std::uint8_t { + Land, + Mountain, + Sand, + Water, + Ice, + _count }; -constexpr std::size_t _tile_types_n - = sizeof(_tiles_types) / sizeof(_tiles_types[0]); +enum class SurfaceTileType : std::uint8_t { + Empty, + Wood, + Snow, + Structure, // Indicates this tile is occupied by a player-built structure, + // should never be natually generated. + _count +}; + +constexpr std::uint8_t base_tile_count + = static_cast(BaseTileType::_count); + +constexpr std::uint8_t surface_tile_count + = static_cast(SurfaceTileType::_count); + +static_assert(base_tile_count <= 16, "Base tile don't fit in 4 bits"); +static_assert(surface_tile_count <= 16, "Surface tile don't fit in 4 bits"); struct Tile { - std::uint8_t type; - - /** - * @brief Contruct a Tile with the given type. - * Use human readable strings as identifier in code without any runtime - * overhead - * @param type The tile type as a string - */ - static consteval Tile from_name(const char *name) { - // Find the index of the name in the _tiles_types array at compile time - for (std::size_t i = 0; i < _tile_types_n; ++i) { - const char *p = name; - const char *q = _tiles_types[i]; - // Compare strings character by character - while (*p && *q && *p == *q) { - ++p; - ++q; - } - if (*p == '\0' && *q == '\0') { - return Tile{static_cast(i)}; - } - } - - throw std::invalid_argument("Invalid tile type name"); - } + BaseTileType base : 4; + SurfaceTileType surface : 4; }; static_assert(sizeof(Tile) == 1); diff --git a/tilemap/src/biome.cpp b/tilemap/src/biome.cpp index bef1326..0062a0f 100644 --- a/tilemap/src/biome.cpp +++ b/tilemap/src/biome.cpp @@ -1,141 +1,198 @@ #include "biome.h" -#include #include #include +#include namespace istd { // Biome properties lookup table -constexpr std::array biome_properties = { - {// Desert: Hot & Dry - {.water_threshold = 0.1, - .sand_threshold = 0.7, - .wood_threshold = 0.85, - .mountain_threshold = 0.9, - .scale = 0.03, - .octaves = 3, - .persistence = 0.4, - .name = "Desert"}, - // Savanna: Hot & Moderate - {.water_threshold = 0.15, - .sand_threshold = 0.3, - .wood_threshold = 0.75, - .mountain_threshold = 0.88, - .scale = 0.025, - .octaves = 4, - .persistence = 0.5, - .name = "Savanna"}, - // TropicalRainforest: Hot & Wet - {.water_threshold = 0.25, - .sand_threshold = 0.35, - .wood_threshold = 0.8, - .mountain_threshold = 0.9, - .scale = 0.02, - .octaves = 5, - .persistence = 0.6, - .name = "Tropical Rainforest"}, - // Grassland: Temperate & Dry - {.water_threshold = 0.2, - .sand_threshold = 0.4, - .wood_threshold = 0.7, - .mountain_threshold = 0.85, - .scale = 0.035, - .octaves = 3, - .persistence = 0.45, - .name = "Grassland"}, - // DeciduousForest: Temperate & Moderate - {.water_threshold = 0.3, - .sand_threshold = 0.4, - .wood_threshold = 0.75, - .mountain_threshold = 0.87, - .scale = 0.025, - .octaves = 4, - .persistence = 0.55, - .name = "Deciduous Forest"}, - // TemperateRainforest: Temperate & Wet - {.water_threshold = 0.35, - .sand_threshold = 0.45, - .wood_threshold = 0.8, - .mountain_threshold = 0.9, - .scale = 0.02, - .octaves = 5, - .persistence = 0.6, - .name = "Temperate Rainforest"}, - // Tundra: Cold & Dry - {.water_threshold = 0.15, - .sand_threshold = 0.25, - .wood_threshold = 0.5, - .mountain_threshold = 0.8, - .scale = 0.04, - .octaves = 2, - .persistence = 0.3, - .name = "Tundra"}, - // Taiga: Cold & Moderate - {.water_threshold = 0.25, - .sand_threshold = 0.35, - .wood_threshold = 0.75, - .mountain_threshold = 0.85, - .scale = 0.03, - .octaves = 4, - .persistence = 0.5, - .name = "Taiga"}, - // ColdRainforest: Cold & Wet - {.water_threshold = 0.3, - .sand_threshold = 0.4, - .wood_threshold = 0.8, - .mountain_threshold = 0.9, - .scale = 0.025, - .octaves = 5, - .persistence = 0.6, - .name = "Cold Rainforest"} +constexpr std::array biome_properties = {{ + // Desert: Hot & Dry + { + .water_threshold = 0.1, + .mountain_threshold = 0.85, + .sand_threshold = 0.8, + .ice_threshold = 0.0, // No ice in desert + .wood_threshold = 0.05, // Very sparse vegetation + .snow_threshold = 0.0, // No snow in desert + .base_scale = 0.03, + .base_octaves = 3, + .base_persistence = 0.4, + .surface_scale = 0.05, + .surface_octaves = 2, + .surface_persistence = 0.3, + .name = "Desert" + }, + // Savanna: Hot & Moderate + { + .water_threshold = 0.15, + .mountain_threshold = 0.8, + .sand_threshold = 0.3, + .ice_threshold = 0.0, + .wood_threshold = 0.25, // Scattered trees + .snow_threshold = 0.0, + .base_scale = 0.025, + .base_octaves = 4, + .base_persistence = 0.5, + .surface_scale = 0.04, + .surface_octaves = 3, + .surface_persistence = 0.4, + .name = "Savanna" + }, + // TropicalRainforest: Hot & Wet + { + .water_threshold = 0.25, + .mountain_threshold = 0.85, + .sand_threshold = 0.1, + .ice_threshold = 0.0, + .wood_threshold = 0.7, // Dense forest + .snow_threshold = 0.0, + .base_scale = 0.02, + .base_octaves = 5, + .base_persistence = 0.6, + .surface_scale = 0.03, + .surface_octaves = 4, + .surface_persistence = 0.5, + .name = "Tropical Rainforest" + }, + // Grassland: Temperate & Dry + { + .water_threshold = 0.2, + .mountain_threshold = 0.8, + .sand_threshold = 0.15, + .ice_threshold = 0.0, + .wood_threshold = 0.15, // Sparse trees + .snow_threshold = 0.05, // Occasional snow + .base_scale = 0.035, + .base_octaves = 3, + .base_persistence = 0.45, + .surface_scale = 0.06, + .surface_octaves = 2, + .surface_persistence = 0.35, + .name = "Grassland" + }, + // DeciduousForest: Temperate & Moderate + { + .water_threshold = 0.3, + .mountain_threshold = 0.82, + .sand_threshold = 0.05, + .ice_threshold = 0.0, + .wood_threshold = 0.6, // Dense deciduous forest + .snow_threshold = 0.1, // Some snow coverage + .base_scale = 0.025, + .base_octaves = 4, + .base_persistence = 0.55, + .surface_scale = 0.04, + .surface_octaves = 3, + .surface_persistence = 0.45, + .name = "Deciduous Forest" + }, + // TemperateRainforest: Temperate & Wet + { + .water_threshold = 0.35, + .mountain_threshold = 0.85, + .sand_threshold = 0.05, + .ice_threshold = 0.0, + .wood_threshold = 0.8, // Very dense forest + .snow_threshold = 0.15, // More snow + .base_scale = 0.02, + .base_octaves = 5, + .base_persistence = 0.6, + .surface_scale = 0.03, + .surface_octaves = 4, + .surface_persistence = 0.5, + .name = "Temperate Rainforest" + }, + // Tundra: Cold & Dry + { + .water_threshold = 0.15, + .mountain_threshold = 0.75, + .sand_threshold = 0.05, + .ice_threshold = 0.3, // Lots of ice + .wood_threshold = 0.05, // Very sparse vegetation + .snow_threshold = 0.4, // Lots of snow + .base_scale = 0.04, + .base_octaves = 2, + .base_persistence = 0.3, + .surface_scale = 0.08, + .surface_octaves = 2, + .surface_persistence = 0.25, + .name = "Tundra" + }, + // Taiga: Cold & Moderate + { + .water_threshold = 0.25, + .mountain_threshold = 0.8, + .sand_threshold = 0.02, + .ice_threshold = 0.15, + .wood_threshold = 0.5, // Coniferous forest + .snow_threshold = 0.6, // Heavy snow coverage + .base_scale = 0.03, + .base_octaves = 4, + .base_persistence = 0.5, + .surface_scale = 0.05, + .surface_octaves = 3, + .surface_persistence = 0.4, + .name = "Taiga" + }, + // FrozenOcean: Cold & Wet + { + .water_threshold = 0.4, + .mountain_threshold = 0.85, + .sand_threshold = 0.02, + .ice_threshold = 0.6, // Mostly ice + .wood_threshold = 0.1, // Very sparse trees + .snow_threshold = 0.8, // Almost all snow + .base_scale = 0.025, + .base_octaves = 3, + .base_persistence = 0.4, + .surface_scale = 0.04, + .surface_octaves = 2, + .surface_persistence = 0.3, + .name = "Frozen Ocean" } -}; +}}; -const BiomeProperties &get_biome_properties(BiomeType biome) { - return biome_properties[static_cast(biome)]; +const BiomeProperties& get_biome_properties(BiomeType biome) { + return biome_properties[static_cast(biome)]; } BiomeType determine_biome(double temperature, double humidity) { - // Normalize temperature and humidity to 0-1 range if needed - temperature = std::clamp(temperature, 0.0, 1.0); - humidity = std::clamp(humidity, 0.0, 1.0); - - // Determine temperature category - int temp_category; - if (temperature < 0.33) { - temp_category = 0; // Cold - } else if (temperature < 0.67) { - temp_category = 1; // Temperate - } else { - temp_category = 2; // Hot - } - - // Determine humidity category - int humidity_category; - if (humidity < 0.33) { - humidity_category = 0; // Dry - } else if (humidity < 0.67) { - humidity_category = 1; // Moderate - } else { - humidity_category = 2; // Wet - } - - // Map to biome type (3x3 grid) - // Cold (0): Tundra, Taiga, ColdRainforest - // Temperate (1): Grassland, DeciduousForest, TemperateRainforest - // Hot (2): Desert, Savanna, TropicalRainforest - - static constexpr BiomeType biome_matrix[3][3] = { - // Cold row - {BiomeType::Tundra, BiomeType::Taiga, BiomeType::ColdRainforest }, - // Temperate row - {BiomeType::Grassland, BiomeType::DeciduousForest, - BiomeType::TemperateRainforest }, - // Hot row - {BiomeType::Desert, BiomeType::Savanna, BiomeType::TropicalRainforest} - }; - - return biome_matrix[temp_category][humidity_category]; + // Normalize temperature and humidity to 0-1 range if needed + temperature = std::clamp(temperature, 0.0, 1.0); + humidity = std::clamp(humidity, 0.0, 1.0); + + // Determine temperature category + int temp_category; + if (temperature < 0.33) { + temp_category = 0; // Cold + } else if (temperature < 0.67) { + temp_category = 1; // Temperate + } else { + temp_category = 2; // Hot + } + + // Determine humidity category + int humidity_category; + if (humidity < 0.33) { + humidity_category = 0; // Dry + } else if (humidity < 0.67) { + humidity_category = 1; // Moderate + } else { + humidity_category = 2; // Wet + } + + // Map to biome type (3x3 grid) + static constexpr BiomeType biome_matrix[3][3] = { + // Cold row + {BiomeType::Tundra, BiomeType::Taiga, BiomeType::FrozenOcean}, + // Temperate row + {BiomeType::Grassland, BiomeType::DeciduousForest, BiomeType::TemperateRainforest}, + // Hot row + {BiomeType::Desert, BiomeType::Savanna, BiomeType::TropicalRainforest} + }; + + return biome_matrix[temp_category][humidity_category]; } } // namespace istd diff --git a/tilemap/src/generation.cpp b/tilemap/src/generation.cpp index 298bbfa..b109c97 100644 --- a/tilemap/src/generation.cpp +++ b/tilemap/src/generation.cpp @@ -7,38 +7,33 @@ namespace istd { TerrainGenerator::TerrainGenerator(const GenerationConfig &config) : config_(config) - , terrain_noise_(config.seed) + , base_noise_(config.seed) + , surface_noise_(config.seed + 500) // Different seed for surface features , temperature_noise_(config.seed + 1000) // Different seed for temperature , humidity_noise_(config.seed + 2000) // Different seed for humidity {} void TerrainGenerator::generate_map(TileMap &tilemap) { - std::uint8_t map_size = tilemap.get_size(); - // First, generate biome data for all chunks - generate_biomes(map_size); + generate_biomes(tilemap); // Then generate terrain for each chunk + std::uint8_t map_size = tilemap.get_size(); for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) { for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) { generate_chunk(tilemap, chunk_x, chunk_y); } } - - // Clear biome data to free memory - chunk_biomes_.clear(); } -void TerrainGenerator::generate_biomes(std::uint8_t map_size) { - // Initialize biome data storage - chunk_biomes_.resize(map_size); - for (auto &row : chunk_biomes_) { - row.resize(map_size); - } +void TerrainGenerator::generate_biomes(TileMap &tilemap) { + std::uint8_t map_size = tilemap.get_size(); // Generate biomes for each sub-chunk for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) { for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) { + Chunk &chunk = tilemap.get_chunk(chunk_x, chunk_y); + for (std::uint8_t sub_y = 0; sub_y < 4; ++sub_y) { for (std::uint8_t sub_x = 0; sub_x < 4; ++sub_x) { // Calculate global position for this sub-chunk's center @@ -53,9 +48,9 @@ void TerrainGenerator::generate_biomes(std::uint8_t map_size) { auto [temperature, humidity] = get_climate(global_x, global_y); - // Determine biome + // Determine biome and store directly in chunk BiomeType biome = determine_biome(temperature, humidity); - chunk_biomes_[chunk_y][chunk_x][sub_y][sub_x] = biome; + chunk.biome[sub_y][sub_x] = biome; } } } @@ -65,11 +60,13 @@ void TerrainGenerator::generate_biomes(std::uint8_t map_size) { void TerrainGenerator::generate_chunk( TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y ) { + const Chunk &chunk = tilemap.get_chunk(chunk_x, chunk_y); + // Generate each sub-chunk with its corresponding biome for (std::uint8_t sub_y = 0; sub_y < 4; ++sub_y) { for (std::uint8_t sub_x = 0; sub_x < 4; ++sub_x) { SubChunkPos sub_pos(sub_x, sub_y); - BiomeType biome = chunk_biomes_[chunk_y][chunk_x][sub_y][sub_x]; + BiomeType biome = chunk.biome[sub_y][sub_x]; generate_subchunk(tilemap, chunk_x, chunk_y, sub_pos, biome); } } @@ -94,14 +91,33 @@ void TerrainGenerator::generate_subchunk( double global_y = static_cast(chunk_y * Chunk::size + local_y); - // Generate terrain noise value using biome-specific parameters - double noise_value = terrain_noise_.octave_noise( - global_x * properties.scale, global_y * properties.scale, - properties.octaves, properties.persistence + // Generate base terrain noise value + double base_noise_value = base_noise_.octave_noise( + global_x * properties.base_scale, + global_y * properties.base_scale, properties.base_octaves, + properties.base_persistence ); - // Determine tile type based on noise and biome properties - Tile tile = determine_tile_type(noise_value, properties); + // Generate surface feature noise value + double surface_noise_value = surface_noise_.octave_noise( + global_x * properties.surface_scale, + global_y * properties.surface_scale, properties.surface_octaves, + properties.surface_persistence + ); + + // Determine base terrain type + BaseTileType base_type + = determine_base_type(base_noise_value, properties); + + // Determine surface feature type + SurfaceTileType surface_type = determine_surface_type( + surface_noise_value, properties, base_type + ); + + // Create tile with base and surface components + Tile tile; + tile.base = base_type; + tile.surface = surface_type; // Set the tile TilePos pos{chunk_x, chunk_y, local_x, local_y}; @@ -128,19 +144,39 @@ std::pair TerrainGenerator::get_climate( return {temperature, humidity}; } -Tile TerrainGenerator::determine_tile_type( +BaseTileType TerrainGenerator::determine_base_type( double noise_value, const BiomeProperties &properties ) const { if (noise_value < properties.water_threshold) { - return Tile::from_name("water"); + return BaseTileType::Water; } else if (noise_value < properties.sand_threshold) { - return Tile::from_name("sand"); - } else if (noise_value < properties.wood_threshold) { - return Tile::from_name("wood"); + return BaseTileType::Sand; } else if (noise_value < properties.mountain_threshold) { - return Tile::from_name("mountain"); + return BaseTileType::Mountain; + } else if (properties.ice_threshold > 0.0 + && noise_value < properties.ice_threshold) { + return BaseTileType::Ice; } else { - return Tile::from_name("empty"); + return BaseTileType::Land; + } +} + +SurfaceTileType TerrainGenerator::determine_surface_type( + double noise_value, const BiomeProperties &properties, + BaseTileType base_type +) const { + // Don't place surface features on water or ice + if (base_type == BaseTileType::Water || base_type == BaseTileType::Ice) { + return SurfaceTileType::Empty; + } + + // Check for surface features based on thresholds + if (noise_value < properties.wood_threshold) { + return SurfaceTileType::Wood; + } else if (noise_value < properties.snow_threshold) { + return SurfaceTileType::Snow; + } else { + return SurfaceTileType::Empty; } }