feat: Xoroshiro128++ Random Engine

Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
方而静 2025-08-02 00:05:12 +08:00
parent ebdbf62a19
commit 06a60b1a19
Signed by: szTom
GPG Key ID: 072D999D60C6473C
12 changed files with 289 additions and 336 deletions

View File

@ -7,6 +7,7 @@ set(ISTD_TILEMAP_SRC
src/noise.cpp src/noise.cpp
src/biome.cpp src/biome.cpp
src/chunk.cpp src/chunk.cpp
src/xoroshiro.cpp
) )
# Create the tilemap library # Create the tilemap library

View File

@ -114,7 +114,7 @@ Configuration parameters for terrain generation.
```cpp ```cpp
struct GenerationConfig { struct GenerationConfig {
std::uint64_t seed = 0; // Seed for random generation Seed seed; // 128-bit seed for random generation
// Temperature noise parameters // Temperature noise parameters
double temperature_scale = 0.005; // Scale for temperature noise double temperature_scale = 0.005; // Scale for temperature noise
@ -135,7 +135,7 @@ struct GenerationConfig {
**Parameters:** **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_scale`: Controls the scale/frequency of temperature variation across the map
- `temperature_octaves`: Number of noise octaves for temperature (more octaves = more detail) - `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) - `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); 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 ## Noise System
### PerlinNoise ### PerlinNoise
Standard Perlin noise implementation for procedural generation. Standard Perlin noise implementation using Xoroshiro128++ for procedural generation.
```cpp ```cpp
class PerlinNoise { class PerlinNoise {
public: public:
explicit PerlinNoise(std::uint64_t seed = 0); explicit PerlinNoise(Xoroshiro128PP rng);
double noise(double x, double y) const; double noise(double x, double y) const;
double octave_noise(double x, double y, int octaves = 4, double persistence = 0.5) const; double octave_noise(double x, double y, int octaves = 4, double persistence = 0.5) const;
}; };
@ -183,12 +231,12 @@ public:
### UniformPerlinNoise ### UniformPerlinNoise
Advanced noise generator that provides uniform distribution mapping. Advanced noise generator using Xoroshiro128++ that provides uniform distribution mapping.
```cpp ```cpp
class UniformPerlinNoise { class UniformPerlinNoise {
public: 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); void calibrate(double scale, int octaves = 1, double persistence = 0.5, int sample_size = 10000);
double uniform_noise(double x, double y) const; double uniform_noise(double x, double y) const;
bool is_calibrated() const; bool is_calibrated() const;
@ -200,6 +248,7 @@ public:
- **Uniform Mapping**: Maps raw Perlin values to uniform [0,1] distribution - **Uniform Mapping**: Maps raw Perlin values to uniform [0,1] distribution
- **Balanced Output**: Ensures even distribution across all value ranges - **Balanced Output**: Ensures even distribution across all value ranges
- **Automatic Use**: TerrainGenerator uses this internally for balanced terrain - **Automatic Use**: TerrainGenerator uses this internally for balanced terrain
- **Xoroshiro128++ Backend**: Uses high-quality random number generation
## Biome System ## Biome System
@ -257,7 +306,7 @@ istd::TileMap tilemap(4);
// Configure generation // Configure generation
istd::GenerationConfig config; istd::GenerationConfig config;
config.seed = 12345; config.seed = istd::Seed::from_string("hello_world"); // 128-bit seed from string
// Temperature noise settings // Temperature noise settings
config.temperature_scale = 0.005; 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 ### Accessing Individual Tiles
```cpp ```cpp
@ -323,32 +391,44 @@ std::cout << "Biome: " << props.name << std::endl;
- Each chunk contains 4,096 tiles (64×64) - Each chunk contains 4,096 tiles (64×64)
- Sub-chunks provide efficient biome management - Sub-chunks provide efficient biome management
- Tiles are packed into 1 byte each for memory efficiency - 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 - Noise calibration is performed once during generator construction
- 128-bit seeds provide excellent randomness and reproducibility
## Noise Distribution ## 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 ### 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. 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 1. **Xoroshiro128++ RNG**: High-quality pseudo-random number generator with:
2. **Builds a CDF** (Cumulative Distribution Function) from the samples - **Long Period**: 2^128 - 1 sequence length before repetition
3. **Maps raw noise values** to uniform [0,1] distribution using quantiles - **High Performance**: Optimized for speed and memory efficiency
4. **Ensures balanced** terrain type distribution according to biome properties - **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 ### Usage in Terrain Generation
```cpp ```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); TerrainGenerator generator(config);
generator.generate_map(tilemap); // Uses calibrated uniform noise internally generator.generate_map(tilemap); // Uses calibrated uniform noise with Xoroshiro128++
``` ```
## Thread Safety ## Thread Safety

View File

@ -7,13 +7,3 @@ cmake_minimum_required(VERSION 3.27)
add_executable(biome_demo biome_demo.cpp) add_executable(biome_demo biome_demo.cpp)
target_link_libraries(biome_demo PRIVATE istd_tilemap) target_link_libraries(biome_demo PRIVATE istd_tilemap)
target_include_directories(biome_demo PRIVATE ../include) 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)

View File

@ -3,6 +3,7 @@
#include "tile.h" #include "tile.h"
#include "tilemap.h" #include "tilemap.h"
#include <cstdlib> #include <cstdlib>
#include <format>
#include <iomanip> #include <iomanip>
#include <iostream> #include <iostream>
#include <string> #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)) { if (!bmp.save(filename)) {
std::cerr << "Error: Could not save BMP file: " << filename std::cerr << "Error: Could not save BMP file: " << filename
<< std::endl; << std::endl;
@ -144,8 +126,8 @@ int main(int argc, char *argv[]) {
return 1; return 1;
} }
std::uint64_t seed istd::Seed seed
= argc >= 2 ? std::strtoull(argv[1], nullptr, 10) : 541234; = istd::Seed::from_string(argc >= 2 ? argv[1] : "hello_world");
std::string output_filename = argc >= 3 ? argv[2] : "output.bmp"; std::string output_filename = argc >= 3 ? argv[2] : "output.bmp";
int chunks_per_side = 4; // Default value 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 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 // Create tilemap with specified size
istd::TileMap tilemap(chunks_per_side); istd::TileMap tilemap(chunks_per_side);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -12,7 +12,7 @@
namespace istd { namespace istd {
struct GenerationConfig { struct GenerationConfig {
std::uint64_t seed = 0; // Seed for random generation Seed seed;
// Noise parameters // Noise parameters
double temperature_scale = 0.005; // Scale for temperature noise double temperature_scale = 0.005; // Scale for temperature noise
@ -33,13 +33,6 @@ class TerrainGenerator {
private: private:
GenerationConfig config_; 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 UniformPerlinNoise
base_noise_; // For base terrain generation (uniform distribution) base_noise_; // For base terrain generation (uniform distribution)
PerlinNoise temperature_noise_; // For temperature PerlinNoise temperature_noise_; // For temperature

View File

@ -1,6 +1,7 @@
#ifndef ISTD_TILEMAP_NOISE_H #ifndef ISTD_TILEMAP_NOISE_H
#define ISTD_TILEMAP_NOISE_H #define ISTD_TILEMAP_NOISE_H
#include "xoroshiro.h"
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
@ -18,9 +19,11 @@ private:
public: public:
/** /**
* @brief Construct a PerlinNoise generator with the given seed * @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 * @brief Generate 2D Perlin noise value at the given coordinates
@ -39,7 +42,7 @@ public:
* @return Noise value between 0.0 and 1.0 * @return Noise value between 0.0 and 1.0
*/ */
double octave_noise( double octave_noise(
double x, double y, int octaves = 4, double persistence = 0.5 double x, double y, int octaves, double persistence
) const; ) const;
}; };
@ -53,6 +56,7 @@ public:
class UniformPerlinNoise { class UniformPerlinNoise {
private: private:
PerlinNoise noise_; PerlinNoise noise_;
Xoroshiro128PP calibrate_rng_;
std::vector<double> cdf_values_; // Sorted noise values for CDF std::vector<double> cdf_values_; // Sorted noise values for CDF
bool is_calibrated_; bool is_calibrated_;
@ -66,7 +70,9 @@ public:
* @brief Construct a UniformPerlinNoise generator * @brief Construct a UniformPerlinNoise generator
* @param seed Random seed for noise generation * @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 * @brief Calibrate the noise distribution by sampling
@ -76,8 +82,7 @@ public:
* @param sample_size Number of samples to use for CDF (default: 10000) * @param sample_size Number of samples to use for CDF (default: 10000)
*/ */
void calibrate( void calibrate(
double scale, int octaves = 1, double persistence = 0.5, double scale, int octaves, double persistence, int sample_size = 10000
int sample_size = 10000
); );
/** /**

View 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

View File

@ -7,10 +7,14 @@
namespace istd { namespace istd {
TerrainGenerator::TerrainGenerator(const GenerationConfig &config) TerrainGenerator::TerrainGenerator(const GenerationConfig &config)
: config_(config) : config_(config) {
, base_noise_(config.seed ^ base_seed_mask) Xoroshiro128PP rng{config.seed};
, temperature_noise_(config.seed ^ temperature_seed_mask) base_noise_ = UniformPerlinNoise(rng);
, humidity_noise_(config.seed ^ humidity_seed_mask) { 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 // Calibrate the uniform base noise with the same parameters that will be
// used for generation // used for generation
base_noise_.calibrate( base_noise_.calibrate(

View File

@ -7,14 +7,13 @@
namespace istd { namespace istd {
PerlinNoise::PerlinNoise(std::uint64_t seed) { PerlinNoise::PerlinNoise(Xoroshiro128PP rng) {
// Initialize permutation array with values 0-255 // Initialize permutation array with values 0-255
permutation_.resize(256); permutation_.resize(256);
std::iota(permutation_.begin(), permutation_.end(), 0); std::iota(permutation_.begin(), permutation_.end(), 0);
// Shuffle using the provided seed // Shuffle using the provided seed
std::default_random_engine generator(seed); std::shuffle(permutation_.begin(), permutation_.end(), rng);
std::shuffle(permutation_.begin(), permutation_.end(), generator);
// Duplicate the permutation to avoid overflow // Duplicate the permutation to avoid overflow
permutation_.insert( permutation_.insert(
@ -92,12 +91,8 @@ double PerlinNoise::octave_noise(
return value / max_value; return value / max_value;
} }
UniformPerlinNoise::UniformPerlinNoise(std::uint64_t seed) UniformPerlinNoise::UniformPerlinNoise(Xoroshiro128PP rng)
: noise_(seed) : noise_(rng), calibrate_rng_(rng), is_calibrated_(false) {}
, is_calibrated_(false)
, scale_(0.0)
, octaves_(1)
, persistence_(0.5) {}
void UniformPerlinNoise::calibrate( void UniformPerlinNoise::calibrate(
double scale, int octaves, double persistence, int sample_size double scale, int octaves, double persistence, int sample_size
@ -111,14 +106,13 @@ void UniformPerlinNoise::calibrate(
cdf_values_.reserve(sample_size); cdf_values_.reserve(sample_size);
// Sample noise values across a reasonable range // Sample noise values across a reasonable range
std::random_device rd; Xoroshiro128PP rng = calibrate_rng_;
std::mt19937 gen(rd());
std::uniform_real_distribution<double> coord_dist(0.0, 1000.0); std::uniform_real_distribution<double> coord_dist(0.0, 1000.0);
// Collect samples // Collect samples
for (int i = 0; i < sample_size; ++i) { for (int i = 0; i < sample_size; ++i) {
double x = coord_dist(gen); double x = coord_dist(rng);
double y = coord_dist(gen); double y = coord_dist(rng);
double noise_value; double noise_value;
if (octaves_ == 1) { if (octaves_ == 1) {

89
tilemap/src/xoroshiro.cpp Normal file
View 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