feat: Implement uniform distribution mapping for Perlin noise and add comparison demo
Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
parent
1cb4c19b77
commit
2b5f62da08
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
203
tilemap/examples/noise_comparison.cpp
Normal file
203
tilemap/examples/noise_comparison.cpp
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user