Refactor tilemap examples and enhance biome generation

- Removed dual_noise_demo example as it was deemed unnecessary.
- Added perlin_demo example for visualizing Perlin noise.
- Updated biome_demo to generate SVG visualizations of tilemaps.
- Changed biome properties from thresholds to ratios for better control.
- Modified terrain generation logic to accommodate new biome properties.
- Improved documentation with detailed API and usage examples.

Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
方而静 2025-08-01 19:25:36 +08:00
parent 1289e99fc3
commit 1cb4c19b77
Signed by: szTom
GPG Key ID: 072D999D60C6473C
11 changed files with 652 additions and 700 deletions

View File

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

203
README.md
View File

@ -1,205 +1,4 @@
# Instructed Project
一个使用现代C++实现的地图生成系统项目。
A MMO RTS programming game that involves writing code to control units, build structures, manage resources, and fight with other players in a multiplayer online environment.
## 项目组成
### 🗺️ Tilemap Library
位于 `tilemap/` 目录下的核心地图生成库,提供:
- **Perlin噪声地形生成**: 基于噪声算法的自然地形
- **生物群系系统**: 9种基于气候的生物群系
- **高效区块系统**: 支持大规模地图生成
- **现代C++设计**: 使用C++23标准
### 📁 项目结构
```
instructed/
├── tilemap/ # Tilemap库
│ ├── include/ # 头文件
│ ├── src/ # 库源代码
│ ├── examples/ # 示例程序
│ └── README.md # 库文档
├── CMakeLists.txt # 主构建文件
└── README.md # 项目说明
```
## 🚀 快速开始
### 构建项目
```bash
# 克隆或下载项目
cd instructed
# 构建
mkdir build && cd build
cmake ..
make
# 运行示例
./build/tilemap/examples/basic_demo
```
### 禁用示例程序构建
```bash
cmake -DBUILD_EXAMPLES=OFF ..
make
```
## 🎮 示例程序
| 程序 | 描述 |
|------|------|
| `basic_demo` | 基础地图生成演示 |
| `advanced_demo` | 高级功能和统计信息 |
| `biome_demo` | 生物群系系统演示 |
| `advanced_biome_demo` | 生物群系分析和可视化 |
## 📖 文档
- [Tilemap库使用指南](tilemap/README.md)
- [生物群系系统详解](BIOME_SYSTEM_GUIDE.md)
- [传统使用方法](TILEMAP_USAGE.md)
## 🛠️ 技术要求
- **C++23**: 现代C++特性支持
- **CMake 3.27+**: 构建系统
- **支持的编译器**: GCC 13+, Clang 16+, MSVC 2022+
## 📝 开发说明
这个项目展示了:
- 清晰的库和示例分离
- 现代CMake最佳实践
- 模块化的C++库设计
- 完整的文档和示例
## Features
- **Chunk-based Architecture**: Divides the world into 64x64 tile chunks for efficient memory management
- **Multiple Tile Types**: Supports different terrain types (Empty, Grass, Stone, Water, Sand, Forest)
- **Perlin Noise Generation**: Uses Perlin noise algorithm for natural-looking terrain generation
- **Flexible Map Size**: Support for n×n chunks (configurable map dimensions)
- **Efficient Access**: Fast tile and chunk access with coordinate conversion utilities
- **Tile Properties**: Tiles have properties like walkability and liquid state
## Project Structure
```
include/
├── tile.h # Individual tile class
├── chunk.h # 64x64 chunk of tiles
├── tilemap.h # Main tilemap manager
└── random.h # Random number and noise generation
src/
├── tile.cpp
├── chunk.cpp
├── tilemap.cpp
├── random.cpp
└── main.cpp # Demo application
```
## Building
### Option 1: Using Make
```bash
make
```
### Option 2: Using CMake
```bash
make cmake
```
### Option 3: Manual compilation
```bash
g++ -std=c++17 -Wall -Wextra -O2 -Iinclude src/*.cpp -o tilemap_demo
```
## Running
```bash
# Using make
make run
# Or directly
./build/tilemap_demo
```
## Usage Example
```cpp
#include "tilemap.h"
int main() {
// Create a 4x4 tilemap (256x256 tiles total)
TileMap tileMap(4, 4);
// Generate terrain using Perlin noise
tileMap.generatePerlin(54321, 0.05f);
// Access individual tiles
Tile& tile = tileMap.getTile(100, 100);
tile.setType(Tile::WATER);
// Access chunks
Chunk* chunk = tileMap.getChunk(1, 1);
if (chunk) {
chunk->setTile(32, 32, Tile(Tile::STONE));
}
// Print map overview
tileMap.printMap();
return 0;
}
```
## Key Classes
### Tile
- Represents a single tile with a type (Empty, Grass, Stone, Water, Sand, Forest)
- Provides utility methods like `isWalkable()` and `isLiquid()`
### Chunk
- Contains a 64x64 array of tiles
- Supports Perlin noise generation for natural terrain
- Manages local tile coordinates within the chunk
### TileMap
- Manages multiple chunks to form a complete world
- Handles coordinate conversion between world and chunk coordinates
- Provides unified access to tiles across chunk boundaries
### Random
- Utility class for random number generation
- Implements Perlin noise for natural terrain generation
- Supports seeded generation for reproducible results
## Coordinate System
- **World Coordinates**: Global tile positions (0,0) to (worldWidth-1, worldHeight-1)
- **Chunk Coordinates**: Chunk positions (0,0) to (mapWidth-1, mapHeight-1)
- **Local Coordinates**: Tile positions within a chunk (0,0) to (63,63)
The system automatically converts between coordinate systems as needed.
## Customization
You can easily extend the system by:
- Adding new tile types to the `Tile::Type` enum
- Implementing custom generation algorithms in `Chunk`
- Modifying tile properties and behaviors
- Adding new terrain features or biomes
## Performance Notes
- Each chunk contains 4,096 tiles (64×64)
- Memory usage scales with the number of active chunks
- Coordinate conversion is O(1)
- Tile access within a chunk is O(1)

