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