feat: Add mineral generation pass
Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
parent
7735da6e85
commit
fcb71be9e8
@ -5,6 +5,7 @@ set(ISTD_TILEMAP_SRC
|
|||||||
src/pass/base_tile_type.cpp
|
src/pass/base_tile_type.cpp
|
||||||
src/pass/biome.cpp
|
src/pass/biome.cpp
|
||||||
src/pass/deepwater.cpp
|
src/pass/deepwater.cpp
|
||||||
|
src/pass/mineral_cluster.cpp
|
||||||
src/pass/mountain_hole_fill.cpp
|
src/pass/mountain_hole_fill.cpp
|
||||||
src/pass/oil.cpp
|
src/pass/oil.cpp
|
||||||
src/pass/smoothen_mountain.cpp
|
src/pass/smoothen_mountain.cpp
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
# Tilemap Library
|
# Tilemap Library
|
||||||
|
|
||||||
The Tilemap System use in Instructed. Generates 2D tilemaps with biomes and features.
|
The Tilemap System used in Instructed. Generates 2D tilemaps with biomes, terrain features, and resource deposits.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Multi-layered terrain generation**: Base terrain (Land, Mountain, Sand, Water, Ice, Deepwater) with surface features
|
||||||
|
- **9 biome system**: Climate-based biome assignment using temperature/humidity noise
|
||||||
|
- **Resource generation**: Oil deposits and mineral clusters (Hematite, Titanomagnetite, Gibbsite)
|
||||||
|
- **Mineral deposits**: Three types of minerals generated on mountain edges in small clusters
|
||||||
|
- **Procedural algorithms**: Perlin noise, cellular automata, connected component analysis
|
||||||
|
- **Deterministic generation**: Same seed produces identical results
|
||||||
|
@ -30,7 +30,8 @@ tilemap/
|
|||||||
│ ├── smoothen_island.cpp # Island smoothing
|
│ ├── smoothen_island.cpp # Island smoothing
|
||||||
│ ├── mountain_hole_fill.cpp # Hole filling
|
│ ├── mountain_hole_fill.cpp # Hole filling
|
||||||
│ ├── deepwater.cpp # Deep water placement
|
│ ├── deepwater.cpp # Deep water placement
|
||||||
│ └── oil.cpp # Oil resource generation
|
│ ├── oil.cpp # Oil resource generation
|
||||||
|
│ └── mineral_cluster.cpp # Mineral cluster generation
|
||||||
├── examples/ # Usage examples
|
├── examples/ # Usage examples
|
||||||
└── docs/ # Documentation
|
└── docs/ # Documentation
|
||||||
```
|
```
|
||||||
@ -57,6 +58,7 @@ Terrain generation uses a multi-pass pipeline for modularity and control:
|
|||||||
5. **Hole Fill Pass**: Fills small terrain holes
|
5. **Hole Fill Pass**: Fills small terrain holes
|
||||||
6. **Deep Water Pass**: Places deep water areas
|
6. **Deep Water Pass**: Places deep water areas
|
||||||
7. **Oil Pass**: Generates sparse oil deposits as surface features
|
7. **Oil Pass**: Generates sparse oil deposits as surface features
|
||||||
|
8. **Mineral Cluster Pass**: Generates mineral clusters (Hematite, Titanomagnetite, Gibbsite) on mountain edges using cellular automata
|
||||||
|
|
||||||
Each pass operates independently with its own RNG state, ensuring deterministic results.
|
Each pass operates independently with its own RNG state, ensuring deterministic results.
|
||||||
|
|
||||||
@ -91,13 +93,13 @@ Several passes use BFS (Breadth-First Search) for terrain analysis:
|
|||||||
- **Hole Filling**: Identify and fill isolated terrain holes
|
- **Hole Filling**: Identify and fill isolated terrain holes
|
||||||
- Components touching map boundaries are preserved
|
- Components touching map boundaries are preserved
|
||||||
|
|
||||||
### Oil Resource Generation
|
### Resource Generation
|
||||||
|
|
||||||
The oil generation pass creates sparse resource deposits:
|
The oil generation pass and mineral generation pass creates sparse resource deposits:
|
||||||
- **Poisson Disk Sampling**: Ensures minimum distance between oil fields
|
- **Poisson Disk Sampling**: Ensures minimum distance between oil fields
|
||||||
- **Biome Preference**: Higher probability in desert and plains biomes
|
- **Biome Preference**: Higher probability in desert and plains biomes
|
||||||
- **Cluster Growth**: Random walk creates 2-6 tile clusters
|
- **Cluster Growth**: Random walk creates larger tile clusters
|
||||||
- **Surface Placement**: Oil appears as surface features on land/sand tiles
|
- **Surface Placement**: Oil appears as surface features on land/sand tiles, Minerals on mountain edges
|
||||||
|
|
||||||
## Random Number Generation
|
## Random Number Generation
|
||||||
|
|
||||||
|
122
tilemap/docs/mineral_generation.md
Normal file
122
tilemap/docs/mineral_generation.md
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
# 矿物生成系统实现总结
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
为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的稀有度关系
|
||||||
|
|
||||||
|
这个实现提供了灵活、高效且平衡的矿物生成系统,完全满足了"控制生成数量,以小簇方式生成在山的边缘"的需求。
|
@ -9,12 +9,22 @@
|
|||||||
|
|
||||||
// Get BMP color for different tile types, considering surface tiles
|
// Get BMP color for different tile types, considering surface tiles
|
||||||
BmpColors::Color get_tile_color(const istd::Tile &tile) {
|
BmpColors::Color get_tile_color(const istd::Tile &tile) {
|
||||||
// Oil surface tile overrides base color
|
// Surface tiles override base color
|
||||||
if (tile.surface == istd::SurfaceTileType::Oil) {
|
switch (tile.surface) {
|
||||||
|
case istd::SurfaceTileType::Oil:
|
||||||
return BmpColors::OIL;
|
return BmpColors::OIL;
|
||||||
|
case istd::SurfaceTileType::Hematite:
|
||||||
|
return BmpColors::HEMATITE;
|
||||||
|
case istd::SurfaceTileType::Titanomagnetite:
|
||||||
|
return BmpColors::TITANOMAGNETITE;
|
||||||
|
case istd::SurfaceTileType::Gibbsite:
|
||||||
|
return BmpColors::GIBBSITE;
|
||||||
|
case istd::SurfaceTileType::Empty:
|
||||||
|
default:
|
||||||
|
break; // Fall through to base tile color
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise use base tile color
|
// Use base tile color
|
||||||
switch (tile.base) {
|
switch (tile.base) {
|
||||||
case istd::BaseTileType::Land:
|
case istd::BaseTileType::Land:
|
||||||
return BmpColors::LAND;
|
return BmpColors::LAND;
|
||||||
@ -87,7 +97,11 @@ void print_statistics(const istd::TileMap &tilemap) {
|
|||||||
int tile_counts[6] = {
|
int tile_counts[6] = {
|
||||||
0
|
0
|
||||||
}; // Count for each base tile type (now 6 types including Deepwater)
|
}; // Count for each base tile type (now 6 types including Deepwater)
|
||||||
int oil_count = 0; // Count oil surface tiles
|
int oil_count = 0; // Count oil surface tiles
|
||||||
|
int hematite_count = 0; // Count hematite surface tiles
|
||||||
|
int titanomagnetite_count = 0; // Count titanomagnetite surface tiles
|
||||||
|
int gibbsite_count = 0; // Count gibbsite surface tiles
|
||||||
|
int mountain_edge_count = 0; // Count mountain edge tiles
|
||||||
const int chunks_per_side = tilemap.get_size();
|
const int chunks_per_side = tilemap.get_size();
|
||||||
const int tiles_per_chunk = istd::Chunk::size;
|
const int tiles_per_chunk = istd::Chunk::size;
|
||||||
|
|
||||||
@ -100,9 +114,42 @@ void print_statistics(const istd::TileMap &tilemap) {
|
|||||||
const auto &tile = chunk.tiles[tile_x][tile_y];
|
const auto &tile = chunk.tiles[tile_x][tile_y];
|
||||||
tile_counts[static_cast<int>(tile.base)]++;
|
tile_counts[static_cast<int>(tile.base)]++;
|
||||||
|
|
||||||
// Count oil surface tiles
|
// Count surface tiles
|
||||||
if (tile.surface == istd::SurfaceTileType::Oil) {
|
switch (tile.surface) {
|
||||||
|
case istd::SurfaceTileType::Oil:
|
||||||
oil_count++;
|
oil_count++;
|
||||||
|
break;
|
||||||
|
case istd::SurfaceTileType::Hematite:
|
||||||
|
hematite_count++;
|
||||||
|
break;
|
||||||
|
case istd::SurfaceTileType::Titanomagnetite:
|
||||||
|
titanomagnetite_count++;
|
||||||
|
break;
|
||||||
|
case istd::SurfaceTileType::Gibbsite:
|
||||||
|
gibbsite_count++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count mountain edge tiles for mineral statistics
|
||||||
|
if (tile.base == istd::BaseTileType::Mountain) {
|
||||||
|
istd::TilePos pos(chunk_x, chunk_y, tile_x, tile_y);
|
||||||
|
auto neighbors = tilemap.get_neighbors(pos);
|
||||||
|
bool is_edge = false;
|
||||||
|
for (const auto neighbor_pos : neighbors) {
|
||||||
|
const auto &neighbor_tile = tilemap.get_tile(
|
||||||
|
neighbor_pos
|
||||||
|
);
|
||||||
|
if (neighbor_tile.base
|
||||||
|
!= istd::BaseTileType::Mountain) {
|
||||||
|
is_edge = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_edge) {
|
||||||
|
mountain_edge_count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,16 +170,58 @@ void print_statistics(const istd::TileMap &tilemap) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::println("\nSurface Resource Statistics:");
|
||||||
|
std::println("============================");
|
||||||
|
|
||||||
// Print oil statistics
|
// Print oil statistics
|
||||||
double oil_percentage = (double)oil_count / total_tiles * 100.0;
|
double oil_percentage = (double)oil_count / total_tiles * 100.0;
|
||||||
double oil_per_chunk = (double)oil_count
|
double oil_per_chunk = (double)oil_count
|
||||||
/ (chunks_per_side * chunks_per_side);
|
/ (chunks_per_side * chunks_per_side);
|
||||||
std::println(
|
std::println(
|
||||||
"{:>10}: {:>8} ({:.1f}%, {:.2f} per chunk)", "Oil", oil_count,
|
"{:>15}: {:>8} ({:.3f}%, {:.2f} per chunk)", "Oil", oil_count,
|
||||||
oil_percentage, oil_per_chunk
|
oil_percentage, oil_per_chunk
|
||||||
);
|
);
|
||||||
|
|
||||||
std::println("Total tiles: {}", total_tiles);
|
// Print mineral statistics
|
||||||
|
auto print_mineral_stats = [&](const char *name, int count) {
|
||||||
|
double percentage = (double)count / total_tiles * 100.0;
|
||||||
|
double per_chunk = (double)count / (chunks_per_side * chunks_per_side);
|
||||||
|
std::println(
|
||||||
|
"{:>15}: {:>8} ({:.3f}%, {:.2f} per chunk)", name, count,
|
||||||
|
percentage, per_chunk
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
print_mineral_stats("Hematite", hematite_count);
|
||||||
|
print_mineral_stats("Titanomagnetite", titanomagnetite_count);
|
||||||
|
print_mineral_stats("Gibbsite", gibbsite_count);
|
||||||
|
|
||||||
|
// Mountain edge statistics for mineral context
|
||||||
|
int mountain_count = tile_counts[static_cast<int>(
|
||||||
|
istd::BaseTileType::Mountain
|
||||||
|
)];
|
||||||
|
if (mountain_count > 0) {
|
||||||
|
double edge_percentage = (double)mountain_edge_count / mountain_count
|
||||||
|
* 100.0;
|
||||||
|
std::println(
|
||||||
|
"{:>15}: {:>8} ({:.1f}% of mountains)", "Mountain edges",
|
||||||
|
mountain_edge_count, edge_percentage
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate mineral coverage on mountain edges
|
||||||
|
int total_minerals = hematite_count + titanomagnetite_count
|
||||||
|
+ gibbsite_count;
|
||||||
|
if (mountain_edge_count > 0) {
|
||||||
|
double mineral_coverage = (double)total_minerals
|
||||||
|
/ mountain_edge_count * 100.0;
|
||||||
|
std::println(
|
||||||
|
"\n{:>15}: {:.2f}% of mountain edges", "Mineral coverage",
|
||||||
|
mineral_coverage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::println("\nTotal tiles: {}", total_tiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
@ -236,6 +236,11 @@ constexpr Color WATER(30, 144, 255); // Dodger blue
|
|||||||
constexpr Color ICE(176, 224, 230); // Powder blue
|
constexpr Color ICE(176, 224, 230); // Powder blue
|
||||||
constexpr Color DEEPWATER(0, 0, 139); // Dark blue
|
constexpr Color DEEPWATER(0, 0, 139); // Dark blue
|
||||||
constexpr Color OIL(0, 0, 0); // Black
|
constexpr Color OIL(0, 0, 0); // Black
|
||||||
|
|
||||||
|
// Mineral colors
|
||||||
|
constexpr Color HEMATITE(255, 0, 0); // Red
|
||||||
|
constexpr Color TITANOMAGNETITE(128, 0, 128); // Purple
|
||||||
|
constexpr Color GIBBSITE(255, 255, 0); // Yellow
|
||||||
} // namespace BmpColors
|
} // namespace BmpColors
|
||||||
|
|
||||||
#endif // BMP_H
|
#endif // BMP_H
|
||||||
|
167
tilemap/examples/mineral_demo.cpp
Normal file
167
tilemap/examples/mineral_demo.cpp
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
#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;
|
||||||
|
}
|
@ -45,6 +45,18 @@ struct GenerationConfig {
|
|||||||
// (should be <= 24)
|
// (should be <= 24)
|
||||||
std::uint8_t oil_base_probe = 128; // Biome preference multiplier (out
|
std::uint8_t oil_base_probe = 128; // Biome preference multiplier (out
|
||||||
// of 255)
|
// of 255)
|
||||||
|
|
||||||
|
// Mineral cluster generation parameters
|
||||||
|
std::uint16_t hematite_density = 450; // ~1.8 per chunk (out of 255)
|
||||||
|
std::uint16_t titanomagnetite_density = 300; // ~1.2 per chunk (out of 255)
|
||||||
|
std::uint16_t gibbsite_density = 235; // ~0.9 per chunk (out of 255)
|
||||||
|
|
||||||
|
std::uint8_t mineral_cluster_min_size = 2; // Minimum tiles per mineral
|
||||||
|
// cluster
|
||||||
|
std::uint8_t mineral_cluster_max_size = 5; // Maximum tiles per mineral
|
||||||
|
// cluster
|
||||||
|
std::uint8_t mineral_base_probe = 192; // Base probability for mineral
|
||||||
|
// placement
|
||||||
};
|
};
|
||||||
|
|
||||||
// Terrain generator class that manages the generation process
|
// Terrain generator class that manages the generation process
|
||||||
@ -108,6 +120,12 @@ private:
|
|||||||
* @param tilemap The tilemap to process
|
* @param tilemap The tilemap to process
|
||||||
*/
|
*/
|
||||||
void oil_pass(TileMap &tilemap);
|
void oil_pass(TileMap &tilemap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate mineral clusters on suitable terrain
|
||||||
|
* @param tilemap The tilemap to process
|
||||||
|
*/
|
||||||
|
void mineral_cluster_pass(TileMap &tilemap);
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* @brief Generate a tilemap using the new biome-based system
|
* @brief Generate a tilemap using the new biome-based system
|
||||||
|
87
tilemap/include/tilemap/pass/mineral_cluster.h
Normal file
87
tilemap/include/tilemap/pass/mineral_cluster.h
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#ifndef TILEMAP_PASS_MINERAL_CLUSTER_H
|
||||||
|
#define TILEMAP_PASS_MINERAL_CLUSTER_H
|
||||||
|
|
||||||
|
#include "tilemap/generation.h"
|
||||||
|
#include "tilemap/noise.h"
|
||||||
|
|
||||||
|
namespace istd {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generates mineral clusters (Hematite, Titanomagnetite, Gibbsite) on
|
||||||
|
* mountain edges
|
||||||
|
*/
|
||||||
|
class MineralClusterGenerationPass {
|
||||||
|
private:
|
||||||
|
const GenerationConfig &config_;
|
||||||
|
Xoroshiro128PP rng_;
|
||||||
|
DiscreteRandomNoise noise_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a mineral cluster generation pass
|
||||||
|
* @param config Generation configuration parameters
|
||||||
|
* @param rng Random number generator for mineral placement
|
||||||
|
* @param noise_rng Random number generator for noise-based operations
|
||||||
|
*/
|
||||||
|
MineralClusterGenerationPass(
|
||||||
|
const GenerationConfig &config, Xoroshiro128PP rng,
|
||||||
|
Xoroshiro128PP noise_rng
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate mineral clusters on mountain edges
|
||||||
|
* @param tilemap The tilemap to process
|
||||||
|
*/
|
||||||
|
void operator()(TileMap &tilemap);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Generate mineral centers for a specific mineral type
|
||||||
|
* @param tilemap The tilemap to analyze
|
||||||
|
* @param mineral_type The type of mineral to generate
|
||||||
|
* @param density The generation density (out of 255)
|
||||||
|
* @return Vector of positions where mineral clusters should be placed
|
||||||
|
*/
|
||||||
|
std::vector<TilePos> generate_mineral_centers(
|
||||||
|
const TileMap &tilemap, SurfaceTileType mineral_type,
|
||||||
|
std::uint16_t density
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate a mineral cluster around a center position
|
||||||
|
* @param tilemap The tilemap to modify
|
||||||
|
* @param center Center position for the mineral cluster
|
||||||
|
* @param mineral_type The type of mineral to place
|
||||||
|
*/
|
||||||
|
void generate_mineral_cluster(
|
||||||
|
TileMap &tilemap, TilePos center, SurfaceTileType mineral_type
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if a tile is suitable for mineral placement
|
||||||
|
* @param tilemap The tilemap to check
|
||||||
|
* @param pos Position to check
|
||||||
|
* @return True if minerals can be placed at this position
|
||||||
|
*/
|
||||||
|
bool is_suitable_for_mineral(const TileMap &tilemap, TilePos pos) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if a mountain tile is on the edge (adjacent to non-mountain)
|
||||||
|
* @param tilemap The tilemap to check
|
||||||
|
* @param pos Position to check
|
||||||
|
* @return True if this mountain tile is on the edge
|
||||||
|
*/
|
||||||
|
bool is_mountain_edge(const TileMap &tilemap, TilePos pos) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate minimum distance between mineral clusters based on map
|
||||||
|
* size
|
||||||
|
* @param density The mineral density setting
|
||||||
|
* @return Minimum distance in tiles
|
||||||
|
*/
|
||||||
|
std::uint32_t calculate_min_mineral_distance(std::uint16_t density) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace istd
|
||||||
|
|
||||||
|
#endif // TILEMAP_PASS_MINERAL_CLUSTER_H
|
@ -18,6 +18,9 @@ enum class BaseTileType : std::uint8_t {
|
|||||||
enum class SurfaceTileType : std::uint8_t {
|
enum class SurfaceTileType : std::uint8_t {
|
||||||
Empty,
|
Empty,
|
||||||
Oil,
|
Oil,
|
||||||
|
Hematite,
|
||||||
|
Titanomagnetite,
|
||||||
|
Gibbsite,
|
||||||
_count
|
_count
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ void TerrainGenerator::operator()(TileMap &tilemap) {
|
|||||||
mountain_hole_fill_pass(tilemap);
|
mountain_hole_fill_pass(tilemap);
|
||||||
deepwater_pass(tilemap);
|
deepwater_pass(tilemap);
|
||||||
oil_pass(tilemap);
|
oil_pass(tilemap);
|
||||||
|
mineral_cluster_pass(tilemap);
|
||||||
}
|
}
|
||||||
|
|
||||||
void map_generate(TileMap &tilemap, const GenerationConfig &config) {
|
void map_generate(TileMap &tilemap, const GenerationConfig &config) {
|
||||||
|
238
tilemap/src/pass/mineral_cluster.cpp
Normal file
238
tilemap/src/pass/mineral_cluster.cpp
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
#include "tilemap/pass/mineral_cluster.h"
|
||||||
|
#include "tilemap/chunk.h"
|
||||||
|
#include "tilemap/generation.h"
|
||||||
|
#include "tilemap/noise.h"
|
||||||
|
#include "tilemap/xoroshiro.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <queue>
|
||||||
|
#include <random>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace istd {
|
||||||
|
|
||||||
|
MineralClusterGenerationPass::MineralClusterGenerationPass(
|
||||||
|
const GenerationConfig &config, Xoroshiro128PP rng, Xoroshiro128PP noise_rng
|
||||||
|
)
|
||||||
|
: config_(config), rng_(rng), noise_(noise_rng) {}
|
||||||
|
|
||||||
|
void MineralClusterGenerationPass::operator()(TileMap &tilemap) {
|
||||||
|
// Generate each mineral type with different densities
|
||||||
|
const std::vector<std::pair<SurfaceTileType, std::uint16_t>> minerals = {
|
||||||
|
{SurfaceTileType::Hematite, config_.hematite_density },
|
||||||
|
{SurfaceTileType::Titanomagnetite, config_.titanomagnetite_density},
|
||||||
|
{SurfaceTileType::Gibbsite, config_.gibbsite_density }
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto [mineral_type, density] : minerals) {
|
||||||
|
// Generate mineral centers using Poisson disk sampling approach
|
||||||
|
auto mineral_centers = generate_mineral_centers(
|
||||||
|
tilemap, mineral_type, density
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generate mineral clusters around each center
|
||||||
|
for (const auto ¢er : mineral_centers) {
|
||||||
|
generate_mineral_cluster(tilemap, center, mineral_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<TilePos> MineralClusterGenerationPass::generate_mineral_centers(
|
||||||
|
const TileMap &tilemap, SurfaceTileType mineral_type, std::uint16_t density
|
||||||
|
) {
|
||||||
|
std::vector<TilePos> centers;
|
||||||
|
std::uint8_t map_size = tilemap.get_size();
|
||||||
|
std::uint32_t total_chunks = map_size * map_size;
|
||||||
|
|
||||||
|
// Calculate expected number of mineral clusters based on density
|
||||||
|
std::uint32_t expected_clusters = (total_chunks * density) / 255;
|
||||||
|
|
||||||
|
// Minimum distance between mineral clusters to ensure spacing
|
||||||
|
std::uint32_t min_distance = calculate_min_mineral_distance(density);
|
||||||
|
|
||||||
|
const std::uint32_t max_coord = map_size * Chunk::size - 1;
|
||||||
|
|
||||||
|
// Generate candidates with rejection sampling
|
||||||
|
std::uint32_t attempts = 0;
|
||||||
|
const std::uint32_t max_attempts = expected_clusters
|
||||||
|
* 64; // More attempts for sparse minerals
|
||||||
|
std::uniform_int_distribution<std::uint16_t> dist(0, max_coord);
|
||||||
|
|
||||||
|
while (centers.size() < expected_clusters && attempts < max_attempts) {
|
||||||
|
++attempts;
|
||||||
|
|
||||||
|
// Generate random position
|
||||||
|
const auto global_x = dist(rng_);
|
||||||
|
const auto global_y = dist(rng_);
|
||||||
|
|
||||||
|
TilePos candidate = TilePos::from_global(global_x, global_y);
|
||||||
|
|
||||||
|
// Check if position is suitable for minerals (mountain edge)
|
||||||
|
if (!is_suitable_for_mineral(tilemap, candidate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check distance to existing mineral centers
|
||||||
|
auto distance_checker = [candidate, min_distance](TilePos existing) {
|
||||||
|
return candidate.sqr_distance_to(existing)
|
||||||
|
< (min_distance * min_distance);
|
||||||
|
};
|
||||||
|
if (std::ranges::any_of(centers, distance_checker)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use base probability for mineral placement
|
||||||
|
std::uint8_t sample = noise_.noise(
|
||||||
|
global_x, global_y,
|
||||||
|
static_cast<std::uint32_t>(
|
||||||
|
mineral_type
|
||||||
|
) // Use mineral type as seed variation
|
||||||
|
);
|
||||||
|
if (sample < config_.mineral_base_probe) {
|
||||||
|
centers.push_back(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return centers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MineralClusterGenerationPass::generate_mineral_cluster(
|
||||||
|
TileMap &tilemap, TilePos center, SurfaceTileType mineral_type
|
||||||
|
) {
|
||||||
|
auto [global_x, global_y] = center.to_global();
|
||||||
|
|
||||||
|
// Calculate cluster size using similar approach to oil
|
||||||
|
auto span = config_.mineral_cluster_max_size
|
||||||
|
- config_.mineral_cluster_min_size;
|
||||||
|
auto cluster_size = config_.mineral_cluster_min_size;
|
||||||
|
|
||||||
|
// Use binomial distribution for cluster size
|
||||||
|
for (int i = 1; i <= span; ++i) {
|
||||||
|
auto sample = noise_.noise(
|
||||||
|
global_x, global_y,
|
||||||
|
i + static_cast<std::uint32_t>(mineral_type) * 16
|
||||||
|
)
|
||||||
|
& 1;
|
||||||
|
cluster_size += sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscreteRandomNoiseStream rng(
|
||||||
|
noise_, global_x, global_y,
|
||||||
|
64 + static_cast<std::uint32_t>(mineral_type) * 16
|
||||||
|
);
|
||||||
|
|
||||||
|
std::vector<TilePos> cluster_tiles;
|
||||||
|
std::unordered_set<TilePos> visited;
|
||||||
|
|
||||||
|
// Start with center if suitable
|
||||||
|
cluster_tiles.push_back(center);
|
||||||
|
visited.insert(center);
|
||||||
|
|
||||||
|
// Grow cluster using random walk
|
||||||
|
std::queue<TilePos> candidates;
|
||||||
|
candidates.push(center);
|
||||||
|
|
||||||
|
while (!candidates.empty() && cluster_tiles.size() < cluster_size) {
|
||||||
|
TilePos current = candidates.front();
|
||||||
|
candidates.pop();
|
||||||
|
|
||||||
|
auto neighbors = tilemap.get_neighbors(current);
|
||||||
|
std::shuffle(neighbors.begin(), neighbors.end(), rng);
|
||||||
|
|
||||||
|
for (const auto neighbor : neighbors) {
|
||||||
|
// 40% chance to skip this neighbor (slightly less dense than oil)
|
||||||
|
auto [neighbor_global_x, neighbor_global_y] = neighbor.to_global();
|
||||||
|
auto sample = noise_.noise(
|
||||||
|
neighbor_global_x, neighbor_global_y,
|
||||||
|
0x3c73dde4
|
||||||
|
+ static_cast<std::uint32_t>(
|
||||||
|
mineral_type
|
||||||
|
) // random seed per mineral
|
||||||
|
);
|
||||||
|
if ((sample % 5) < 2) { // 40% chance to skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visited.count(neighbor) > 0) {
|
||||||
|
continue; // Already visited
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_suitable_for_mineral(tilemap, neighbor)) {
|
||||||
|
continue; // Not suitable for minerals
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to cluster
|
||||||
|
cluster_tiles.push_back(neighbor);
|
||||||
|
visited.insert(neighbor);
|
||||||
|
|
||||||
|
// Stop if we reached the desired cluster size
|
||||||
|
if (cluster_tiles.size() >= cluster_size) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.push(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place minerals on all cluster tiles
|
||||||
|
for (const auto &pos : cluster_tiles) {
|
||||||
|
Tile &tile = tilemap.get_tile(pos);
|
||||||
|
tile.surface = mineral_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MineralClusterGenerationPass::is_suitable_for_mineral(
|
||||||
|
const TileMap &tilemap, TilePos pos
|
||||||
|
) const {
|
||||||
|
const Tile &tile = tilemap.get_tile(pos);
|
||||||
|
|
||||||
|
// Minerals can only be placed on mountains with empty surface
|
||||||
|
if (tile.base != BaseTileType::Mountain
|
||||||
|
|| tile.surface != SurfaceTileType::Empty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be on mountain edge (adjacent to non-mountain tiles)
|
||||||
|
return is_mountain_edge(tilemap, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MineralClusterGenerationPass::is_mountain_edge(
|
||||||
|
const TileMap &tilemap, TilePos pos
|
||||||
|
) const {
|
||||||
|
// Check if this mountain tile has at least one non-mountain neighbor
|
||||||
|
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; // Found a non-mountain neighbor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // All neighbors are mountains, not an edge
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t MineralClusterGenerationPass::calculate_min_mineral_distance(
|
||||||
|
std::uint16_t density
|
||||||
|
) const {
|
||||||
|
// Base distance on chunk size, but allow closer spacing than oil
|
||||||
|
// since minerals are rarer and smaller clusters
|
||||||
|
std::uint32_t base_distance = Chunk::size / 2;
|
||||||
|
|
||||||
|
// Scale inversely with density, but with a minimum distance
|
||||||
|
std::uint32_t scaled_distance = base_distance * 128
|
||||||
|
/ std::max<std::uint16_t>(density, 1);
|
||||||
|
|
||||||
|
// Ensure minimum distance of at least 8 tiles
|
||||||
|
return std::max(scaled_distance, 8u);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerrainGenerator::mineral_cluster_pass(TileMap &tilemap) {
|
||||||
|
auto rng = master_rng_;
|
||||||
|
master_rng_ = master_rng_.jump_96();
|
||||||
|
auto noise_rng = master_rng_;
|
||||||
|
master_rng_ = master_rng_.jump_96();
|
||||||
|
MineralClusterGenerationPass pass(config_, rng, noise_rng);
|
||||||
|
pass(tilemap);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace istd
|
Loading…
x
Reference in New Issue
Block a user