View File

@ -1,113 +1,3 @@
# Tilemap Library
一个基于Perlin噪声和生物群系系统的C++地图生成库。
## 项目结构
```
tilemap/
├── include/ # 库头文件
│ ├── biome.h # 生物群系系统
│ ├── chunk.h # 区块和瓦片定义
│ ├── generation.h # 地形生成器
│ ├── noise.h # Perlin噪声实现
│ ├── tile.h # 瓦片类型定义
│ └── tilemap.h # 地图容器类
├── src/ # 库源文件
│ ├── biome.cpp # 生物群系实现
│ ├── generation.cpp # 地形生成实现
│ ├── noise.cpp # Perlin噪声实现
│ └── tilemap.cpp # 地图容器实现
├── examples/ # 示例程序
│ ├── basic_demo.cpp # 基础功能演示
│ ├── advanced_demo.cpp # 高级功能演示
│ ├── biome_demo.cpp # 生物群系演示
│ ├── advanced_biome_demo.cpp # 高级生物群系分析
│ └── CMakeLists.txt # 示例程序构建配置
└── CMakeLists.txt # 主构建配置
```
## 构建
### 构建库和示例程序
```bash
mkdir build
cd build
cmake ..
make
```
### 仅构建库(不构建示例)
```bash
mkdir build
cd build
cmake -DBUILD_EXAMPLES=OFF ..
make
```
## 运行示例
构建完成后,可执行文件位于 `build/tilemap/examples/` 目录下:
```bash
# 基础演示
./build/tilemap/examples/basic_demo
# 高级功能演示
./build/tilemap/examples/advanced_demo
# 生物群系系统演示
./build/tilemap/examples/biome_demo
# 高级生物群系分析
./build/tilemap/examples/advanced_biome_demo
```
## 库使用
### 基本用法
```cpp
#include "tilemap.h"
#include "generation.h"
// 创建一个10x10区块的地图
istd::TileMap tilemap(10);
// 配置生成参数
istd::GenerationConfig config;
config.seed = 42;
// 生成地图
istd::map_generate(tilemap, config);
```
### 使用生物群系系统
```cpp
#include "generation.h"
istd::GenerationConfig config;
config.seed = 12345;
config.temperature_scale = 0.005; // 温度变化尺度
config.humidity_scale = 0.007; // 湿度变化尺度
istd::TerrainGenerator generator(config);
generator.generate_map(tilemap);
```
## 核心特性
- **Perlin噪声地形生成**: 生成自然的地形特征
- **生物群系系统**: 基于温度和湿度的9种生物群系
- **区块系统**: 支持大型地图的高效存储
- **子区块分级**: 16x16瓦片的子区块每个具有一致的生物群系
- **高性能**: 优化的内存使用和生成算法
## 编译要求
- C++23标准支持
- CMake 3.27或更高版本
- 支持C++23的编译器GCC 13+, Clang 16+, MSVC 2022+
The Tilemap System use in Instructed. Generates 2D tilemaps with biomes and features.

