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:
parent
1289e99fc3
commit
1cb4c19b77
2
.github/copilot-instructions.md
vendored
2
.github/copilot-instructions.md
vendored
@ -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.
|
+ Don't create getters or setters if they are not necessary.
|
||||||
|
203
README.md
203
README.md
@ -1,205 +1,4 @@
|
|||||||
# Instructed Project
|
# 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)
|
|
||||||
|
@ -1,113 +1,3 @@
|
|||||||
# Tilemap Library
|
# Tilemap Library
|
||||||
|
|
||||||
一个基于Perlin噪声和生物群系系统的C++地图生成库。
|
The Tilemap System use in Instructed. Generates 2D tilemaps with biomes and features.
|
||||||
|
|
||||||
## 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
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+)
|
|
||||||
|
245
tilemap/docs/api.md
Normal file
245
tilemap/docs/api.md
Normal 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.
|
@ -3,12 +3,12 @@ cmake_minimum_required(VERSION 3.27)
|
|||||||
# Examples for the tilemap library
|
# Examples for the tilemap library
|
||||||
# Each example is built as a separate executable
|
# 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
|
# Biome system demonstration
|
||||||
add_executable(biome_demo biome_demo.cpp)
|
add_executable(biome_demo biome_demo.cpp)
|
||||||
target_link_libraries(biome_demo PRIVATE istd_tilemap)
|
target_link_libraries(biome_demo PRIVATE istd_tilemap)
|
||||||
target_include_directories(biome_demo PRIVATE ../include)
|
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)
|
||||||
|
@ -1,187 +1,202 @@
|
|||||||
#include "biome.h"
|
|
||||||
#include "generation.h"
|
#include "generation.h"
|
||||||
|
#include "tile.h"
|
||||||
#include "tilemap.h"
|
#include "tilemap.h"
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
using namespace istd;
|
// Color mapping for different base tile types
|
||||||
|
const char *get_tile_color(istd::BaseTileType type) {
|
||||||
// Function to get character representation for biome visualization
|
switch (type) {
|
||||||
char get_biome_char(BiomeType biome) {
|
case istd::BaseTileType::Land:
|
||||||
switch (biome) {
|
return "#90EE90"; // Light green
|
||||||
case BiomeType::Desert:
|
case istd::BaseTileType::Mountain:
|
||||||
return 'D';
|
return "#8B4513"; // Saddle brown
|
||||||
case BiomeType::Savanna:
|
case istd::BaseTileType::Sand:
|
||||||
return 'S';
|
return "#F4A460"; // Sandy brown
|
||||||
case BiomeType::TropicalRainforest:
|
case istd::BaseTileType::Water:
|
||||||
return 'T';
|
return "#1E90FF"; // Dodger blue
|
||||||
case BiomeType::Grassland:
|
case istd::BaseTileType::Ice:
|
||||||
return 'G';
|
return "#B0E0E6"; // Powder blue
|
||||||
case BiomeType::DeciduousForest:
|
default:
|
||||||
return 'F';
|
return "#808080"; // Gray for unknown types
|
||||||
case BiomeType::TemperateRainforest:
|
|
||||||
return 'R';
|
|
||||||
case BiomeType::Tundra:
|
|
||||||
return 'U';
|
|
||||||
case BiomeType::Taiga:
|
|
||||||
return 'A';
|
|
||||||
case BiomeType::FrozenOcean:
|
|
||||||
return 'O';
|
|
||||||
}
|
}
|
||||||
return '?';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get biome name as string
|
// Generate SVG file from tilemap
|
||||||
const char *get_biome_name(BiomeType biome) {
|
void generate_svg(const istd::TileMap &tilemap, const std::string &filename) {
|
||||||
switch (biome) {
|
std::ofstream file(filename);
|
||||||
case BiomeType::Desert:
|
if (!file.is_open()) {
|
||||||
return "Desert";
|
std::cerr << "Error: Could not open output file: " << filename
|
||||||
case BiomeType::Savanna:
|
<< std::endl;
|
||||||
return "Savanna";
|
return;
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
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() {
|
// Print statistics about the generated map
|
||||||
std::cout << "=== Biome System Demo ===" << std::endl;
|
void print_statistics(const istd::TileMap &tilemap) {
|
||||||
std::cout << "This demo shows biome distribution based on temperature and "
|
int tile_counts[5] = {0}; // Count for each base tile type
|
||||||
"humidity"
|
const int chunks_per_side = tilemap.get_size();
|
||||||
<< std::endl;
|
const int tiles_per_chunk = istd::Chunk::size;
|
||||||
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 tilemap for biome demonstration
|
for (int chunk_y = 0; chunk_y < chunks_per_side; ++chunk_y) {
|
||||||
constexpr std::uint8_t map_size = 3; // 3x3 chunks for compact display
|
for (int chunk_x = 0; chunk_x < chunks_per_side; ++chunk_x) {
|
||||||
TileMap tilemap(map_size);
|
const auto &chunk = tilemap.get_chunk(chunk_x, chunk_y);
|
||||||
|
|
||||||
// Configure generation with good biome variety
|
for (int tile_y = 0; tile_y < tiles_per_chunk; ++tile_y) {
|
||||||
GenerationConfig config;
|
for (int tile_x = 0; tile_x < tiles_per_chunk; ++tile_x) {
|
||||||
config.seed = 98765;
|
const auto &tile = chunk.tiles[tile_x][tile_y];
|
||||||
config.temperature_scale = 0.003; // Larger temperature zones
|
tile_counts[static_cast<int>(tile.base)]++;
|
||||||
config.humidity_scale = 0.004; // Larger humidity zones
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::cout << "Generating " << static_cast<int>(map_size) << "x"
|
const char *tile_names[] = {"Land", "Mountain", "Sand", "Water", "Ice"};
|
||||||
<< static_cast<int>(map_size) << " chunks..." << std::endl;
|
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
|
// 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
|
// Print statistics
|
||||||
std::cout << "=== Biome Properties ===" << std::endl;
|
print_statistics(tilemap);
|
||||||
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;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
@ -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;
|
|
||||||
}
|
|
167
tilemap/examples/perlin_demo.cpp
Normal file
167
tilemap/examples/perlin_demo.cpp
Normal 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;
|
||||||
|
}
|
@ -36,11 +36,11 @@ struct BiomeProperties {
|
|||||||
// Biome name for debugging
|
// Biome name for debugging
|
||||||
std::string_view name;
|
std::string_view name;
|
||||||
|
|
||||||
// Base terrain thresholds (0.0 - 1.0)
|
// Base terrain ratios (0.0 - 1.0)
|
||||||
double water_threshold;
|
double water_ratio;
|
||||||
double ice_threshold;
|
double ice_ratio;
|
||||||
double sand_threshold;
|
double sand_ratio;
|
||||||
double land_threshold;
|
double land_ratio;
|
||||||
|
|
||||||
// Noise parameters for base terrain
|
// Noise parameters for base terrain
|
||||||
int base_octaves = 3;
|
int base_octaves = 3;
|
||||||
|
@ -11,74 +11,74 @@ constexpr BiomeProperties biome_properties[] = {
|
|||||||
{
|
{
|
||||||
.name = "Snowy Peeks",
|
.name = "Snowy Peeks",
|
||||||
|
|
||||||
.water_threshold = .05,
|
.water_ratio = .05,
|
||||||
.ice_threshold = .15,
|
.ice_ratio = .15,
|
||||||
.sand_threshold = .1,
|
.sand_ratio = .1,
|
||||||
.land_threshold = .2,
|
.land_ratio = .2,
|
||||||
},
|
},
|
||||||
// Snowy Plains (Cold & Moderate)
|
// Snowy Plains (Cold & Moderate)
|
||||||
{
|
{
|
||||||
.name = "Snowy Plains",
|
.name = "Snowy Plains",
|
||||||
.water_threshold = .05,
|
.water_ratio = .05,
|
||||||
.ice_threshold = .25,
|
.ice_ratio = .25,
|
||||||
.sand_threshold = .1,
|
.sand_ratio = .1,
|
||||||
.land_threshold = .4,
|
.land_ratio = .4,
|
||||||
},
|
},
|
||||||
// Frozen Ocean (Cold & Wet)
|
// Frozen Ocean (Cold & Wet)
|
||||||
{
|
{
|
||||||
.name = "Frozen Ocean",
|
.name = "Frozen Ocean",
|
||||||
.water_threshold = .3,
|
.water_ratio = .3,
|
||||||
.ice_threshold = .4,
|
.ice_ratio = .4,
|
||||||
.sand_threshold = .25,
|
.sand_ratio = .25,
|
||||||
.land_threshold = .05,
|
.land_ratio = .05,
|
||||||
},
|
},
|
||||||
// Plains (Temperate & Dry)
|
// Plains (Temperate & Dry)
|
||||||
{
|
{
|
||||||
.name = "Plains",
|
.name = "Plains",
|
||||||
.water_threshold = .1,
|
.water_ratio = .1,
|
||||||
.ice_threshold = .0,
|
.ice_ratio = .0,
|
||||||
.sand_threshold = .05,
|
.sand_ratio = .05,
|
||||||
.land_threshold = .65,
|
.land_ratio = .65,
|
||||||
},
|
},
|
||||||
// Forest (Temperate & Moderate)
|
// Forest (Temperate & Moderate)
|
||||||
{
|
{
|
||||||
.name = "Forest",
|
.name = "Forest",
|
||||||
.water_threshold = .2,
|
.water_ratio = .2,
|
||||||
.ice_threshold = .0,
|
.ice_ratio = .0,
|
||||||
.sand_threshold = .1,
|
.sand_ratio = .1,
|
||||||
.land_threshold = .5,
|
.land_ratio = .5,
|
||||||
},
|
},
|
||||||
// Ocean (Temperate & Wet)
|
// Ocean (Temperate & Wet)
|
||||||
{
|
{
|
||||||
.name = "Ocean",
|
.name = "Ocean",
|
||||||
.water_threshold = .7,
|
.water_ratio = .7,
|
||||||
.ice_threshold = .0,
|
.ice_ratio = .0,
|
||||||
.sand_threshold = .2,
|
.sand_ratio = .2,
|
||||||
.land_threshold = .1,
|
.land_ratio = .1,
|
||||||
},
|
},
|
||||||
// Desert (Hot & Dry)
|
// Desert (Hot & Dry)
|
||||||
{
|
{
|
||||||
.name = "Desert",
|
.name = "Desert",
|
||||||
.water_threshold = .0,
|
.water_ratio = .0,
|
||||||
.ice_threshold = .0,
|
.ice_ratio = .0,
|
||||||
.sand_threshold = .75,
|
.sand_ratio = .75,
|
||||||
.land_threshold = .05,
|
.land_ratio = .05,
|
||||||
},
|
},
|
||||||
// Savanna (Hot & Moderate)
|
// Savanna (Hot & Moderate)
|
||||||
{
|
{
|
||||||
.name = "Savanna",
|
.name = "Savanna",
|
||||||
.water_threshold = .2,
|
.water_ratio = .2,
|
||||||
.ice_threshold = .0,
|
.ice_ratio = .0,
|
||||||
.sand_threshold = .1,
|
.sand_ratio = .1,
|
||||||
.land_threshold = .5,
|
.land_ratio = .5,
|
||||||
},
|
},
|
||||||
// Luke Ocean (Hot & Wet)
|
// Luke Ocean (Hot & Wet)
|
||||||
{
|
{
|
||||||
.name = "Luke Ocean",
|
.name = "Luke Ocean",
|
||||||
.water_threshold = .8,
|
.water_ratio = .8,
|
||||||
.ice_threshold = .0,
|
.ice_ratio = .0,
|
||||||
.sand_threshold = .2,
|
.sand_ratio = .2,
|
||||||
.land_threshold = .0,
|
.land_ratio = .0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -133,19 +133,19 @@ std::pair<double, double> TerrainGenerator::get_climate(
|
|||||||
BaseTileType TerrainGenerator::determine_base_type(
|
BaseTileType TerrainGenerator::determine_base_type(
|
||||||
double noise_value, const BiomeProperties &properties
|
double noise_value, const BiomeProperties &properties
|
||||||
) const {
|
) const {
|
||||||
const std::pair<BaseTileType, double> thresholds[] = {
|
const std::pair<BaseTileType, double> ratios[] = {
|
||||||
{BaseTileType::Water, properties.water_threshold},
|
{BaseTileType::Water, properties.water_ratio},
|
||||||
{BaseTileType::Ice, properties.ice_threshold },
|
{BaseTileType::Ice, properties.ice_ratio },
|
||||||
{BaseTileType::Sand, properties.sand_threshold },
|
{BaseTileType::Sand, properties.sand_ratio },
|
||||||
{BaseTileType::Land, properties.land_threshold },
|
{BaseTileType::Land, properties.land_ratio },
|
||||||
{BaseTileType::Mountain, 1.0 },
|
{BaseTileType::Mountain, 1.0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto &[type, threshold] : thresholds) {
|
for (const auto &[type, ratio] : ratios) {
|
||||||
if (noise_value < threshold) {
|
if (noise_value < ratio) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
noise_value -= threshold; // Adjust noise value for next type
|
noise_value -= ratio; // Adjust noise value for next type
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user