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:
方而静 2025-08-01 15:32:36 +08:00
parent b6656f5023
commit 0b245e0483
Signed by: szTom
GPG Key ID: 072D999D60C6473C
15 changed files with 660 additions and 1240 deletions

2
.github/copilot-instructions.md vendored Normal file
View File

@ -0,0 +1,2 @@
+ Always use English for code comments.
+ Don't create getters or setters if they are not necessary.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 &center_chunk = tilemap.get_chunk(3, 3);
for (int y = 20; y < 44; ++y) {
for (int x = 20; x < 44; ++x) {
std::cout << get_tile_char(center_chunk.tiles[y][x]);
}
std::cout << std::endl;
}
// Demonstrate TilePos usage
std::cout << std::endl << "TilePos demonstration:" << std::endl;
istd::TilePos test_pos{2, 3, 15, 25};
istd::Tile original = tilemap.get_tile(test_pos);
std::cout << "Tile at chunk(2,3) local(15,25): "
<< get_tile_char(original) << std::endl;
// Modify the tile
istd::Tile water = istd::Tile::from_name("water");
tilemap.set_tile(test_pos, water);
istd::Tile modified = tilemap.get_tile(test_pos);
std::cout << "After setting to water: " << get_tile_char(modified)
<< std::endl;
std::cout << std::endl << "Map Statistics:" << std::endl;
std::cout << "- Total chunks: " << static_cast<int>(map_size * map_size)
<< std::endl;
std::cout << "- Total tiles: "
<< static_cast<int>(map_size * map_size * 64 * 64)
<< std::endl;
std::cout << "- Memory usage: ~"
<< static_cast<int>(map_size * map_size * 64 * 64) << " bytes"
<< std::endl;
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}

View File

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

View 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;
}

View File

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

View File

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

View File

@ -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;
};
/**

View File

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

View File

@ -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);
// 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];
// 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)
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}
};
return biome_matrix[temp_category][humidity_category];
}
} // namespace istd

View File

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