szdytom 5ea6d46a9f
refactor: re-organize tilemap library structure
Signed-off-by: szdytom <szdytom@qq.com>
2025-08-03 15:22:33 +08:00

236 lines
5.9 KiB
C++

#include "tilemap/noise.h"
#include <algorithm>
#include <bit>
#include <cmath>
#include <numeric>
#include <random>
#include <stdexcept>
namespace istd {
DiscreteRandomNoise::DiscreteRandomNoise(Xoroshiro128PP rng) noexcept {
mask = rng.next();
std::iota(permutation_.begin(), permutation_.end(), 0);
std::shuffle(permutation_.begin(), permutation_.end(), rng);
}
std::uint8_t DiscreteRandomNoise::perm(int x) const noexcept {
// Map x to [0, 255] range
x &= 0xFF;
return permutation_[x];
}
std::uint32_t DiscreteRandomNoise::rot8(std::uint32_t x) const noexcept {
return std::rotl(x, 8);
}
std::uint32_t DiscreteRandomNoise::map_once(std::uint32_t x) const noexcept {
std::uint8_t a = x & 0xFF;
std::uint8_t b = (x >> 8) & 0xFF;
std::uint8_t c = (x >> 16) & 0xFF;
std::uint8_t d = (x >> 24) & 0xFF;
a = perm(a);
b = perm(b ^ a);
c = perm(c ^ b);
d = perm(d ^ c);
return (d << 24U) | (c << 16U) | (b << 8U) | a;
}
std::uint32_t DiscreteRandomNoise::map(std::uint32_t x) const noexcept {
for (int i = 0; i < 3; ++i) {
x = map_once(x);
x = rot8(x);
}
return x;
}
std::uint64_t DiscreteRandomNoise::noise(
std::uint32_t x, std::uint32_t y, std::uint32_t z
) const noexcept {
auto A = map(x);
auto B = map(y ^ A);
auto C = map(z ^ B);
auto D = map(z);
auto E = map(y ^ D);
auto F = map(x ^ E);
return ((static_cast<std::uint64_t>(C) << 32) | F) ^ mask;
}
DiscreteRandomNoiseStream::DiscreteRandomNoiseStream(
const DiscreteRandomNoise &noise, std::uint32_t x, std::uint32_t y,
std::uint32_t idx
)
: noise_(noise), x_(x), y_(y), idx_(idx) {}
std::uint64_t DiscreteRandomNoiseStream::next() noexcept {
std::uint64_t value = noise_.noise(x_, y_, idx_);
++idx_;
return value;
}
std::uint64_t DiscreteRandomNoiseStream::operator()() noexcept {
return next();
}
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::shuffle(permutation_.begin(), permutation_.end(), rng);
// Duplicate the permutation to avoid overflow
permutation_.insert(
permutation_.end(), permutation_.begin(), permutation_.end()
);
}
double PerlinNoise::fade(double t) const {
// Fade function: 6t^5 - 15t^4 + 10t^3
return t * t * t * (t * (t * 6 - 15) + 10);
}
double PerlinNoise::lerp(double t, double a, double b) const {
return a + t * (b - a);
}
double PerlinNoise::grad(int hash, double x, double y) const {
// Convert low 4 bits of hash code into 12 gradient directions
int h = hash & 15;
double u = h < 8 ? x : y;
double v = h < 4 ? y : h == 12 || h == 14 ? x : 0;
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
double PerlinNoise::noise(double x, double y) const {
// Find unit grid cell containing point
int X = static_cast<int>(std::floor(x)) & 255;
int Y = static_cast<int>(std::floor(y)) & 255;
// Get relative xy coordinates of point within that cell
x -= std::floor(x);
y -= std::floor(y);
// Compute fade curves for each coordinate
double u = fade(x);
double v = fade(y);
// Hash coordinates of the 4 cube corners
int A = permutation_[X] + Y;
int AA = permutation_[A];
int AB = permutation_[A + 1];
int B = permutation_[X + 1] + Y;
int BA = permutation_[B];
int BB = permutation_[B + 1];
// Add blended results from 4 corners of the square
double result = lerp(
v,
lerp(u, grad(permutation_[AA], x, y), grad(permutation_[BA], x - 1, y)),
lerp(
u, grad(permutation_[AB], x, y - 1),
grad(permutation_[BB], x - 1, y - 1)
)
);
// Convert from [-1,1] to [0,1]
return (result + 1.0) * 0.5;
}
double PerlinNoise::octave_noise(
double x, double y, int octaves, double persistence
) const {
double value = 0.0;
double amplitude = 1.0;
double frequency = 1.0;
double max_value = 0.0;
for (int i = 0; i < octaves; i++) {
value += noise(x * frequency, y * frequency) * amplitude;
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0;
}
return value / max_value;
}
UniformPerlinNoise::UniformPerlinNoise(Xoroshiro128PP rng)
: noise_(rng), calibrate_rng_(rng), is_calibrated_(false) {}
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
Xoroshiro128PP rng = calibrate_rng_;
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(rng);
double y = coord_dist(rng);
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