commit b6656f50238a7666452c74deeefd7dcaf52b8b7a Author: szdytom Date: Fri Aug 1 14:28:36 2025 +0800 feat: Add biome-based terrain generation and Perlin noise implementation - Introduced a new biome system with various biome types and properties. - Implemented terrain generation using Perlin noise to create diverse landscapes. - Added examples for basic tilemap generation and biome analysis. - Created helper functions for displaying tiles and biomes in the console. - Enhanced the TileMap class to support chunk-based tile management. - Developed a PerlinNoise class for generating smooth noise patterns. - Updated generation configuration to include climate parameters for biomes. - Implemented error handling for out-of-bounds access in TileMap. Signed-off-by: szdytom diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..20aae66 --- /dev/null +++ b/.clang-format @@ -0,0 +1,148 @@ +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: BlockIndent +AlignArrayOfStructures: Left +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 2 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortEnumsOnASingleLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortLambdasOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: [] +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: All +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeComma +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: true +BreakConstructorInitializers: BeforeComma +BreakStringLiterals: true +ColumnLimit: 80 +QualifierAlignment: Left +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: Always +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: NextLine +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: false +FixNamespaceComments: true +ForEachMacros: [] +IfMacros: [] +IncludeBlocks: Merge +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertBraces: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: OuterScope +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Right +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: false +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Latest +TabWidth: 4 +UseCRLF: false +UseTab: AlignWithSpaces \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a2e5c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.vscode/ +build/ +*.out +*.swp +*.o +tilemap_demo +CMakeCache.txt +CMakeFiles/ +Makefile +cmake_install.cmake diff --git a/BIOME_SYSTEM_GUIDE.md b/BIOME_SYSTEM_GUIDE.md new file mode 100644 index 0000000..ce5cea4 --- /dev/null +++ b/BIOME_SYSTEM_GUIDE.md @@ -0,0 +1,155 @@ +# 生物群系系统使用指南 + +## 概述 + +新的生物群系系统基于温度和湿度参数来决定地形生成,提供了更加真实和多样化的地图生成体验。 + +## 核心特性 + +### 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/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..346667f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.27) +project(instructed LANGUAGES CXX) + +# Set C++ standard +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Add tilemap library and examples +add_subdirectory(tilemap) diff --git a/README.md b/README.md new file mode 100644 index 0000000..5fc1621 --- /dev/null +++ b/README.md @@ -0,0 +1,205 @@ +# Instructed Project + +一个使用现代C++实现的地图生成系统项目。 + +## 项目组成 + +### 🗺️ 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_USAGE.md b/TILEMAP_USAGE.md new file mode 100644 index 0000000..91aa462 --- /dev/null +++ b/TILEMAP_USAGE.md @@ -0,0 +1,92 @@ +# 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/CMakeLists.txt b/tilemap/CMakeLists.txt new file mode 100644 index 0000000..2285666 --- /dev/null +++ b/tilemap/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.27) + +# Define the tilemap library source files +set(ISTD_TILEMAP_SRC + src/generation.cpp + src/tilemap.cpp + src/noise.cpp + src/biome.cpp +) + +# Create the tilemap library +add_library(istd_tilemap ${ISTD_TILEMAP_SRC}) + +# Set library properties +target_compile_features(istd_tilemap PUBLIC cxx_std_23) +target_include_directories(istd_tilemap PUBLIC include) + +# Optionally build examples +option(BUILD_EXAMPLES "Build example programs" ON) +if(BUILD_EXAMPLES) + add_subdirectory(examples) +endif() diff --git a/tilemap/README.md b/tilemap/README.md new file mode 100644 index 0000000..88f76ff --- /dev/null +++ b/tilemap/README.md @@ -0,0 +1,113 @@ +# 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+) diff --git a/tilemap/examples/CMakeLists.txt b/tilemap/examples/CMakeLists.txt new file mode 100644 index 0000000..00596d5 --- /dev/null +++ b/tilemap/examples/CMakeLists.txt @@ -0,0 +1,24 @@ +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) + +# 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 +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 new file mode 100644 index 0000000..08699f6 --- /dev/null +++ b/tilemap/examples/advanced_biome_demo.cpp @@ -0,0 +1,299 @@ +#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 new file mode 100644 index 0000000..dba9590 --- /dev/null +++ b/tilemap/examples/advanced_demo.cpp @@ -0,0 +1,175 @@ +#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 new file mode 100644 index 0000000..109862f --- /dev/null +++ b/tilemap/examples/basic_demo.cpp @@ -0,0 +1,90 @@ +#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 new file mode 100644 index 0000000..7b7d150 --- /dev/null +++ b/tilemap/examples/biome_demo.cpp @@ -0,0 +1,226 @@ +#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 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; + } + } + } + + 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; +} + +int main() { + try { + std::cout << "=== Biome-Based Terrain Generation Demo ===" << std::endl; + + // Create a 6x6 chunk tilemap + std::uint8_t map_size = 6; + istd::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; + + std::cout << "Generating " << static_cast(map_size) << "x" + << static_cast(map_size) + << " chunk map with biome system..." << std::endl; + + // Generate the map using the new biome-based system + istd::map_generate(tilemap, config); + + 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 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; + } + + return 0; +} diff --git a/tilemap/include/biome.h b/tilemap/include/biome.h new file mode 100644 index 0000000..26cbc40 --- /dev/null +++ b/tilemap/include/biome.h @@ -0,0 +1,69 @@ +#ifndef ISTD_TILEMAP_BIOME_H +#define ISTD_TILEMAP_BIOME_H + +#include +#include + +namespace istd { + +// Biome types based on temperature and humidity +enum class BiomeType : std::uint8_t { + Desert = 0, // Hot & Dry + Savanna = 1, // Hot & Moderate + TropicalRainforest = 2, // Hot & Wet + Grassland = 3, // Temperate & Dry + DeciduousForest = 4, // Temperate & Moderate + TemperateRainforest = 5, // Temperate & Wet + Tundra = 6, // Cold & Dry + Taiga = 7, // Cold & Moderate + ColdRainforest = 8 // Cold & Wet +}; + +// Biome properties for terrain generation +struct BiomeProperties { + // Terrain thresholds (0.0 - 1.0) + double water_threshold; + double sand_threshold; + double wood_threshold; + double mountain_threshold; + + // Noise parameters + double scale; + int octaves; + double persistence; + + // Biome name for debugging + std::string_view name; +}; + +// Get biome properties for terrain generation +const BiomeProperties &get_biome_properties(BiomeType biome); + +// Determine biome type based on temperature and humidity +BiomeType determine_biome(double temperature, double humidity); + +// Sub-chunk position within a chunk (4x4 grid of 16x16 sub-chunks) +struct SubChunkPos { + std::uint8_t sub_x; // 0-3 + std::uint8_t sub_y; // 0-3 + + constexpr SubChunkPos(std::uint8_t x, std::uint8_t y): sub_x(x), sub_y(y) {} +}; + +// Convert local tile coordinates to sub-chunk position +constexpr SubChunkPos tile_to_subchunk( + std::uint8_t local_x, std::uint8_t local_y +) { + return SubChunkPos(local_x / 16, local_y / 16); +} + +// Get the starting tile coordinates for a sub-chunk +constexpr std::pair subchunk_to_tile_start( + const SubChunkPos &pos +) { + return {pos.sub_x * 16, pos.sub_y * 16}; +} + +} // namespace istd + +#endif diff --git a/tilemap/include/chunk.h b/tilemap/include/chunk.h new file mode 100644 index 0000000..5f5882a --- /dev/null +++ b/tilemap/include/chunk.h @@ -0,0 +1,23 @@ +#ifndef ISTD_TILEMAP_CHUNK_H +#define ISTD_TILEMAP_CHUNK_H +#include "tile.h" +#include + +namespace istd { +// Represents the position of a tile in the map, using chunk and local +// coordinates +struct TilePos { + uint8_t chunk_x; + uint8_t chunk_y; + uint8_t local_x; + uint8_t local_y; +}; + +struct Chunk { + static constexpr uint8_t size = 64; + Tile tiles[size][size]; // 64x64 array of tile types +}; + +} // namespace istd + +#endif \ No newline at end of file diff --git a/tilemap/include/generation.h b/tilemap/include/generation.h new file mode 100644 index 0000000..97a6eb6 --- /dev/null +++ b/tilemap/include/generation.h @@ -0,0 +1,118 @@ +#ifndef TILEMAP_GENERATION_H +#define TILEMAP_GENERATION_H + +#include "biome.h" +#include "chunk.h" +#include "noise.h" +#include "tilemap.h" +#include +#include +#include + +namespace istd { + +struct GenerationConfig { + std::uint64_t seed = 0; // Seed for random generation + + // 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_; + +public: + /** + * @brief Construct a terrain generator with the given configuration + * @param config Generation configuration + */ + explicit TerrainGenerator(const GenerationConfig &config); + + /** + * @brief Generate terrain for the entire tilemap + * @param tilemap The tilemap to generate into + */ + void generate_map(TileMap &tilemap); + +private: + /** + * @brief Generate biome data for all chunks + * @param map_size Number of chunks per side + */ + void generate_biomes(std::uint8_t map_size); + + /** + * @brief Generate terrain for a single chunk + * @param tilemap The tilemap to modify + * @param chunk_x Chunk X coordinate + * @param chunk_y Chunk Y coordinate + */ + void generate_chunk( + TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y + ); + + /** + * @brief Generate terrain for a sub-chunk with specific biome + * @param tilemap The tilemap to modify + * @param chunk_x Chunk X coordinate + * @param chunk_y Chunk Y coordinate + * @param sub_pos Sub-chunk position within the chunk + * @param biome The biome type for this sub-chunk + */ + void generate_subchunk( + TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y, + const SubChunkPos &sub_pos, BiomeType biome + ); + + /** + * @brief Get climate values at a global position + * @param global_x Global X coordinate + * @param global_y Global Y coordinate + * @return Pair of (temperature, humidity) in range [0,1] + */ + std::pair get_climate( + double global_x, double global_y + ) const; + + /** + * @brief Determine tile type based on noise value and biome properties + * @param noise_value Terrain noise value [0,1] + * @param properties Biome properties to use + * @return The appropriate tile type + */ + Tile determine_tile_type( + double noise_value, const BiomeProperties &properties + ) const; +}; + +/** + * @brief Generate a tilemap using the new biome-based system + * @param tilemap The tilemap to generate into + * @param config Configuration for generation + */ +void map_generate(TileMap &tilemap, const GenerationConfig &config); + +} // namespace istd + +#endif diff --git a/tilemap/include/noise.h b/tilemap/include/noise.h new file mode 100644 index 0000000..17651b7 --- /dev/null +++ b/tilemap/include/noise.h @@ -0,0 +1,48 @@ +#ifndef ISTD_TILEMAP_NOISE_H +#define ISTD_TILEMAP_NOISE_H + +#include +#include + +namespace istd { + +class PerlinNoise { +private: + std::vector permutation_; + + // Helper functions for Perlin noise calculation + double fade(double t) const; + double lerp(double t, double a, double b) const; + double grad(int hash, double x, double y) const; + +public: + /** + * @brief Construct a PerlinNoise generator with the given seed + * @param seed Random seed for noise generation + */ + explicit PerlinNoise(std::uint64_t seed = 0); + + /** + * @brief Generate 2D Perlin noise value at the given coordinates + * @param x X coordinate + * @param y Y coordinate + * @return Noise value between 0.0 and 1.0 + */ + double noise(double x, double y) const; + + /** + * @brief Generate octave noise (multiple frequencies combined) + * @param x X coordinate + * @param y Y coordinate + * @param octaves Number of octaves to combine + * @param persistence How much each octave contributes + * @return Noise value between 0.0 and 1.0 + */ + double octave_noise( + double x, double y, int octaves = 4, double persistence = 0.5 + ) const; +}; + +} // namespace istd + +#endif \ No newline at end of file diff --git a/tilemap/include/tile.h b/tilemap/include/tile.h new file mode 100644 index 0000000..d38c29f --- /dev/null +++ b/tilemap/include/tile.h @@ -0,0 +1,49 @@ +#ifndef ISTD_TILEMAP_TILE_H +#define ISTD_TILEMAP_TILE_H + +#include +#include // For std::invalid_argument + +namespace istd { + +// Array of tile types +constexpr const char *_tiles_types[] = { + "empty", "mountain", "wood", "sand", "water", +}; + +constexpr std::size_t _tile_types_n + = sizeof(_tiles_types) / sizeof(_tiles_types[0]); + +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"); + } +}; + +static_assert(sizeof(Tile) == 1); + +} // namespace istd + +#endif diff --git a/tilemap/include/tilemap.h b/tilemap/include/tilemap.h new file mode 100644 index 0000000..35b0f66 --- /dev/null +++ b/tilemap/include/tilemap.h @@ -0,0 +1,54 @@ +#ifndef ISTD_TILEMAP_TILEMAP_H +#define ISTD_TILEMAP_TILEMAP_H + +#include "chunk.h" +#include +#include + +namespace istd { + +class TileMap { +private: + std::uint8_t size_; // Number of chunks in each dimension (n×n) + std::vector> chunks_; // 2D array of chunks + +public: + /** + * @brief Construct a TileMap with n×n chunks + * @param size Number of chunks in each dimension (max 100) + */ + explicit TileMap(std::uint8_t size); + + /** + * @brief Get the size of the tilemap (number of chunks per side) + */ + std::uint8_t get_size() const { + return size_; + } + + /** + * @brief Get a reference to a chunk at the given coordinates + * @param chunk_x X coordinate of the chunk + * @param chunk_y Y coordinate of the chunk + */ + 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; + + /** + * @brief Get a tile at the given position + * @param pos The position of the tile + */ + Tile &get_tile(const TilePos &pos); + const Tile &get_tile(const TilePos &pos) const; + + /** + * @brief Set a tile at the given position + * @param pos The position of the tile + * @param tile The tile to set + */ + void set_tile(const TilePos &pos, const Tile &tile); +}; + +} // namespace istd + +#endif \ No newline at end of file diff --git a/tilemap/src/biome.cpp b/tilemap/src/biome.cpp new file mode 100644 index 0000000..bef1326 --- /dev/null +++ b/tilemap/src/biome.cpp @@ -0,0 +1,141 @@ +#include "biome.h" +#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"} + } +}; + +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]; +} + +} // namespace istd diff --git a/tilemap/src/generation.cpp b/tilemap/src/generation.cpp new file mode 100644 index 0000000..298bbfa --- /dev/null +++ b/tilemap/src/generation.cpp @@ -0,0 +1,153 @@ +#include "generation.h" +#include "biome.h" +#include +#include + +namespace istd { + +TerrainGenerator::TerrainGenerator(const GenerationConfig &config) + : config_(config) + , terrain_noise_(config.seed) + , 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); + + // Then generate terrain for each 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) { + 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); + } + + // 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) { + 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 * Chunk::size + sub_x * 16 + 8 + ); + double global_y = static_cast( + chunk_y * Chunk::size + sub_y * 16 + 8 + ); + + // Get climate values + auto [temperature, humidity] + = get_climate(global_x, global_y); + + // Determine biome + BiomeType biome = determine_biome(temperature, humidity); + chunk_biomes_[chunk_y][chunk_x][sub_y][sub_x] = biome; + } + } + } + } +} + +void TerrainGenerator::generate_chunk( + TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t 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]; + generate_subchunk(tilemap, chunk_x, chunk_y, sub_pos, biome); + } + } +} + +void TerrainGenerator::generate_subchunk( + TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y, + const SubChunkPos &sub_pos, BiomeType biome +) { + const BiomeProperties &properties = get_biome_properties(biome); + + // Get starting tile coordinates for this sub-chunk + auto [start_x, start_y] = subchunk_to_tile_start(sub_pos); + + // Generate terrain for each tile in the 16x16 sub-chunk + for (std::uint8_t local_y = start_y; local_y < start_y + 16; ++local_y) { + for (std::uint8_t local_x = start_x; local_x < start_x + 16; + ++local_x) { + // Calculate global coordinates + double global_x + = static_cast(chunk_x * Chunk::size + local_x); + 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 + ); + + // Determine tile type based on noise and biome properties + Tile tile = determine_tile_type(noise_value, properties); + + // Set the tile + TilePos pos{chunk_x, chunk_y, local_x, local_y}; + tilemap.set_tile(pos, tile); + } + } +} + +std::pair TerrainGenerator::get_climate( + double global_x, double global_y +) const { + // Generate temperature noise (0-1 range) + double temperature = temperature_noise_.octave_noise( + global_x * config_.temperature_scale, + global_y * config_.temperature_scale, 3, 0.5 + ); + + // Generate humidity noise (0-1 range) + double humidity = humidity_noise_.octave_noise( + global_x * config_.humidity_scale, global_y * config_.humidity_scale, 3, + 0.5 + ); + + return {temperature, humidity}; +} + +Tile TerrainGenerator::determine_tile_type( + double noise_value, const BiomeProperties &properties +) const { + if (noise_value < properties.water_threshold) { + return Tile::from_name("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"); + } else if (noise_value < properties.mountain_threshold) { + return Tile::from_name("mountain"); + } else { + return Tile::from_name("empty"); + } +} + +// Legacy function for backward compatibility +void map_generate(TileMap &tilemap, const GenerationConfig &config) { + TerrainGenerator generator(config); + generator.generate_map(tilemap); +} + +} // namespace istd diff --git a/tilemap/src/noise.cpp b/tilemap/src/noise.cpp new file mode 100644 index 0000000..e1ff3cc --- /dev/null +++ b/tilemap/src/noise.cpp @@ -0,0 +1,94 @@ +#include "noise.h" +#include +#include +#include +#include + +namespace istd { + +PerlinNoise::PerlinNoise(std::uint64_t seed) { + // Initialize permutation array with values 0-255 + permutation_.resize(256); + std::iota(permutation_.begin(), permutation_.end(), 0); + + // Shuffle using the provided seed + std::default_random_engine generator(seed); + std::shuffle(permutation_.begin(), permutation_.end(), generator); + + // Duplicate the permutation to avoid overflow + permutation_.insert( + permutation_.end(), permutation_.begin(), permutation_.end() + ); +} + +double PerlinNoise::fade(double t) const { + // Fade function: 6t^5 - 15t^4 + 10t^3 + return t * t * t * (t * (t * 6 - 15) + 10); +} + +double PerlinNoise::lerp(double t, double a, double b) const { + return a + t * (b - a); +} + +double PerlinNoise::grad(int hash, double x, double y) const { + // Convert low 4 bits of hash code into 12 gradient directions + int h = hash & 15; + double u = h < 8 ? x : y; + double v = h < 4 ? y : h == 12 || h == 14 ? x : 0; + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); +} + +double PerlinNoise::noise(double x, double y) const { + // Find unit grid cell containing point + int X = static_cast(std::floor(x)) & 255; + int Y = static_cast(std::floor(y)) & 255; + + // Get relative xy coordinates of point within that cell + x -= std::floor(x); + y -= std::floor(y); + + // Compute fade curves for each coordinate + double u = fade(x); + double v = fade(y); + + // Hash coordinates of the 4 cube corners + int A = permutation_[X] + Y; + int AA = permutation_[A]; + int AB = permutation_[A + 1]; + int B = permutation_[X + 1] + Y; + int BA = permutation_[B]; + int BB = permutation_[B + 1]; + + // Add blended results from 4 corners of the square + double result = lerp( + v, + lerp(u, grad(permutation_[AA], x, y), grad(permutation_[BA], x - 1, y)), + lerp( + u, grad(permutation_[AB], x, y - 1), + grad(permutation_[BB], x - 1, y - 1) + ) + ); + + // Convert from [-1,1] to [0,1] + return (result + 1.0) * 0.5; +} + +double PerlinNoise::octave_noise( + double x, double y, int octaves, double persistence +) const { + double value = 0.0; + double amplitude = 1.0; + double frequency = 1.0; + double max_value = 0.0; + + for (int i = 0; i < octaves; i++) { + value += noise(x * frequency, y * frequency) * amplitude; + max_value += amplitude; + amplitude *= persistence; + frequency *= 2.0; + } + + return value / max_value; +} + +} // namespace istd diff --git a/tilemap/src/tilemap.cpp b/tilemap/src/tilemap.cpp new file mode 100644 index 0000000..f1b9f02 --- /dev/null +++ b/tilemap/src/tilemap.cpp @@ -0,0 +1,64 @@ +#include "tilemap.h" +#include + +namespace istd { + +TileMap::TileMap(std::uint8_t size): size_(size) { + if (size == 0 || size > 100) { + throw std::invalid_argument("TileMap size must be between 1 and 100"); + } + + // Initialize the 2D vector of chunks + chunks_.resize(size); + for (auto &row : chunks_) { + row.resize(size); + } +} + +Chunk &TileMap::get_chunk(std::uint8_t chunk_x, std::uint8_t chunk_y) { + if (chunk_x >= size_ || chunk_y >= size_) { + throw std::out_of_range("Chunk coordinates out of bounds"); + } + return chunks_[chunk_y][chunk_x]; +} + +const Chunk &TileMap::get_chunk( + std::uint8_t chunk_x, std::uint8_t chunk_y +) const { + if (chunk_x >= size_ || chunk_y >= size_) { + throw std::out_of_range("Chunk coordinates out of bounds"); + } + return chunks_[chunk_y][chunk_x]; +} + +Tile &TileMap::get_tile(const TilePos &pos) { + if (pos.chunk_x >= size_ || pos.chunk_y >= size_) { + throw std::out_of_range("Chunk coordinates out of bounds"); + } + if (pos.local_x >= Chunk::size || pos.local_y >= Chunk::size) { + throw std::out_of_range("Local coordinates out of bounds"); + } + return chunks_[pos.chunk_y][pos.chunk_x].tiles[pos.local_y][pos.local_x]; +} + +const Tile &TileMap::get_tile(const TilePos &pos) const { + if (pos.chunk_x >= size_ || pos.chunk_y >= size_) { + throw std::out_of_range("Chunk coordinates out of bounds"); + } + if (pos.local_x >= Chunk::size || pos.local_y >= Chunk::size) { + throw std::out_of_range("Local coordinates out of bounds"); + } + return chunks_[pos.chunk_y][pos.chunk_x].tiles[pos.local_y][pos.local_x]; +} + +void TileMap::set_tile(const TilePos &pos, const Tile &tile) { + if (pos.chunk_x >= size_ || pos.chunk_y >= size_) { + throw std::out_of_range("Chunk coordinates out of bounds"); + } + if (pos.local_x >= Chunk::size || pos.local_y >= Chunk::size) { + throw std::out_of_range("Local coordinates out of bounds"); + } + chunks_[pos.chunk_y][pos.chunk_x].tiles[pos.local_y][pos.local_x] = tile; +} + +} // namespace istd