use crate::fixed::Fixed;
use crate::prime::PpfHash;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct NoiseParams {
pub seed: u32,
pub octaves: u8,
pub scale: u8,
pub persistence: f32,
pub lacunarity: f32,
}
impl Default for NoiseParams {
fn default() -> Self {
NoiseParams {
seed: 0,
octaves: 4,
scale: 1,
persistence: 0.5,
lacunarity: 2.0,
}
}
}
pub struct PpfNoise {
hash: PpfHash,
params: NoiseParams,
}
impl PpfNoise {
pub fn new(params: NoiseParams) -> Self {
PpfNoise {
hash: PpfHash::new(params.seed),
params,
}
}
pub fn noise_2d(&self, x: i32, y: i32) -> f32 {
self.hash.hash(x as u32, y as u32)
}
pub fn noise_2d_fixed(&self, x: Fixed, y: Fixed) -> Fixed {
let xi = x.to_int();
let yi = y.to_int();
let xf = x.fract();
let yf = y.fract();
let n00 = self.noise_2d(xi, yi);
let n10 = self.noise_2d(xi + 1, yi);
let n01 = self.noise_2d(xi, yi + 1);
let n11 = self.noise_2d(xi + 1, yi + 1);
let n00_fixed = Fixed::from_f32(n00);
let n10_fixed = Fixed::from_f32(n10);
let n01_fixed = Fixed::from_f32(n01);
let n11_fixed = Fixed::from_f32(n11);
let nx0 = n00_fixed + (n10_fixed - n00_fixed) * xf;
let nx1 = n01_fixed + (n11_fixed - n01_fixed) * xf;
nx0 + (nx1 - nx0) * yf
}
pub fn fbm(&self, x: Fixed, y: Fixed) -> Fixed {
let mut total = Fixed::ZERO;
let mut amplitude = Fixed::ONE;
let mut frequency = Fixed::from_f32(1.0 / (1 << self.params.scale) as f32);
let persistence = Fixed::from_f32(self.params.persistence);
let lacunarity = Fixed::from_f32(self.params.lacunarity);
for octave in 0..self.params.octaves {
let octave_hash = PpfHash::new(self.params.seed.wrapping_add(octave as u32));
let octave_noise = PpfNoise {
hash: octave_hash,
params: self.params,
};
let sample_x = x * frequency;
let sample_y = y * frequency;
let noise_val = octave_noise.noise_2d_fixed(sample_x, sample_y);
total = total + noise_val * amplitude;
amplitude = amplitude * persistence;
frequency = frequency * lacunarity;
}
total
}
pub fn turbulence(&self, x: Fixed, y: Fixed) -> Fixed {
let mut total = Fixed::ZERO;
let mut amplitude = Fixed::ONE;
let mut frequency = Fixed::from_f32(1.0 / (1 << self.params.scale) as f32);
let persistence = Fixed::from_f32(self.params.persistence);
let lacunarity = Fixed::from_f32(self.params.lacunarity);
for octave in 0..self.params.octaves {
let octave_hash = PpfHash::new(self.params.seed.wrapping_add(octave as u32));
let octave_noise = PpfNoise {
hash: octave_hash,
params: self.params,
};
let sample_x = x * frequency;
let sample_y = y * frequency;
let noise_val = octave_noise.noise_2d_fixed(sample_x, sample_y);
let turbulence_val = noise_val.abs();
total = total + turbulence_val * amplitude;
amplitude = amplitude * persistence;
frequency = frequency * lacunarity;
}
total
}
pub fn ridged(&self, x: Fixed, y: Fixed) -> Fixed {
let mut total = Fixed::ZERO;
let mut amplitude = Fixed::ONE;
let mut frequency = Fixed::from_f32(1.0 / (1 << self.params.scale) as f32);
let persistence = Fixed::from_f32(self.params.persistence);
let lacunarity = Fixed::from_f32(self.params.lacunarity);
for octave in 0..self.params.octaves {
let octave_hash = PpfHash::new(self.params.seed.wrapping_add(octave as u32));
let octave_noise = PpfNoise {
hash: octave_hash,
params: self.params,
};
let sample_x = x * frequency;
let sample_y = y * frequency;
let noise_val = octave_noise.noise_2d_fixed(sample_x, sample_y);
let centered = noise_val - Fixed::HALF;
let ridge = Fixed::ONE - centered.abs() * Fixed::from_int(2);
total = total + ridge * amplitude;
amplitude = amplitude * persistence;
frequency = frequency * lacunarity;
}
total
}
pub fn warped(&self, x: Fixed, y: Fixed, warp_strength: Fixed) -> Fixed {
let warp_x = self.fbm(x, y) * warp_strength;
let warp_y = self.fbm(x + Fixed::from_int(100), y + Fixed::from_int(100)) * warp_strength;
self.fbm(x + warp_x, y + warp_y)
}
pub fn cellular(&self, x: Fixed, y: Fixed, cell_size: Fixed) -> Fixed {
let cell_x = (x / cell_size).floor();
let cell_y = (y / cell_size).floor();
let mut min_dist = Fixed::from_int(1000);
for dy in -1..=1 {
for dx in -1..=1 {
let neighbor_x = cell_x + Fixed::from_int(dx);
let neighbor_y = cell_y + Fixed::from_int(dy);
let hash_x = self.hash.hash(neighbor_x.to_int() as u32, neighbor_y.to_int() as u32);
let hash_y = self.hash.hash(
(neighbor_x.to_int() as u32).wrapping_add(1),
(neighbor_y.to_int() as u32).wrapping_add(1),
);
let point_x = (neighbor_x + Fixed::from_f32(hash_x)) * cell_size;
let point_y = (neighbor_y + Fixed::from_f32(hash_y)) * cell_size;
let dx = x - point_x;
let dy = y - point_y;
let dist_sq = dx * dx + dy * dy;
let dist = dist_sq.sqrt().unwrap_or(Fixed::ZERO);
if dist < min_dist {
min_dist = dist;
}
}
}
(min_dist / cell_size).min(Fixed::ONE)
}
}
pub struct PerlinNoise {
hash: PpfHash,
}
impl PerlinNoise {
pub fn new(seed: u32) -> Self {
PerlinNoise {
hash: PpfHash::new(seed),
}
}
fn gradient(&self, x: i32, y: i32) -> (Fixed, Fixed) {
let hash_val = self.hash.hash(x as u32, y as u32);
let angle = Fixed::from_f32(hash_val * 2.0 * std::f32::consts::PI);
(angle.cos(), angle.sin())
}
pub fn noise(&self, x: Fixed, y: Fixed) -> Fixed {
let xi = x.floor().to_int();
let yi = y.floor().to_int();
let xf = x - Fixed::from_int(xi);
let yf = y - Fixed::from_int(yi);
let g00 = self.gradient(xi, yi);
let g10 = self.gradient(xi + 1, yi);
let g01 = self.gradient(xi, yi + 1);
let g11 = self.gradient(xi + 1, yi + 1);
let d00 = g00.0 * xf + g00.1 * yf;
let d10 = g10.0 * (xf - Fixed::ONE) + g10.1 * yf;
let d01 = g01.0 * xf + g01.1 * (yf - Fixed::ONE);
let d11 = g11.0 * (xf - Fixed::ONE) + g11.1 * (yf - Fixed::ONE);
let u = self.fade(xf);
let v = self.fade(yf);
let x1 = self.lerp(d00, d10, u);
let x2 = self.lerp(d01, d11, u);
self.lerp(x1, x2, v)
}
fn fade(&self, t: Fixed) -> Fixed {
let t2 = t * t;
let t3 = t2 * t;
let t4 = t3 * t;
let t5 = t4 * t;
t3 * Fixed::from_int(10) - t4 * Fixed::from_int(15) + t5 * Fixed::from_int(6)
}
fn lerp(&self, a: Fixed, b: Fixed, t: Fixed) -> Fixed {
a + (b - a) * t
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_noise_determinism() {
let params = NoiseParams {
seed: 42,
octaves: 4,
scale: 1,
persistence: 0.5,
lacunarity: 2.0,
};
let noise = PpfNoise::new(params);
let n1 = noise.noise_2d(10, 20);
let n2 = noise.noise_2d(10, 20);
assert_eq!(n1, n2);
for _ in 0..100 {
let n3 = noise.noise_2d(10, 20);
assert_eq!(n1, n3);
}
}
#[test]
fn test_noise_range() {
let params = NoiseParams::default();
let noise = PpfNoise::new(params);
for x in -100..100 {
for y in -100..100 {
let n = noise.noise_2d(x, y);
assert!(n >= 0.0 && n < 1.0, "Noise value {} out of range", n);
}
}
}
#[test]
fn test_fbm_properties() {
let params = NoiseParams {
seed: 123,
octaves: 4,
scale: 1,
persistence: 0.5,
lacunarity: 2.0,
};
let noise = PpfNoise::new(params);
let x = Fixed::from_f32(10.5);
let y = Fixed::from_f32(20.3);
let fbm_val = noise.fbm(x, y);
assert!(fbm_val >= Fixed::ZERO);
assert!(fbm_val <= Fixed::from_int(2));
let fbm_val2 = noise.fbm(x, y);
assert_eq!(fbm_val, fbm_val2);
}
#[test]
fn test_turbulence() {
let params = NoiseParams::default();
let noise = PpfNoise::new(params);
let x = Fixed::from_int(5);
let y = Fixed::from_int(7);
let turb = noise.turbulence(x, y);
assert!(turb >= Fixed::ZERO);
}
#[test]
fn test_perlin_determinism() {
let perlin = PerlinNoise::new(42);
let x = Fixed::from_f32(10.5);
let y = Fixed::from_f32(20.3);
let n1 = perlin.noise(x, y);
let n2 = perlin.noise(x, y);
assert_eq!(n1, n2);
}
#[test]
fn test_different_seeds() {
let noise1 = PpfNoise::new(NoiseParams {
seed: 42,
..Default::default()
});
let noise2 = PpfNoise::new(NoiseParams {
seed: 43,
..Default::default()
});
let n1 = noise1.noise_2d(10, 20);
let n2 = noise2.noise_2d(10, 20);
assert_ne!(n1, n2);
}
#[test]
fn test_cellular_noise() {
let params = NoiseParams::default();
let noise = PpfNoise::new(params);
let x = Fixed::from_int(5);
let y = Fixed::from_int(7);
let cell_size = Fixed::from_int(2);
let cell = noise.cellular(x, y, cell_size);
assert!(cell >= Fixed::ZERO);
assert!(cell <= Fixed::ONE);
}
#[test]
fn test_warped_noise() {
let params = NoiseParams::default();
let noise = PpfNoise::new(params);
let x = Fixed::from_int(5);
let y = Fixed::from_int(7);
let warp_strength = Fixed::from_int(1);
let warped = noise.warped(x, y, warp_strength);
assert!(warped >= Fixed::ZERO);
}
#[test]
fn test_octave_variation() {
let x = Fixed::from_int(10);
let y = Fixed::from_int(20);
let noise_2 = PpfNoise::new(NoiseParams {
octaves: 2,
..Default::default()
});
let noise_8 = PpfNoise::new(NoiseParams {
octaves: 8,
..Default::default()
});
let fbm_2 = noise_2.fbm(x, y);
let fbm_8 = noise_8.fbm(x, y);
assert_ne!(fbm_2, fbm_8);
}
}