feat: Implement uniform distribution mapping for Perlin noise and add comparison demo

Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
方而静 2025-08-01 19:41:11 +08:00
parent 1cb4c19b77
commit 2b5f62da08
Signed by: szTom
GPG Key ID: 072D999D60C6473C
8 changed files with 609 additions and 167 deletions

View File

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

View File

@ -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)

View File

@ -0,0 +1,203 @@
#include "noise.h"
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
// 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<int>(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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
file << "<svg width=\"" << svg_width << "\" height=\"" << svg_height
<< "\" xmlns=\"http://www.w3.org/2000/svg\">\n";
file << "<title>Noise Comparison: Raw vs Uniform (Scale: " << scale
<< ", Octaves: " << octaves << ", Seed: " << seed << ")</title>\n";
// 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]++;
std::string color = noise_to_grayscale(noise_value);
int svg_x = x * pixel_size;
int svg_y = y * pixel_size;
file << "<rect x=\"" << svg_x << "\" y=\"" << svg_y << "\" width=\""
<< pixel_size << "\" height=\"" << pixel_size << "\" fill=\""
<< color << "\"/>\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<int>(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 << "<rect x=\"" << svg_x << "\" y=\"" << svg_y << "\" width=\""
<< pixel_size << "\" height=\"" << pixel_size << "\" fill=\""
<< color << "\"/>\n";
}
}
// Add labels
file << "<text x=\"" << (panel_width / 2)
<< "\" y=\"20\" font-family=\"Arial\" font-size=\"16\" "
"font-weight=\"bold\" text-anchor=\"middle\" fill=\"white\" "
"stroke=\"black\" stroke-width=\"1\">Raw Perlin Noise</text>\n";
file << "<text x=\"" << (panel_offset + panel_width / 2)
<< "\" y=\"20\" font-family=\"Arial\" font-size=\"16\" "
"font-weight=\"bold\" text-anchor=\"middle\" fill=\"white\" "
"stroke=\"black\" stroke-width=\"1\">Uniform Distribution</text>\n";
// Add parameter info
file << "<g transform=\"translate(10, " << (svg_height - 120) << ")\">\n";
file << "<rect x=\"0\" y=\"0\" width=\"200\" height=\"100\" fill=\"white\" "
"stroke=\"black\" stroke-width=\"1\" opacity=\"0.9\"/>\n";
file << "<text x=\"10\" y=\"20\" font-family=\"Arial\" font-size=\"12\" "
"font-weight=\"bold\">Parameters</text>\n";
file << "<text x=\"10\" y=\"35\" font-family=\"Arial\" "
"font-size=\"10\">Size: "
<< size << "x" << size << "</text>\n";
file << "<text x=\"10\" y=\"50\" font-family=\"Arial\" "
"font-size=\"10\">Scale: "
<< scale << "</text>\n";
file << "<text x=\"10\" y=\"65\" font-family=\"Arial\" "
"font-size=\"10\">Octaves: "
<< octaves << "</text>\n";
file << "<text x=\"10\" y=\"80\" font-family=\"Arial\" "
"font-size=\"10\">Seed: "
<< seed << "</text>\n";
file << "</g>\n";
file << "</svg>\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;
}

View File

