diff --git a/tilemap/CMakeLists.txt b/tilemap/CMakeLists.txt index ab54fca..1935df6 100644 --- a/tilemap/CMakeLists.txt +++ b/tilemap/CMakeLists.txt @@ -7,6 +7,7 @@ set(ISTD_TILEMAP_SRC src/noise.cpp src/biome.cpp src/chunk.cpp + src/xoroshiro.cpp ) # Create the tilemap library diff --git a/tilemap/docs/api.md b/tilemap/docs/api.md index e72c7c7..bcfd3fb 100644 --- a/tilemap/docs/api.md +++ b/tilemap/docs/api.md @@ -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 diff --git a/tilemap/examples/CMakeLists.txt b/tilemap/examples/CMakeLists.txt index fb6f688..85a6c5f 100644 --- a/tilemap/examples/CMakeLists.txt +++ b/tilemap/examples/CMakeLists.txt @@ -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) diff --git a/tilemap/examples/biome_demo.cpp b/tilemap/examples/biome_demo.cpp index 234104e..c7b657c 100644 --- a/tilemap/examples/biome_demo.cpp +++ b/tilemap/examples/biome_demo.cpp @@ -3,6 +3,7 @@ #include "tile.h" #include "tilemap.h" #include +#include #include #include #include @@ -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); diff --git a/tilemap/examples/noise_comparison.cpp b/tilemap/examples/noise_comparison.cpp deleted file mode 100644 index f18501f..0000000 --- a/tilemap/examples/noise_comparison.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include "bmp.h" -#include "noise.h" -#include -#include -#include -#include -#include - -// 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 raw_histogram(10, 0); - std::vector 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(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(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; -} diff --git a/tilemap/examples/perlin_demo.cpp b/tilemap/examples/perlin_demo.cpp deleted file mode 100644 index de692e6..0000000 --- a/tilemap/examples/perlin_demo.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "bmp.h" -#include "noise.h" -#include -#include -#include -#include - -// 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; -} diff --git a/tilemap/include/generation.h b/tilemap/include/generation.h index 949e37c..7dab490 100644 --- a/tilemap/include/generation.h +++ b/tilemap/include/generation.h @@ -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 diff --git a/tilemap/include/noise.h b/tilemap/include/noise.h index c4b2b8f..c2a8ae9 100644 --- a/tilemap/include/noise.h +++ b/tilemap/include/noise.h @@ -1,6 +1,7 @@ #ifndef ISTD_TILEMAP_NOISE_H #define ISTD_TILEMAP_NOISE_H +#include "xoroshiro.h" #include #include @@ -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 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 ); /** diff --git a/tilemap/include/xoroshiro.h b/tilemap/include/xoroshiro.h new file mode 100644 index 0000000..cabdacf --- /dev/null +++ b/tilemap/include/xoroshiro.h @@ -0,0 +1,70 @@ +#ifndef ISTD_TILEMAP_XOROSHIRO_H +#define ISTD_TILEMAP_XOROSHIRO_H + +#include +#include + +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::min(); + } + static constexpr result_type max() { + return std::numeric_limits::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 diff --git a/tilemap/src/generation.cpp b/tilemap/src/generation.cpp index 93bd245..d9fd6dc 100644 --- a/tilemap/src/generation.cpp +++ b/tilemap/src/generation.cpp @@ -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( diff --git a/tilemap/src/noise.cpp b/tilemap/src/noise.cpp index a668715..b9c8516 100644 --- a/tilemap/src/noise.cpp +++ b/tilemap/src/noise.cpp @@ -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 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) { diff --git a/tilemap/src/xoroshiro.cpp b/tilemap/src/xoroshiro.cpp new file mode 100644 index 0000000..2a59201 --- /dev/null +++ b/tilemap/src/xoroshiro.cpp @@ -0,0 +1,89 @@ +#include "xoroshiro.h" +#include +#include +#include + +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 dist( + std::numeric_limits::min(), + std::numeric_limits::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 \ No newline at end of file