feat: add oil generation pass and update related configurations and documentation
Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
parent
6a3f23f989
commit
937a1324eb
@ -6,6 +6,7 @@ set(ISTD_TILEMAP_SRC
|
||||
src/pass/biome.cpp
|
||||
src/pass/deepwater.cpp
|
||||
src/pass/mountain_hole_fill.cpp
|
||||
src/pass/oil.cpp
|
||||
src/pass/smoothen_mountain.cpp
|
||||
src/pass/smoothen_island.cpp
|
||||
src/generation.cpp
|
||||
|
@ -51,10 +51,10 @@ struct Tile {
|
||||
```
|
||||
|
||||
**Base Tile Types:**
|
||||
- `Land`, `Mountain`, `Sand`, `Water`, `Ice`
|
||||
- `Land`, `Mountain`, `Sand`, `Water`, `Ice`, `Deepwater`
|
||||
|
||||
**Surface Tile Types:**
|
||||
- `Empty`, `Wood`, `Structure`
|
||||
- `Empty`, `Oil`
|
||||
|
||||
### Position Types
|
||||
|
||||
@ -90,6 +90,12 @@ struct GenerationConfig {
|
||||
int temperature_octaves = 3;
|
||||
int humidity_octaves = 3;
|
||||
int base_octaves = 3;
|
||||
|
||||
// Oil generation parameters
|
||||
double oil_density = 0.8; // Average oil fields per chunk
|
||||
std::uint32_t oil_cluster_min_size = 2; // Minimum tiles per cluster
|
||||
std::uint32_t oil_cluster_max_size = 6; // Maximum tiles per cluster
|
||||
double oil_biome_preference = 2.0; // Multiplier for preferred biomes
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -27,8 +27,10 @@ tilemap/
|
||||
│ ├── biome.cpp # Climate-based biome generation
|
||||
│ ├── base_tile_type.cpp # Base terrain generation
|
||||
│ ├── smoothen_mountain.cpp # Mountain smoothing
|
||||
│ ├── smoothen_island.cpp # Island smoothing
|
||||
│ ├── mountain_hole_fill.cpp # Hole filling
|
||||
│ └── deepwater.cpp # Deep water placement
|
||||
│ ├── deepwater.cpp # Deep water placement
|
||||
│ └── oil.cpp # Oil resource generation
|
||||
├── examples/ # Usage examples
|
||||
└── docs/ # Documentation
|
||||
```
|
||||
@ -51,8 +53,10 @@ Terrain generation uses a multi-pass pipeline for modularity and control:
|
||||
1. **Biome Pass**: Generates climate data and assigns biomes to sub-chunks
|
||||
2. **Base Tile Type Pass**: Creates base terrain based on biomes
|
||||
3. **Mountain Smoothing Pass**: Removes isolated mountain clusters
|
||||
4. **Hole Fill Pass**: Fills small terrain holes
|
||||
5. **Deep Water Pass**: Places deep water areas
|
||||
4. **Island Smoothing Pass**: Smooths island coastlines using cellular automata
|
||||
5. **Hole Fill Pass**: Fills small terrain holes
|
||||
6. **Deep Water Pass**: Places deep water areas
|
||||
7. **Oil Pass**: Generates sparse oil deposits as surface features
|
||||
|
||||
Each pass operates independently with its own RNG state, ensuring deterministic results.
|
||||
|
||||
@ -83,9 +87,18 @@ The library addresses Perlin noise distribution issues:
|
||||
|
||||
Several passes use BFS (Breadth-First Search) for terrain analysis:
|
||||
- **Mountain Smoothing**: Find and remove small mountain components
|
||||
- **Island Smoothing**: Apply cellular automata for natural coastlines
|
||||
- **Hole Filling**: Identify and fill isolated terrain holes
|
||||
- Components touching map boundaries are preserved
|
||||
|
||||
### Oil Resource Generation
|
||||
|
||||
The oil generation pass creates sparse resource deposits:
|
||||
- **Poisson Disk Sampling**: Ensures minimum distance between oil fields
|
||||
- **Biome Preference**: Higher probability in desert and plains biomes
|
||||
- **Cluster Growth**: Random walk creates 2-6 tile clusters
|
||||
- **Surface Placement**: Oil appears as surface features on land/sand tiles
|
||||
|
||||
## Random Number Generation
|
||||
|
||||
### Xoroshiro128++ Implementation
|
||||
|
@ -5,13 +5,18 @@
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <print>
|
||||
#include <string>
|
||||
|
||||
// Get BMP color for different base tile types
|
||||
BmpColors::Color get_tile_color(istd::BaseTileType type) {
|
||||
switch (type) {
|
||||
// Get BMP color for different tile types, considering surface tiles
|
||||
BmpColors::Color get_tile_color(const istd::Tile &tile) {
|
||||
// Oil surface tile overrides base color
|
||||
if (tile.surface == istd::SurfaceTileType::Oil) {
|
||||
return BmpColors::OIL;
|
||||
}
|
||||
|
||||
// Otherwise use base tile color
|
||||
switch (tile.base) {
|
||||
case istd::BaseTileType::Land:
|
||||
return BmpColors::LAND;
|
||||
case istd::BaseTileType::Mountain:
|
||||
@ -50,7 +55,7 @@ void generate_bmp(const istd::TileMap &tilemap, const std::string &filename) {
|
||||
int global_x = chunk_x * tiles_per_chunk + tile_x;
|
||||
int global_y = chunk_y * tiles_per_chunk + tile_y;
|
||||
|
||||
auto color = get_tile_color(tile.base);
|
||||
auto color = get_tile_color(tile);
|
||||
|
||||
// Draw a tile_size x tile_size block
|
||||
for (int dy = 0; dy < tile_size; ++dy) {
|
||||
@ -83,6 +88,7 @@ void print_statistics(const istd::TileMap &tilemap) {
|
||||
int tile_counts[6] = {
|
||||
0
|
||||
}; // Count for each base tile type (now 6 types including Deepwater)
|
||||
int oil_count = 0; // Count oil surface tiles
|
||||
const int chunks_per_side = tilemap.get_size();
|
||||
const int tiles_per_chunk = istd::Chunk::size;
|
||||
|
||||
@ -94,15 +100,20 @@ void print_statistics(const istd::TileMap &tilemap) {
|
||||
for (int tile_y = 0; tile_y < tiles_per_chunk; ++tile_y) {
|
||||
const auto &tile = chunk.tiles[tile_x][tile_y];
|
||||
tile_counts[static_cast<int>(tile.base)]++;
|
||||
|
||||
// Count oil surface tiles
|
||||
if (tile.surface == istd::SurfaceTileType::Oil) {
|
||||
oil_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *tile_names[]
|
||||
= {"Land", "Mountain", "Sand", "Water", "Ice", "Deepwater"};
|
||||
int total_tiles
|
||||
= chunks_per_side * chunks_per_side * tiles_per_chunk * tiles_per_chunk;
|
||||
const char *tile_names[] = {"Land", "Mountain", "Sand",
|
||||
"Water", "Ice", "Deepwater"};
|
||||
int total_tiles = chunks_per_side * chunks_per_side * tiles_per_chunk
|
||||
* tiles_per_chunk;
|
||||
|
||||
std::println("\nTile Statistics:");
|
||||
std::println("================");
|
||||
@ -112,6 +123,16 @@ void print_statistics(const istd::TileMap &tilemap) {
|
||||
"{:>10}: {:>8} ({:.1f}%)", tile_names[i], tile_counts[i], percentage
|
||||
);
|
||||
}
|
||||
|
||||
// Print oil statistics
|
||||
double oil_percentage = (double)oil_count / total_tiles * 100.0;
|
||||
double oil_per_chunk = (double)oil_count
|
||||
/ (chunks_per_side * chunks_per_side);
|
||||
std::println(
|
||||
"{:>10}: {:>8} ({:.1f}%, {:.2f} per chunk)", "Oil", oil_count,
|
||||
oil_percentage, oil_per_chunk
|
||||
);
|
||||
|
||||
std::println("Total tiles: {}", total_tiles);
|
||||
}
|
||||
|
||||
@ -131,8 +152,9 @@ int main(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
istd::Seed seed
|
||||
= istd::Seed::from_string(argc >= 2 ? argv[1] : "hello_world");
|
||||
istd::Seed seed = istd::Seed::from_string(
|
||||
argc >= 2 ? argv[1] : "hello_world"
|
||||
);
|
||||
std::string output_filename = argc >= 3 ? argv[2] : "output.bmp";
|
||||
int chunks_per_side = 8; // Default value
|
||||
|
||||
|
@ -235,6 +235,7 @@ constexpr Color SAND(244, 164, 96); // Sandy brown
|
||||
constexpr Color WATER(30, 144, 255); // Dodger blue
|
||||
constexpr Color ICE(176, 224, 230); // Powder blue
|
||||
constexpr Color DEEPWATER(0, 0, 139); // Dark blue
|
||||
constexpr Color OIL(0, 0, 0); // Black
|
||||
} // namespace BmpColors
|
||||
|
||||
#endif // BMP_H
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "tile.h"
|
||||
#include <compare>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
namespace istd {
|
||||
|
||||
@ -27,6 +29,13 @@ struct TilePos {
|
||||
uint8_t local_x;
|
||||
uint8_t local_y;
|
||||
|
||||
/**
|
||||
* @brief Calculate squared distance to another TilePos
|
||||
* @param other Other TilePos to compare with
|
||||
* @return Squared distance between the two positions
|
||||
*/
|
||||
std::uint32_t sqr_distance_to(TilePos other) const;
|
||||
|
||||
/**
|
||||
* @brief Convert TilePos to global coordinates
|
||||
* @return Pair of global X and Y coordinates
|
||||
@ -40,15 +49,23 @@ struct TilePos {
|
||||
* @return TilePos corresponding to the global coordinates
|
||||
*/
|
||||
static TilePos from_global(std::uint16_t global_x, std::uint16_t global_y);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Three-way comparison operator for TilePos
|
||||
* @param lhs Left-hand side TilePos
|
||||
* @param rhs Right-hand side TilePos
|
||||
* @return Strong ordering comparison result
|
||||
*/
|
||||
std::strong_ordering operator<=>(const TilePos &lhs, const TilePos &rhs);
|
||||
/**
|
||||
* @brief Three-way comparison operator for TilePos
|
||||
* @param lhs Left-hand side TilePos
|
||||
* @param rhs Right-hand side TilePos
|
||||
* @return Strong ordering comparison result
|
||||
*/
|
||||
friend std::strong_ordering operator<=>(TilePos lhs, TilePos rhs);
|
||||
|
||||
/**
|
||||
* @brief Equality comparison operator for TilePos
|
||||
* @param lhs Left-hand side TilePos
|
||||
* @param rhs Right-hand side TilePos
|
||||
* @return True if positions are equal
|
||||
*/
|
||||
friend bool operator==(TilePos lhs, TilePos rhs) = default;
|
||||
};
|
||||
|
||||
struct Chunk {
|
||||
// Size of a chunk in tiles (64 x 64)
|
||||
@ -83,6 +100,28 @@ struct Chunk {
|
||||
const BiomeType &get_biome(SubChunkPos pos) const {
|
||||
return biome[pos.sub_x][pos.sub_y];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get biome for a specific local tile position
|
||||
* @param local_x Local X coordinate within the chunk
|
||||
* @param local_y Local Y coordinate within the chunk
|
||||
* @return Reference to biome type
|
||||
*/
|
||||
const BiomeType &get_biome(
|
||||
std::uint8_t local_x, std::uint8_t local_y
|
||||
) const {
|
||||
SubChunkPos sub_pos(local_x / subchunk_size, local_y / subchunk_size);
|
||||
return get_biome(sub_pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get biome for a specific TilePos
|
||||
* @param pos TilePos to get the biome for
|
||||
* @return Reference to biome type
|
||||
*/
|
||||
const BiomeType &get_biome(TilePos pos) const {
|
||||
return get_biome(pos.local_x, pos.local_y);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -94,4 +133,14 @@ std::pair<std::uint8_t, std::uint8_t> subchunk_to_tile_start(SubChunkPos pos);
|
||||
|
||||
} // namespace istd
|
||||
|
||||
template<>
|
||||
struct std::hash<istd::TilePos> {
|
||||
::std::size_t operator()(istd::TilePos pos) const {
|
||||
return (static_cast<::std::size_t>(pos.chunk_x) << 24)
|
||||
| (static_cast<::std::size_t>(pos.chunk_y) << 16)
|
||||
| (static_cast<::std::size_t>(pos.local_x) << 8)
|
||||
| static_cast<::std::size_t>(pos.local_y);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
@ -5,6 +5,7 @@
|
||||
#include "chunk.h"
|
||||
#include "noise.h"
|
||||
#include "tilemap.h"
|
||||
#include "xoroshiro.h"
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
@ -40,6 +41,14 @@ struct GenerationConfig {
|
||||
|
||||
std::uint32_t fill_threshold = 10; // Fill holes smaller than this size
|
||||
std::uint32_t deepwater_radius = 2; // Radius for deepwater generation
|
||||
|
||||
// Oil generation parameters
|
||||
std::uint8_t oil_density = 204; // Average oil fields per 255 chunk (~0.8)
|
||||
std::uint8_t oil_cluster_min_size = 1; // Minimum tiles per oil cluster
|
||||
std::uint8_t oil_cluster_max_size = 7; // Maximum tiles per oil cluster
|
||||
// (should be <= 24)
|
||||
std::uint8_t oil_base_probe = 128; // Biome preference multiplier (out
|
||||
// of 255)
|
||||
};
|
||||
|
||||
class BiomeGenerationPass {
|
||||
@ -351,6 +360,12 @@ private:
|
||||
* @param tilemap The tilemap to process
|
||||
*/
|
||||
void deepwater_pass(TileMap &tilemap);
|
||||
|
||||
/**
|
||||
* @brief Generate oil clusters on suitable terrain
|
||||
* @param tilemap The tilemap to process
|
||||
*/
|
||||
void oil_pass(TileMap &tilemap);
|
||||
};
|
||||
|
||||
class DeepwaterGenerationPass {
|
||||
@ -407,6 +422,67 @@ private:
|
||||
) const;
|
||||
};
|
||||
|
||||
class OilGenerationPass {
|
||||
private:
|
||||
const GenerationConfig &config_;
|
||||
Xoroshiro128PP rng_;
|
||||
DiscreteRandomNoise noise_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct an oil generation pass
|
||||
* @param config Generation configuration parameters
|
||||
* @param rng Random number generator for oil placement
|
||||
* @param noise_rng Random number generator for noise-based operations
|
||||
*/
|
||||
OilGenerationPass(
|
||||
const GenerationConfig &config, Xoroshiro128PP rng,
|
||||
Xoroshiro128PP noise_rng
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Generate oil clusters on the tilemap
|
||||
* @param tilemap The tilemap to process
|
||||
*/
|
||||
void operator()(TileMap &tilemap);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Generate oil center positions using Poisson disk sampling
|
||||
* @param tilemap The tilemap to analyze
|
||||
* @return Vector of positions where oil clusters should be placed
|
||||
*/
|
||||
std::vector<TilePos> generate_oil_centers(const TileMap &tilemap);
|
||||
|
||||
/**
|
||||
* @brief Generate an oil cluster around a center position
|
||||
* @param tilemap The tilemap to modify
|
||||
* @param center Center position for the oil cluster
|
||||
*/
|
||||
void generate_oil_cluster(TileMap &tilemap, TilePos center);
|
||||
|
||||
/**
|
||||
* @brief Check if a tile is suitable for oil placement
|
||||
* @param tilemap The tilemap to check
|
||||
* @param pos Position to check
|
||||
* @return True if oil can be placed at this position
|
||||
*/
|
||||
bool is_suitable_for_oil(const TileMap &tilemap, TilePos pos) const;
|
||||
|
||||
/**
|
||||
* @brief Get biome preference multiplier for oil generation (out of 255)
|
||||
* @param biome The biome type to check
|
||||
* @return Preference value (0-255)
|
||||
*/
|
||||
std::uint8_t get_biome_oil_preference(BiomeType biome) const;
|
||||
|
||||
/**
|
||||
* @brief Calculate minimum distance between oil fields based on map size
|
||||
* @return Minimum distance in tiles
|
||||
*/
|
||||
std::uint32_t calculate_min_oil_distance() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Generate a tilemap using the new biome-based system
|
||||
* @param tilemap The tilemap to generate into
|
||||
|
@ -19,28 +19,55 @@ private:
|
||||
std::uint64_t mask;
|
||||
std::array<std::uint8_t, 256> permutation_;
|
||||
|
||||
std::uint8_t perm(int x) const;
|
||||
std::uint32_t map(std::uint32_t x) const;
|
||||
std::uint8_t perm(int x) const noexcept;
|
||||
std::uint32_t rot8(std::uint32_t x) const noexcept;
|
||||
std::uint32_t map_once(std::uint32_t x) const noexcept;
|
||||
std::uint32_t map(std::uint32_t x) const noexcept;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a DiscreteRandomNoise generator with the given seed
|
||||
* @param rng Random number generator for noise
|
||||
*/
|
||||
explicit DiscreteRandomNoise(Xoroshiro128PP rng);
|
||||
|
||||
DiscreteRandomNoise() = default;
|
||||
explicit DiscreteRandomNoise(Xoroshiro128PP rng) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Generate a discrete random value at the given coordinates
|
||||
* @param x X coordinate
|
||||
* @param y Y coordinate
|
||||
* @param z Z coordinate (optional)
|
||||
* @return Discrete random value between 0 and 255
|
||||
* @return Discrete random value between 0 and 2^64-1
|
||||
*/
|
||||
std::uint64_t noise(
|
||||
std::uint32_t x, std::uint32_t y, std::uint32_t z = 0
|
||||
) const;
|
||||
) const noexcept;
|
||||
};
|
||||
|
||||
class DiscreteRandomNoiseStream {
|
||||
private:
|
||||
const DiscreteRandomNoise &noise_;
|
||||
std::uint32_t x_;
|
||||
std::uint32_t y_;
|
||||
std::uint32_t idx_;
|
||||
|
||||
public:
|
||||
DiscreteRandomNoiseStream(
|
||||
const DiscreteRandomNoise &noise, std::uint32_t x, std::uint32_t y,
|
||||
std::uint32_t idx = 0
|
||||
);
|
||||
|
||||
std::uint64_t next() noexcept;
|
||||
|
||||
// Adaption for STL RandomEngine named requirements
|
||||
using result_type = std::uint64_t;
|
||||
static constexpr result_type min() noexcept {
|
||||
return std::numeric_limits<result_type>::min();
|
||||
}
|
||||
static constexpr result_type max() noexcept {
|
||||
return std::numeric_limits<result_type>::max();
|
||||
}
|
||||
// Equivalent to next(), for STL compatibility
|
||||
result_type operator()() noexcept;
|
||||
};
|
||||
|
||||
class PerlinNoise {
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define ISTD_TILEMAP_TILE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept> // For std::invalid_argument
|
||||
|
||||
namespace istd {
|
||||
|
||||
@ -18,14 +17,17 @@ enum class BaseTileType : std::uint8_t {
|
||||
|
||||
enum class SurfaceTileType : std::uint8_t {
|
||||
Empty,
|
||||
Oil,
|
||||
_count
|
||||
};
|
||||
|
||||
constexpr std::uint8_t base_tile_count
|
||||
= static_cast<std::uint8_t>(BaseTileType::_count);
|
||||
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);
|
||||
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");
|
||||
|
@ -34,6 +34,13 @@ public:
|
||||
Chunk &get_chunk(std::uint8_t chunk_x, std::uint8_t chunk_y);
|
||||
const Chunk &get_chunk(std::uint8_t chunk_x, std::uint8_t chunk_y) const;
|
||||
|
||||
/**
|
||||
* @brief Get a reference to the chunk containing the given TilePos
|
||||
* @param pos The TilePos to get the chunk for
|
||||
*/
|
||||
Chunk &get_chunk_of(TilePos pos);
|
||||
const Chunk &get_chunk_of(TilePos pos) const;
|
||||
|
||||
/**
|
||||
* @brief Get a tile at the given position
|
||||
* @param pos The position of the tile
|
||||
|
@ -1,8 +1,9 @@
|
||||
#include "chunk.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace istd {
|
||||
|
||||
std::strong_ordering operator<=>(const TilePos &lhs, const TilePos &rhs) {
|
||||
std::strong_ordering operator<=>(TilePos lhs, TilePos rhs) {
|
||||
if (lhs.chunk_x != rhs.chunk_x) {
|
||||
return lhs.chunk_x <=> rhs.chunk_x;
|
||||
}
|
||||
@ -23,6 +24,24 @@ std::pair<std::uint8_t, std::uint8_t> subchunk_to_tile_start(SubChunkPos pos) {
|
||||
return {pos.sub_x * Chunk::subchunk_size, pos.sub_y * Chunk::subchunk_size};
|
||||
}
|
||||
|
||||
std::uint32_t TilePos::sqr_distance_to(TilePos other) const {
|
||||
auto [this_global_x, this_global_y] = to_global();
|
||||
auto [other_global_x, other_global_y] = other.to_global();
|
||||
|
||||
using std::swap;
|
||||
if (this_global_x < other_global_x) {
|
||||
swap(this_global_x, other_global_x);
|
||||
}
|
||||
|
||||
if (this_global_y < other_global_y) {
|
||||
swap(this_global_y, other_global_y);
|
||||
}
|
||||
|
||||
std::uint32_t dx = this_global_x - other_global_x;
|
||||
std::uint32_t dy = this_global_y - other_global_y;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
std::pair<std::uint16_t, std::uint16_t> TilePos::to_global() const {
|
||||
return {chunk_x * Chunk::size + local_x, chunk_y * Chunk::size + local_y};
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ void TerrainGenerator::operator()(TileMap &tilemap) {
|
||||
smoothen_islands_pass(tilemap);
|
||||
mountain_hole_fill_pass(tilemap);
|
||||
deepwater_pass(tilemap);
|
||||
oil_pass(tilemap);
|
||||
}
|
||||
|
||||
void map_generate(TileMap &tilemap, const GenerationConfig &config) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "noise.h"
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
@ -7,19 +8,23 @@
|
||||
|
||||
namespace istd {
|
||||
|
||||
DiscreteRandomNoise::DiscreteRandomNoise(Xoroshiro128PP rng) {
|
||||
DiscreteRandomNoise::DiscreteRandomNoise(Xoroshiro128PP rng) noexcept {
|
||||
mask = rng.next();
|
||||
std::iota(permutation_.begin(), permutation_.end(), 0);
|
||||
std::shuffle(permutation_.begin(), permutation_.end(), rng);
|
||||
}
|
||||
|
||||
std::uint8_t DiscreteRandomNoise::perm(int x) const {
|
||||
std::uint8_t DiscreteRandomNoise::perm(int x) const noexcept {
|
||||
// Map x to [0, 255] range
|
||||
x &= 0xFF;
|
||||
return permutation_[x];
|
||||
}
|
||||
|
||||
std::uint32_t DiscreteRandomNoise::map(std::uint32_t x) const {
|
||||
std::uint32_t DiscreteRandomNoise::rot8(std::uint32_t x) const noexcept {
|
||||
return std::rotl(x, 8);
|
||||
}
|
||||
|
||||
std::uint32_t DiscreteRandomNoise::map_once(std::uint32_t x) const noexcept {
|
||||
std::uint8_t a = x & 0xFF;
|
||||
std::uint8_t b = (x >> 8) & 0xFF;
|
||||
std::uint8_t c = (x >> 16) & 0xFF;
|
||||
@ -31,9 +36,17 @@ std::uint32_t DiscreteRandomNoise::map(std::uint32_t x) const {
|
||||
return (d << 24U) | (c << 16U) | (b << 8U) | a;
|
||||
}
|
||||
|
||||
std::uint32_t DiscreteRandomNoise::map(std::uint32_t x) const noexcept {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
x = map_once(x);
|
||||
x = rot8(x);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
std::uint64_t DiscreteRandomNoise::noise(
|
||||
std::uint32_t x, std::uint32_t y, std::uint32_t z
|
||||
) const {
|
||||
) const noexcept {
|
||||
auto A = map(x);
|
||||
auto B = map(y ^ A);
|
||||
auto C = map(z ^ B);
|
||||
@ -43,6 +56,22 @@ std::uint64_t DiscreteRandomNoise::noise(
|
||||
return ((static_cast<std::uint64_t>(C) << 32) | F) ^ mask;
|
||||
}
|
||||
|
||||
DiscreteRandomNoiseStream::DiscreteRandomNoiseStream(
|
||||
const DiscreteRandomNoise &noise, std::uint32_t x, std::uint32_t y,
|
||||
std::uint32_t idx
|
||||
)
|
||||
: noise_(noise), x_(x), y_(y), idx_(idx) {}
|
||||
|
||||
std::uint64_t DiscreteRandomNoiseStream::next() noexcept {
|
||||
std::uint64_t value = noise_.noise(x_, y_, idx_);
|
||||
++idx_;
|
||||
return value;
|
||||
}
|
||||
|
||||
std::uint64_t DiscreteRandomNoiseStream::operator()() noexcept {
|
||||
return next();
|
||||
}
|
||||
|
||||
PerlinNoise::PerlinNoise(Xoroshiro128PP rng) {
|
||||
// Initialize permutation array with values 0-255
|
||||
permutation_.resize(256);
|
||||
@ -191,8 +220,9 @@ double UniformPerlinNoise::uniform_noise(double x, double y) const {
|
||||
|
||||
double UniformPerlinNoise::map_to_uniform(double raw_value) const {
|
||||
// Find position in CDF using binary search
|
||||
auto it
|
||||
= std::lower_bound(cdf_values_.begin(), cdf_values_.end(), raw_value);
|
||||
auto it = std::lower_bound(
|
||||
cdf_values_.begin(), cdf_values_.end(), raw_value
|
||||
);
|
||||
|
||||
// Calculate quantile (position in CDF as fraction)
|
||||
size_t position = std::distance(cdf_values_.begin(), it);
|
||||
|
209
tilemap/src/pass/oil.cpp
Normal file
209
tilemap/src/pass/oil.cpp
Normal file
@ -0,0 +1,209 @@
|
||||
#include "biome.h"
|
||||
#include "chunk.h"
|
||||
#include "generation.h"
|
||||
#include "noise.h"
|
||||
#include "xoroshiro.h"
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include <random>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace istd {
|
||||
|
||||
OilGenerationPass::OilGenerationPass(
|
||||
const GenerationConfig &config, Xoroshiro128PP rng, Xoroshiro128PP noise_rng
|
||||
)
|
||||
: config_(config), rng_(rng), noise_(noise_rng) {}
|
||||
|
||||
void OilGenerationPass::operator()(TileMap &tilemap) {
|
||||
// Generate oil center positions using Poisson disk sampling
|
||||
auto oil_centers = generate_oil_centers(tilemap);
|
||||
|
||||
// Generate oil clusters around each center
|
||||
for (const auto ¢er : oil_centers) {
|
||||
generate_oil_cluster(tilemap, center);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TilePos> OilGenerationPass::generate_oil_centers(
|
||||
const TileMap &tilemap
|
||||
) {
|
||||
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 oil fields based on density (out of 255)
|
||||
std::uint32_t expected_oil_fields = (total_chunks * config_.oil_density)
|
||||
/ 255;
|
||||
|
||||
// Minimum distance between oil fields to ensure spacing
|
||||
std::uint32_t min_distance = calculate_min_oil_distance();
|
||||
|
||||
// Use Poisson disk sampling approach
|
||||
const std::uint32_t max_coord = map_size * Chunk::size - 1;
|
||||
|
||||
// Generate candidates with rejection sampling
|
||||
// Avoid infinite loops
|
||||
std::uint32_t attempts = 0;
|
||||
const std::uint32_t max_attempts = expected_oil_fields * 32;
|
||||
std::uniform_int_distribution<std::uint16_t> dist(0, max_coord);
|
||||
|
||||
while (centers.size() < expected_oil_fields && attempts < max_attempts) {
|
||||
++attempts;
|
||||
|
||||
// Generate random position using xoroshiro
|
||||
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 oil
|
||||
if (!is_suitable_for_oil(tilemap, candidate)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check distance to existing oil 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;
|
||||
}
|
||||
|
||||
// Apply biome preference
|
||||
const Chunk &chunk = tilemap.get_chunk_of(candidate);
|
||||
|
||||
BiomeType biome = chunk.get_biome(candidate);
|
||||
std::uint8_t biome_preference = get_biome_oil_preference(biome);
|
||||
|
||||
// Use integer probability check (0-255)
|
||||
std::uint8_t sample = noise_.noise(global_x, global_y);
|
||||
if (sample < biome_preference) {
|
||||
centers.push_back(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
return centers;
|
||||
}
|
||||
|
||||
void OilGenerationPass::generate_oil_cluster(TileMap &tilemap, TilePos center) {
|
||||
auto [global_x, global_y] = center.to_global();
|
||||
|
||||
auto span = config_.oil_cluster_max_size - config_.oil_cluster_min_size;
|
||||
auto cluster_size = config_.oil_cluster_min_size;
|
||||
// Binomial distribution for cluster size
|
||||
for (int i = 1; i <= span; ++i) {
|
||||
auto sample = noise_.noise(global_x, global_y, i) & 1;
|
||||
cluster_size += sample; // Increase cluster size based on noise
|
||||
}
|
||||
|
||||
DiscreteRandomNoiseStream rng(noise_, global_x, global_y, 48);
|
||||
|
||||
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) {
|
||||
// 50% chance to skip this neighbor
|
||||
auto [neighbor_global_x, neighbor_global_y] = neighbor.to_global();
|
||||
auto sample = noise_.noise(
|
||||
neighbor_global_x, neighbor_global_y,
|
||||
0x2b52aaed // random seed
|
||||
);
|
||||
if ((sample & 1) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (visited.count(neighbor) > 0) {
|
||||
continue; // Already visited
|
||||
}
|
||||
|
||||
if (!is_suitable_for_oil(tilemap, neighbor)) {
|
||||
continue; // Not suitable for oil
|
||||
}
|
||||
|
||||
// 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 oil on all cluster tiles
|
||||
for (const auto &pos : cluster_tiles) {
|
||||
Tile &tile = tilemap.get_tile(pos);
|
||||
tile.surface = SurfaceTileType::Oil;
|
||||
}
|
||||
}
|
||||
|
||||
bool OilGenerationPass::is_suitable_for_oil(
|
||||
const TileMap &tilemap, TilePos pos
|
||||
) const {
|
||||
const Tile &tile = tilemap.get_tile(pos);
|
||||
|
||||
// Oil can only be placed on land or sand, and surface must be empty
|
||||
return (tile.base == BaseTileType::Land || tile.base == BaseTileType::Sand)
|
||||
&& tile.surface == SurfaceTileType::Empty;
|
||||
}
|
||||
|
||||
std::uint8_t OilGenerationPass::get_biome_oil_preference(
|
||||
BiomeType biome
|
||||
) const {
|
||||
std::uint8_t base_preference = config_.oil_base_probe;
|
||||
|
||||
switch (biome) {
|
||||
case BiomeType::Desert:
|
||||
case BiomeType::Plains:
|
||||
return base_preference; // Full preference for desert/plains
|
||||
case BiomeType::Savanna:
|
||||
case BiomeType::SnowyPlains:
|
||||
return (base_preference * 204) / 255; // ~80% preference
|
||||
case BiomeType::Forest:
|
||||
return (base_preference * 128) / 255; // ~50% preference
|
||||
case BiomeType::SnowyPeeks:
|
||||
return (base_preference * 77) / 255; // ~30% preference
|
||||
case BiomeType::FrozenOcean:
|
||||
case BiomeType::Ocean:
|
||||
case BiomeType::LukeOcean:
|
||||
default:
|
||||
return 0; // No oil in oceans
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t OilGenerationPass::calculate_min_oil_distance() const {
|
||||
// Base minimum distance on chunk size and oil density
|
||||
// Lower density = higher minimum distance
|
||||
std::uint32_t base_distance = Chunk::size * 4 / 5;
|
||||
return base_distance * 255 / config_.oil_density;
|
||||
}
|
||||
|
||||
void TerrainGenerator::oil_pass(TileMap &tilemap) {
|
||||
auto rng = master_rng_;
|
||||
master_rng_ = master_rng_.jump_96();
|
||||
auto noise_rng = master_rng_;
|
||||
master_rng_ = master_rng_.jump_96();
|
||||
OilGenerationPass pass(config_, rng, noise_rng);
|
||||
pass(tilemap);
|
||||
}
|
||||
|
||||
} // namespace istd
|
@ -1,4 +1,5 @@
|
||||
#include "tilemap.h"
|
||||
#include "chunk.h"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace istd {
|
||||
@ -31,6 +32,14 @@ const Chunk &TileMap::get_chunk(
|
||||
return chunks_[chunk_x][chunk_y];
|
||||
}
|
||||
|
||||
Chunk &TileMap::get_chunk_of(TilePos pos) {
|
||||
return get_chunk(pos.chunk_x, pos.chunk_y);
|
||||
}
|
||||
|
||||
const Chunk &TileMap::get_chunk_of(TilePos pos) const {
|
||||
return get_chunk(pos.chunk_x, pos.chunk_y);
|
||||
}
|
||||
|
||||
Tile &TileMap::get_tile(TilePos pos) {
|
||||
if (pos.chunk_x >= size_ || pos.chunk_y >= size_) {
|
||||
throw std::out_of_range("Chunk coordinates out of bounds");
|
||||
@ -68,7 +77,7 @@ bool TileMap::is_at_boundary(TilePos pos) const {
|
||||
std::uint32_t max_global = map_size * Chunk::size - 1;
|
||||
|
||||
return global_x == 0 || global_x == max_global || global_y == 0
|
||||
|| global_y == max_global;
|
||||
|| global_y == max_global;
|
||||
}
|
||||
|
||||
std::vector<TilePos> TileMap::get_neighbors(TilePos pos, bool chebyshiv) const {
|
||||
|
Loading…
x
Reference in New Issue
Block a user