feat: remove unused files & code. rename demo
Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
parent
fcb71be9e8
commit
1354d9c08a
@ -1,122 +0,0 @@
|
|||||||
# 矿物生成系统实现总结
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
为tilemap库实现了三种新矿石(Hematite赤铁矿、Titanomagnetite钛磁铁矿、Gibbsite三水铝石)的生成系统。该系统基于Oil生成方式的改进版本,专门针对山脉边缘的矿物分布进行了优化。
|
|
||||||
|
|
||||||
## 设计选择
|
|
||||||
|
|
||||||
### 为什么选择基于Oil的方案而不是胞元自动机?
|
|
||||||
|
|
||||||
1. **精确控制性**:Poisson disk采样方式能够精确控制矿物密度和分布间距
|
|
||||||
2. **效率优势**:单次随机游走比多轮胞元自动机迭代更高效
|
|
||||||
3. **参数直观性**:密度、集群大小、最小距离等参数更易于调整和平衡
|
|
||||||
4. **稀有资源特性**:矿物作为稀有资源,稀疏分布更符合游戏设计需求
|
|
||||||
|
|
||||||
## 核心特性
|
|
||||||
|
|
||||||
### 1. 位置限制
|
|
||||||
- 矿物只在 `BaseTileType::Mountain` 且 `SurfaceTileType::Empty` 的瓦片上生成
|
|
||||||
- 必须位于山脉边缘(至少有一个相邻瓦片不是山地)
|
|
||||||
- 确保矿物出现在山脉与其他地形的交界处,便于开采
|
|
||||||
|
|
||||||
### 2. 分层稀有度
|
|
||||||
```cpp
|
|
||||||
// 默认配置
|
|
||||||
hematite_density = 51; // ~0.2 per chunk (最常见)
|
|
||||||
titanomagnetite_density = 25; // ~0.1 per chunk (中等稀有)
|
|
||||||
gibbsite_density = 13; // ~0.05 per chunk (最稀有)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 集群生成
|
|
||||||
- 最小集群大小:2个瓦片
|
|
||||||
- 最大集群大小:5个瓦片
|
|
||||||
- 使用随机游走算法形成自然的小簇分布
|
|
||||||
- 40%概率跳过相邻瓦片,形成合适的密度
|
|
||||||
|
|
||||||
### 4. 距离控制
|
|
||||||
- 基于密度动态计算最小间距
|
|
||||||
- 确保矿物集群不会过于密集
|
|
||||||
- 最小间距至少8个瓦片
|
|
||||||
|
|
||||||
## 实现细节
|
|
||||||
|
|
||||||
### 核心算法
|
|
||||||
1. **Poisson disk采样**:生成候选位置,确保合适的分布
|
|
||||||
2. **山脉边缘检测**:验证位置是否在山脉边缘
|
|
||||||
3. **随机游走集群生长**:从中心点开始生成小簇
|
|
||||||
4. **冲突避免**:确保不同矿物集群之间保持距离
|
|
||||||
|
|
||||||
### 山脉边缘检测逻辑
|
|
||||||
```cpp
|
|
||||||
bool is_mountain_edge(const TileMap &tilemap, TilePos pos) const {
|
|
||||||
auto neighbors = tilemap.get_neighbors(pos);
|
|
||||||
for (const auto neighbor_pos : neighbors) {
|
|
||||||
const Tile &neighbor_tile = tilemap.get_tile(neighbor_pos);
|
|
||||||
if (neighbor_tile.base != BaseTileType::Mountain) {
|
|
||||||
return true; // 找到非山地邻居
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false; // 所有邻居都是山地,不是边缘
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 配置参数
|
|
||||||
```cpp
|
|
||||||
struct GenerationConfig {
|
|
||||||
// 矿物集群生成参数
|
|
||||||
std::uint8_t hematite_density = 51; // ~0.2 per chunk
|
|
||||||
std::uint8_t titanomagnetite_density = 25; // ~0.1 per chunk
|
|
||||||
std::uint8_t gibbsite_density = 13; // ~0.05 per chunk
|
|
||||||
|
|
||||||
std::uint8_t mineral_cluster_min_size = 2; // 最小集群大小
|
|
||||||
std::uint8_t mineral_cluster_max_size = 5; // 最大集群大小
|
|
||||||
std::uint8_t mineral_base_probe = 192; // 基础放置概率
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## 生成流水线集成
|
|
||||||
|
|
||||||
矿物生成作为独立的pass添加到地形生成流水线的最后阶段:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
void TerrainGenerator::operator()(TileMap &tilemap) {
|
|
||||||
biome_pass(tilemap);
|
|
||||||
base_tile_type_pass(tilemap);
|
|
||||||
smoothen_mountains_pass(tilemap);
|
|
||||||
smoothen_islands_pass(tilemap);
|
|
||||||
mountain_hole_fill_pass(tilemap);
|
|
||||||
deepwater_pass(tilemap);
|
|
||||||
oil_pass(tilemap);
|
|
||||||
mineral_cluster_pass(tilemap); // 新增的矿物生成pass
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 测试结果
|
|
||||||
|
|
||||||
通过mineral_demo测试程序验证:
|
|
||||||
- 8x8 chunk地图 (262,144个瓦片)
|
|
||||||
- 山地瓦片:35,916个 (13.7%)
|
|
||||||
- 山脉边缘瓦片:15,881个 (44.2%的山地)
|
|
||||||
- 生成矿物分布:
|
|
||||||
- 赤铁矿:47个瓦片
|
|
||||||
- 钛磁铁矿:38个瓦片
|
|
||||||
- 三水铝石:15个瓦片
|
|
||||||
- 山脉边缘矿物覆盖率:0.63%
|
|
||||||
|
|
||||||
## 优势
|
|
||||||
|
|
||||||
1. **游戏平衡性**:稀有度分层,符合游戏经济设计
|
|
||||||
2. **真实感**:矿物出现在山脉边缘,符合地质常识
|
|
||||||
3. **可扩展性**:易于添加新的矿物类型和调整参数
|
|
||||||
4. **性能优秀**:单次生成,不需要多轮迭代
|
|
||||||
5. **确定性**:相同种子产生相同结果,支持多人游戏
|
|
||||||
|
|
||||||
## 使用建议
|
|
||||||
|
|
||||||
1. **密度调整**:根据游戏需求调整各矿物的density参数
|
|
||||||
2. **集群大小**:可以为不同矿物设置不同的集群大小范围
|
|
||||||
3. **生成位置**:如需其他位置生成矿物,可修改`is_suitable_for_mineral`函数
|
|
||||||
4. **稀有度平衡**:建议保持gibbsite < titanomagnetite < hematite的稀有度关系
|
|
||||||
|
|
||||||
这个实现提供了灵活、高效且平衡的矿物生成系统,完全满足了"控制生成数量,以小簇方式生成在山的边缘"的需求。
|
|
@ -4,6 +4,6 @@ cmake_minimum_required(VERSION 3.27)
|
|||||||
# Each example is built as a separate executable
|
# Each example is built as a separate executable
|
||||||
|
|
||||||
# Biome system demonstration
|
# Biome system demonstration
|
||||||
add_executable(biome_demo biome_demo.cpp)
|
add_executable(tilemap_demo tilemap_demo.cpp)
|
||||||
target_link_libraries(biome_demo PRIVATE istd_tilemap)
|
target_link_libraries(tilemap_demo PRIVATE istd_tilemap)
|
||||||
target_include_directories(biome_demo PRIVATE ../include)
|
target_include_directories(tilemap_demo PRIVATE ../include)
|
||||||
|
@ -1,167 +0,0 @@
|
|||||||
#include "bmp.h"
|
|
||||||
#include "tilemap/generation.h"
|
|
||||||
#include "tilemap/tilemap.h"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
using namespace istd;
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
constexpr std::uint8_t map_size = 8; // 8x8 chunks
|
|
||||||
TileMap tilemap(map_size);
|
|
||||||
|
|
||||||
// Create generation config with adjusted mineral parameters
|
|
||||||
GenerationConfig config;
|
|
||||||
config.seed = Seed::from_string("mineral_demo_seed");
|
|
||||||
|
|
||||||
// Increase mineral density for demo
|
|
||||||
config.hematite_density = 102; // ~0.4 per chunk
|
|
||||||
config.titanomagnetite_density = 76; // ~0.3 per chunk
|
|
||||||
config.gibbsite_density = 51; // ~0.2 per chunk
|
|
||||||
|
|
||||||
// Smaller clusters for better visibility
|
|
||||||
config.mineral_cluster_min_size = 1;
|
|
||||||
config.mineral_cluster_max_size = 4;
|
|
||||||
|
|
||||||
// Generate the terrain
|
|
||||||
map_generate(tilemap, config);
|
|
||||||
|
|
||||||
// Create BMP to visualize the mineral distribution
|
|
||||||
constexpr std::uint32_t tile_size = 4; // Each tile is 4x4 pixels
|
|
||||||
std::uint32_t image_size = map_size * Chunk::size * tile_size;
|
|
||||||
|
|
||||||
BmpWriter bmp(image_size, image_size);
|
|
||||||
|
|
||||||
// Define colors for different tile types
|
|
||||||
auto get_tile_color = [](const Tile &tile)
|
|
||||||
-> std::tuple<std::uint8_t, std::uint8_t, std::uint8_t> {
|
|
||||||
// Override with mineral colors if present first
|
|
||||||
switch (tile.surface) {
|
|
||||||
case SurfaceTileType::Oil:
|
|
||||||
return {0, 0, 0}; // Black
|
|
||||||
case SurfaceTileType::Hematite:
|
|
||||||
return {255, 0, 0}; // Red
|
|
||||||
case SurfaceTileType::Titanomagnetite:
|
|
||||||
return {128, 0, 128}; // Purple
|
|
||||||
case SurfaceTileType::Gibbsite:
|
|
||||||
return {255, 255, 0}; // Yellow
|
|
||||||
case SurfaceTileType::Empty:
|
|
||||||
default:
|
|
||||||
break; // Fall through to base terrain colors
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base terrain colors
|
|
||||||
switch (tile.base) {
|
|
||||||
case BaseTileType::Land:
|
|
||||||
return {0, 128, 0}; // Green
|
|
||||||
case BaseTileType::Mountain:
|
|
||||||
return {139, 69, 19}; // Brown
|
|
||||||
case BaseTileType::Sand:
|
|
||||||
return {238, 203, 173}; // Beige
|
|
||||||
case BaseTileType::Water:
|
|
||||||
return {0, 0, 255}; // Blue
|
|
||||||
case BaseTileType::Ice:
|
|
||||||
return {173, 216, 230}; // Light Blue
|
|
||||||
case BaseTileType::Deepwater:
|
|
||||||
return {0, 0, 139}; // Dark Blue
|
|
||||||
default:
|
|
||||||
return {128, 128, 128}; // Gray
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fill the BMP with tile data
|
|
||||||
for (std::uint32_t y = 0; y < image_size; ++y) {
|
|
||||||
for (std::uint32_t x = 0; x < image_size; ++x) {
|
|
||||||
// Calculate which tile this pixel belongs to
|
|
||||||
std::uint32_t tile_x = x / tile_size;
|
|
||||||
std::uint32_t tile_y = y / tile_size;
|
|
||||||
|
|
||||||
TilePos pos = TilePos::from_global(tile_x, tile_y);
|
|
||||||
const Tile &tile = tilemap.get_tile(pos);
|
|
||||||
|
|
||||||
auto [r, g, b] = get_tile_color(tile);
|
|
||||||
bmp.set_pixel(x, y, r, g, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the BMP
|
|
||||||
bmp.save("mineral_demo.bmp");
|
|
||||||
|
|
||||||
// Print statistics
|
|
||||||
std::uint32_t hematite_count = 0;
|
|
||||||
std::uint32_t titanomagnetite_count = 0;
|
|
||||||
std::uint32_t gibbsite_count = 0;
|
|
||||||
std::uint32_t mountain_edge_count = 0;
|
|
||||||
std::uint32_t total_mountain_count = 0;
|
|
||||||
|
|
||||||
std::uint32_t total_tiles = map_size * Chunk::size * map_size * Chunk::size;
|
|
||||||
|
|
||||||
for (std::uint32_t y = 0; y < map_size * Chunk::size; ++y) {
|
|
||||||
for (std::uint32_t x = 0; x < map_size * Chunk::size; ++x) {
|
|
||||||
TilePos pos = TilePos::from_global(x, y);
|
|
||||||
const Tile &tile = tilemap.get_tile(pos);
|
|
||||||
|
|
||||||
if (tile.base == BaseTileType::Mountain) {
|
|
||||||
total_mountain_count++;
|
|
||||||
|
|
||||||
// Check if it's a mountain edge
|
|
||||||
auto neighbors = tilemap.get_neighbors(pos);
|
|
||||||
bool is_edge = false;
|
|
||||||
for (const auto neighbor_pos : neighbors) {
|
|
||||||
const Tile &neighbor_tile = tilemap.get_tile(neighbor_pos);
|
|
||||||
if (neighbor_tile.base != BaseTileType::Mountain) {
|
|
||||||
is_edge = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (is_edge) {
|
|
||||||
mountain_edge_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (tile.surface) {
|
|
||||||
case SurfaceTileType::Hematite:
|
|
||||||
hematite_count++;
|
|
||||||
break;
|
|
||||||
case SurfaceTileType::Titanomagnetite:
|
|
||||||
titanomagnetite_count++;
|
|
||||||
break;
|
|
||||||
case SurfaceTileType::Gibbsite:
|
|
||||||
gibbsite_count++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "Mineral Generation Demo Results:\n";
|
|
||||||
std::cout << "================================\n";
|
|
||||||
std::cout << "Total tiles: " << total_tiles << "\n";
|
|
||||||
std::cout << "Mountain tiles: " << total_mountain_count << " ("
|
|
||||||
<< (100.0 * total_mountain_count / total_tiles) << "%)\n";
|
|
||||||
std::cout << "Mountain edge tiles: " << mountain_edge_count << " ("
|
|
||||||
<< (100.0 * mountain_edge_count / total_mountain_count)
|
|
||||||
<< "% of mountains)\n";
|
|
||||||
std::cout << "\nMineral Distribution:\n";
|
|
||||||
std::cout << "Hematite tiles: " << hematite_count << "\n";
|
|
||||||
std::cout << "Titanomagnetite tiles: " << titanomagnetite_count << "\n";
|
|
||||||
std::cout << "Gibbsite tiles: " << gibbsite_count << "\n";
|
|
||||||
std::cout << "Total mineral tiles: "
|
|
||||||
<< (hematite_count + titanomagnetite_count + gibbsite_count)
|
|
||||||
<< "\n";
|
|
||||||
|
|
||||||
if (mountain_edge_count > 0) {
|
|
||||||
double mineral_coverage = 100.0
|
|
||||||
* (hematite_count + titanomagnetite_count + gibbsite_count)
|
|
||||||
/ mountain_edge_count;
|
|
||||||
std::cout << "Mineral coverage on mountain edges: " << mineral_coverage
|
|
||||||
<< "%\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "\nGenerated mineral_demo.bmp with visualization\n";
|
|
||||||
std::cout
|
|
||||||
<< "Colors: Red=Hematite, Purple=Titanomagnetite, Yellow=Gibbsite\n";
|
|
||||||
std::cout << " Brown=Mountain, Green=Land, Blue=Water, etc.\n";
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -6,35 +6,26 @@
|
|||||||
namespace istd {
|
namespace istd {
|
||||||
|
|
||||||
enum class BaseTileType : std::uint8_t {
|
enum class BaseTileType : std::uint8_t {
|
||||||
|
Mountain = 0x0,
|
||||||
Land,
|
Land,
|
||||||
Mountain,
|
|
||||||
Sand,
|
Sand,
|
||||||
Water,
|
Water,
|
||||||
Ice,
|
Ice,
|
||||||
Deepwater,
|
Deepwater,
|
||||||
_count
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class SurfaceTileType : std::uint8_t {
|
enum class SurfaceTileType : std::uint8_t {
|
||||||
Empty,
|
Empty = 0,
|
||||||
Oil,
|
Oil,
|
||||||
Hematite,
|
Hematite,
|
||||||
Titanomagnetite,
|
Titanomagnetite,
|
||||||
Gibbsite,
|
Gibbsite,
|
||||||
_count
|
|
||||||
|
// Player built structures
|
||||||
|
// (not used in generation, but can be placed by player)
|
||||||
|
Structure = 0xF,
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr std::uint8_t base_tile_count = static_cast<std::uint8_t>(
|
|
||||||
BaseTileType::_count
|
|
||||||
);
|
|
||||||
|
|
||||||
constexpr std::uint8_t surface_tile_count = static_cast<std::uint8_t>(
|
|
||||||
SurfaceTileType::_count
|
|
||||||
);
|
|
||||||
|
|
||||||
static_assert(base_tile_count <= 16, "Base tile don't fit in 4 bits");
|
|
||||||
static_assert(surface_tile_count <= 16, "Surface tile don't fit in 4 bits");
|
|
||||||
|
|
||||||
struct Tile {
|
struct Tile {
|
||||||
BaseTileType base : 4;
|
BaseTileType base : 4;
|
||||||
SurfaceTileType surface : 4;
|
SurfaceTileType surface : 4;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user