From 1cb4c19b7797efac609d4461bc2dd653e168c66c Mon Sep 17 00:00:00 2001 From: szdytom Date: Fri, 1 Aug 2025 19:25:36 +0800 Subject: [PATCH] Refactor tilemap examples and enhance biome generation - Removed dual_noise_demo example as it was deemed unnecessary. - Added perlin_demo example for visualizing Perlin noise. - Updated biome_demo to generate SVG visualizations of tilemaps. - Changed biome properties from thresholds to ratios for better control. - Modified terrain generation logic to accommodate new biome properties. - Improved documentation with detailed API and usage examples. Signed-off-by: szdytom --- .github/copilot-instructions.md | 2 +- README.md | 203 +--------------- tilemap/README.md | 112 +-------- tilemap/docs/api.md | 245 +++++++++++++++++++ tilemap/examples/CMakeLists.txt | 10 +- tilemap/examples/biome_demo.cpp | 349 ++++++++++++++------------- tilemap/examples/dual_noise_demo.cpp | 164 ------------- tilemap/examples/perlin_demo.cpp | 167 +++++++++++++ tilemap/include/biome.h | 10 +- tilemap/src/biome.cpp | 72 +++--- tilemap/src/generation.cpp | 18 +- 11 files changed, 652 insertions(+), 700 deletions(-) create mode 100644 tilemap/docs/api.md delete mode 100644 tilemap/examples/dual_noise_demo.cpp create mode 100644 tilemap/examples/perlin_demo.cpp diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 9da8c94..9be5d61 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,2 +1,2 @@ -+ Always use English for code comments. ++ Always use English for code comments and documents. + Don't create getters or setters if they are not necessary. diff --git a/README.md b/README.md index 5fc1621..3261e81 100644 --- a/README.md +++ b/README.md @@ -1,205 +1,4 @@ # Instructed Project -一个使用现代C++实现的地图生成系统项目。 +A MMO RTS programming game that involves writing code to control units, build structures, manage resources, and fight with other players in a multiplayer online environment. -## 项目组成 - -### 🗺️ Tilemap Library -位于 `tilemap/` 目录下的核心地图生成库,提供: - -- **Perlin噪声地形生成**: 基于噪声算法的自然地形 -- **生物群系系统**: 9种基于气候的生物群系 -- **高效区块系统**: 支持大规模地图生成 -- **现代C++设计**: 使用C++23标准 - -### 📁 项目结构 - -``` -instructed/ -├── tilemap/ # Tilemap库 -│ ├── include/ # 头文件 -│ ├── src/ # 库源代码 -│ ├── examples/ # 示例程序 -│ └── README.md # 库文档 -├── CMakeLists.txt # 主构建文件 -└── README.md # 项目说明 -``` - -## 🚀 快速开始 - -### 构建项目 - -```bash -# 克隆或下载项目 -cd instructed - -# 构建 -mkdir build && cd build -cmake .. -make - -# 运行示例 -./build/tilemap/examples/basic_demo -``` - -### 禁用示例程序构建 - -```bash -cmake -DBUILD_EXAMPLES=OFF .. -make -``` - -## 🎮 示例程序 - -| 程序 | 描述 | -|------|------| -| `basic_demo` | 基础地图生成演示 | -| `advanced_demo` | 高级功能和统计信息 | -| `biome_demo` | 生物群系系统演示 | -| `advanced_biome_demo` | 生物群系分析和可视化 | - -## 📖 文档 - -- [Tilemap库使用指南](tilemap/README.md) -- [生物群系系统详解](BIOME_SYSTEM_GUIDE.md) -- [传统使用方法](TILEMAP_USAGE.md) - -## 🛠️ 技术要求 - -- **C++23**: 现代C++特性支持 -- **CMake 3.27+**: 构建系统 -- **支持的编译器**: GCC 13+, Clang 16+, MSVC 2022+ - -## 📝 开发说明 - -这个项目展示了: -- 清晰的库和示例分离 -- 现代CMake最佳实践 -- 模块化的C++库设计 -- 完整的文档和示例 - -## Features - -- **Chunk-based Architecture**: Divides the world into 64x64 tile chunks for efficient memory management -- **Multiple Tile Types**: Supports different terrain types (Empty, Grass, Stone, Water, Sand, Forest) -- **Perlin Noise Generation**: Uses Perlin noise algorithm for natural-looking terrain generation -- **Flexible Map Size**: Support for n×n chunks (configurable map dimensions) -- **Efficient Access**: Fast tile and chunk access with coordinate conversion utilities -- **Tile Properties**: Tiles have properties like walkability and liquid state - -## Project Structure - -``` -include/ -├── tile.h # Individual tile class -├── chunk.h # 64x64 chunk of tiles -├── tilemap.h # Main tilemap manager -└── random.h # Random number and noise generation - -src/ -├── tile.cpp -├── chunk.cpp -├── tilemap.cpp -├── random.cpp -└── main.cpp # Demo application -``` - -## Building - -### Option 1: Using Make -```bash -make -``` - -### Option 2: Using CMake -```bash -make cmake -``` - -### Option 3: Manual compilation -```bash -g++ -std=c++17 -Wall -Wextra -O2 -Iinclude src/*.cpp -o tilemap_demo -``` - -## Running - -```bash -# Using make -make run - -# Or directly -./build/tilemap_demo -``` - -## Usage Example - -```cpp -#include "tilemap.h" - -int main() { - // Create a 4x4 tilemap (256x256 tiles total) - TileMap tileMap(4, 4); - - // Generate terrain using Perlin noise - tileMap.generatePerlin(54321, 0.05f); - - // Access individual tiles - Tile& tile = tileMap.getTile(100, 100); - tile.setType(Tile::WATER); - - // Access chunks - Chunk* chunk = tileMap.getChunk(1, 1); - if (chunk) { - chunk->setTile(32, 32, Tile(Tile::STONE)); - } - - // Print map overview - tileMap.printMap(); - - return 0; -} -``` - -## Key Classes - -### Tile -- Represents a single tile with a type (Empty, Grass, Stone, Water, Sand, Forest) -- Provides utility methods like `isWalkable()` and `isLiquid()` - -### Chunk -- Contains a 64x64 array of tiles -- Supports Perlin noise generation for natural terrain -- Manages local tile coordinates within the chunk - -### TileMap -- Manages multiple chunks to form a complete world -- Handles coordinate conversion between world and chunk coordinates -- Provides unified access to tiles across chunk boundaries - -### Random -- Utility class for random number generation -- Implements Perlin noise for natural terrain generation -- Supports seeded generation for reproducible results - -## Coordinate System - -- **World Coordinates**: Global tile positions (0,0) to (worldWidth-1, worldHeight-1) -- **Chunk Coordinates**: Chunk positions (0,0) to (mapWidth-1, mapHeight-1) -- **Local Coordinates**: Tile positions within a chunk (0,0) to (63,63) - -The system automatically converts between coordinate systems as needed. - -## Customization - -You can easily extend the system by: -- Adding new tile types to the `Tile::Type` enum -- Implementing custom generation algorithms in `Chunk` -- Modifying tile properties and behaviors -- Adding new terrain features or biomes - -## Performance Notes - -- Each chunk contains 4,096 tiles (64×64) -- Memory usage scales with the number of active chunks -- Coordinate conversion is O(1) -- Tile access within a chunk is O(1) diff --git a/tilemap/README.md b/tilemap/README.md index 88f76ff..453fdb0 100644 --- a/tilemap/README.md +++ b/tilemap/README.md @@ -1,113 +1,3 @@ # Tilemap Library -一个基于Perlin噪声和生物群系系统的C++地图生成库。 - -## 项目结构 - -``` -tilemap/ -├── include/ # 库头文件 -│ ├── biome.h # 生物群系系统 -│ ├── chunk.h # 区块和瓦片定义 -│ ├── generation.h # 地形生成器 -│ ├── noise.h # Perlin噪声实现 -│ ├── tile.h # 瓦片类型定义 -│ └── tilemap.h # 地图容器类 -├── src/ # 库源文件 -│ ├── biome.cpp # 生物群系实现 -│ ├── generation.cpp # 地形生成实现 -│ ├── noise.cpp # Perlin噪声实现 -│ └── tilemap.cpp # 地图容器实现 -├── examples/ # 示例程序 -│ ├── basic_demo.cpp # 基础功能演示 -│ ├── advanced_demo.cpp # 高级功能演示 -│ ├── biome_demo.cpp # 生物群系演示 -│ ├── advanced_biome_demo.cpp # 高级生物群系分析 -│ └── CMakeLists.txt # 示例程序构建配置 -└── CMakeLists.txt # 主构建配置 -``` - -## 构建 - -### 构建库和示例程序 - -```bash -mkdir build -cd build -cmake .. -make -``` - -### 仅构建库(不构建示例) - -```bash -mkdir build -cd build -cmake -DBUILD_EXAMPLES=OFF .. -make -``` - -## 运行示例 - -构建完成后,可执行文件位于 `build/tilemap/examples/` 目录下: - -```bash -# 基础演示 -./build/tilemap/examples/basic_demo - -# 高级功能演示 -./build/tilemap/examples/advanced_demo - -# 生物群系系统演示 -./build/tilemap/examples/biome_demo - -# 高级生物群系分析 -./build/tilemap/examples/advanced_biome_demo -``` - -## 库使用 - -### 基本用法 - -```cpp -#include "tilemap.h" -#include "generation.h" - -// 创建一个10x10区块的地图 -istd::TileMap tilemap(10); - -// 配置生成参数 -istd::GenerationConfig config; -config.seed = 42; - -// 生成地图 -istd::map_generate(tilemap, config); -``` - -### 使用生物群系系统 - -```cpp -#include "generation.h" - -istd::GenerationConfig config; -config.seed = 12345; -config.temperature_scale = 0.005; // 温度变化尺度 -config.humidity_scale = 0.007; // 湿度变化尺度 - -istd::TerrainGenerator generator(config); -generator.generate_map(tilemap); -``` - -## 核心特性 - -- **Perlin噪声地形生成**: 生成自然的地形特征 -- **生物群系系统**: 基于温度和湿度的9种生物群系 -- **区块系统**: 支持大型地图的高效存储 -- **子区块分级**: 16x16瓦片的子区块,每个具有一致的生物群系 -- **高性能**: 优化的内存使用和生成算法 - -## 编译要求 - -- C++23标准支持 -- CMake 3.27或更高版本 -- 支持C++23的编译器(GCC 13+, Clang 16+, MSVC 2022+) +The Tilemap System use in Instructed. Generates 2D tilemaps with biomes and features. diff --git a/tilemap/docs/api.md b/tilemap/docs/api.md new file mode 100644 index 0000000..3ab1f52 --- /dev/null +++ b/tilemap/docs/api.md @@ -0,0 +1,245 @@ +# Tilemap Library API Documentation + +## Overview + +The tilemap library provides a flexible system for generating and managing tile-based terrain with biome support. The library consists of several main components: + +- **TileMap**: The main map container holding chunks of tiles +- **Chunk**: 64x64 tile containers with biome information +- **Tile**: Individual map tiles with base and surface types +- **TerrainGenerator**: Procedural terrain generation system +- **Biome System**: Climate-based terrain variation + +## Core Classes + +### TileMap + +The main container for the entire map, organized as an n×n grid of chunks. + +```cpp +class TileMap { +public: + explicit TileMap(std::uint8_t size); + + std::uint8_t get_size() const; + Chunk& get_chunk(std::uint8_t chunk_x, std::uint8_t chunk_y); + const Chunk& get_chunk(std::uint8_t chunk_x, std::uint8_t chunk_y) const; + + Tile& get_tile(const TilePos& pos); + const Tile& get_tile(const TilePos& pos) const; + void set_tile(const TilePos& pos, const Tile& tile); +}; +``` + +**Constructor Parameters:** +- `size`: Number of chunks per side (max 100), creating an n×n grid + +### Chunk + +Each chunk contains 64×64 tiles and 4×4 sub-chunk biome information. + +```cpp +struct Chunk { + static constexpr uint8_t size = 64; // Tiles per side + static constexpr uint8_t subchunk_size = 16; // Tiles per sub-chunk side + static constexpr uint8_t subchunk_count = 4; // Sub-chunks per side + + Tile tiles[size][size]; // 64x64 tile grid + BiomeType biome[subchunk_count][subchunk_count]; // 4x4 biome grid +}; +``` + +### Tile + +Individual map tiles with base terrain and surface features. + +```cpp +struct Tile { + BaseTileType base : 4; // Base terrain type + SurfaceTileType surface : 4; // Surface features +}; +``` + +**Base Tile Types:** +- `Land`: Standard ground terrain +- `Mountain`: Rocky elevated terrain +- `Sand`: Desert/beach terrain +- `Water`: Water bodies +- `Ice`: Frozen terrain + +**Surface Tile Types:** +- `Empty`: No surface features +- `Wood`: Trees/vegetation +- `Structure`: Player-built structures + +### TilePos + +Position structure for locating tiles within the map. + +```cpp +struct TilePos { + uint8_t chunk_x; // Chunk X coordinate + uint8_t chunk_y; // Chunk Y coordinate + uint8_t local_x; // Tile X within chunk (0-63) + uint8_t local_y; // Tile Y within chunk (0-63) +}; +``` + +## Terrain Generation + +### GenerationConfig + +Configuration parameters for terrain generation. + +```cpp +struct GenerationConfig { + std::uint64_t seed = 0; // Random seed + double temperature_scale = 0.005; // Temperature noise scale + double humidity_scale = 0.007; // Humidity noise scale + double base_scale = 0.08; // Base terrain noise scale +}; +``` + +### TerrainGenerator + +Main class for procedural terrain generation. + +```cpp +class TerrainGenerator { +public: + explicit TerrainGenerator(const GenerationConfig& config); + void generate_map(TileMap& tilemap); +}; +``` + +### Generation Function + +Convenience function for map generation. + +```cpp +void map_generate(TileMap& tilemap, const GenerationConfig& config); +``` + +## Biome System + +### BiomeType + +Available biome types based on temperature and humidity. + +```cpp +enum class BiomeType : std::uint8_t { + SnowyPeeks, // Cold & Dry + SnowyPlains, // Cold & Moderate + FrozenOcean, // Cold & Wet + Plains, // Temperate & Dry + Forest, // Temperate & Moderate + Ocean, // Temperate & Wet + Desert, // Hot & Dry + Savanna, // Hot & Moderate + LukeOcean, // Hot & Wet +}; +``` + +### BiomeProperties + +Properties that control terrain generation for each biome. + +```cpp +struct BiomeProperties { + std::string_view name; // Biome name + double water_ratio; // Water generation ratio + double ice_ratio; // Ice generation ratio + double sand_ratio; // Sand generation ratio + double land_ratio; // Land generation ratio + int base_octaves = 3; // Noise octaves + double base_persistence = 0.5; // Noise persistence +}; +``` + +### Biome Functions + +```cpp +const BiomeProperties& get_biome_properties(BiomeType biome); +BiomeType determine_biome(double temperature, double humidity); +``` + +### SubChunkPos + +Position within a chunk's 4×4 sub-chunk grid. + +```cpp +struct SubChunkPos { + std::uint8_t sub_x; // 0-3 + std::uint8_t sub_y; // 0-3 +}; + +constexpr std::pair subchunk_to_tile_start( + const SubChunkPos& pos +); +``` + +## Usage Examples + +### Basic Map Generation + +```cpp +#include "tilemap.h" +#include "generation.h" + +// Create a 4x4 chunk map +istd::TileMap tilemap(4); + +// Configure generation +istd::GenerationConfig config; +config.seed = 12345; +config.temperature_scale = 0.005; +config.humidity_scale = 0.007; +config.base_scale = 0.08; + +// Generate terrain +istd::map_generate(tilemap, config); + +// Access tiles +for (int chunk_y = 0; chunk_y < 4; ++chunk_y) { + for (int chunk_x = 0; chunk_x < 4; ++chunk_x) { + const auto& chunk = tilemap.get_chunk(chunk_x, chunk_y); + // Process chunk tiles... + } +} +``` + +### Accessing Individual Tiles + +```cpp +// Using TilePos +istd::TilePos pos{0, 0, 32, 32}; // Chunk (0,0), tile (32,32) +const auto& tile = tilemap.get_tile(pos); + +// Direct chunk access +const auto& chunk = tilemap.get_chunk(0, 0); +const auto& tile2 = chunk.tiles[32][32]; +``` + +### Working with Biomes + +```cpp +// Get biome for a sub-chunk +const auto& chunk = tilemap.get_chunk(0, 0); +istd::BiomeType biome = chunk.biome[1][1]; // Sub-chunk (1,1) + +// Get biome properties +const auto& props = istd::get_biome_properties(biome); +std::cout << "Biome: " << props.name << std::endl; +``` + +## Performance Notes + +- Each chunk contains 4,096 tiles (64×64) +- A 4×4 chunk map contains 65,536 total tiles +- Sub-chunks provide efficient biome management (16×16 tile regions) +- Tiles are packed into 1 byte each for memory efficiency +- Generation uses Perlin noise for natural-looking terrain + +## Thread Safety + +The library is not inherently thread-safe. External synchronization is required for concurrent access to TileMap objects. diff --git a/tilemap/examples/CMakeLists.txt b/tilemap/examples/CMakeLists.txt index ed70802..71b96c6 100644 --- a/tilemap/examples/CMakeLists.txt +++ b/tilemap/examples/CMakeLists.txt @@ -3,12 +3,12 @@ cmake_minimum_required(VERSION 3.27) # Examples for the tilemap library # Each example is built as a separate executable -# 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) - # 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) + +# Perlin noise visualization +add_executable(perlin_demo perlin_demo.cpp) +target_link_libraries(perlin_demo PRIVATE istd_tilemap) +target_include_directories(perlin_demo PRIVATE ../include) diff --git a/tilemap/examples/biome_demo.cpp b/tilemap/examples/biome_demo.cpp index f77bd42..7306099 100644 --- a/tilemap/examples/biome_demo.cpp +++ b/tilemap/examples/biome_demo.cpp @@ -1,187 +1,202 @@ -#include "biome.h" #include "generation.h" +#include "tile.h" #include "tilemap.h" +#include +#include #include #include +#include -using namespace istd; - -// Function to get character representation for biome visualization -char get_biome_char(BiomeType biome) { - switch (biome) { - case BiomeType::Desert: - return 'D'; - case BiomeType::Savanna: - return 'S'; - case BiomeType::TropicalRainforest: - return 'T'; - case BiomeType::Grassland: - return 'G'; - case BiomeType::DeciduousForest: - return 'F'; - case BiomeType::TemperateRainforest: - return 'R'; - case BiomeType::Tundra: - return 'U'; - case BiomeType::Taiga: - return 'A'; - case BiomeType::FrozenOcean: - return 'O'; +// Color mapping for different base tile types +const char *get_tile_color(istd::BaseTileType type) { + switch (type) { + case istd::BaseTileType::Land: + return "#90EE90"; // Light green + case istd::BaseTileType::Mountain: + return "#8B4513"; // Saddle brown + case istd::BaseTileType::Sand: + return "#F4A460"; // Sandy brown + case istd::BaseTileType::Water: + return "#1E90FF"; // Dodger blue + case istd::BaseTileType::Ice: + return "#B0E0E6"; // Powder blue + default: + return "#808080"; // Gray for unknown types } - return '?'; } -// 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"; +// Generate SVG file from tilemap +void generate_svg(const istd::TileMap &tilemap, const std::string &filename) { + std::ofstream file(filename); + if (!file.is_open()) { + std::cerr << "Error: Could not open output file: " << filename + << std::endl; + return; } - return "Unknown"; + + const int chunks_per_side = tilemap.get_size(); + const int tiles_per_chunk = istd::Chunk::size; + const int total_tiles = chunks_per_side * tiles_per_chunk; + const int tile_size = 2; // Size of each tile in SVG pixels + const int svg_size = total_tiles * tile_size; + + // SVG header + file << "\n"; + file << "\n"; + file << "Tilemap Visualization\n"; + + // Generate tiles + for (int chunk_y = 0; chunk_y < chunks_per_side; ++chunk_y) { + for (int chunk_x = 0; chunk_x < chunks_per_side; ++chunk_x) { + const auto &chunk = tilemap.get_chunk(chunk_x, chunk_y); + + for (int tile_y = 0; tile_y < tiles_per_chunk; ++tile_y) { + for (int tile_x = 0; tile_x < tiles_per_chunk; ++tile_x) { + const auto &tile = chunk.tiles[tile_x][tile_y]; + + int global_x = chunk_x * tiles_per_chunk + tile_x; + int global_y = chunk_y * tiles_per_chunk + tile_y; + + int svg_x = global_x * tile_size; + int svg_y = global_y * tile_size; + + const char *color = get_tile_color(tile.base); + + file << "\n"; + } + } + } + } + + // Add grid lines for chunk boundaries + file << "\n"; + for (int i = 0; i <= chunks_per_side; ++i) { + int pos = i * tiles_per_chunk * tile_size; + // Vertical lines + file << "\n"; + // Horizontal lines + file << "\n"; + } + + // Legend + file << "\n"; + file << "\n"; + file << "\n"; + file << "Legend\n"; + + const std::pair legend_items[] = { + {istd::BaseTileType::Land, "Land" }, + {istd::BaseTileType::Mountain, "Mountain"}, + {istd::BaseTileType::Sand, "Sand" }, + {istd::BaseTileType::Water, "Water" }, + {istd::BaseTileType::Ice, "Ice" } + }; + + for (int i = 0; i < 5; ++i) { + int y_pos = 40 + i * 20; + file << "\n"; + file << "" + << legend_items[i].second << "\n"; + } + file << "\n"; + + file << "\n"; + file.close(); + + std::cout << "SVG file generated: " << filename << std::endl; + std::cout << "Tilemap size: " << total_tiles << "x" << total_tiles + << " tiles" << std::endl; + std::cout << "Chunks: " << chunks_per_side << "x" << chunks_per_side + << std::endl; } -int main() { - 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; +// Print statistics about the generated map +void print_statistics(const istd::TileMap &tilemap) { + int tile_counts[5] = {0}; // Count for each base tile type + const int chunks_per_side = tilemap.get_size(); + const int tiles_per_chunk = istd::Chunk::size; - // Create a tilemap for biome demonstration - constexpr std::uint8_t map_size = 3; // 3x3 chunks for compact display - TileMap tilemap(map_size); + for (int chunk_y = 0; chunk_y < chunks_per_side; ++chunk_y) { + for (int chunk_x = 0; chunk_x < chunks_per_side; ++chunk_x) { + const auto &chunk = tilemap.get_chunk(chunk_x, chunk_y); - // 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 + for (int tile_y = 0; tile_y < tiles_per_chunk; ++tile_y) { + for (int tile_x = 0; tile_x < tiles_per_chunk; ++tile_x) { + const auto &tile = chunk.tiles[tile_x][tile_y]; + tile_counts[static_cast(tile.base)]++; + } + } + } + } - std::cout << "Generating " << static_cast(map_size) << "x" - << static_cast(map_size) << " chunks..." << std::endl; + const char *tile_names[] = {"Land", "Mountain", "Sand", "Water", "Ice"}; + int total_tiles + = chunks_per_side * chunks_per_side * tiles_per_chunk * tiles_per_chunk; + + std::cout << "\nTile Statistics:\n"; + std::cout << "================\n"; + for (int i = 0; i < 5; ++i) { + double percentage = (double)tile_counts[i] / total_tiles * 100.0; + std::cout << std::setw(10) << tile_names[i] << ": " << std::setw(8) + << tile_counts[i] << " (" << std::fixed + << std::setprecision(1) << percentage << "%)\n"; + } + std::cout << "Total tiles: " << total_tiles << std::endl; +} + +int main(int argc, char *argv[]) { + // Parse command line arguments + if (argc != 3) { + std::cerr << "Usage: " << argv[0] << " \n"; + std::cerr << "Example: " << argv[0] << " 12345 output.svg\n"; + return 1; + } + + std::uint64_t seed = std::strtoull(argv[1], nullptr, 10); + std::string output_filename = argv[2]; + + // Validate output filename + if (output_filename.length() < 4 + || output_filename.substr(output_filename.length() - 4) != ".svg") { + std::cerr << "Error: Output filename must end with .svg\n"; + return 1; + } + + std::cout << "Generating 4x4 chunk tilemap with seed: " << seed + << std::endl; + + // Create 4x4 chunk tilemap + istd::TileMap tilemap(4); + + // Configure generation parameters + istd::GenerationConfig config; + config.seed = seed; + config.temperature_scale = 0.005; + config.humidity_scale = 0.007; + config.base_scale = 0.08; // Generate the map - map_generate(tilemap, config); + std::cout << "Generating terrain..." << std::endl; + istd::map_generate(tilemap, config); - std::cout << "Generation complete!" << std::endl << std::endl; + // Generate SVG output + std::cout << "Creating SVG visualization..." << std::endl; + generate_svg(tilemap, output_filename); - // 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}; - - 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; + // Print statistics + print_statistics(tilemap); return 0; -} +} \ No newline at end of file diff --git a/tilemap/examples/dual_noise_demo.cpp b/tilemap/examples/dual_noise_demo.cpp deleted file mode 100644 index b550009..0000000 --- a/tilemap/examples/dual_noise_demo.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#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/examples/perlin_demo.cpp b/tilemap/examples/perlin_demo.cpp new file mode 100644 index 0000000..9a7fd00 --- /dev/null +++ b/tilemap/examples/perlin_demo.cpp @@ -0,0 +1,167 @@ +#include "noise.h" +#include +#include +#include +#include +#include + +// Convert a noise value [0,1] to a grayscale color +std::string noise_to_grayscale(double noise_value) { + // Clamp to [0,1] range + noise_value = std::max(0.0, std::min(1.0, noise_value)); + + // Convert to 0-255 range + int gray = static_cast(noise_value * 255); + + // Convert to hex color + std::ostringstream oss; + oss << "#" << std::hex << std::setfill('0') << std::setw(2) << gray + << std::setw(2) << gray << std::setw(2) << gray; + return oss.str(); +} + +// Generate SVG visualization of Perlin noise +void generate_noise_svg(const std::string& filename, int size, double scale, + std::uint64_t seed, int octaves = 1, double persistence = 0.5) { + std::ofstream file(filename); + if (!file.is_open()) { + std::cerr << "Error: Could not open output file: " << filename << std::endl; + return; + } + + // Create noise generator + istd::PerlinNoise noise(seed); + + const int pixel_size = 2; // Size of each pixel in SVG units + const int svg_size = size * pixel_size; + + // SVG header + file << "\n"; + file << "\n"; + file << "Perlin Noise Visualization (Scale: " << scale + << ", Octaves: " << octaves << ", Seed: " << seed << ")\n"; + + // Generate noise values and create rectangles + double min_value = 1.0, max_value = 0.0; + + for (int y = 0; y < size; ++y) { + for (int x = 0; x < size; ++x) { + double noise_value; + if (octaves == 1) { + noise_value = noise.noise(x * scale, y * scale); + } else { + noise_value = noise.octave_noise(x * scale, y * scale, octaves, persistence); + } + + // Track min/max for statistics + min_value = std::min(min_value, noise_value); + max_value = std::max(max_value, noise_value); + + std::string color = noise_to_grayscale(noise_value); + + int svg_x = x * pixel_size; + int svg_y = y * pixel_size; + + file << "\n"; + } + } + + // Add information text + file << "\n"; + + // Add parameter info text overlay + file << "\n"; + file << "\n"; + file << "Perlin Noise Parameters\n"; + file << "Size: " << size << "x" << size << "\n"; + file << "Scale: " << scale << "\n"; + file << "Seed: " << seed << "\n"; + file << "Octaves: " << octaves << "\n"; + file << "Range: [" + << std::fixed << std::setprecision(3) << min_value << ", " << max_value << "]\n"; + file << "\n"; + + // Add grayscale legend + file << "\n"; + file << "Value\n"; + for (int i = 0; i <= 10; ++i) { + double value = i / 10.0; + std::string color = noise_to_grayscale(value); + int y_pos = 20 + i * 15; + file << "\n"; + file << "" + << std::fixed << std::setprecision(1) << value << "\n"; + } + file << "\n"; + + file << "\n"; + file.close(); + + std::cout << "Perlin noise SVG generated: " << filename << std::endl; + std::cout << "Size: " << size << "x" << size << " pixels" << std::endl; + std::cout << "Scale: " << scale << ", Octaves: " << octaves << std::endl; + std::cout << "Value range: [" << std::fixed << std::setprecision(3) + << min_value << ", " << max_value << "]" << std::endl; +} + +int main(int argc, char* argv[]) { + // Default parameters + std::uint64_t seed = 12345; + std::string output_filename = "perlin_noise.svg"; + double scale = 0.02; + int octaves = 1; + double persistence = 0.5; + + // Parse command line arguments + if (argc >= 2) { + seed = std::strtoull(argv[1], nullptr, 10); + } + if (argc >= 3) { + output_filename = argv[2]; + } + if (argc >= 4) { + scale = std::strtod(argv[3], nullptr); + } + if (argc >= 5) { + octaves = std::strtol(argv[4], nullptr, 10); + } + if (argc >= 6) { + persistence = std::strtod(argv[5], nullptr); + } + + if (argc == 1 || argc > 6) { + std::cout << "Usage: " << argv[0] << " [seed] [output.svg] [scale] [octaves] [persistence]\n"; + std::cout << "Defaults: seed=12345, output=perlin_noise.svg, scale=0.02, octaves=1, persistence=0.5\n"; + std::cout << "Examples:\n"; + std::cout << " " << argv[0] << " 54321 noise1.svg 0.01\n"; + std::cout << " " << argv[0] << " 12345 octave_noise.svg 0.02 4 0.5\n"; + if (argc > 6) return 1; + } + + // Validate parameters + if (scale <= 0) { + std::cerr << "Error: Scale must be positive\n"; + return 1; + } + if (octaves < 1 || octaves > 10) { + std::cerr << "Error: Octaves must be between 1 and 10\n"; + return 1; + } + if (persistence <= 0 || persistence > 1) { + std::cerr << "Error: Persistence must be between 0 and 1\n"; + return 1; + } + + std::cout << "Generating 256x256 Perlin noise visualization..." << std::endl; + std::cout << "Parameters: seed=" << seed << ", scale=" << scale + << ", octaves=" << octaves << ", persistence=" << persistence << std::endl; + + // Generate the noise visualization + generate_noise_svg(output_filename, 256, scale, seed, octaves, persistence); + + return 0; +} diff --git a/tilemap/include/biome.h b/tilemap/include/biome.h index d7482b7..bb880ac 100644 --- a/tilemap/include/biome.h +++ b/tilemap/include/biome.h @@ -36,11 +36,11 @@ struct BiomeProperties { // Biome name for debugging std::string_view name; - // Base terrain thresholds (0.0 - 1.0) - double water_threshold; - double ice_threshold; - double sand_threshold; - double land_threshold; + // Base terrain ratios (0.0 - 1.0) + double water_ratio; + double ice_ratio; + double sand_ratio; + double land_ratio; // Noise parameters for base terrain int base_octaves = 3; diff --git a/tilemap/src/biome.cpp b/tilemap/src/biome.cpp index 6020c97..7ef9339 100644 --- a/tilemap/src/biome.cpp +++ b/tilemap/src/biome.cpp @@ -11,74 +11,74 @@ constexpr BiomeProperties biome_properties[] = { { .name = "Snowy Peeks", - .water_threshold = .05, - .ice_threshold = .15, - .sand_threshold = .1, - .land_threshold = .2, + .water_ratio = .05, + .ice_ratio = .15, + .sand_ratio = .1, + .land_ratio = .2, }, // Snowy Plains (Cold & Moderate) { .name = "Snowy Plains", - .water_threshold = .05, - .ice_threshold = .25, - .sand_threshold = .1, - .land_threshold = .4, + .water_ratio = .05, + .ice_ratio = .25, + .sand_ratio = .1, + .land_ratio = .4, }, // Frozen Ocean (Cold & Wet) { .name = "Frozen Ocean", - .water_threshold = .3, - .ice_threshold = .4, - .sand_threshold = .25, - .land_threshold = .05, + .water_ratio = .3, + .ice_ratio = .4, + .sand_ratio = .25, + .land_ratio = .05, }, // Plains (Temperate & Dry) { .name = "Plains", - .water_threshold = .1, - .ice_threshold = .0, - .sand_threshold = .05, - .land_threshold = .65, + .water_ratio = .1, + .ice_ratio = .0, + .sand_ratio = .05, + .land_ratio = .65, }, // Forest (Temperate & Moderate) { .name = "Forest", - .water_threshold = .2, - .ice_threshold = .0, - .sand_threshold = .1, - .land_threshold = .5, + .water_ratio = .2, + .ice_ratio = .0, + .sand_ratio = .1, + .land_ratio = .5, }, // Ocean (Temperate & Wet) { .name = "Ocean", - .water_threshold = .7, - .ice_threshold = .0, - .sand_threshold = .2, - .land_threshold = .1, + .water_ratio = .7, + .ice_ratio = .0, + .sand_ratio = .2, + .land_ratio = .1, }, // Desert (Hot & Dry) { .name = "Desert", - .water_threshold = .0, - .ice_threshold = .0, - .sand_threshold = .75, - .land_threshold = .05, + .water_ratio = .0, + .ice_ratio = .0, + .sand_ratio = .75, + .land_ratio = .05, }, // Savanna (Hot & Moderate) { .name = "Savanna", - .water_threshold = .2, - .ice_threshold = .0, - .sand_threshold = .1, - .land_threshold = .5, + .water_ratio = .2, + .ice_ratio = .0, + .sand_ratio = .1, + .land_ratio = .5, }, // Luke Ocean (Hot & Wet) { .name = "Luke Ocean", - .water_threshold = .8, - .ice_threshold = .0, - .sand_threshold = .2, - .land_threshold = .0, + .water_ratio = .8, + .ice_ratio = .0, + .sand_ratio = .2, + .land_ratio = .0, }, }; diff --git a/tilemap/src/generation.cpp b/tilemap/src/generation.cpp index 646895b..f1c33a1 100644 --- a/tilemap/src/generation.cpp +++ b/tilemap/src/generation.cpp @@ -133,19 +133,19 @@ std::pair TerrainGenerator::get_climate( BaseTileType TerrainGenerator::determine_base_type( double noise_value, const BiomeProperties &properties ) const { - const std::pair thresholds[] = { - {BaseTileType::Water, properties.water_threshold}, - {BaseTileType::Ice, properties.ice_threshold }, - {BaseTileType::Sand, properties.sand_threshold }, - {BaseTileType::Land, properties.land_threshold }, - {BaseTileType::Mountain, 1.0 }, + const std::pair ratios[] = { + {BaseTileType::Water, properties.water_ratio}, + {BaseTileType::Ice, properties.ice_ratio }, + {BaseTileType::Sand, properties.sand_ratio }, + {BaseTileType::Land, properties.land_ratio }, + {BaseTileType::Mountain, 1.0 }, }; - for (const auto &[type, threshold] : thresholds) { - if (noise_value < threshold) { + for (const auto &[type, ratio] : ratios) { + if (noise_value < ratio) { return type; } - noise_value -= threshold; // Adjust noise value for next type + noise_value -= ratio; // Adjust noise value for next type } std::unreachable();