245
tilemap/docs/api.md Normal file
View File

@ -0,0 +1,245 @@
# Tilemap Library API Documentation
## Overview
The tilemap library provides a flexible system for generating and managing tile-based terrain with biome support. The library consists of several main components:
- **TileMap**: The main map container holding chunks of tiles
- **Chunk**: 64x64 tile containers with biome information
- **Tile**: Individual map tiles with base and surface types
- **TerrainGenerator**: Procedural terrain generation system
- **Biome System**: Climate-based terrain variation
## Core Classes
### TileMap
The main container for the entire map, organized as an n×n grid of chunks.
```cpp
class TileMap {
public:
explicit TileMap(std::uint8_t size);
std::uint8_t get_size() const;
Chunk& get_chunk(std::uint8_t chunk_x, std::uint8_t chunk_y);
const Chunk& get_chunk(std::uint8_t chunk_x, std::uint8_t chunk_y) const;
Tile& get_tile(const TilePos& pos);
const Tile& get_tile(const TilePos& pos) const;
void set_tile(const TilePos& pos, const Tile& tile);
};
```
**Constructor Parameters:**
- `size`: Number of chunks per side (max 100), creating an n×n grid
### Chunk
Each chunk contains 64×64 tiles and 4×4 sub-chunk biome information.
```cpp
struct Chunk {
static constexpr uint8_t size = 64; // Tiles per side
static constexpr uint8_t subchunk_size = 16; // Tiles per sub-chunk side
static constexpr uint8_t subchunk_count = 4; // Sub-chunks per side
Tile tiles[size][size]; // 64x64 tile grid
BiomeType biome[subchunk_count][subchunk_count]; // 4x4 biome grid
};
```
### Tile
Individual map tiles with base terrain and surface features.
```cpp
struct Tile {
BaseTileType base : 4; // Base terrain type
SurfaceTileType surface : 4; // Surface features
};
```
**Base Tile Types:**
- `Land`: Standard ground terrain
- `Mountain`: Rocky elevated terrain
- `Sand`: Desert/beach terrain
- `Water`: Water bodies
- `Ice`: Frozen terrain
**Surface Tile Types:**
- `Empty`: No surface features
- `Wood`: Trees/vegetation
- `Structure`: Player-built structures
### TilePos
Position structure for locating tiles within the map.
```cpp
struct TilePos {
uint8_t chunk_x; // Chunk X coordinate
uint8_t chunk_y; // Chunk Y coordinate
uint8_t local_x; // Tile X within chunk (0-63)
uint8_t local_y; // Tile Y within chunk (0-63)
};
```
## Terrain Generation
### GenerationConfig
Configuration parameters for terrain generation.
```cpp
struct GenerationConfig {
std::uint64_t seed = 0; // Random seed
double temperature_scale = 0.005; // Temperature noise scale
double humidity_scale = 0.007; // Humidity noise scale
double base_scale = 0.08; // Base terrain noise scale
};
```
### TerrainGenerator
Main class for procedural terrain generation.
```cpp
class TerrainGenerator {
public:
explicit TerrainGenerator(const GenerationConfig& config);
void generate_map(TileMap& tilemap);
};
```
### Generation Function
Convenience function for map generation.
```cpp
void map_generate(TileMap& tilemap, const GenerationConfig& config);
```
## Biome System
### BiomeType
Available biome types based on temperature and humidity.
```cpp
enum class BiomeType : std::uint8_t {
SnowyPeeks, // Cold & Dry
SnowyPlains, // Cold & Moderate
FrozenOcean, // Cold & Wet
Plains, // Temperate & Dry
Forest, // Temperate & Moderate
Ocean, // Temperate & Wet
Desert, // Hot & Dry
Savanna, // Hot & Moderate
LukeOcean, // Hot & Wet
};
```
### BiomeProperties
Properties that control terrain generation for each biome.
```cpp
struct BiomeProperties {
std::string_view name; // Biome name
double water_ratio; // Water generation ratio
double ice_ratio; // Ice generation ratio
double sand_ratio; // Sand generation ratio
double land_ratio; // Land generation ratio
int base_octaves = 3; // Noise octaves
double base_persistence = 0.5; // Noise persistence
};
```
### Biome Functions
```cpp
const BiomeProperties& get_biome_properties(BiomeType biome);
BiomeType determine_biome(double temperature, double humidity);
```
### SubChunkPos
Position within a chunk's 4×4 sub-chunk grid.
```cpp
struct SubChunkPos {
std::uint8_t sub_x; // 0-3
std::uint8_t sub_y; // 0-3
};
constexpr std::pair<std::uint8_t, std::uint8_t> subchunk_to_tile_start(
const SubChunkPos& pos
);
```
## Usage Examples
### Basic Map Generation
```cpp
#include "tilemap.h"
#include "generation.h"
// Create a 4x4 chunk map
istd::TileMap tilemap(4);
// Configure generation
istd::GenerationConfig config;
config.seed = 12345;
config.temperature_scale = 0.005;
config.humidity_scale = 0.007;
config.base_scale = 0.08;
// Generate terrain
istd::map_generate(tilemap, config);
// Access tiles
for (int chunk_y = 0; chunk_y < 4; ++chunk_y) {
for (int chunk_x = 0; chunk_x < 4; ++chunk_x) {
const auto& chunk = tilemap.get_chunk(chunk_x, chunk_y);
// Process chunk tiles...
}
}
```
### Accessing Individual Tiles
```cpp
// Using TilePos
istd::TilePos pos{0, 0, 32, 32}; // Chunk (0,0), tile (32,32)
const auto& tile = tilemap.get_tile(pos);
// Direct chunk access
const auto& chunk = tilemap.get_chunk(0, 0);
const auto& tile2 = chunk.tiles[32][32];
```
### Working with Biomes
```cpp
// Get biome for a sub-chunk
const auto& chunk = tilemap.get_chunk(0, 0);
istd::BiomeType biome = chunk.biome[1][1]; // Sub-chunk (1,1)
// Get biome properties
const auto& props = istd::get_biome_properties(biome);
std::cout << "Biome: " << props.name << std::endl;
```
## Performance Notes
- Each chunk contains 4,096 tiles (64×64)
- A 4×4 chunk map contains 65,536 total tiles
- Sub-chunks provide efficient biome management (16×16 tile regions)
- Tiles are packed into 1 byte each for memory efficiency
- Generation uses Perlin noise for natural-looking terrain
## Thread Safety
The library is not inherently thread-safe. External synchronization is required for concurrent access to TileMap objects.

