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 <szdytom@qq.com>
This commit is contained in:
方而静 2025-08-01 14:28:36 +08:00
commit b6656f5023
Signed by: szTom
GPG Key ID: 072D999D60C6473C
23 changed files with 2381 additions and 0 deletions

148
.clang-format Normal file
View File

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

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
.vscode/
build/
*.out
*.swp
*.o
tilemap_demo
CMakeCache.txt
CMakeFiles/
Makefile
cmake_install.cmake

155
BIOME_SYSTEM_GUIDE.md Normal file
View File

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

9
CMakeLists.txt Normal file
View File

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

205
README.md Normal file
View File

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

92
TILEMAP_USAGE.md Normal file
View File

@ -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(...)`: 生成多八度噪声值

22
tilemap/CMakeLists.txt Normal file
View File

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

113
tilemap/README.md Normal file
View File

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

View File

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

View File

@ -0,0 +1,299 @@
#include "biome.h"
#include "generation.h"
#include "tile.h"
#include "tilemap.h"
#include <iomanip>
#include <iostream>
#include <map>
// 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<double>(
chunk_x * istd::Chunk::size + sub_x * 16 + 8
);
double global_y = static_cast<double>(
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<istd::BiomeType, std::map<std::uint8_t, int>> 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<double>(
chunk_x * istd::Chunk::size + sub_x * 16 + 8
);
double global_y = static_cast<double>(
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<double>(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<int>(map_size) << "x"
<< static_cast<int>(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<BiomeSample> 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<double>(
sample.chunk_x * istd::Chunk::size + sample.sub_x * 16 + 8
);
double global_y = static_cast<double>(
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<int>(map_size) << "x"
<< static_cast<int>(map_size) << " chunks" << std::endl;
std::cout << "- Total sub-chunks: "
<< static_cast<int>(map_size) * static_cast<int>(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;
}

View File

@ -0,0 +1,175 @@
#include "generation.h"
#include "tile.h"
#include "tilemap.h"
#include <iomanip>
#include <iostream>
#include <map>
// 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<std::uint8_t, int> 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<std::uint8_t, int> 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<int>(map_size) << "x"
<< static_cast<int>(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<std::pair<std::uint8_t, std::uint8_t>> 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<int>(chunk_pos.first) << ","
<< static_cast<int>(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<int>(map_size) << "x"
<< static_cast<int>(map_size) << " chunks" << std::endl;
std::cout << "- Chunk size: " << static_cast<int>(istd::Chunk::size)
<< "x" << static_cast<int>(istd::Chunk::size) << " tiles"
<< std::endl;
std::cout << "- Total tiles: "
<< static_cast<int>(map_size) * static_cast<int>(map_size)
* static_cast<int>(istd::Chunk::size)
* static_cast<int>(istd::Chunk::size)
<< std::endl;
std::cout << "- Total chunks: "
<< static_cast<int>(map_size) * static_cast<int>(map_size)
<< std::endl;
// Test TilePos functionality
std::cout << std::endl << "=== TilePos Testing ===" << std::endl;
std::vector<istd::TilePos> 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<int>(pos.chunk_x)
<< "," << static_cast<int>(pos.chunk_y) << ") local("
<< static_cast<int>(pos.local_x) << ","
<< static_cast<int>(pos.local_y)
<< "): " << get_tile_char(tile) << " (type "
<< static_cast<int>(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;
}

View File

@ -0,0 +1,90 @@
#include "generation.h"
#include "tile.h"
#include "tilemap.h"
#include <iostream>
// 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<int>(map_size) << "x"
<< static_cast<int>(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 &center_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<int>(map_size * map_size)
<< std::endl;
std::cout << "- Total tiles: "
<< static_cast<int>(map_size * map_size * 64 * 64)
<< std::endl;
std::cout << "- Memory usage: ~"
<< static_cast<int>(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;
}

View File

@ -0,0 +1,226 @@
#include "biome.h"
#include "generation.h"
#include "tile.h"
#include "tilemap.h"
#include <iomanip>
#include <iostream>
#include <map>
// 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<int>(chunk_x) << ","
<< static_cast<int>(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<istd::BiomeType, int> 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<double>(
chunk_x * istd::Chunk::size + sub_x * 16 + 8
);
double global_y = static_cast<double>(
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<int>(chunk_x)
<< "," << static_cast<int>(chunk_y) << ") sub-chunk("
<< static_cast<int>(sub_x) << "," << static_cast<int>(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<int>(map_size) << "x"
<< static_cast<int>(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<int>(map_size) << "x"
<< static_cast<int>(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<int>(map_size) * static_cast<int>(map_size)
* 16
<< std::endl;
std::cout << "- Total tiles: "
<< static_cast<int>(map_size) * static_cast<int>(map_size)
* static_cast<int>(istd::Chunk::size)
* static_cast<int>(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<int>(sub_pos.sub_x) << ","
<< static_cast<int>(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<int>(tile_start_x) << ","
<< static_cast<int>(tile_start_y) << ")" << std::endl;
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}

69
tilemap/include/biome.h Normal file
View File

@ -0,0 +1,69 @@
#ifndef ISTD_TILEMAP_BIOME_H
#define ISTD_TILEMAP_BIOME_H
#include <cstdint>
#include <string_view>
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<std::uint8_t, std::uint8_t> subchunk_to_tile_start(
const SubChunkPos &pos
) {
return {pos.sub_x * 16, pos.sub_y * 16};
}
} // namespace istd
#endif

23
tilemap/include/chunk.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef ISTD_TILEMAP_CHUNK_H
#define ISTD_TILEMAP_CHUNK_H
#include "tile.h"
#include <cstdint>
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

View File

@ -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 <array>
#include <cstdint>
#include <vector>
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<std::vector<std::array<std::array<BiomeType, 4>, 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<double, double> 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

48
tilemap/include/noise.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef ISTD_TILEMAP_NOISE_H
#define ISTD_TILEMAP_NOISE_H
#include <cstdint>
#include <vector>
namespace istd {
class PerlinNoise {
private:
std::vector<int> 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

49
tilemap/include/tile.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef ISTD_TILEMAP_TILE_H
#define ISTD_TILEMAP_TILE_H
#include <cstdint>
#include <stdexcept> // 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<std::uint8_t>(i)};
}
}
throw std::invalid_argument("Invalid tile type name");
}
};
static_assert(sizeof(Tile) == 1);
} // namespace istd
#endif

54
tilemap/include/tilemap.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef ISTD_TILEMAP_TILEMAP_H
#define ISTD_TILEMAP_TILEMAP_H
#include "chunk.h"
#include <cstdint>
#include <vector>
namespace istd {
class TileMap {
private:
std::uint8_t size_; // Number of chunks in each dimension (n×n)
std::vector<std::vector<Chunk>> 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

141
tilemap/src/biome.cpp Normal file
View File

@ -0,0 +1,141 @@
#include "biome.h"
#include <algorithm>
#include <array>
#include <cmath>
namespace istd {
// Biome properties lookup table
constexpr std::array<BiomeProperties, 9> 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<std::uint8_t>(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

153
tilemap/src/generation.cpp Normal file
View File

@ -0,0 +1,153 @@
#include "generation.h"
#include "biome.h"
#include <cmath>
#include <random>
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<double>(
chunk_x * Chunk::size + sub_x * 16 + 8
);
double global_y = static_cast<double>(
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<double>(chunk_x * Chunk::size + local_x);
double global_y
= static_cast<double>(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<double, double> 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

94
tilemap/src/noise.cpp Normal file
View File

@ -0,0 +1,94 @@
#include "noise.h"
#include <algorithm>
#include <cmath>
#include <numeric>
#include <random>
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<int>(std::floor(x)) & 255;
int Y = static_cast<int>(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

64
tilemap/src/tilemap.cpp Normal file
View File

@ -0,0 +1,64 @@
#include "tilemap.h"
#include <stdexcept>
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