From 2b5f62da0836493b73d6216d2d999c6ee65fb00d Mon Sep 17 00:00:00 2001 From: szdytom Date: Fri, 1 Aug 2025 19:41:11 +0800 Subject: [PATCH] feat: Implement uniform distribution mapping for Perlin noise and add comparison demo Signed-off-by: szdytom --- tilemap/docs/api.md | 71 +++++- tilemap/examples/CMakeLists.txt | 5 + tilemap/examples/noise_comparison.cpp | 203 ++++++++++++++++ tilemap/examples/perlin_demo.cpp | 337 ++++++++++++++------------ tilemap/include/generation.h | 3 +- tilemap/include/noise.h | 62 +++++ tilemap/src/generation.cpp | 14 +- tilemap/src/noise.cpp | 81 +++++++ 8 files changed, 609 insertions(+), 167 deletions(-) create mode 100644 tilemap/examples/noise_comparison.cpp diff --git a/tilemap/docs/api.md b/tilemap/docs/api.md index 3ab1f52..6022c3c 100644 --- a/tilemap/docs/api.md +++ b/tilemap/docs/api.md @@ -120,6 +120,41 @@ Convenience function for map generation. void map_generate(TileMap& tilemap, const GenerationConfig& config); ``` +## Noise System + +### PerlinNoise + +Standard Perlin noise implementation for procedural generation. + +```cpp +class PerlinNoise { +public: + explicit PerlinNoise(std::uint64_t seed = 0); + double noise(double x, double y) const; + double octave_noise(double x, double y, int octaves = 4, double persistence = 0.5) const; +}; +``` + +### UniformPerlinNoise + +Advanced noise generator that provides uniform distribution mapping. + +```cpp +class UniformPerlinNoise { +public: + explicit UniformPerlinNoise(std::uint64_t seed = 0); + 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; +}; +``` + +**Key Features:** +- **Calibration**: Samples noise distribution to build CDF +- **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 + ## Biome System ### BiomeType @@ -147,10 +182,10 @@ Properties that control terrain generation for each biome. ```cpp struct BiomeProperties { std::string_view name; // Biome name - double water_ratio; // Water generation ratio - double ice_ratio; // Ice generation ratio - double sand_ratio; // Sand generation ratio - double land_ratio; // Land generation ratio + double water_ratio; // Water generation ratio + double ice_ratio; // Ice generation ratio + double sand_ratio; // Sand generation ratio + double land_ratio; // Land generation ratio int base_octaves = 3; // Noise octaves double base_persistence = 0.5; // Noise persistence }; @@ -238,7 +273,33 @@ std::cout << "Biome: " << props.name << std::endl; - A 4×4 chunk map contains 65,536 total tiles - Sub-chunks provide efficient biome management (16×16 tile regions) - Tiles are packed into 1 byte each for memory efficiency -- Generation uses Perlin noise for natural-looking terrain +- Generation uses Perlin noise with uniform distribution mapping for balanced terrain +- Noise calibration is performed once during generator construction + +## Noise Distribution + +The library uses an advanced noise system 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 + +The `UniformPerlinNoise` class: + +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 + +### Usage in Terrain Generation + +```cpp +// The terrain generator automatically uses uniform noise +TerrainGenerator generator(config); +generator.generate_map(tilemap); // Uses calibrated uniform noise internally +``` ## Thread Safety diff --git a/tilemap/examples/CMakeLists.txt b/tilemap/examples/CMakeLists.txt index 71b96c6..fb6f688 100644 --- a/tilemap/examples/CMakeLists.txt +++ b/tilemap/examples/CMakeLists.txt @@ -12,3 +12,8 @@ target_include_directories(biome_demo PRIVATE ../include) 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/noise_comparison.cpp b/tilemap/examples/noise_comparison.cpp new file mode 100644 index 0000000..3ec65b4 --- /dev/null +++ b/tilemap/examples/noise_comparison.cpp @@ -0,0 +1,203 @@ +#include "noise.h" +#include +#include +#include +#include +#include +#include + +// Convert a noise value [0,1] to a grayscale color +std::string noise_to_grayscale(double noise_value) { + // Clamp to [0,1] range + noise_value = std::max(0.0, std::min(1.0, noise_value)); + + // Convert to 0-255 range + int gray = static_cast(noise_value * 255); + + // Convert to hex color + std::ostringstream oss; + oss << "#" << std::hex << std::setfill('0') << std::setw(2) << gray + << std::setw(2) << gray << std::setw(2) << gray; + return oss.str(); +} + +// Generate comparison SVG showing both raw and uniform noise side by side +void generate_comparison_svg( + const std::string &filename, int size, double scale, std::uint64_t seed, + int octaves = 3, double persistence = 0.5 +) { + std::ofstream file(filename); + if (!file.is_open()) { + std::cerr << "Error: Could not open output file: " << filename + << std::endl; + return; + } + + // Create noise generators + istd::PerlinNoise raw_noise(seed); + istd::UniformPerlinNoise uniform_noise(seed); + + // Calibrate uniform noise + uniform_noise.calibrate(scale, octaves, persistence); + + const int pixel_size = 2; + const int panel_width = size * pixel_size; + const int svg_width = panel_width * 2 + 20; // Space for two panels + gap + const int svg_height = size * pixel_size; + + // SVG header + file << "\n"; + file << "\n"; + file << "Noise Comparison: Raw vs Uniform (Scale: " << scale + << ", Octaves: " << octaves << ", Seed: " << seed << ")\n"; + + // 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]++; + + std::string color = noise_to_grayscale(noise_value); + + int svg_x = x * pixel_size; + int svg_y = y * pixel_size; + + file << "\n"; + } + } + + // Generate right panel (uniform noise) + int panel_offset = panel_width + 20; + 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]++; + + std::string color = noise_to_grayscale(noise_value); + + int svg_x = panel_offset + x * pixel_size; + int svg_y = y * pixel_size; + + file << "\n"; + } + } + + // Add labels + file << "Raw Perlin Noise\n"; + file << "Uniform Distribution\n"; + + // Add parameter info + file << "\n"; + file << "\n"; + file << "Parameters\n"; + file << "Size: " + << size << "x" << size << "\n"; + file << "Scale: " + << scale << "\n"; + file << "Octaves: " + << octaves << "\n"; + file << "Seed: " + << seed << "\n"; + file << "\n"; + + file << "\n"; + file.close(); + + std::cout << "Noise comparison SVG 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.svg"; + 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.svg] [scale] [octaves] [persistence]\n"; + std::cout << "Defaults: seed=12345, output=noise_comparison.svg, " + "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_svg( + output_filename, 256, scale, seed, octaves, persistence + ); + + return 0; +} diff --git a/tilemap/examples/perlin_demo.cpp b/tilemap/examples/perlin_demo.cpp index 9a7fd00..109192a 100644 --- a/tilemap/examples/perlin_demo.cpp +++ b/tilemap/examples/perlin_demo.cpp @@ -1,167 +1,194 @@ #include "noise.h" -#include -#include -#include #include +#include #include +#include +#include // Convert a noise value [0,1] to a grayscale color std::string noise_to_grayscale(double noise_value) { - // Clamp to [0,1] range - noise_value = std::max(0.0, std::min(1.0, noise_value)); - - // Convert to 0-255 range - int gray = static_cast(noise_value * 255); - - // Convert to hex color - std::ostringstream oss; - oss << "#" << std::hex << std::setfill('0') << std::setw(2) << gray - << std::setw(2) << gray << std::setw(2) << gray; - return oss.str(); + // Clamp to [0,1] range + noise_value = std::max(0.0, std::min(1.0, noise_value)); + + // Convert to 0-255 range + int gray = static_cast(noise_value * 255); + + // Convert to hex color + std::ostringstream oss; + oss << "#" << std::hex << std::setfill('0') << std::setw(2) << gray + << std::setw(2) << gray << std::setw(2) << gray; + return oss.str(); } // Generate SVG visualization of Perlin noise -void generate_noise_svg(const std::string& filename, int size, double scale, - std::uint64_t seed, int octaves = 1, double persistence = 0.5) { - std::ofstream file(filename); - if (!file.is_open()) { - std::cerr << "Error: Could not open output file: " << filename << std::endl; - return; - } - - // Create noise generator - istd::PerlinNoise noise(seed); - - const int pixel_size = 2; // Size of each pixel in SVG units - const int svg_size = size * pixel_size; - - // SVG header - file << "\n"; - file << "\n"; - file << "Perlin Noise Visualization (Scale: " << scale - << ", Octaves: " << octaves << ", Seed: " << seed << ")\n"; - - // Generate noise values and create rectangles - 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); - - std::string color = noise_to_grayscale(noise_value); - - int svg_x = x * pixel_size; - int svg_y = y * pixel_size; - - file << "\n"; - } - } - - // Add information text - file << "\n"; - - // Add parameter info text overlay - file << "\n"; - file << "\n"; - file << "Perlin Noise Parameters\n"; - file << "Size: " << size << "x" << size << "\n"; - file << "Scale: " << scale << "\n"; - file << "Seed: " << seed << "\n"; - file << "Octaves: " << octaves << "\n"; - file << "Range: [" - << std::fixed << std::setprecision(3) << min_value << ", " << max_value << "]\n"; - file << "\n"; - - // Add grayscale legend - file << "\n"; - file << "Value\n"; - for (int i = 0; i <= 10; ++i) { - double value = i / 10.0; - std::string color = noise_to_grayscale(value); - int y_pos = 20 + i * 15; - file << "\n"; - file << "" - << std::fixed << std::setprecision(1) << value << "\n"; - } - file << "\n"; - - file << "\n"; - file.close(); - - std::cout << "Perlin noise SVG 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; +void generate_noise_svg( + const std::string &filename, int size, double scale, std::uint64_t seed, + int octaves = 1, double persistence = 0.5 +) { + std::ofstream file(filename); + if (!file.is_open()) { + std::cerr << "Error: Could not open output file: " << filename + << std::endl; + return; + } + + // Create noise generator + istd::PerlinNoise noise(seed); + + const int pixel_size = 2; // Size of each pixel in SVG units + const int svg_size = size * pixel_size; + + // SVG header + file << "\n"; + file << "\n"; + file << "Perlin Noise Visualization (Scale: " << scale + << ", Octaves: " << octaves << ", Seed: " << seed << ")\n"; + + // Generate noise values and create rectangles + 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); + + std::string color = noise_to_grayscale(noise_value); + + int svg_x = x * pixel_size; + int svg_y = y * pixel_size; + + file << "\n"; + } + } + + // Add information text + file << "\n"; + + // Add parameter info text overlay + file << "\n"; + file << "\n"; + file << "Perlin Noise Parameters\n"; + file << "Size: " + << size << "x" << size << "\n"; + file << "Scale: " + << scale << "\n"; + file << "Seed: " + << seed << "\n"; + file << "Octaves: " + << octaves << "\n"; + file << "Range: [" + << std::fixed << std::setprecision(3) << min_value << ", " << max_value + << "]\n"; + file << "\n"; + + // Add grayscale legend + file << "\n"; + file << "Value\n"; + for (int i = 0; i <= 10; ++i) { + double value = i / 10.0; + std::string color = noise_to_grayscale(value); + int y_pos = 20 + i * 15; + file << "\n"; + file << "" << std::fixed + << std::setprecision(1) << value << "\n"; + } + file << "\n"; + + file << "\n"; + file.close(); + + std::cout << "Perlin noise SVG 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.svg"; - 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.svg] [scale] [octaves] [persistence]\n"; - std::cout << "Defaults: seed=12345, output=perlin_noise.svg, scale=0.02, octaves=1, persistence=0.5\n"; - std::cout << "Examples:\n"; - std::cout << " " << argv[0] << " 54321 noise1.svg 0.01\n"; - std::cout << " " << argv[0] << " 12345 octave_noise.svg 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_svg(output_filename, 256, scale, seed, octaves, persistence); - - return 0; +int main(int argc, char *argv[]) { + // Default parameters + std::uint64_t seed = 12345; + std::string output_filename = "perlin_noise.svg"; + 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.svg] [scale] [octaves] [persistence]\n"; + std::cout << "Defaults: seed=12345, output=perlin_noise.svg, " + "scale=0.02, octaves=1, persistence=0.5\n"; + std::cout << "Examples:\n"; + std::cout << " " << argv[0] << " 54321 noise1.svg 0.01\n"; + std::cout << " " << argv[0] << " 12345 octave_noise.svg 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_svg(output_filename, 256, scale, seed, octaves, persistence); + + return 0; } diff --git a/tilemap/include/generation.h b/tilemap/include/generation.h index f1f7610..716ad91 100644 --- a/tilemap/include/generation.h +++ b/tilemap/include/generation.h @@ -32,7 +32,8 @@ private: static constexpr std::uint64_t humidity_seed_mask = 0x5e'10'be'e4'd2'6f'34'c2; - PerlinNoise base_noise_; // For base terrain generation + UniformPerlinNoise + base_noise_; // For base terrain generation (uniform distribution) PerlinNoise temperature_noise_; // For temperature PerlinNoise humidity_noise_; // For humidity diff --git a/tilemap/include/noise.h b/tilemap/include/noise.h index 17651b7..c4b2b8f 100644 --- a/tilemap/include/noise.h +++ b/tilemap/include/noise.h @@ -43,6 +43,68 @@ public: ) const; }; +/** + * @brief A wrapper that provides uniform distribution mapping for Perlin noise + * + * This class samples the noise distribution and builds a CDF (Cumulative + * Distribution Function) to map the non-uniform Perlin noise values to a + * uniform [0,1] distribution using quantiles. + */ +class UniformPerlinNoise { +private: + PerlinNoise noise_; + std::vector cdf_values_; // Sorted noise values for CDF + bool is_calibrated_; + + // Parameters used for calibration + double scale_; + int octaves_; + double persistence_; + +public: + /** + * @brief Construct a UniformPerlinNoise generator + * @param seed Random seed for noise generation + */ + explicit UniformPerlinNoise(std::uint64_t seed = 0); + + /** + * @brief Calibrate the noise distribution by sampling + * @param scale The scale parameter that will be used for generation + * @param octaves Number of octaves for octave noise + * @param persistence Persistence for octave noise + * @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 + ); + + /** + * @brief Generate uniform noise value at the given coordinates + * @param x X coordinate + * @param y Y coordinate + * @return Uniformly distributed noise value between 0.0 and 1.0 + * @note Must call calibrate() first + */ + double uniform_noise(double x, double y) const; + + /** + * @brief Check if the noise generator has been calibrated + */ + bool is_calibrated() const { + return is_calibrated_; + } + +private: + /** + * @brief Map a raw noise value to uniform distribution using the CDF + * @param raw_value Raw noise value from Perlin noise + * @return Uniformly distributed value between 0.0 and 1.0 + */ + double map_to_uniform(double raw_value) const; +}; + } // namespace istd #endif \ No newline at end of file diff --git a/tilemap/src/generation.cpp b/tilemap/src/generation.cpp index f1c33a1..3d647a0 100644 --- a/tilemap/src/generation.cpp +++ b/tilemap/src/generation.cpp @@ -10,7 +10,11 @@ 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) {} + , humidity_noise_(config.seed ^ humidity_seed_mask) { + // Calibrate the uniform base noise with the same parameters that will be + // used for generation + base_noise_.calibrate(config.base_scale, 3, 0.5); +} void TerrainGenerator::generate_map(TileMap &tilemap) { // First, generate biome data for all chunks @@ -90,11 +94,9 @@ void TerrainGenerator::generate_subchunk( double global_y = static_cast(chunk_y * Chunk::size + local_y); - // Generate base terrain noise value - double base_noise_value = base_noise_.octave_noise( - global_x * config_.base_scale, global_y * config_.base_scale, - properties.base_octaves, properties.base_persistence - ); + // Generate base terrain noise value using uniform distribution + double base_noise_value + = base_noise_.uniform_noise(global_x, global_y); // Determine base terrain type BaseTileType base_type diff --git a/tilemap/src/noise.cpp b/tilemap/src/noise.cpp index e1ff3cc..a668715 100644 --- a/tilemap/src/noise.cpp +++ b/tilemap/src/noise.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace istd { @@ -91,4 +92,84 @@ 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) {} + +void UniformPerlinNoise::calibrate( + double scale, int octaves, double persistence, int sample_size +) { + scale_ = scale; + octaves_ = octaves; + persistence_ = persistence; + + // Clear previous calibration + cdf_values_.clear(); + cdf_values_.reserve(sample_size); + + // Sample noise values across a reasonable range + std::random_device rd; + std::mt19937 gen(rd()); + 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 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_ + ); + } + + cdf_values_.push_back(noise_value); + } + + // Sort values to create CDF + std::sort(cdf_values_.begin(), cdf_values_.end()); + + is_calibrated_ = true; +} + +double UniformPerlinNoise::uniform_noise(double x, double y) const { + if (!is_calibrated_) { + throw std::runtime_error( + "UniformPerlinNoise must be calibrated before use" + ); + } + + // Generate raw noise value + double raw_value; + if (octaves_ == 1) { + raw_value = noise_.noise(x * scale_, y * scale_); + } else { + raw_value = noise_.octave_noise( + x * scale_, y * scale_, octaves_, persistence_ + ); + } + + // Map to uniform distribution + return map_to_uniform(raw_value); +} + +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); + + // Calculate quantile (position in CDF as fraction) + size_t position = std::distance(cdf_values_.begin(), it); + double quantile = static_cast(position) / cdf_values_.size(); + + // Clamp to [0,1] range + return std::max(0.0, std::min(1.0, quantile)); +} + } // namespace istd