View File

@ -3,12 +3,12 @@ cmake_minimum_required(VERSION 3.27)
# Examples for the tilemap library
# Each example is built as a separate executable
# 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)
# 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)
# Perlin noise visualization
add_executable(perlin_demo perlin_demo.cpp)
target_link_libraries(perlin_demo PRIVATE istd_tilemap)
target_include_directories(perlin_demo PRIVATE ../include)

View File

@ -1,187 +1,202 @@
#include "biome.h"
#include "generation.h"
#include "tile.h"
#include "tilemap.h"
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
using namespace istd;
// Function to get character representation for biome visualization
char get_biome_char(BiomeType biome) {
switch (biome) {
case BiomeType::Desert:
return 'D';
case BiomeType::Savanna:
return 'S';
case BiomeType::TropicalRainforest:
return 'T';
case BiomeType::Grassland:
return 'G';
case BiomeType::DeciduousForest:
return 'F';
case BiomeType::TemperateRainforest:
return 'R';
case BiomeType::Tundra:
return 'U';
case BiomeType::Taiga:
return 'A';
case BiomeType::FrozenOcean:
return 'O';
// Color mapping for different base tile types
const char *get_tile_color(istd::BaseTileType type) {
switch (type) {
case istd::BaseTileType::Land:
return "#90EE90"; // Light green
case istd::BaseTileType::Mountain:
return "#8B4513"; // Saddle brown
case istd::BaseTileType::Sand:
return "#F4A460"; // Sandy brown
case istd::BaseTileType::Water:
return "#1E90FF"; // Dodger blue
case istd::BaseTileType::Ice:
return "#B0E0E6"; // Powder blue
default:
return "#808080"; // Gray for unknown types
}
return '?';
}
// 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";
// Generate SVG file from tilemap
void generate_svg(const istd::TileMap &tilemap, const std::string &filename) {
std::ofstream file(filename);
if (!file.is_open()) {
std::cerr << "Error: Could not open output file: " << filename
<< std::endl;
return;
}
return "Unknown";
const int chunks_per_side = tilemap.get_size();
const int tiles_per_chunk = istd::Chunk::size;
const int total_tiles = chunks_per_side * tiles_per_chunk;
const int tile_size = 2; // Size of each tile in SVG pixels
const int svg_size = total_tiles * tile_size;
// SVG header
file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
file << "<svg width=\"" << svg_size << "\" height=\"" << svg_size
<< "\" xmlns=\"http://www.w3.org/2000/svg\">\n";
file << "<title>Tilemap Visualization</title>\n";
// Generate tiles
for (int chunk_y = 0; chunk_y < chunks_per_side; ++chunk_y) {
for (int chunk_x = 0; chunk_x < chunks_per_side; ++chunk_x) {
const auto &chunk = tilemap.get_chunk(chunk_x, chunk_y);
for (int tile_y = 0; tile_y < tiles_per_chunk; ++tile_y) {
for (int tile_x = 0; tile_x < tiles_per_chunk; ++tile_x) {
const auto &tile = chunk.tiles[tile_x][tile_y];
int global_x = chunk_x * tiles_per_chunk + tile_x;
int global_y = chunk_y * tiles_per_chunk + tile_y;
int svg_x = global_x * tile_size;
int svg_y = global_y * tile_size;
const char *color = get_tile_color(tile.base);
file << "<rect x=\"" << svg_x << "\" y=\"" << svg_y
<< "\" width=\"" << tile_size << "\" height=\""
<< tile_size << "\" fill=\"" << color << "\"/>\n";
}
}
}
}
// Add grid lines for chunk boundaries
file << "<!-- Chunk boundaries -->\n";
for (int i = 0; i <= chunks_per_side; ++i) {
int pos = i * tiles_per_chunk * tile_size;
// Vertical lines
file << "<line x1=\"" << pos << "\" y1=\"0\" x2=\"" << pos << "\" y2=\""
<< svg_size << "\" stroke=\"black\" stroke-width=\"2\"/>\n";
// Horizontal lines
file << "<line x1=\"0\" y1=\"" << pos << "\" x2=\"" << svg_size
<< "\" y2=\"" << pos
<< "\" stroke=\"black\" stroke-width=\"2\"/>\n";
}
// Legend
file << "<!-- Legend -->\n";
file << "<g transform=\"translate(10, 10)\">\n";
file << "<rect x=\"0\" y=\"0\" width=\"200\" height=\"140\" fill=\"white\" "
"stroke=\"black\" stroke-width=\"1\" opacity=\"0.9\"/>\n";
file << "<text x=\"10\" y=\"20\" font-family=\"Arial\" font-size=\"14\" "
"font-weight=\"bold\">Legend</text>\n";
const std::pair<istd::BaseTileType, const char *> legend_items[] = {
{istd::BaseTileType::Land, "Land" },
{istd::BaseTileType::Mountain, "Mountain"},
{istd::BaseTileType::Sand, "Sand" },
{istd::BaseTileType::Water, "Water" },
{istd::BaseTileType::Ice, "Ice" }
};
for (int i = 0; i < 5; ++i) {
int y_pos = 40 + i * 20;
file << "<rect x=\"10\" y=\"" << (y_pos - 10)
<< "\" width=\"15\" height=\"15\" fill=\""
<< get_tile_color(legend_items[i].first)
<< "\" stroke=\"black\" stroke-width=\"1\"/>\n";
file << "<text x=\"30\" y=\"" << y_pos
<< "\" font-family=\"Arial\" font-size=\"12\">"
<< legend_items[i].second << "</text>\n";
}
file << "</g>\n";
file << "</svg>\n";
file.close();
std::cout << "SVG file generated: " << filename << std::endl;
std::cout << "Tilemap size: " << total_tiles << "x" << total_tiles
<< " tiles" << std::endl;
std::cout << "Chunks: " << chunks_per_side << "x" << chunks_per_side
<< std::endl;
}
int main() {
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;
// Print statistics about the generated map
void print_statistics(const istd::TileMap &tilemap) {
int tile_counts[5] = {0}; // Count for each base tile type
const int chunks_per_side = tilemap.get_size();
const int tiles_per_chunk = istd::Chunk::size;
// Create a tilemap for biome demonstration
constexpr std::uint8_t map_size = 3; // 3x3 chunks for compact display
TileMap tilemap(map_size);
for (int chunk_y = 0; chunk_y < chunks_per_side; ++chunk_y) {
for (int chunk_x = 0; chunk_x < chunks_per_side; ++chunk_x) {
const auto &chunk = tilemap.get_chunk(chunk_x, chunk_y);
// 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
for (int tile_y = 0; tile_y < tiles_per_chunk; ++tile_y) {
for (int tile_x = 0; tile_x < tiles_per_chunk; ++tile_x) {
const auto &tile = chunk.tiles[tile_x][tile_y];
tile_counts[static_cast<int>(tile.base)]++;
}
}
}
}
std::cout << "Generating " << static_cast<int>(map_size) << "x"
<< static_cast<int>(map_size) << " chunks..." << std::endl;
const char *tile_names[] = {"Land", "Mountain", "Sand", "Water", "Ice"};
int total_tiles
= chunks_per_side * chunks_per_side * tiles_per_chunk * tiles_per_chunk;
std::cout << "\nTile Statistics:\n";
std::cout << "================\n";
for (int i = 0; i < 5; ++i) {
double percentage = (double)tile_counts[i] / total_tiles * 100.0;
std::cout << std::setw(10) << tile_names[i] << ": " << std::setw(8)
<< tile_counts[i] << " (" << std::fixed
<< std::setprecision(1) << percentage << "%)\n";
}
std::cout << "Total tiles: " << total_tiles << std::endl;
}
int main(int argc, char *argv[]) {
// Parse command line arguments
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <seed> <output_file.svg>\n";
std::cerr << "Example: " << argv[0] << " 12345 output.svg\n";
return 1;
}
std::uint64_t seed = std::strtoull(argv[1], nullptr, 10);
std::string output_filename = argv[2];
// Validate output filename
if (output_filename.length() < 4
|| output_filename.substr(output_filename.length() - 4) != ".svg") {
std::cerr << "Error: Output filename must end with .svg\n";
return 1;
}
std::cout << "Generating 4x4 chunk tilemap with seed: " << seed
<< std::endl;
// Create 4x4 chunk tilemap
istd::TileMap tilemap(4);
// Configure generation parameters
istd::GenerationConfig config;
config.seed = seed;
config.temperature_scale = 0.005;
config.humidity_scale = 0.007;
config.base_scale = 0.08;
// Generate the map
map_generate(tilemap, config);
std::cout << "Generating terrain..." << std::endl;
istd::map_generate(tilemap, config);
std::cout << "Generation complete!" << std::endl << std::endl;
// Generate SVG output
std::cout << "Creating SVG visualization..." << std::endl;
generate_svg(tilemap, output_filename);
// 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};
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;
// Print statistics
print_statistics(tilemap);
return 0;
}

View File

@ -1,164 +0,0 @@
#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

@ -0,0 +1,167 @@
#include "noise.h"
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
#include <iomanip>
// Convert a noise value [0,1] to a grayscale color
std::string noise_to_grayscale(double noise_value) {
// Clamp to [0,1] range
noise_value = std::max(0.0, std::min(1.0, noise_value));
// Convert to 0-255 range
int gray = static_cast<int>(noise_value * 255);
// Convert to hex color
std::ostringstream oss;
oss << "#" << std::hex << std::setfill('0') << std::setw(2) << gray
<< std::setw(2) << gray << std::setw(2) << gray;
return oss.str();
}
// Generate SVG visualization of Perlin noise
void generate_noise_svg(const std::string& filename, int size, double scale,
std::uint64_t seed, int octaves = 1, double persistence = 0.5) {
std::ofstream file(filename);
if (!file.is_open()) {
std::cerr << "Error: Could not open output file: " << filename << std::endl;
return;
}
// Create noise generator
istd::PerlinNoise noise(seed);
const int pixel_size = 2; // Size of each pixel in SVG units
const int svg_size = size * pixel_size;
// SVG header
file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
file << "<svg width=\"" << svg_size << "\" height=\"" << svg_size
<< "\" xmlns=\"http://www.w3.org/2000/svg\">\n";
file << "<title>Perlin Noise Visualization (Scale: " << scale
<< ", Octaves: " << octaves << ", Seed: " << seed << ")</title>\n";
// Generate noise values and create rectangles
double min_value = 1.0, max_value = 0.0;
for (int y = 0; y < size; ++y) {
for (int x = 0; x < size; ++x) {
double noise_value;
if (octaves == 1) {
noise_value = noise.noise(x * scale, y * scale);
} else {
noise_value = noise.octave_noise(x * scale, y * scale, octaves, persistence);
}
// Track min/max for statistics
min_value = std::min(min_value, noise_value);
max_value = std::max(max_value, noise_value);
std::string color = noise_to_grayscale(noise_value);
int svg_x = x * pixel_size;
int svg_y = y * pixel_size;
file << "<rect x=\"" << svg_x << "\" y=\"" << svg_y
<< "\" width=\"" << pixel_size << "\" height=\"" << pixel_size
<< "\" fill=\"" << color << "\"/>\n";
}
}
// Add information text
file << "<!-- Statistics: Min=" << min_value << " Max=" << max_value << " -->\n";
// Add parameter info text overlay
file << "<g transform=\"translate(10, 10)\">\n";
file << "<rect x=\"0\" y=\"0\" width=\"300\" height=\"120\" fill=\"white\" stroke=\"black\" stroke-width=\"1\" opacity=\"0.9\"/>\n";
file << "<text x=\"10\" y=\"20\" font-family=\"Arial\" font-size=\"14\" font-weight=\"bold\">Perlin Noise Parameters</text>\n";
file << "<text x=\"10\" y=\"40\" font-family=\"Arial\" font-size=\"12\">Size: " << size << "x" << size << "</text>\n";
file << "<text x=\"10\" y=\"55\" font-family=\"Arial\" font-size=\"12\">Scale: " << scale << "</text>\n";
file << "<text x=\"10\" y=\"70\" font-family=\"Arial\" font-size=\"12\">Seed: " << seed << "</text>\n";
file << "<text x=\"10\" y=\"85\" font-family=\"Arial\" font-size=\"12\">Octaves: " << octaves << "</text>\n";
file << "<text x=\"10\" y=\"100\" font-family=\"Arial\" font-size=\"12\">Range: ["
<< std::fixed << std::setprecision(3) << min_value << ", " << max_value << "]</text>\n";
file << "</g>\n";
// Add grayscale legend
file << "<g transform=\"translate(" << (svg_size - 60) << ", 10)\">\n";
file << "<text x=\"0\" y=\"15\" font-family=\"Arial\" font-size=\"12\" font-weight=\"bold\">Value</text>\n";
for (int i = 0; i <= 10; ++i) {
double value = i / 10.0;
std::string color = noise_to_grayscale(value);
int y_pos = 20 + i * 15;
file << "<rect x=\"0\" y=\"" << y_pos << "\" width=\"20\" height=\"12\" fill=\""
<< color << "\" stroke=\"black\" stroke-width=\"0.5\"/>\n";
file << "<text x=\"25\" y=\"" << (y_pos + 9) << "\" font-family=\"Arial\" font-size=\"10\">"
<< std::fixed << std::setprecision(1) << value << "</text>\n";
}
file << "</g>\n";
file << "</svg>\n";
file.close();
std::cout << "Perlin noise SVG generated: " << filename << std::endl;
std::cout << "Size: " << size << "x" << size << " pixels" << std::endl;
std::cout << "Scale: " << scale << ", Octaves: " << octaves << std::endl;
std::cout << "Value range: [" << std::fixed << std::setprecision(3)
<< min_value << ", " << max_value << "]" << std::endl;
}
int main(int argc, char* argv[]) {
// Default parameters
std::uint64_t seed = 12345;
std::string output_filename = "perlin_noise.svg";
double scale = 0.02;
int octaves = 1;
double persistence = 0.5;
// Parse command line arguments
if (argc >= 2) {
seed = std::strtoull(argv[1], nullptr, 10);
}
if (argc >= 3) {
output_filename = argv[2];
}
if (argc >= 4) {
scale = std::strtod(argv[3], nullptr);
}
if (argc >= 5) {
octaves = std::strtol(argv[4], nullptr, 10);
}
if (argc >= 6) {
persistence = std::strtod(argv[5], nullptr);
}
if (argc == 1 || argc > 6) {
std::cout << "Usage: " << argv[0] << " [seed] [output.svg] [scale] [octaves] [persistence]\n";
std::cout << "Defaults: seed=12345, output=perlin_noise.svg, scale=0.02, octaves=1, persistence=0.5\n";
std::cout << "Examples:\n";
std::cout << " " << argv[0] << " 54321 noise1.svg 0.01\n";
std::cout << " " << argv[0] << " 12345 octave_noise.svg 0.02 4 0.5\n";
if (argc > 6) return 1;
}
// Validate parameters
if (scale <= 0) {
std::cerr << "Error: Scale must be positive\n";
return 1;
}
if (octaves < 1 || octaves > 10) {
std::cerr << "Error: Octaves must be between 1 and 10\n";
return 1;
}
if (persistence <= 0 || persistence > 1) {
std::cerr << "Error: Persistence must be between 0 and 1\n";
return 1;
}
std::cout << "Generating 256x256 Perlin noise visualization..." << std::endl;
std::cout << "Parameters: seed=" << seed << ", scale=" << scale
<< ", octaves=" << octaves << ", persistence=" << persistence << std::endl;
// Generate the noise visualization
generate_noise_svg(output_filename, 256, scale, seed, octaves, persistence);
return 0;
}

View File

@ -36,11 +36,11 @@ struct BiomeProperties {
// Biome name for debugging
std::string_view name;
// Base terrain thresholds (0.0 - 1.0)
double water_threshold;
double ice_threshold;
double sand_threshold;
double land_threshold;
// Base terrain ratios (0.0 - 1.0)
double water_ratio;
double ice_ratio;
double sand_ratio;
double land_ratio;
// Noise parameters for base terrain
int base_octaves = 3;

View File

@ -11,74 +11,74 @@ constexpr BiomeProperties biome_properties[] = {
{
.name = "Snowy Peeks",
.water_threshold = .05,
.ice_threshold = .15,
.sand_threshold = .1,
.land_threshold = .2,
.water_ratio = .05,
.ice_ratio = .15,
.sand_ratio = .1,
.land_ratio = .2,
},
// Snowy Plains (Cold & Moderate)
{
.name = "Snowy Plains",
.water_threshold = .05,
.ice_threshold = .25,
.sand_threshold = .1,
.land_threshold = .4,
.water_ratio = .05,
.ice_ratio = .25,
.sand_ratio = .1,
.land_ratio = .4,
},
// Frozen Ocean (Cold & Wet)
{
.name = "Frozen Ocean",
.water_threshold = .3,
.ice_threshold = .4,
.sand_threshold = .25,
.land_threshold = .05,
.water_ratio = .3,
.ice_ratio = .4,
.sand_ratio = .25,
.land_ratio = .05,
},
// Plains (Temperate & Dry)
{
.name = "Plains",
.water_threshold = .1,
.ice_threshold = .0,
.sand_threshold = .05,
.land_threshold = .65,
.water_ratio = .1,
.ice_ratio = .0,
.sand_ratio = .05,
.land_ratio = .65,
},
// Forest (Temperate & Moderate)
{
.name = "Forest",
.water_threshold = .2,
.ice_threshold = .0,
.sand_threshold = .1,
.land_threshold = .5,
.water_ratio = .2,
.ice_ratio = .0,
.sand_ratio = .1,
.land_ratio = .5,
},
// Ocean (Temperate & Wet)
{
.name = "Ocean",
.water_threshold = .7,
.ice_threshold = .0,
.sand_threshold = .2,
.land_threshold = .1,
.water_ratio = .7,
.ice_ratio = .0,
.sand_ratio = .2,
.land_ratio = .1,
},
// Desert (Hot & Dry)
{
.name = "Desert",
.water_threshold = .0,
.ice_threshold = .0,
.sand_threshold = .75,
.land_threshold = .05,
.water_ratio = .0,
.ice_ratio = .0,
.sand_ratio = .75,
.land_ratio = .05,
},
// Savanna (Hot & Moderate)
{
.name = "Savanna",
.water_threshold = .2,
.ice_threshold = .0,
.sand_threshold = .1,
.land_threshold = .5,
.water_ratio = .2,
.ice_ratio = .0,
.sand_ratio = .1,
.land_ratio = .5,
},
// Luke Ocean (Hot & Wet)
{
.name = "Luke Ocean",
.water_threshold = .8,
.ice_threshold = .0,
.sand_threshold = .2,
.land_threshold = .0,
.water_ratio = .8,
.ice_ratio = .0,
.sand_ratio = .2,
.land_ratio = .0,
},
};

View File

@ -133,19 +133,19 @@ std::pair<double, double> TerrainGenerator::get_climate(
BaseTileType TerrainGenerator::determine_base_type(
double noise_value, const BiomeProperties &properties
) const {
const std::pair<BaseTileType, double> thresholds[] = {
{BaseTileType::Water, properties.water_threshold},
{BaseTileType::Ice, properties.ice_threshold },
{BaseTileType::Sand, properties.sand_threshold },
{BaseTileType::Land, properties.land_threshold },
const std::pair<BaseTileType, double> ratios[] = {
{BaseTileType::Water, properties.water_ratio},
{BaseTileType::Ice, properties.ice_ratio },
{BaseTileType::Sand, properties.sand_ratio },
{BaseTileType::Land, properties.land_ratio },
{BaseTileType::Mountain, 1.0 },
};
for (const auto &[type, threshold] : thresholds) {
if (noise_value < threshold) {
for (const auto &[type, ratio] : ratios) {
if (noise_value < ratio) {
return type;
}
noise_value -= threshold; // Adjust noise value for next type
noise_value -= ratio; // Adjust noise value for next type
}
std::unreachable();