feat: Xoroshiro128++ Random Engine
Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
parent
ebdbf62a19
commit
06a60b1a19
@ -7,6 +7,7 @@ set(ISTD_TILEMAP_SRC
|
||||
src/noise.cpp
|
||||
src/biome.cpp
|
||||
src/chunk.cpp
|
||||
src/xoroshiro.cpp
|
||||
)
|
||||
|
||||
# Create the tilemap library
|
||||
|
@ -114,7 +114,7 @@ Configuration parameters for terrain generation.
|
||||
|
||||
```cpp
|
||||
struct GenerationConfig {
|
||||
std::uint64_t seed = 0; // Seed for random generation
|
||||
Seed seed; // 128-bit seed for random generation
|
||||
|
||||
// Temperature noise parameters
|
||||
double temperature_scale = 0.005; // Scale for temperature noise
|
||||
@ -135,7 +135,7 @@ struct GenerationConfig {
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `seed`: Random seed for all noise generators
|
||||
- `seed`: 128-bit seed for all noise generators (see Seed structure)
|
||||
- `temperature_scale`: Controls the scale/frequency of temperature variation across the map
|
||||
- `temperature_octaves`: Number of noise octaves for temperature (more octaves = more detail)
|
||||
- `temperature_persistence`: How much each octave contributes to temperature noise (0.0-1.0)
|
||||
@ -166,16 +166,64 @@ Convenience function for map generation.
|
||||
void map_generate(TileMap& tilemap, const GenerationConfig& config);
|
||||
```
|
||||
|
||||
## Random Number Generation
|
||||
|
||||
### Seed
|
||||
|
||||
128-bit seed structure for random number generation.
|
||||
|
||||
```cpp
|
||||
struct Seed {
|
||||
std::uint64_t s[2]; // 128-bit seed value (two 64-bit components)
|
||||
|
||||
static Seed from_string(const char* str); // Create seed from string
|
||||
static Seed device_random(); // Create seed from hardware random device
|
||||
};
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- **128-bit precision**: Uses two 64-bit integers for extended seed space
|
||||
- **String generation**: Deterministic seed creation from text strings
|
||||
- **Hardware random**: True random seed generation using system entropy
|
||||
|
||||
### Xoroshiro128++
|
||||
|
||||
High-performance random number generator using the Xoroshiro128++ algorithm.
|
||||
|
||||
```cpp
|
||||
class Xoroshiro128PP {
|
||||
public:
|
||||
Xoroshiro128PP() = default;
|
||||
Xoroshiro128PP(Seed seed);
|
||||
|
||||
// STL RandomEngine interface
|
||||
using result_type = std::uint64_t;
|
||||
static constexpr result_type min();
|
||||
static constexpr result_type max();
|
||||
result_type operator()();
|
||||
|
||||
std::uint64_t next(); // Generate next random number
|
||||
Xoroshiro128PP jump_64() const; // Jump equivalent to 2^64 calls
|
||||
Xoroshiro128PP jump_96() const; // Jump equivalent to 2^96 calls
|
||||
};
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- **High Performance**: Optimized for speed with excellent statistical properties
|
||||
- **128-bit State**: Internal state provides long period (2^128 - 1)
|
||||
- **Jump Functions**: Enable parallel random number generation
|
||||
- **STL Compatible**: Implements standard random engine interface
|
||||
|
||||
## Noise System
|
||||
|
||||
### PerlinNoise
|
||||
|
||||
Standard Perlin noise implementation for procedural generation.
|
||||
Standard Perlin noise implementation using Xoroshiro128++ for procedural generation.
|
||||
|
||||
```cpp
|
||||
class PerlinNoise {
|
||||
public:
|
||||
explicit PerlinNoise(std::uint64_t seed = 0);
|
||||
explicit PerlinNoise(Xoroshiro128PP rng);
|
||||
double noise(double x, double y) const;
|
||||
double octave_noise(double x, double y, int octaves = 4, double persistence = 0.5) const;
|
||||
};
|
||||
@ -183,12 +231,12 @@ public:
|
||||
|
||||
### UniformPerlinNoise
|
||||
|
||||
Advanced noise generator that provides uniform distribution mapping.
|
||||
Advanced noise generator using Xoroshiro128++ that provides uniform distribution mapping.
|
||||
|
||||
```cpp
|
||||
class UniformPerlinNoise {
|
||||
public:
|
||||
explicit UniformPerlinNoise(std::uint64_t seed = 0);
|
||||
explicit UniformPerlinNoise(Xoroshiro128PP rng);
|
||||
void calibrate(double scale, int octaves = 1, double persistence = 0.5, int sample_size = 10000);
|
||||
double uniform_noise(double x, double y) const;
|
||||
bool is_calibrated() const;
|
||||
@ -200,6 +248,7 @@ public:
|
||||
- **Uniform Mapping**: Maps raw Perlin values to uniform [0,1] distribution
|
||||
- **Balanced Output**: Ensures even distribution across all value ranges
|
||||
- **Automatic Use**: TerrainGenerator uses this internally for balanced terrain
|
||||
- **Xoroshiro128++ Backend**: Uses high-quality random number generation
|
||||
|
||||
## Biome System
|
||||
|
||||
@ -257,7 +306,7 @@ istd::TileMap tilemap(4);
|
||||
|
||||
// Configure generation
|
||||
istd::GenerationConfig config;
|
||||
config.seed = 12345;
|
||||
config.seed = istd::Seed::from_string("hello_world"); // 128-bit seed from string
|
||||
|
||||
// Temperature noise settings
|
||||
config.temperature_scale = 0.005;
|
||||
@ -286,6 +335,25 @@ for (int chunk_y = 0; chunk_y < Chunk::subchunk_count; ++chunk_y) {
|
||||
}
|
||||
```
|
||||
|
||||
### Seed Usage Examples
|
||||
|
||||
```cpp
|
||||
// Create seed from string (deterministic)
|
||||
istd::Seed seed1 = istd::Seed::from_string("my_world");
|
||||
|
||||
// Create random seed from hardware
|
||||
istd::Seed seed2 = istd::Seed::device_random();
|
||||
|
||||
// Manual seed creation
|
||||
istd::Seed seed3;
|
||||
seed3.s[0] = 0x123456789abcdef0;
|
||||
seed3.s[1] = 0xfedcba9876543210;
|
||||
|
||||
// Use seed in generation
|
||||
istd::GenerationConfig config;
|
||||
config.seed = seed1;
|
||||
```
|
||||
|
||||
### Accessing Individual Tiles
|
||||
|
||||
```cpp
|
||||
@ -323,32 +391,44 @@ std::cout << "Biome: " << props.name << std::endl;
|
||||
- Each chunk contains 4,096 tiles (64×64)
|
||||
- Sub-chunks provide efficient biome management
|
||||
- Tiles are packed into 1 byte each for memory efficiency
|
||||
- Generation uses Perlin noise with uniform distribution mapping for balanced terrain
|
||||
- Generation uses Xoroshiro128++ random number generator with uniform distribution mapping for balanced terrain
|
||||
- Noise calibration is performed once during generator construction
|
||||
- 128-bit seeds provide excellent randomness and reproducibility
|
||||
|
||||
## Noise Distribution
|
||||
|
||||
The library uses an advanced noise system that addresses the non-uniform distribution of Perlin noise:
|
||||
The library uses an advanced noise system based on Xoroshiro128++ random number generation that addresses the non-uniform distribution of Perlin noise:
|
||||
|
||||
### Problem with Raw Perlin Noise
|
||||
|
||||
Raw Perlin noise follows a bell-curve distribution, with most values concentrated around 0.5. This leads to unbalanced terrain generation where certain tile types (like Land) dominate the map.
|
||||
|
||||
### Solution: Uniform Distribution Mapping
|
||||
### Solution: Xoroshiro128++ + Uniform Distribution Mapping
|
||||
|
||||
The `UniformPerlinNoise` class:
|
||||
The library combines two key improvements:
|
||||
|
||||
1. **Samples** the noise distribution during calibration
|
||||
2. **Builds a CDF** (Cumulative Distribution Function) from the samples
|
||||
3. **Maps raw noise values** to uniform [0,1] distribution using quantiles
|
||||
4. **Ensures balanced** terrain type distribution according to biome properties
|
||||
1. **Xoroshiro128++ RNG**: High-quality pseudo-random number generator with:
|
||||
- **Long Period**: 2^128 - 1 sequence length before repetition
|
||||
- **High Performance**: Optimized for speed and memory efficiency
|
||||
- **Excellent Statistics**: Passes rigorous randomness tests
|
||||
- **128-bit State**: Two 64-bit values providing extensive seed space
|
||||
|
||||
2. **Uniform Distribution Mapping**: The `UniformPerlinNoise` class:
|
||||
- **Samples** the noise distribution during calibration
|
||||
- **Builds a CDF** (Cumulative Distribution Function) from the samples
|
||||
- **Maps raw noise values** to uniform [0,1] distribution using quantiles
|
||||
- **Ensures balanced** terrain type distribution according to biome properties
|
||||
|
||||
### Usage in Terrain Generation
|
||||
|
||||
```cpp
|
||||
// The terrain generator automatically uses uniform noise
|
||||
// The terrain generator automatically uses Xoroshiro128++ and uniform noise
|
||||
istd::Seed seed = istd::Seed::from_string("consistent_world");
|
||||
istd::GenerationConfig config;
|
||||
config.seed = seed;
|
||||
|
||||
TerrainGenerator generator(config);
|
||||
generator.generate_map(tilemap); // Uses calibrated uniform noise internally
|
||||
generator.generate_map(tilemap); // Uses calibrated uniform noise with Xoroshiro128++
|
||||
```
|
||||
|
||||
## Thread Safety
|
||||
|
@ -7,13 +7,3 @@ cmake_minimum_required(VERSION 3.27)
|
||||
add_executable(biome_demo biome_demo.cpp)
|
||||
target_link_libraries(biome_demo PRIVATE istd_tilemap)
|
||||
target_include_directories(biome_demo PRIVATE ../include)
|
||||
|
||||
# Perlin noise visualization
|
||||
add_executable(perlin_demo perlin_demo.cpp)
|
||||
target_link_libraries(perlin_demo PRIVATE istd_tilemap)
|
||||
target_include_directories(perlin_demo PRIVATE ../include)
|
||||
|
||||
# Noise comparison (raw vs uniform)
|
||||
add_executable(noise_comparison noise_comparison.cpp)
|
||||
target_link_libraries(noise_comparison PRIVATE istd_tilemap)
|
||||
target_include_directories(noise_comparison PRIVATE ../include)
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "tile.h"
|
||||
#include "tilemap.h"
|
||||
#include <cstdlib>
|
||||
#include <format>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
@ -63,25 +64,6 @@ void generate_bmp(const istd::TileMap &tilemap, const std::string &filename) {
|
||||
}
|
||||
}
|
||||
|
||||
// Add chunk boundary lines (optional - makes file larger)
|
||||
/*
|
||||
for (int i = 0; i <= chunks_per_side; ++i) {
|
||||
int pos = i * tiles_per_chunk * tile_size;
|
||||
// Vertical lines
|
||||
for (int y = 0; y < image_size; ++y) {
|
||||
if (pos < image_size) {
|
||||
bmp.set_pixel(pos, y, 0, 0, 0); // Black
|
||||
}
|
||||
}
|
||||
// Horizontal lines
|
||||
for (int x = 0; x < image_size; ++x) {
|
||||
if (pos < image_size) {
|
||||
bmp.set_pixel(x, pos, 0, 0, 0); // Black
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (!bmp.save(filename)) {
|
||||
std::cerr << "Error: Could not save BMP file: " << filename
|
||||
<< std::endl;
|
||||
@ -144,8 +126,8 @@ int main(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::uint64_t seed
|
||||
= argc >= 2 ? std::strtoull(argv[1], nullptr, 10) : 541234;
|
||||
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 = 4; // Default value
|
||||
|
||||
@ -170,7 +152,8 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
std::cout << "Generating " << chunks_per_side << "x" << chunks_per_side
|
||||
<< " chunk tilemap with seed: " << seed << std::endl;
|
||||
<< " chunk tilemap with seed: " << seed.s[0] << ", " << seed.s[1]
|
||||
<< std::endl;
|
||||
|
||||
// Create tilemap with specified size
|
||||
istd::TileMap tilemap(chunks_per_side);
|
||||
|
@ -1,141 +0,0 @@
|
||||
#include "bmp.h"
|
||||
#include "noise.h"
|
||||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Generate comparison BMP showing both raw and uniform noise side by side
|
||||
void generate_comparison_bmp(
|
||||
const std::string &filename, int size, double scale, std::uint64_t seed,
|
||||
int octaves = 3, double persistence = 0.5
|
||||
) {
|
||||
// Create noise generators
|
||||
istd::PerlinNoise raw_noise(seed);
|
||||
istd::UniformPerlinNoise uniform_noise(seed);
|
||||
|
||||
// Calibrate uniform noise
|
||||
uniform_noise.calibrate(scale, octaves, persistence);
|
||||
|
||||
const int panel_width = size;
|
||||
const int gap = 10;
|
||||
const int total_width = panel_width * 2 + gap;
|
||||
|
||||
BmpWriter bmp(total_width, size);
|
||||
|
||||
// Fill gap with white
|
||||
for (int y = 0; y < size; ++y) {
|
||||
for (int x = panel_width; x < panel_width + gap; ++x) {
|
||||
bmp.set_pixel(x, y, 255, 255, 255);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate histograms for statistics
|
||||
std::vector<int> raw_histogram(10, 0);
|
||||
std::vector<int> uniform_histogram(10, 0);
|
||||
|
||||
// Generate left panel (raw noise)
|
||||
for (int y = 0; y < size; ++y) {
|
||||
for (int x = 0; x < size; ++x) {
|
||||
double noise_value = raw_noise.octave_noise(
|
||||
x * scale, y * scale, octaves, persistence
|
||||
);
|
||||
|
||||
// Update histogram
|
||||
int bin = std::min(9, static_cast<int>(noise_value * 10));
|
||||
raw_histogram[bin]++;
|
||||
|
||||
bmp.set_pixel_normalized(x, y, noise_value);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate right panel (uniform noise)
|
||||
int panel_offset = panel_width + gap;
|
||||
for (int y = 0; y < size; ++y) {
|
||||
for (int x = 0; x < size; ++x) {
|
||||
double noise_value = uniform_noise.uniform_noise(x, y);
|
||||
|
||||
// Update histogram
|
||||
int bin = std::min(9, static_cast<int>(noise_value * 10));
|
||||
uniform_histogram[bin]++;
|
||||
|
||||
bmp.set_pixel_normalized(panel_offset + x, y, noise_value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!bmp.save(filename)) {
|
||||
std::cerr << "Error: Could not save BMP file: " << filename
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Noise comparison BMP generated: " << filename << std::endl;
|
||||
std::cout << "Size: " << size << "x" << size << " pixels per panel"
|
||||
<< std::endl;
|
||||
std::cout << "Parameters: scale=" << scale << ", octaves=" << octaves
|
||||
<< ", seed=" << seed << std::endl;
|
||||
|
||||
// Print histogram comparison
|
||||
std::cout << "\nValue Distribution Analysis:\n";
|
||||
std::cout << "Range | Raw Noise | Uniform Noise\n";
|
||||
std::cout << "---------|-----------|--------------\n";
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
double range_start = i * 0.1;
|
||||
double range_end = (i + 1) * 0.1;
|
||||
std::cout << std::fixed << std::setprecision(1) << range_start << "-"
|
||||
<< range_end << " | " << std::setw(9) << raw_histogram[i]
|
||||
<< " | " << std::setw(12) << uniform_histogram[i] << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Default parameters
|
||||
std::uint64_t seed = 12345;
|
||||
std::string output_filename = "noise_comparison.bmp";
|
||||
double scale = 0.08;
|
||||
int octaves = 3;
|
||||
double persistence = 0.5;
|
||||
|
||||
// Parse command line arguments
|
||||
if (argc >= 2) {
|
||||
seed = std::strtoull(argv[1], nullptr, 10);
|
||||
}
|
||||
if (argc >= 3) {
|
||||
output_filename = argv[2];
|
||||
}
|
||||
if (argc >= 4) {
|
||||
scale = std::strtod(argv[3], nullptr);
|
||||
}
|
||||
if (argc >= 5) {
|
||||
octaves = std::strtol(argv[4], nullptr, 10);
|
||||
}
|
||||
if (argc >= 6) {
|
||||
persistence = std::strtod(argv[5], nullptr);
|
||||
}
|
||||
|
||||
if (argc == 1 || argc > 6) {
|
||||
std::cout << "Usage: " << argv[0]
|
||||
<< " [seed] [output.bmp] [scale] [octaves] [persistence]\n";
|
||||
std::cout << "Defaults: seed=12345, output=noise_comparison.bmp, "
|
||||
"scale=0.08, octaves=3, persistence=0.5\n";
|
||||
std::cout << "This will generate a side-by-side comparison of raw vs "
|
||||
"uniform Perlin noise\n";
|
||||
if (argc > 6) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Generating noise comparison (256x256 per panel)..."
|
||||
<< std::endl;
|
||||
std::cout << "Parameters: seed=" << seed << ", scale=" << scale
|
||||
<< ", octaves=" << octaves << ", persistence=" << persistence
|
||||
<< std::endl;
|
||||
|
||||
// Generate the comparison
|
||||
generate_comparison_bmp(
|
||||
output_filename, 256, scale, seed, octaves, persistence
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
#include "bmp.h"
|
||||
#include "noise.h"
|
||||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
// Generate BMP file from Perlin noise
|
||||
void generate_noise_bmp(
|
||||
const std::string &filename, int size, double scale, std::uint64_t seed,
|
||||
int octaves = 1, double persistence = 0.5
|
||||
) {
|
||||
// Create noise generator
|
||||
istd::PerlinNoise noise(seed);
|
||||
|
||||
BmpWriter bmp(size, size);
|
||||
|
||||
// Generate noise values and statistics
|
||||
double min_value = 1.0, max_value = 0.0;
|
||||
|
||||
for (int y = 0; y < size; ++y) {
|
||||
for (int x = 0; x < size; ++x) {
|
||||
double noise_value;
|
||||
if (octaves == 1) {
|
||||
noise_value = noise.noise(x * scale, y * scale);
|
||||
} else {
|
||||
noise_value = noise.octave_noise(
|
||||
x * scale, y * scale, octaves, persistence
|
||||
);
|
||||
}
|
||||
|
||||
// Track min/max for statistics
|
||||
min_value = std::min(min_value, noise_value);
|
||||
max_value = std::max(max_value, noise_value);
|
||||
|
||||
bmp.set_pixel_normalized(x, y, noise_value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!bmp.save(filename)) {
|
||||
std::cerr << "Error: Could not save BMP file: " << filename
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Perlin noise BMP generated: " << filename << std::endl;
|
||||
std::cout << "Size: " << size << "x" << size << " pixels" << std::endl;
|
||||
std::cout << "Scale: " << scale << ", Octaves: " << octaves << std::endl;
|
||||
std::cout << "Value range: [" << std::fixed << std::setprecision(3)
|
||||
<< min_value << ", " << max_value << "]" << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Default parameters
|
||||
std::uint64_t seed = 12345;
|
||||
std::string output_filename = "perlin_noise.bmp";
|
||||
double scale = 0.02;
|
||||
int octaves = 1;
|
||||
double persistence = 0.5;
|
||||
|
||||
// Parse command line arguments
|
||||
if (argc >= 2) {
|
||||
seed = std::strtoull(argv[1], nullptr, 10);
|
||||
}
|
||||
if (argc >= 3) {
|
||||
output_filename = argv[2];
|
||||
}
|
||||
if (argc >= 4) {
|
||||
scale = std::strtod(argv[3], nullptr);
|
||||
}
|
||||
if (argc >= 5) {
|
||||
octaves = std::strtol(argv[4], nullptr, 10);
|
||||
}
|
||||
if (argc >= 6) {
|
||||
persistence = std::strtod(argv[5], nullptr);
|
||||
}
|
||||
|
||||
if (argc == 1 || argc > 6) {
|
||||
std::cout << "Usage: " << argv[0]
|
||||
<< " [seed] [output.bmp] [scale] [octaves] [persistence]\n";
|
||||
std::cout << "Defaults: seed=12345, output=perlin_noise.bmp, "
|
||||
"scale=0.02, octaves=1, persistence=0.5\n";
|
||||
std::cout << "Examples:\n";
|
||||
std::cout << " " << argv[0] << " 54321 noise1.bmp 0.01\n";
|
||||
std::cout << " " << argv[0] << " 12345 octave_noise.bmp 0.02 4 0.5\n";
|
||||
if (argc > 6) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if (scale <= 0) {
|
||||
std::cerr << "Error: Scale must be positive\n";
|
||||
return 1;
|
||||
}
|
||||
if (octaves < 1 || octaves > 10) {
|
||||
std::cerr << "Error: Octaves must be between 1 and 10\n";
|
||||
return 1;
|
||||
}
|
||||
if (persistence <= 0 || persistence > 1) {
|
||||
std::cerr << "Error: Persistence must be between 0 and 1\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Generating 256x256 Perlin noise visualization..."
|
||||
<< std::endl;
|
||||
std::cout << "Parameters: seed=" << seed << ", scale=" << scale
|
||||
<< ", octaves=" << octaves << ", persistence=" << persistence
|
||||
<< std::endl;
|
||||
|
||||
// Generate the noise visualization
|
||||
generate_noise_bmp(output_filename, 256, scale, seed, octaves, persistence);
|
||||
|
||||
return 0;
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
namespace istd {
|
||||
|
||||
struct GenerationConfig {
|
||||
std::uint64_t seed = 0; // Seed for random generation
|
||||
Seed seed;
|
||||
|
||||
// Noise parameters
|
||||
double temperature_scale = 0.005; // Scale for temperature noise
|
||||
@ -33,13 +33,6 @@ class TerrainGenerator {
|
||||
private:
|
||||
GenerationConfig config_;
|
||||
|
||||
// Just some random numbers to mask the seeds
|
||||
static constexpr std::uint64_t base_seed_mask = 0x06'a9'cb'b1'b3'96'f3'50;
|
||||
static constexpr std::uint64_t temperature_seed_mask
|
||||
= 0x79'c8'a7'a1'09'99'd0'e3;
|
||||
static constexpr std::uint64_t humidity_seed_mask
|
||||
= 0x5e'10'be'e4'd2'6f'34'c2;
|
||||
|
||||
UniformPerlinNoise
|
||||
base_noise_; // For base terrain generation (uniform distribution)
|
||||
PerlinNoise temperature_noise_; // For temperature
|
||||
|
@ -1,6 +1,7 @@
|
||||
#ifndef ISTD_TILEMAP_NOISE_H
|
||||
#define ISTD_TILEMAP_NOISE_H
|
||||
|
||||
#include "xoroshiro.h"
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
@ -18,9 +19,11 @@ private:
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a PerlinNoise generator with the given seed
|
||||
* @param seed Random seed for noise generation
|
||||
* @param rng Random number generator for noise
|
||||
*/
|
||||
explicit PerlinNoise(std::uint64_t seed = 0);
|
||||
explicit PerlinNoise(Xoroshiro128PP rng);
|
||||
|
||||
PerlinNoise() = default;
|
||||
|
||||
/**
|
||||
* @brief Generate 2D Perlin noise value at the given coordinates
|
||||
@ -39,7 +42,7 @@ public:
|
||||
* @return Noise value between 0.0 and 1.0
|
||||
*/
|
||||
double octave_noise(
|
||||
double x, double y, int octaves = 4, double persistence = 0.5
|
||||
double x, double y, int octaves, double persistence
|
||||
) const;
|
||||
};
|
||||
|
||||
@ -53,6 +56,7 @@ public:
|
||||
class UniformPerlinNoise {
|
||||
private:
|
||||
PerlinNoise noise_;
|
||||
Xoroshiro128PP calibrate_rng_;
|
||||
std::vector<double> cdf_values_; // Sorted noise values for CDF
|
||||
bool is_calibrated_;
|
||||
|
||||
@ -66,7 +70,9 @@ public:
|
||||
* @brief Construct a UniformPerlinNoise generator
|
||||
* @param seed Random seed for noise generation
|
||||
*/
|
||||
explicit UniformPerlinNoise(std::uint64_t seed = 0);
|
||||
explicit UniformPerlinNoise(Xoroshiro128PP rng);
|
||||
|
||||
UniformPerlinNoise() = default;
|
||||
|
||||
/**
|
||||
* @brief Calibrate the noise distribution by sampling
|
||||
@ -76,8 +82,7 @@ public:
|
||||
* @param sample_size Number of samples to use for CDF (default: 10000)
|
||||
*/
|
||||
void calibrate(
|
||||
double scale, int octaves = 1, double persistence = 0.5,
|
||||
int sample_size = 10000
|
||||
double scale, int octaves, double persistence, int sample_size = 10000
|
||||
);
|
||||
|
||||
/**
|
||||
|
70
tilemap/include/xoroshiro.h
Normal file
70
tilemap/include/xoroshiro.h
Normal file
@ -0,0 +1,70 @@
|
||||
#ifndef ISTD_TILEMAP_XOROSHIRO_H
|
||||
#define ISTD_TILEMAP_XOROSHIRO_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace istd {
|
||||
|
||||
struct Seed {
|
||||
std::uint64_t s[2];
|
||||
|
||||
static Seed from_string(const char *str);
|
||||
static Seed device_random();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Xoroshiro128++ random number generator.
|
||||
* @note This generator is not thread-safe and should not be used concurrently.
|
||||
* It is designed for high performance and provides a good quality of
|
||||
* randomness.
|
||||
* @link https://prng.di.unimi.it/xoroshiro128plusplus.c @endlink
|
||||
*/
|
||||
class Xoroshiro128PP {
|
||||
public:
|
||||
Xoroshiro128PP() = default;
|
||||
Xoroshiro128PP(Seed seed);
|
||||
|
||||
// Adaption for STL RandomEngine named requirements
|
||||
using result_type = std::uint64_t;
|
||||
static constexpr result_type min() {
|
||||
return std::numeric_limits<result_type>::min();
|
||||
}
|
||||
static constexpr result_type max() {
|
||||
return std::numeric_limits<result_type>::max();
|
||||
}
|
||||
result_type operator()(); // Equivalent to next(), for STL compatibility
|
||||
|
||||
/**
|
||||
* @brief Generates a random 64-bit unsigned integer.
|
||||
* @return A random 64-bit unsigned integer.
|
||||
* @note This function will modify the internal state of the generator.
|
||||
* It is not thread-safe and should not be called concurrently.
|
||||
* The generated number is uniformly distributed.
|
||||
*/
|
||||
std::uint64_t next();
|
||||
|
||||
/**
|
||||
* @brief Equivalent to 2^64 calls to next(), returns a new generator state.
|
||||
* @return A new Xoroshiro128PP generator state.
|
||||
* @note It can be used to generate two 2^64 non-overlapping sequences of
|
||||
* random numbers for parallel processing.
|
||||
*/
|
||||
Xoroshiro128PP jump_64() const;
|
||||
|
||||
/**
|
||||
* @brief Equivalent to 2^96 calls to next(), returns a new generator state.
|
||||
* @return A new Xoroshiro128PP generator state.
|
||||
* @note It can be used to generate 2^32 starting points, from each of which
|
||||
* `jump_64()` will generate 2^32 non-overlapping subsequences for parallel
|
||||
* distributed computations.
|
||||
*/
|
||||
Xoroshiro128PP jump_96() const;
|
||||
|
||||
private:
|
||||
Seed seed;
|
||||
};
|
||||
|
||||
} // namespace istd
|
||||
|
||||
#endif
|
@ -7,10 +7,14 @@
|
||||
namespace istd {
|
||||
|
||||
TerrainGenerator::TerrainGenerator(const GenerationConfig &config)
|
||||
: config_(config)
|
||||
, base_noise_(config.seed ^ base_seed_mask)
|
||||
, temperature_noise_(config.seed ^ temperature_seed_mask)
|
||||
, humidity_noise_(config.seed ^ humidity_seed_mask) {
|
||||
: config_(config) {
|
||||
Xoroshiro128PP rng{config.seed};
|
||||
base_noise_ = UniformPerlinNoise(rng);
|
||||
rng = rng.jump_96();
|
||||
temperature_noise_ = PerlinNoise(rng);
|
||||
rng = rng.jump_96();
|
||||
humidity_noise_ = PerlinNoise(rng);
|
||||
|
||||
// Calibrate the uniform base noise with the same parameters that will be
|
||||
// used for generation
|
||||
base_noise_.calibrate(
|
||||
|
@ -7,14 +7,13 @@
|
||||
|
||||
namespace istd {
|
||||
|
||||
PerlinNoise::PerlinNoise(std::uint64_t seed) {
|
||||
PerlinNoise::PerlinNoise(Xoroshiro128PP rng) {
|
||||
// Initialize permutation array with values 0-255
|
||||
permutation_.resize(256);
|
||||
std::iota(permutation_.begin(), permutation_.end(), 0);
|
||||
|
||||
// Shuffle using the provided seed
|
||||
std::default_random_engine generator(seed);
|
||||
std::shuffle(permutation_.begin(), permutation_.end(), generator);
|
||||
std::shuffle(permutation_.begin(), permutation_.end(), rng);
|
||||
|
||||
// Duplicate the permutation to avoid overflow
|
||||
permutation_.insert(
|
||||
@ -92,12 +91,8 @@ double PerlinNoise::octave_noise(
|
||||
return value / max_value;
|
||||
}
|
||||
|
||||
UniformPerlinNoise::UniformPerlinNoise(std::uint64_t seed)
|
||||
: noise_(seed)
|
||||
, is_calibrated_(false)
|
||||
, scale_(0.0)
|
||||
, octaves_(1)
|
||||
, persistence_(0.5) {}
|
||||
UniformPerlinNoise::UniformPerlinNoise(Xoroshiro128PP rng)
|
||||
: noise_(rng), calibrate_rng_(rng), is_calibrated_(false) {}
|
||||
|
||||
void UniformPerlinNoise::calibrate(
|
||||
double scale, int octaves, double persistence, int sample_size
|
||||
@ -111,14 +106,13 @@ void UniformPerlinNoise::calibrate(
|
||||
cdf_values_.reserve(sample_size);
|
||||
|
||||
// Sample noise values across a reasonable range
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
Xoroshiro128PP rng = calibrate_rng_;
|
||||
std::uniform_real_distribution<double> coord_dist(0.0, 1000.0);
|
||||
|
||||
// Collect samples
|
||||
for (int i = 0; i < sample_size; ++i) {
|
||||
double x = coord_dist(gen);
|
||||
double y = coord_dist(gen);
|
||||
double x = coord_dist(rng);
|
||||
double y = coord_dist(rng);
|
||||
|
||||
double noise_value;
|
||||
if (octaves_ == 1) {
|
||||
|
89
tilemap/src/xoroshiro.cpp
Normal file
89
tilemap/src/xoroshiro.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
#include "xoroshiro.h"
|
||||
#include <bit>
|
||||
#include <initializer_list>
|
||||
#include <random>
|
||||
|
||||
namespace istd {
|
||||
|
||||
Seed Seed::from_string(const char *str) {
|
||||
Seed res{0xfcc3a80ff25bae88, 0x78ac504431a5b8e6};
|
||||
constexpr std::uint64_t p1 = 0xb2209ed48ff3455b, p2 = 0x9f9a70d28f55f29f;
|
||||
for (int i = 0; str[i] != 0; ++i) {
|
||||
std::uint8_t c = str[i];
|
||||
res.s[0] ^= c;
|
||||
res.s[1] ^= c;
|
||||
res.s[0] *= p1;
|
||||
res.s[1] *= p2;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Seed Seed::device_random() {
|
||||
Seed seed;
|
||||
std::random_device rd;
|
||||
std::uniform_int_distribution<std::uint64_t> dist(
|
||||
std::numeric_limits<std::uint64_t>::min(),
|
||||
std::numeric_limits<std::uint64_t>::max()
|
||||
);
|
||||
seed.s[0] = dist(rd);
|
||||
seed.s[1] = dist(rd);
|
||||
return seed;
|
||||
}
|
||||
|
||||
Xoroshiro128PP::Xoroshiro128PP(Seed seed): seed(seed) {}
|
||||
|
||||
Xoroshiro128PP::result_type Xoroshiro128PP::operator()() {
|
||||
return next();
|
||||
}
|
||||
|
||||
std::uint64_t Xoroshiro128PP::next() {
|
||||
std::uint64_t s0 = seed.s[0];
|
||||
std::uint64_t s1 = seed.s[1];
|
||||
std::uint64_t result = std::rotl(s0 + s1, 17) + s0;
|
||||
|
||||
s1 ^= s0;
|
||||
seed.s[0] = std::rotl(s0, 49) ^ s1 ^ (s1 << 21);
|
||||
seed.s[1] = std::rotl(s1, 28);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Xoroshiro128PP Xoroshiro128PP::jump_64() const {
|
||||
constexpr std::uint64_t JUMP_64[]
|
||||
= {0x180ec6d33cfd0aba, 0xd5a61266f0c9392c};
|
||||
|
||||
Xoroshiro128PP res{seed};
|
||||
|
||||
for (int i : {0, 1}) {
|
||||
for (int b = 0; b < 64; ++b) {
|
||||
if (JUMP_64[i] & (1ULL << b)) {
|
||||
res.seed.s[0] ^= seed.s[0];
|
||||
res.seed.s[1] ^= seed.s[1];
|
||||
}
|
||||
res.next();
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Xoroshiro128PP Xoroshiro128PP::jump_96() const {
|
||||
constexpr std::uint64_t JUMP_96[]
|
||||
= {0x360fd5f2cf8d5d99, 0x9c6e6877736c46e3};
|
||||
|
||||
Xoroshiro128PP res{seed};
|
||||
|
||||
for (int i : {0, 1}) {
|
||||
for (int b = 0; b < 64; ++b) {
|
||||
if (JUMP_96[i] & (1ULL << b)) {
|
||||
res.seed.s[0] ^= seed.s[0];
|
||||
res.seed.s[1] ^= seed.s[1];
|
||||
}
|
||||
res.next();
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace istd
|
Loading…
x
Reference in New Issue
Block a user