refactor: split each pass to a seperate source file
Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
parent
6b9d6c2463
commit
10ef94302c
@ -2,6 +2,11 @@ cmake_minimum_required(VERSION 3.27)
|
||||
|
||||
# Define the tilemap library source files
|
||||
set(ISTD_TILEMAP_SRC
|
||||
src/pass/base_tile_type.cpp
|
||||
src/pass/biome.cpp
|
||||
src/pass/deepwater.cpp
|
||||
src/pass/mountain_hole_fill.cpp
|
||||
src/pass/smoothen_mountain.cpp
|
||||
src/generation.cpp
|
||||
src/tilemap.cpp
|
||||
src/noise.cpp
|
||||
|
@ -131,7 +131,7 @@ public:
|
||||
) const;
|
||||
};
|
||||
|
||||
class HoleFillPass {
|
||||
class MountainHoleFillPass {
|
||||
private:
|
||||
const GenerationConfig &config_;
|
||||
|
||||
@ -140,7 +140,7 @@ public:
|
||||
* @brief Construct a hole fill pass
|
||||
* @param config Generation configuration parameters
|
||||
*/
|
||||
explicit HoleFillPass(const GenerationConfig &config);
|
||||
explicit MountainHoleFillPass(const GenerationConfig &config);
|
||||
|
||||
/**
|
||||
* @brief Fill small holes in the terrain using BFS
|
||||
@ -264,7 +264,7 @@ private:
|
||||
* @brief Fill small holes in the terrain
|
||||
* @param tilemap The tilemap to process
|
||||
*/
|
||||
void hole_fill_pass(TileMap &tilemap);
|
||||
void mountain_hole_fill_pass(TileMap &tilemap);
|
||||
|
||||
/**
|
||||
* @brief Generate deepwater tiles in ocean biomes
|
||||
|
@ -1,489 +1,7 @@
|
||||
#include "generation.h"
|
||||
#include "biome.h"
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
namespace istd {
|
||||
|
||||
BiomeGenerationPass::BiomeGenerationPass(
|
||||
const GenerationConfig &config, Xoroshiro128PP r1, Xoroshiro128PP r2
|
||||
)
|
||||
: config_(config), temperature_noise_(r1), humidity_noise_(r2) {
|
||||
temperature_noise_.calibrate(
|
||||
config.temperature_scale, config.temperature_octaves,
|
||||
config.temperature_persistence
|
||||
);
|
||||
humidity_noise_.calibrate(
|
||||
config.humidity_scale, config.humidity_octaves,
|
||||
config.humidity_persistence
|
||||
);
|
||||
}
|
||||
|
||||
void BiomeGenerationPass::operator()(TileMap &tilemap) {
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
|
||||
// Generate biomes for each sub-chunk
|
||||
for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) {
|
||||
for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) {
|
||||
Chunk &chunk = tilemap.get_chunk(chunk_x, chunk_y);
|
||||
|
||||
for (std::uint8_t sub_x = 0; sub_x < Chunk::subchunk_count;
|
||||
++sub_x) {
|
||||
for (std::uint8_t sub_y = 0; sub_y < Chunk::subchunk_count;
|
||||
++sub_y) {
|
||||
// Calculate global position for this sub-chunk's center
|
||||
auto [start_x, start_y]
|
||||
= subchunk_to_tile_start(SubChunkPos(sub_x, sub_y));
|
||||
double global_x = chunk_x * Chunk::size + start_x
|
||||
+ Chunk::subchunk_size / 2;
|
||||
|
||||
double global_y = chunk_y * Chunk::size + start_y
|
||||
+ Chunk::subchunk_size / 2;
|
||||
|
||||
// Get climate values
|
||||
auto [temperature, humidity]
|
||||
= get_climate(global_x, global_y);
|
||||
|
||||
// Determine biome and store directly in chunk
|
||||
BiomeType biome = determine_biome(temperature, humidity);
|
||||
chunk.biome[sub_x][sub_y] = biome;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<double, double> BiomeGenerationPass::get_climate(
|
||||
double global_x, double global_y
|
||||
) const {
|
||||
// Generate temperature noise (0-1 range)
|
||||
double temperature = temperature_noise_.uniform_noise(
|
||||
global_x * config_.temperature_scale,
|
||||
global_y * config_.temperature_scale
|
||||
);
|
||||
|
||||
// Generate humidity noise (0-1 range)
|
||||
double humidity = humidity_noise_.uniform_noise(
|
||||
global_x * config_.humidity_scale, global_y * config_.humidity_scale
|
||||
);
|
||||
|
||||
return {temperature, humidity};
|
||||
}
|
||||
|
||||
BaseTileTypeGenerationPass::BaseTileTypeGenerationPass(
|
||||
const GenerationConfig &config, Xoroshiro128PP rng
|
||||
)
|
||||
: config_(config), base_noise_(rng) {
|
||||
base_noise_.calibrate(
|
||||
config.base_scale, config.base_octaves, config.base_persistence
|
||||
);
|
||||
}
|
||||
|
||||
void BaseTileTypeGenerationPass::operator()(TileMap &tilemap) {
|
||||
// Generate base tile types for each chunk
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) {
|
||||
for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) {
|
||||
generate_chunk(tilemap, chunk_x, chunk_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BaseTileTypeGenerationPass::generate_chunk(
|
||||
TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y
|
||||
) {
|
||||
const Chunk &chunk = tilemap.get_chunk(chunk_x, chunk_y);
|
||||
|
||||
// Generate each sub-chunk with its corresponding biome
|
||||
for (std::uint8_t sub_x = 0; sub_x < Chunk::subchunk_count; ++sub_x) {
|
||||
for (std::uint8_t sub_y = 0; sub_y < Chunk::subchunk_count; ++sub_y) {
|
||||
SubChunkPos sub_pos(sub_x, sub_y);
|
||||
BiomeType biome = chunk.get_biome(sub_pos);
|
||||
generate_subchunk(tilemap, chunk_x, chunk_y, sub_pos, biome);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BaseTileTypeGenerationPass::generate_subchunk(
|
||||
TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y,
|
||||
SubChunkPos sub_pos, BiomeType biome
|
||||
) {
|
||||
const BiomeProperties &properties = get_biome_properties(biome);
|
||||
|
||||
// Get starting tile coordinates for this sub-chunk
|
||||
auto [start_x, start_y] = subchunk_to_tile_start(sub_pos);
|
||||
|
||||
// Generate terrain for each tile in the sub-chunk
|
||||
for (std::uint8_t local_x = start_x;
|
||||
local_x < start_x + Chunk::subchunk_size; ++local_x) {
|
||||
for (std::uint8_t local_y = start_y;
|
||||
local_y < start_y + Chunk::subchunk_size; ++local_y) {
|
||||
// Calculate global coordinates
|
||||
double global_x = chunk_x * Chunk::size + local_x;
|
||||
double global_y = chunk_y * Chunk::size + local_y;
|
||||
|
||||
// Generate base terrain noise value using uniform distribution
|
||||
double base_noise_value
|
||||
= base_noise_.uniform_noise(global_x, global_y);
|
||||
|
||||
// Determine base terrain type
|
||||
BaseTileType base_type
|
||||
= determine_base_type(base_noise_value, properties);
|
||||
|
||||
// Create tile with base and surface components
|
||||
Tile tile;
|
||||
tile.base = base_type;
|
||||
tile.surface = SurfaceTileType::Empty;
|
||||
|
||||
// Set the tile
|
||||
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
||||
tilemap.set_tile(pos, tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BaseTileType BaseTileTypeGenerationPass::determine_base_type(
|
||||
double noise_value, const BiomeProperties &properties
|
||||
) const {
|
||||
const std::pair<BaseTileType, double> ratios[] = {
|
||||
{BaseTileType::Water, properties.water_ratio},
|
||||
{BaseTileType::Ice, properties.ice_ratio },
|
||||
{BaseTileType::Sand, properties.sand_ratio },
|
||||
{BaseTileType::Land, properties.land_ratio },
|
||||
{BaseTileType::Mountain, 1.0 },
|
||||
};
|
||||
|
||||
for (const auto &[type, ratio] : ratios) {
|
||||
if (noise_value < ratio) {
|
||||
return type;
|
||||
}
|
||||
noise_value -= ratio; // Adjust noise value for next type
|
||||
}
|
||||
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
HoleFillPass::HoleFillPass(const GenerationConfig &config): config_(config) {}
|
||||
|
||||
void HoleFillPass::operator()(TileMap &tilemap) {
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
std::uint32_t total_tiles = map_size * Chunk::size;
|
||||
|
||||
// Create visited array for the entire map
|
||||
std::vector<std::vector<bool>> visited(
|
||||
total_tiles, std::vector<bool>(total_tiles, false)
|
||||
);
|
||||
|
||||
// Process all tiles in the map
|
||||
for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) {
|
||||
for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) {
|
||||
for (std::uint8_t local_x = 0; local_x < Chunk::size; ++local_x) {
|
||||
for (std::uint8_t local_y = 0; local_y < Chunk::size;
|
||||
++local_y) {
|
||||
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
||||
auto [global_x, global_y] = pos.to_global();
|
||||
|
||||
// Skip if already visited
|
||||
if (visited[global_x][global_y]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Tile &tile = tilemap.get_tile(pos);
|
||||
|
||||
// Only process passable tiles
|
||||
if (!is_passable(tile.base)) {
|
||||
visited[global_x][global_y] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find connected component
|
||||
std::vector<TilePos> component_positions;
|
||||
std::uint32_t component_size = bfs_component_size(
|
||||
tilemap, pos, visited, component_positions
|
||||
);
|
||||
|
||||
// Check if this component touches the boundary
|
||||
bool touches_boundary = false;
|
||||
for (const auto component_pos : component_positions) {
|
||||
if (tilemap.is_at_boundary(component_pos)) {
|
||||
touches_boundary = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill small holes that don't touch the boundary
|
||||
if (!touches_boundary
|
||||
&& component_size <= config_.fill_threshold) {
|
||||
for (const TilePos &fill_pos : component_positions) {
|
||||
Tile fill_tile = tilemap.get_tile(fill_pos);
|
||||
fill_tile.base = BaseTileType::Mountain;
|
||||
tilemap.set_tile(fill_pos, fill_tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HoleFillPass::is_passable(BaseTileType type) const {
|
||||
return type != BaseTileType::Mountain;
|
||||
}
|
||||
|
||||
std::uint32_t HoleFillPass::bfs_component_size(
|
||||
TileMap &tilemap, TilePos start_pos,
|
||||
std::vector<std::vector<bool>> &visited, std::vector<TilePos> &positions
|
||||
) {
|
||||
std::queue<TilePos> queue;
|
||||
queue.push(start_pos);
|
||||
|
||||
auto [start_global_x, start_global_y] = start_pos.to_global();
|
||||
visited[start_global_x][start_global_y] = true;
|
||||
|
||||
std::uint32_t size = 0;
|
||||
positions.clear();
|
||||
|
||||
while (!queue.empty()) {
|
||||
TilePos current = queue.front();
|
||||
queue.pop();
|
||||
positions.push_back(current);
|
||||
++size;
|
||||
|
||||
// Check all neighbors
|
||||
std::vector<TilePos> neighbors = tilemap.get_neighbors(current);
|
||||
for (const auto neighbor : neighbors) {
|
||||
auto [neighbor_global_x, neighbor_global_y] = neighbor.to_global();
|
||||
if (visited[neighbor_global_x][neighbor_global_y]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Tile &neighbor_tile = tilemap.get_tile(neighbor);
|
||||
if (is_passable(neighbor_tile.base)) {
|
||||
visited[neighbor_global_x][neighbor_global_y] = true;
|
||||
queue.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
SmoothenMountainsPass::SmoothenMountainsPass(
|
||||
const GenerationConfig &config, Xoroshiro128PP rng
|
||||
)
|
||||
: config_(config), noise_(rng) {}
|
||||
|
||||
void SmoothenMountainsPass::operator()(TileMap &tilemap) {
|
||||
remove_small_mountain(tilemap);
|
||||
for (int i = 1; i <= config_.mountain_smoothen_steps; ++i) {
|
||||
smoothen_mountains(tilemap, i);
|
||||
}
|
||||
remove_small_mountain(tilemap);
|
||||
}
|
||||
|
||||
void SmoothenMountainsPass::remove_small_mountain(TileMap &tilemap) {
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
std::vector<std::vector<bool>> visited(
|
||||
map_size * Chunk::size, std::vector<bool>(map_size * Chunk::size, false)
|
||||
);
|
||||
|
||||
for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) {
|
||||
for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) {
|
||||
for (std::uint8_t local_x = 0; local_x < Chunk::size; ++local_x) {
|
||||
for (std::uint8_t local_y = 0; local_y < Chunk::size;
|
||||
++local_y) {
|
||||
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
||||
auto [global_x, global_y] = pos.to_global();
|
||||
|
||||
// Skip if already visited
|
||||
if (visited[global_x][global_y]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Tile &tile = tilemap.get_tile(pos);
|
||||
if (tile.base != BaseTileType::Mountain) {
|
||||
visited[global_x][global_y] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find connected component of mountains
|
||||
std::vector<TilePos> component_positions;
|
||||
std::uint32_t component_size = bfs_component_size(
|
||||
tilemap, pos, visited, component_positions
|
||||
);
|
||||
|
||||
// If the component touches the boundary, skip it
|
||||
bool touches_boundary = false;
|
||||
for (auto component_pos : component_positions) {
|
||||
if (tilemap.is_at_boundary(component_pos)) {
|
||||
touches_boundary = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip if it touches the boundary
|
||||
if (touches_boundary) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the component is too small, smooth it out
|
||||
if (component_size <= config_.mountain_remove_threshold) {
|
||||
demountainize(tilemap, component_positions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SmoothenMountainsPass::demountainize(
|
||||
TileMap &tilemap, const std::vector<TilePos> &pos
|
||||
) {
|
||||
// Step 1: Look around the mountain to see what should replace it
|
||||
std::map<BaseTileType, int> type_count;
|
||||
std::set<TilePos> unique_positions;
|
||||
for (auto p : pos) {
|
||||
auto neighbors = tilemap.get_neighbors(p, true);
|
||||
unique_positions.insert(neighbors.begin(), neighbors.end());
|
||||
}
|
||||
|
||||
for (auto p : unique_positions) {
|
||||
const Tile &tile = tilemap.get_tile(p);
|
||||
if (tile.base != BaseTileType::Mountain) {
|
||||
type_count[tile.base]++;
|
||||
}
|
||||
}
|
||||
|
||||
int total_count = 0;
|
||||
for (const auto &[type, count] : type_count) {
|
||||
total_count += count;
|
||||
}
|
||||
|
||||
if (total_count == 0) {
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
// Step 2: Replace each mountain tile with a random type based on the counts
|
||||
for (const auto &p : pos) {
|
||||
Tile tile = tilemap.get_tile(p);
|
||||
auto [global_x, global_y] = p.to_global();
|
||||
auto sample = noise_.noise(global_x, global_y);
|
||||
int index = sample % total_count; // Not perfectly uniform, but works
|
||||
// for small counts
|
||||
for (const auto [type, count] : type_count) {
|
||||
if (index < count) {
|
||||
tile.base = type;
|
||||
break;
|
||||
}
|
||||
index -= count;
|
||||
}
|
||||
tilemap.set_tile(p, tile);
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t SmoothenMountainsPass::bfs_component_size(
|
||||
TileMap &tilemap, TilePos start_pos,
|
||||
std::vector<std::vector<bool>> &visited, std::vector<TilePos> &positions
|
||||
) {
|
||||
std::queue<TilePos> queue;
|
||||
queue.push(start_pos);
|
||||
|
||||
auto [start_global_x, start_global_y] = start_pos.to_global();
|
||||
visited[start_global_x][start_global_y] = true;
|
||||
|
||||
std::uint32_t size = 0;
|
||||
positions.clear();
|
||||
|
||||
while (!queue.empty()) {
|
||||
TilePos current = queue.front();
|
||||
queue.pop();
|
||||
positions.push_back(current);
|
||||
++size;
|
||||
|
||||
// Check all neighbors
|
||||
std::vector<TilePos> neighbors = tilemap.get_neighbors(current, true);
|
||||
for (const auto neighbor : neighbors) {
|
||||
auto [neighbor_global_x, neighbor_global_y] = neighbor.to_global();
|
||||
if (visited[neighbor_global_x][neighbor_global_y]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Tile &neighbor_tile = tilemap.get_tile(neighbor);
|
||||
if (neighbor_tile.base == BaseTileType::Mountain) {
|
||||
visited[neighbor_global_x][neighbor_global_y] = true;
|
||||
queue.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void SmoothenMountainsPass::smoothen_mountains(
|
||||
TileMap &tilemap, std::uint32_t step_i
|
||||
) {
|
||||
struct CAConf {
|
||||
int neighbor_count;
|
||||
int fill_chance = 0; // n / 16
|
||||
int remove_chance = 0; // n / 16
|
||||
};
|
||||
|
||||
// Chance to fill or remove a mountain tile repects to the number of
|
||||
// neighboring mountains (0 - 4)
|
||||
constexpr CAConf cellularAutomataConfigurations[5] = {
|
||||
{0, 0, 12},
|
||||
{1, 0, 4 },
|
||||
{2, 3, 1 },
|
||||
{3, 8, 0 },
|
||||
{4, 16, 0 }
|
||||
};
|
||||
|
||||
for (std::uint8_t chunk_x = 0; chunk_x < tilemap.get_size(); ++chunk_x) {
|
||||
for (std::uint8_t chunk_y = 0; chunk_y < tilemap.get_size();
|
||||
++chunk_y) {
|
||||
for (std::uint8_t local_x = 0; local_x < Chunk::size; ++local_x) {
|
||||
for (std::uint8_t local_y = 0; local_y < Chunk::size;
|
||||
++local_y) {
|
||||
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
||||
auto [global_x, global_y] = pos.to_global();
|
||||
auto neighbors = tilemap.get_neighbors(pos);
|
||||
|
||||
// Ignore if adjacent to the boundary
|
||||
if (neighbors.size() < 4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Count neighboring mountains
|
||||
int mountain_count = 0;
|
||||
for (const auto &neighbor : neighbors) {
|
||||
const Tile &tile = tilemap.get_tile(neighbor);
|
||||
if (tile.base == BaseTileType::Mountain) {
|
||||
mountain_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the configuration for this count
|
||||
const CAConf &conf
|
||||
= cellularAutomataConfigurations[mountain_count];
|
||||
int rd = noise_.noise(global_x, global_y, step_i) & 0xF;
|
||||
|
||||
Tile &tile = tilemap.get_tile(pos);
|
||||
if (tile.base == BaseTileType::Mountain
|
||||
&& conf.remove_chance > rd) {
|
||||
demountainize(tilemap, {pos});
|
||||
} else if (tile.base != BaseTileType::Mountain
|
||||
&& conf.fill_chance > rd) {
|
||||
tile.base = BaseTileType::Mountain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TerrainGenerator::TerrainGenerator(const GenerationConfig &config)
|
||||
: config_(config), master_rng_(config.seed) {}
|
||||
|
||||
@ -491,152 +9,10 @@ void TerrainGenerator::operator()(TileMap &tilemap) {
|
||||
biome_pass(tilemap);
|
||||
base_tile_type_pass(tilemap);
|
||||
smoothen_mountains_pass(tilemap);
|
||||
hole_fill_pass(tilemap);
|
||||
mountain_hole_fill_pass(tilemap);
|
||||
deepwater_pass(tilemap);
|
||||
}
|
||||
|
||||
void TerrainGenerator::biome_pass(TileMap &tilemap) {
|
||||
// Create two RNGs for temperature and humidity noise
|
||||
Xoroshiro128PP temp_rng = master_rng_;
|
||||
master_rng_ = master_rng_.jump_96();
|
||||
Xoroshiro128PP humidity_rng = master_rng_;
|
||||
master_rng_ = master_rng_.jump_96();
|
||||
|
||||
BiomeGenerationPass biome_pass(config_, temp_rng, humidity_rng);
|
||||
biome_pass(tilemap);
|
||||
}
|
||||
|
||||
void TerrainGenerator::base_tile_type_pass(TileMap &tilemap) {
|
||||
BaseTileTypeGenerationPass pass(config_, master_rng_);
|
||||
master_rng_ = master_rng_.jump_96();
|
||||
pass(tilemap);
|
||||
}
|
||||
|
||||
void TerrainGenerator::smoothen_mountains_pass(TileMap &tilemap) {
|
||||
SmoothenMountainsPass pass(config_, master_rng_);
|
||||
master_rng_ = master_rng_.jump_96();
|
||||
pass(tilemap);
|
||||
}
|
||||
|
||||
void TerrainGenerator::hole_fill_pass(TileMap &tilemap) {
|
||||
HoleFillPass pass(config_);
|
||||
pass(tilemap);
|
||||
}
|
||||
|
||||
void TerrainGenerator::deepwater_pass(TileMap &tilemap) {
|
||||
DeepwaterGenerationPass pass(config_.deepwater_radius);
|
||||
pass(tilemap);
|
||||
}
|
||||
|
||||
DeepwaterGenerationPass::DeepwaterGenerationPass(std::uint32_t deepwater_radius)
|
||||
: deepwater_radius_(deepwater_radius) {}
|
||||
|
||||
void DeepwaterGenerationPass::operator()(TileMap &tilemap) {
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
|
||||
// Iterate through all sub-chunks to check biomes efficiently
|
||||
for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) {
|
||||
for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) {
|
||||
const Chunk &chunk = tilemap.get_chunk(chunk_x, chunk_y);
|
||||
|
||||
// Process each sub-chunk
|
||||
for (std::uint8_t sub_x = 0; sub_x < Chunk::subchunk_count;
|
||||
++sub_x) {
|
||||
for (std::uint8_t sub_y = 0; sub_y < Chunk::subchunk_count;
|
||||
++sub_y) {
|
||||
SubChunkPos sub_pos(sub_x, sub_y);
|
||||
BiomeType biome = chunk.get_biome(sub_pos);
|
||||
const BiomeProperties &properties
|
||||
= get_biome_properties(biome);
|
||||
|
||||
// Only process ocean biomes
|
||||
if (!properties.is_ocean) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process all tiles in this ocean sub-chunk
|
||||
process_ocean_subchunk(tilemap, chunk_x, chunk_y, sub_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeepwaterGenerationPass::process_ocean_subchunk(
|
||||
TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y,
|
||||
SubChunkPos sub_pos
|
||||
) {
|
||||
// Get starting tile coordinates for this sub-chunk
|
||||
auto [start_x, start_y] = subchunk_to_tile_start(sub_pos);
|
||||
|
||||
// Process all tiles in this sub-chunk
|
||||
for (std::uint8_t local_x = start_x;
|
||||
local_x < start_x + Chunk::subchunk_size; ++local_x) {
|
||||
for (std::uint8_t local_y = start_y;
|
||||
local_y < start_y + Chunk::subchunk_size; ++local_y) {
|
||||
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
||||
|
||||
// Get the tile at this position
|
||||
const Tile &tile = tilemap.get_tile(pos);
|
||||
|
||||
// Only process water tiles
|
||||
if (tile.base != BaseTileType::Water) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this water tile is surrounded by water/deepwater within
|
||||
// the specified radius
|
||||
if (is_surrounded_by_water(tilemap, pos, deepwater_radius_)) {
|
||||
// Replace water with deepwater
|
||||
Tile new_tile = tile;
|
||||
new_tile.base = BaseTileType::Deepwater;
|
||||
tilemap.set_tile(pos, new_tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DeepwaterGenerationPass::is_surrounded_by_water(
|
||||
const TileMap &tilemap, TilePos center_pos, std::uint32_t radius
|
||||
) const {
|
||||
auto [center_global_x, center_global_y] = center_pos.to_global();
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
std::uint32_t max_global_coord = map_size * Chunk::size;
|
||||
|
||||
// Check all tiles within the radius
|
||||
for (std::int32_t dx = -static_cast<std::int32_t>(radius);
|
||||
dx <= static_cast<std::int32_t>(radius); ++dx) {
|
||||
for (std::int32_t dy = -static_cast<std::int32_t>(radius);
|
||||
dy <= static_cast<std::int32_t>(radius); ++dy) {
|
||||
std::int32_t check_x
|
||||
= static_cast<std::int32_t>(center_global_x) + dx;
|
||||
std::int32_t check_y
|
||||
= static_cast<std::int32_t>(center_global_y) + dy;
|
||||
|
||||
// Check bounds
|
||||
if (check_x < 0 || check_y < 0
|
||||
|| check_x >= static_cast<std::int32_t>(max_global_coord)
|
||||
|| check_y >= static_cast<std::int32_t>(max_global_coord)) {
|
||||
return false; // Out of bounds, consider as non-water
|
||||
}
|
||||
|
||||
// Convert back to TilePos and check if it's water
|
||||
TilePos check_pos = TilePos::from_global(
|
||||
static_cast<std::uint16_t>(check_x),
|
||||
static_cast<std::uint16_t>(check_y)
|
||||
);
|
||||
|
||||
const Tile &check_tile = tilemap.get_tile(check_pos);
|
||||
if (check_tile.base != BaseTileType::Water
|
||||
&& check_tile.base != BaseTileType::Deepwater) {
|
||||
return false; // Found non-water tile within radius
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true; // All tiles within radius are water or deepwater
|
||||
}
|
||||
|
||||
void map_generate(TileMap &tilemap, const GenerationConfig &config) {
|
||||
TerrainGenerator generator(config);
|
||||
generator(tilemap);
|
||||
|
106
tilemap/src/pass/base_tile_type.cpp
Normal file
106
tilemap/src/pass/base_tile_type.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
#include "biome.h"
|
||||
#include "chunk.h"
|
||||
#include "generation.h"
|
||||
|
||||
namespace istd {
|
||||
|
||||
BaseTileTypeGenerationPass::BaseTileTypeGenerationPass(
|
||||
const GenerationConfig &config, Xoroshiro128PP rng
|
||||
)
|
||||
: config_(config), base_noise_(rng) {
|
||||
base_noise_.calibrate(
|
||||
config.base_scale, config.base_octaves, config.base_persistence
|
||||
);
|
||||
}
|
||||
|
||||
void BaseTileTypeGenerationPass::operator()(TileMap &tilemap) {
|
||||
// Generate base tile types for each chunk
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) {
|
||||
for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) {
|
||||
generate_chunk(tilemap, chunk_x, chunk_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BaseTileTypeGenerationPass::generate_chunk(
|
||||
TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y
|
||||
) {
|
||||
const Chunk &chunk = tilemap.get_chunk(chunk_x, chunk_y);
|
||||
|
||||
// Generate each sub-chunk with its corresponding biome
|
||||
for (std::uint8_t sub_x = 0; sub_x < Chunk::subchunk_count; ++sub_x) {
|
||||
for (std::uint8_t sub_y = 0; sub_y < Chunk::subchunk_count; ++sub_y) {
|
||||
SubChunkPos sub_pos(sub_x, sub_y);
|
||||
BiomeType biome = chunk.get_biome(sub_pos);
|
||||
generate_subchunk(tilemap, chunk_x, chunk_y, sub_pos, biome);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BaseTileTypeGenerationPass::generate_subchunk(
|
||||
TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y,
|
||||
SubChunkPos sub_pos, BiomeType biome
|
||||
) {
|
||||
const BiomeProperties &properties = get_biome_properties(biome);
|
||||
|
||||
// Get starting tile coordinates for this sub-chunk
|
||||
auto [start_x, start_y] = subchunk_to_tile_start(sub_pos);
|
||||
|
||||
// Generate terrain for each tile in the sub-chunk
|
||||
for (std::uint8_t local_x = start_x;
|
||||
local_x < start_x + Chunk::subchunk_size; ++local_x) {
|
||||
for (std::uint8_t local_y = start_y;
|
||||
local_y < start_y + Chunk::subchunk_size; ++local_y) {
|
||||
// Calculate global coordinates
|
||||
double global_x = chunk_x * Chunk::size + local_x;
|
||||
double global_y = chunk_y * Chunk::size + local_y;
|
||||
|
||||
// Generate base terrain noise value using uniform distribution
|
||||
double base_noise_value
|
||||
= base_noise_.uniform_noise(global_x, global_y);
|
||||
|
||||
// Determine base terrain type
|
||||
BaseTileType base_type
|
||||
= determine_base_type(base_noise_value, properties);
|
||||
|
||||
// Create tile with base and surface components
|
||||
Tile tile;
|
||||
tile.base = base_type;
|
||||
tile.surface = SurfaceTileType::Empty;
|
||||
|
||||
// Set the tile
|
||||
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
||||
tilemap.set_tile(pos, tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BaseTileType BaseTileTypeGenerationPass::determine_base_type(
|
||||
double noise_value, const BiomeProperties &properties
|
||||
) const {
|
||||
const std::pair<BaseTileType, double> ratios[] = {
|
||||
{BaseTileType::Water, properties.water_ratio},
|
||||
{BaseTileType::Ice, properties.ice_ratio },
|
||||
{BaseTileType::Sand, properties.sand_ratio },
|
||||
{BaseTileType::Land, properties.land_ratio },
|
||||
{BaseTileType::Mountain, 1.0 },
|
||||
};
|
||||
|
||||
for (const auto &[type, ratio] : ratios) {
|
||||
if (noise_value < ratio) {
|
||||
return type;
|
||||
}
|
||||
noise_value -= ratio; // Adjust noise value for next type
|
||||
}
|
||||
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
void TerrainGenerator::base_tile_type_pass(TileMap &tilemap) {
|
||||
BaseTileTypeGenerationPass pass(config_, master_rng_);
|
||||
master_rng_ = master_rng_.jump_96();
|
||||
pass(tilemap);
|
||||
}
|
||||
|
||||
} // namespace istd
|
82
tilemap/src/pass/biome.cpp
Normal file
82
tilemap/src/pass/biome.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include "biome.h"
|
||||
#include "generation.h"
|
||||
|
||||
namespace istd {
|
||||
|
||||
BiomeGenerationPass::BiomeGenerationPass(
|
||||
const GenerationConfig &config, Xoroshiro128PP r1, Xoroshiro128PP r2
|
||||
)
|
||||
: config_(config), temperature_noise_(r1), humidity_noise_(r2) {
|
||||
temperature_noise_.calibrate(
|
||||
config.temperature_scale, config.temperature_octaves,
|
||||
config.temperature_persistence
|
||||
);
|
||||
humidity_noise_.calibrate(
|
||||
config.humidity_scale, config.humidity_octaves,
|
||||
config.humidity_persistence
|
||||
);
|
||||
}
|
||||
|
||||
void BiomeGenerationPass::operator()(TileMap &tilemap) {
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
|
||||
// Generate biomes for each sub-chunk
|
||||
for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) {
|
||||
for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) {
|
||||
Chunk &chunk = tilemap.get_chunk(chunk_x, chunk_y);
|
||||
|
||||
for (std::uint8_t sub_x = 0; sub_x < Chunk::subchunk_count;
|
||||
++sub_x) {
|
||||
for (std::uint8_t sub_y = 0; sub_y < Chunk::subchunk_count;
|
||||
++sub_y) {
|
||||
// Calculate global position for this sub-chunk's center
|
||||
auto [start_x, start_y]
|
||||
= subchunk_to_tile_start(SubChunkPos(sub_x, sub_y));
|
||||
double global_x = chunk_x * Chunk::size + start_x
|
||||
+ Chunk::subchunk_size / 2;
|
||||
|
||||
double global_y = chunk_y * Chunk::size + start_y
|
||||
+ Chunk::subchunk_size / 2;
|
||||
|
||||
// Get climate values
|
||||
auto [temperature, humidity]
|
||||
= get_climate(global_x, global_y);
|
||||
|
||||
// Determine biome and store directly in chunk
|
||||
BiomeType biome = determine_biome(temperature, humidity);
|
||||
chunk.biome[sub_x][sub_y] = biome;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<double, double> BiomeGenerationPass::get_climate(
|
||||
double global_x, double global_y
|
||||
) const {
|
||||
// Generate temperature noise (0-1 range)
|
||||
double temperature = temperature_noise_.uniform_noise(
|
||||
global_x * config_.temperature_scale,
|
||||
global_y * config_.temperature_scale
|
||||
);
|
||||
|
||||
// Generate humidity noise (0-1 range)
|
||||
double humidity = humidity_noise_.uniform_noise(
|
||||
global_x * config_.humidity_scale, global_y * config_.humidity_scale
|
||||
);
|
||||
|
||||
return {temperature, humidity};
|
||||
}
|
||||
|
||||
void TerrainGenerator::biome_pass(TileMap &tilemap) {
|
||||
// Create two RNGs for temperature and humidity noise
|
||||
Xoroshiro128PP temp_rng = master_rng_;
|
||||
master_rng_ = master_rng_.jump_96();
|
||||
Xoroshiro128PP humidity_rng = master_rng_;
|
||||
master_rng_ = master_rng_.jump_96();
|
||||
|
||||
BiomeGenerationPass biome_pass(config_, temp_rng, humidity_rng);
|
||||
biome_pass(tilemap);
|
||||
}
|
||||
|
||||
} // namespace istd
|
120
tilemap/src/pass/deepwater.cpp
Normal file
120
tilemap/src/pass/deepwater.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
#include "biome.h"
|
||||
#include "generation.h"
|
||||
|
||||
namespace istd {
|
||||
|
||||
DeepwaterGenerationPass::DeepwaterGenerationPass(std::uint32_t deepwater_radius)
|
||||
: deepwater_radius_(deepwater_radius) {}
|
||||
|
||||
void DeepwaterGenerationPass::operator()(TileMap &tilemap) {
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
|
||||
// Iterate through all sub-chunks to check biomes efficiently
|
||||
for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) {
|
||||
for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) {
|
||||
const Chunk &chunk = tilemap.get_chunk(chunk_x, chunk_y);
|
||||
|
||||
// Process each sub-chunk
|
||||
for (std::uint8_t sub_x = 0; sub_x < Chunk::subchunk_count;
|
||||
++sub_x) {
|
||||
for (std::uint8_t sub_y = 0; sub_y < Chunk::subchunk_count;
|
||||
++sub_y) {
|
||||
SubChunkPos sub_pos(sub_x, sub_y);
|
||||
BiomeType biome = chunk.get_biome(sub_pos);
|
||||
const BiomeProperties &properties
|
||||
= get_biome_properties(biome);
|
||||
|
||||
// Only process ocean biomes
|
||||
if (!properties.is_ocean) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process all tiles in this ocean sub-chunk
|
||||
process_ocean_subchunk(tilemap, chunk_x, chunk_y, sub_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeepwaterGenerationPass::process_ocean_subchunk(
|
||||
TileMap &tilemap, std::uint8_t chunk_x, std::uint8_t chunk_y,
|
||||
SubChunkPos sub_pos
|
||||
) {
|
||||
// Get starting tile coordinates for this sub-chunk
|
||||
auto [start_x, start_y] = subchunk_to_tile_start(sub_pos);
|
||||
|
||||
// Process all tiles in this sub-chunk
|
||||
for (std::uint8_t local_x = start_x;
|
||||
local_x < start_x + Chunk::subchunk_size; ++local_x) {
|
||||
for (std::uint8_t local_y = start_y;
|
||||
local_y < start_y + Chunk::subchunk_size; ++local_y) {
|
||||
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
||||
|
||||
// Get the tile at this position
|
||||
const Tile &tile = tilemap.get_tile(pos);
|
||||
|
||||
// Only process water tiles
|
||||
if (tile.base != BaseTileType::Water) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this water tile is surrounded by water/deepwater within
|
||||
// the specified radius
|
||||
if (is_surrounded_by_water(tilemap, pos, deepwater_radius_)) {
|
||||
// Replace water with deepwater
|
||||
Tile new_tile = tile;
|
||||
new_tile.base = BaseTileType::Deepwater;
|
||||
tilemap.set_tile(pos, new_tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DeepwaterGenerationPass::is_surrounded_by_water(
|
||||
const TileMap &tilemap, TilePos center_pos, std::uint32_t radius
|
||||
) const {
|
||||
auto [center_global_x, center_global_y] = center_pos.to_global();
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
std::uint32_t max_global_coord = map_size * Chunk::size;
|
||||
|
||||
// Check all tiles within the radius
|
||||
for (std::int32_t dx = -static_cast<std::int32_t>(radius);
|
||||
dx <= static_cast<std::int32_t>(radius); ++dx) {
|
||||
for (std::int32_t dy = -static_cast<std::int32_t>(radius);
|
||||
dy <= static_cast<std::int32_t>(radius); ++dy) {
|
||||
std::int32_t check_x
|
||||
= static_cast<std::int32_t>(center_global_x) + dx;
|
||||
std::int32_t check_y
|
||||
= static_cast<std::int32_t>(center_global_y) + dy;
|
||||
|
||||
// Check bounds
|
||||
if (check_x < 0 || check_y < 0
|
||||
|| check_x >= static_cast<std::int32_t>(max_global_coord)
|
||||
|| check_y >= static_cast<std::int32_t>(max_global_coord)) {
|
||||
return false; // Out of bounds, consider as non-water
|
||||
}
|
||||
|
||||
// Convert back to TilePos and check if it's water
|
||||
TilePos check_pos = TilePos::from_global(
|
||||
static_cast<std::uint16_t>(check_x),
|
||||
static_cast<std::uint16_t>(check_y)
|
||||
);
|
||||
|
||||
const Tile &check_tile = tilemap.get_tile(check_pos);
|
||||
if (check_tile.base != BaseTileType::Water
|
||||
&& check_tile.base != BaseTileType::Deepwater) {
|
||||
return false; // Found non-water tile within radius
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true; // All tiles within radius are water or deepwater
|
||||
}
|
||||
|
||||
void TerrainGenerator::deepwater_pass(TileMap &tilemap) {
|
||||
DeepwaterGenerationPass pass(config_.deepwater_radius);
|
||||
pass(tilemap);
|
||||
}
|
||||
|
||||
} // namespace istd
|
117
tilemap/src/pass/mountain_hole_fill.cpp
Normal file
117
tilemap/src/pass/mountain_hole_fill.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
#include "generation.h"
|
||||
#include <queue>
|
||||
|
||||
namespace istd {
|
||||
|
||||
MountainHoleFillPass::MountainHoleFillPass(const GenerationConfig &config)
|
||||
: config_(config) {}
|
||||
|
||||
void MountainHoleFillPass::operator()(TileMap &tilemap) {
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
std::uint32_t total_tiles = map_size * Chunk::size;
|
||||
|
||||
// Create visited array for the entire map
|
||||
std::vector<std::vector<bool>> visited(
|
||||
total_tiles, std::vector<bool>(total_tiles, false)
|
||||
);
|
||||
|
||||
// Process all tiles in the map
|
||||
for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) {
|
||||
for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) {
|
||||
for (std::uint8_t local_x = 0; local_x < Chunk::size; ++local_x) {
|
||||
for (std::uint8_t local_y = 0; local_y < Chunk::size;
|
||||
++local_y) {
|
||||
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
||||
auto [global_x, global_y] = pos.to_global();
|
||||
|
||||
// Skip if already visited
|
||||
if (visited[global_x][global_y]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Tile &tile = tilemap.get_tile(pos);
|
||||
|
||||
// Only process passable tiles
|
||||
if (!is_passable(tile.base)) {
|
||||
visited[global_x][global_y] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find connected component
|
||||
std::vector<TilePos> component_positions;
|
||||
std::uint32_t component_size = bfs_component_size(
|
||||
tilemap, pos, visited, component_positions
|
||||
);
|
||||
|
||||
// Check if this component touches the boundary
|
||||
bool touches_boundary = false;
|
||||
for (const auto component_pos : component_positions) {
|
||||
if (tilemap.is_at_boundary(component_pos)) {
|
||||
touches_boundary = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill small holes that don't touch the boundary
|
||||
if (!touches_boundary
|
||||
&& component_size <= config_.fill_threshold) {
|
||||
for (const TilePos &fill_pos : component_positions) {
|
||||
Tile fill_tile = tilemap.get_tile(fill_pos);
|
||||
fill_tile.base = BaseTileType::Mountain;
|
||||
tilemap.set_tile(fill_pos, fill_tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MountainHoleFillPass::is_passable(BaseTileType type) const {
|
||||
return type != BaseTileType::Mountain;
|
||||
}
|
||||
|
||||
std::uint32_t MountainHoleFillPass::bfs_component_size(
|
||||
TileMap &tilemap, TilePos start_pos,
|
||||
std::vector<std::vector<bool>> &visited, std::vector<TilePos> &positions
|
||||
) {
|
||||
std::queue<TilePos> queue;
|
||||
queue.push(start_pos);
|
||||
|
||||
auto [start_global_x, start_global_y] = start_pos.to_global();
|
||||
visited[start_global_x][start_global_y] = true;
|
||||
|
||||
std::uint32_t size = 0;
|
||||
positions.clear();
|
||||
|
||||
while (!queue.empty()) {
|
||||
TilePos current = queue.front();
|
||||
queue.pop();
|
||||
positions.push_back(current);
|
||||
++size;
|
||||
|
||||
// Check all neighbors
|
||||
std::vector<TilePos> neighbors = tilemap.get_neighbors(current);
|
||||
for (const auto neighbor : neighbors) {
|
||||
auto [neighbor_global_x, neighbor_global_y] = neighbor.to_global();
|
||||
if (visited[neighbor_global_x][neighbor_global_y]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Tile &neighbor_tile = tilemap.get_tile(neighbor);
|
||||
if (is_passable(neighbor_tile.base)) {
|
||||
visited[neighbor_global_x][neighbor_global_y] = true;
|
||||
queue.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void TerrainGenerator::mountain_hole_fill_pass(TileMap &tilemap) {
|
||||
MountainHoleFillPass pass(config_);
|
||||
pass(tilemap);
|
||||
}
|
||||
|
||||
} // namespace istd
|
227
tilemap/src/pass/smoothen_mountain.cpp
Normal file
227
tilemap/src/pass/smoothen_mountain.cpp
Normal file
@ -0,0 +1,227 @@
|
||||
#include "generation.h"
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
|
||||
namespace istd {
|
||||
|
||||
SmoothenMountainsPass::SmoothenMountainsPass(
|
||||
const GenerationConfig &config, Xoroshiro128PP rng
|
||||
)
|
||||
: config_(config), noise_(rng) {}
|
||||
|
||||
void SmoothenMountainsPass::operator()(TileMap &tilemap) {
|
||||
remove_small_mountain(tilemap);
|
||||
for (int i = 1; i <= config_.mountain_smoothen_steps; ++i) {
|
||||
smoothen_mountains(tilemap, i);
|
||||
}
|
||||
remove_small_mountain(tilemap);
|
||||
}
|
||||
|
||||
void SmoothenMountainsPass::remove_small_mountain(TileMap &tilemap) {
|
||||
std::uint8_t map_size = tilemap.get_size();
|
||||
std::vector<std::vector<bool>> visited(
|
||||
map_size * Chunk::size, std::vector<bool>(map_size * Chunk::size, false)
|
||||
);
|
||||
|
||||
for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) {
|
||||
for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) {
|
||||
for (std::uint8_t local_x = 0; local_x < Chunk::size; ++local_x) {
|
||||
for (std::uint8_t local_y = 0; local_y < Chunk::size;
|
||||
++local_y) {
|
||||
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
||||
auto [global_x, global_y] = pos.to_global();
|
||||
|
||||
// Skip if already visited
|
||||
if (visited[global_x][global_y]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Tile &tile = tilemap.get_tile(pos);
|
||||
if (tile.base != BaseTileType::Mountain) {
|
||||
visited[global_x][global_y] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find connected component of mountains
|
||||
std::vector<TilePos> component_positions;
|
||||
std::uint32_t component_size = bfs_component_size(
|
||||
tilemap, pos, visited, component_positions
|
||||
);
|
||||
|
||||
// If the component touches the boundary, skip it
|
||||
bool touches_boundary = false;
|
||||
for (auto component_pos : component_positions) {
|
||||
if (tilemap.is_at_boundary(component_pos)) {
|
||||
touches_boundary = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip if it touches the boundary
|
||||
if (touches_boundary) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the component is too small, smooth it out
|
||||
if (component_size <= config_.mountain_remove_threshold) {
|
||||
demountainize(tilemap, component_positions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SmoothenMountainsPass::demountainize(
|
||||
TileMap &tilemap, const std::vector<TilePos> &pos
|
||||
) {
|
||||
// Step 1: Look around the mountain to see what should replace it
|
||||
std::map<BaseTileType, int> type_count;
|
||||
std::set<TilePos> unique_positions;
|
||||
for (auto p : pos) {
|
||||
auto neighbors = tilemap.get_neighbors(p, true);
|
||||
unique_positions.insert(neighbors.begin(), neighbors.end());
|
||||
}
|
||||
|
||||
for (auto p : unique_positions) {
|
||||
const Tile &tile = tilemap.get_tile(p);
|
||||
if (tile.base != BaseTileType::Mountain) {
|
||||
type_count[tile.base]++;
|
||||
}
|
||||
}
|
||||
|
||||
int total_count = 0;
|
||||
for (const auto &[type, count] : type_count) {
|
||||
total_count += count;
|
||||
}
|
||||
|
||||
if (total_count == 0) {
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
// Step 2: Replace each mountain tile with a random type based on the counts
|
||||
for (const auto &p : pos) {
|
||||
Tile tile = tilemap.get_tile(p);
|
||||
auto [global_x, global_y] = p.to_global();
|
||||
auto sample = noise_.noise(global_x, global_y);
|
||||
int index = sample % total_count; // Not perfectly uniform, but works
|
||||
// for small counts
|
||||
for (const auto [type, count] : type_count) {
|
||||
if (index < count) {
|
||||
tile.base = type;
|
||||
break;
|
||||
}
|
||||
index -= count;
|
||||
}
|
||||
tilemap.set_tile(p, tile);
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t SmoothenMountainsPass::bfs_component_size(
|
||||
TileMap &tilemap, TilePos start_pos,
|
||||
std::vector<std::vector<bool>> &visited, std::vector<TilePos> &positions
|
||||
) {
|
||||
std::queue<TilePos> queue;
|
||||
queue.push(start_pos);
|
||||
|
||||
auto [start_global_x, start_global_y] = start_pos.to_global();
|
||||
visited[start_global_x][start_global_y] = true;
|
||||
|
||||
std::uint32_t size = 0;
|
||||
positions.clear();
|
||||
|
||||
while (!queue.empty()) {
|
||||
TilePos current = queue.front();
|
||||
queue.pop();
|
||||
positions.push_back(current);
|
||||
++size;
|
||||
|
||||
// Check all neighbors
|
||||
std::vector<TilePos> neighbors = tilemap.get_neighbors(current, true);
|
||||
for (const auto neighbor : neighbors) {
|
||||
auto [neighbor_global_x, neighbor_global_y] = neighbor.to_global();
|
||||
if (visited[neighbor_global_x][neighbor_global_y]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Tile &neighbor_tile = tilemap.get_tile(neighbor);
|
||||
if (neighbor_tile.base == BaseTileType::Mountain) {
|
||||
visited[neighbor_global_x][neighbor_global_y] = true;
|
||||
queue.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void SmoothenMountainsPass::smoothen_mountains(
|
||||
TileMap &tilemap, std::uint32_t step_i
|
||||
) {
|
||||
struct CAConf {
|
||||
int neighbor_count;
|
||||
int fill_chance = 0; // n / 16
|
||||
int remove_chance = 0; // n / 16
|
||||
};
|
||||
|
||||
// Chance to fill or remove a mountain tile repects to the number of
|
||||
// neighboring mountains (0 - 4)
|
||||
constexpr CAConf cellularAutomataConfigurations[5] = {
|
||||
{0, 0, 12},
|
||||
{1, 0, 4 },
|
||||
{2, 3, 1 },
|
||||
{3, 8, 0 },
|
||||
{4, 16, 0 }
|
||||
};
|
||||
|
||||
for (std::uint8_t chunk_x = 0; chunk_x < tilemap.get_size(); ++chunk_x) {
|
||||
for (std::uint8_t chunk_y = 0; chunk_y < tilemap.get_size();
|
||||
++chunk_y) {
|
||||
for (std::uint8_t local_x = 0; local_x < Chunk::size; ++local_x) {
|
||||
for (std::uint8_t local_y = 0; local_y < Chunk::size;
|
||||
++local_y) {
|
||||
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
||||
auto [global_x, global_y] = pos.to_global();
|
||||
auto neighbors = tilemap.get_neighbors(pos);
|
||||
|
||||
// Ignore if adjacent to the boundary
|
||||
if (neighbors.size() < 4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Count neighboring mountains
|
||||
int mountain_count = 0;
|
||||
for (const auto &neighbor : neighbors) {
|
||||
const Tile &tile = tilemap.get_tile(neighbor);
|
||||
if (tile.base == BaseTileType::Mountain) {
|
||||
mountain_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the configuration for this count
|
||||
const CAConf &conf
|
||||
= cellularAutomataConfigurations[mountain_count];
|
||||
int rd = noise_.noise(global_x, global_y, step_i) & 0xF;
|
||||
|
||||
Tile &tile = tilemap.get_tile(pos);
|
||||
if (tile.base == BaseTileType::Mountain
|
||||
&& conf.remove_chance > rd) {
|
||||
demountainize(tilemap, {pos});
|
||||
} else if (tile.base != BaseTileType::Mountain
|
||||
&& conf.fill_chance > rd) {
|
||||
tile.base = BaseTileType::Mountain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainGenerator::smoothen_mountains_pass(TileMap &tilemap) {
|
||||
SmoothenMountainsPass pass(config_, master_rng_);
|
||||
master_rng_ = master_rng_.jump_96();
|
||||
pass(tilemap);
|
||||
}
|
||||
|
||||
} // namespace istd
|
Loading…
x
Reference in New Issue
Block a user