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:
commit
b6656f5023
148
.clang-format
Normal file
148
.clang-format
Normal 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
10
.gitignore
vendored
Normal 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
155
BIOME_SYSTEM_GUIDE.md
Normal 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
9
CMakeLists.txt
Normal 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
205
README.md
Normal 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
92
TILEMAP_USAGE.md
Normal 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
22
tilemap/CMakeLists.txt
Normal 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
113
tilemap/README.md
Normal 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+)
|
24
tilemap/examples/CMakeLists.txt
Normal file
24
tilemap/examples/CMakeLists.txt
Normal 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)
|
299
tilemap/examples/advanced_biome_demo.cpp
Normal file
299
tilemap/examples/advanced_biome_demo.cpp
Normal 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;
|
||||
}
|
175
tilemap/examples/advanced_demo.cpp
Normal file
175
tilemap/examples/advanced_demo.cpp
Normal 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;
|
||||
}
|
90
tilemap/examples/basic_demo.cpp
Normal file
90
tilemap/examples/basic_demo.cpp
Normal 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 ¢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<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;
|
||||
}
|
226
tilemap/examples/biome_demo.cpp
Normal file
226
tilemap/examples/biome_demo.cpp
Normal 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
69
tilemap/include/biome.h
Normal 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
23
tilemap/include/chunk.h
Normal 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
|
118
tilemap/include/generation.h
Normal file
118
tilemap/include/generation.h
Normal 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
48
tilemap/include/noise.h
Normal 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
49
tilemap/include/tile.h
Normal 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
54
tilemap/include/tilemap.h
Normal 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
141
tilemap/src/biome.cpp
Normal 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
153
tilemap/src/generation.cpp
Normal 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
94
tilemap/src/noise.cpp
Normal 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
64
tilemap/src/tilemap.cpp
Normal 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
|
Loading…
x
Reference in New Issue
Block a user