@ -1,167 +1,194 @@
#include "noise.h"
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
// 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<int>(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<int>(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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
file << "<svg width=\"" << svg_size << "\" height=\"" << svg_size
<< "\" xmlns=\"http://www.w3.org/2000/svg\">\n";
file << "<title>Perlin Noise Visualization (Scale: " << scale
<< ", Octaves: " << octaves << ", Seed: " << seed << ")</title>\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 << "<rect x=\"" << svg_x << "\" y=\"" << svg_y
<< "\" width=\"" << pixel_size << "\" height=\"" << pixel_size
<< "\" fill=\"" << color << "\"/>\n";
}
}
// Add information text
file << "<!-- Statistics: Min=" << min_value << " Max=" << max_value << " -->\n";
// Add parameter info text overlay
file << "<g transform=\"translate(10, 10)\">\n";
file << "<rect x=\"0\" y=\"0\" width=\"300\" height=\"120\" fill=\"white\" stroke=\"black\" stroke-width=\"1\" opacity=\"0.9\"/>\n";
file << "<text x=\"10\" y=\"20\" font-family=\"Arial\" font-size=\"14\" font-weight=\"bold\">Perlin Noise Parameters</text>\n";
file << "<text x=\"10\" y=\"40\" font-family=\"Arial\" font-size=\"12\">Size: " << size << "x" << size << "</text>\n";
file << "<text x=\"10\" y=\"55\" font-family=\"Arial\" font-size=\"12\">Scale: " << scale << "</text>\n";
file << "<text x=\"10\" y=\"70\" font-family=\"Arial\" font-size=\"12\">Seed: " << seed << "</text>\n";
file << "<text x=\"10\" y=\"85\" font-family=\"Arial\" font-size=\"12\">Octaves: " << octaves << "</text>\n";
file << "<text x=\"10\" y=\"100\" font-family=\"Arial\" font-size=\"12\">Range: ["
<< std::fixed << std::setprecision(3) << min_value << ", " << max_value << "]</text>\n";
file << "</g>\n";
// Add grayscale legend
file << "<g transform=\"translate(" << (svg_size - 60) << ", 10)\">\n";
file << "<text x=\"0\" y=\"15\" font-family=\"Arial\" font-size=\"12\" font-weight=\"bold\">Value</text>\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 << "<rect x=\"0\" y=\"" << y_pos << "\" width=\"20\" height=\"12\" fill=\""
<< color << "\" stroke=\"black\" stroke-width=\"0.5\"/>\n";
file << "<text x=\"25\" y=\"" << (y_pos + 9) << "\" font-family=\"Arial\" font-size=\"10\">"
<< std::fixed << std::setprecision(1) << value << "</text>\n";
}
file << "</g>\n";
file << "</svg>\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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
file << "<svg width=\"" << svg_size << "\" height=\"" << svg_size
<< "\" xmlns=\"http://www.w3.org/2000/svg\">\n";
file << "<title>Perlin Noise Visualization (Scale: " << scale
<< ", Octaves: " << octaves << ", Seed: " << seed << ")</title>\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 << "<rect x=\"" << svg_x << "\" y=\"" << svg_y << "\" width=\""
<< pixel_size << "\" height=\"" << pixel_size << "\" fill=\""
<< color << "\"/>\n";
}
}
// Add information text
file << "<!-- Statistics: Min=" << min_value << " Max=" << max_value
<< " -->\n";
// Add parameter info text overlay
file << "<g transform=\"translate(10, 10)\">\n";
file << "<rect x=\"0\" y=\"0\" width=\"300\" height=\"120\" fill=\"white\" "
"stroke=\"black\" stroke-width=\"1\" opacity=\"0.9\"/>\n";
file << "<text x=\"10\" y=\"20\" font-family=\"Arial\" font-size=\"14\" "
"font-weight=\"bold\">Perlin Noise Parameters</text>\n";
file << "<text x=\"10\" y=\"40\" font-family=\"Arial\" "
"font-size=\"12\">Size: "
<< size << "x" << size << "</text>\n";
file << "<text x=\"10\" y=\"55\" font-family=\"Arial\" "
"font-size=\"12\">Scale: "
<< scale << "</text>\n";
file << "<text x=\"10\" y=\"70\" font-family=\"Arial\" "
"font-size=\"12\">Seed: "
<< seed << "</text>\n";
file << "<text x=\"10\" y=\"85\" font-family=\"Arial\" "
"font-size=\"12\">Octaves: "
<< octaves << "</text>\n";
file << "<text x=\"10\" y=\"100\" font-family=\"Arial\" "
"font-size=\"12\">Range: ["
<< std::fixed << std::setprecision(3) << min_value << ", " << max_value
<< "]</text>\n";
file << "</g>\n";
// Add grayscale legend
file << "<g transform=\"translate(" << (svg_size - 60) << ", 10)\">\n";
file << "<text x=\"0\" y=\"15\" font-family=\"Arial\" font-size=\"12\" "
"font-weight=\"bold\">Value</text>\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 << "<rect x=\"0\" y=\"" << y_pos
<< "\" width=\"20\" height=\"12\" fill=\"" << color
<< "\" stroke=\"black\" stroke-width=\"0.5\"/>\n";
file << "<text x=\"25\" y=\"" << (y_pos + 9)
<< "\" font-family=\"Arial\" font-size=\"10\">" << std::fixed
<< std::setprecision(1) << value << "</text>\n";
}
file << "</g>\n";
file << "</svg>\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;
}

View File

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

View File

@ -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<double> 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

View File

@ -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<double>(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

View File

@ -3,6 +3,7 @@
#include <cmath>
#include <numeric>
#include <random>
#include <stdexcept>
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<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 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<double>(position) / cdf_values_.size();
// Clamp to [0,1] range
return std::max(0.0, std::min(1.0, quantile));
}
} // namespace istd