use super::rng::mix_u64;
fn hash_lattice(seed: u64, ix: i32, iy: i32) -> f64 {
let packed = seed
^ ((ix as i64 as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15))
^ ((iy as i64 as u64)
.wrapping_mul(0xBF58_476D_1CE4_E5B9)
.rotate_left(17));
let h = mix_u64(packed);
(h >> 11) as f64 / ((1u64 << 53) as f64)
}
fn smoothstep(t: f64) -> f64 {
t * t * (3.0 - 2.0 * t)
}
fn lerp(a: f64, b: f64, t: f64) -> f64 {
a + (b - a) * t
}
pub fn value_noise_2d(seed: u64, x: f64, y: f64) -> f64 {
let xi = x.floor() as i32;
let yi = y.floor() as i32;
let xf = x - x.floor();
let yf = y - y.floor();
let v00 = hash_lattice(seed, xi, yi);
let v10 = hash_lattice(seed, xi + 1, yi);
let v01 = hash_lattice(seed, xi, yi + 1);
let v11 = hash_lattice(seed, xi + 1, yi + 1);
let u = smoothstep(xf);
let v = smoothstep(yf);
let bottom = lerp(v00, v10, u);
let top = lerp(v01, v11, u);
lerp(bottom, top, v)
}
pub fn fbm_2d(seed: u64, x: f64, y: f64) -> f64 {
let mut total = 0.0;
let mut amp = 0.5;
let mut freq = 1.0;
let mut max = 0.0;
for octave in 0..3 {
let s = seed ^ ((octave as u64).wrapping_mul(0xABCD_EF01_2345_6789));
total += amp * value_noise_2d(s, x * freq, y * freq);
max += amp;
amp *= 0.5;
freq *= 2.0;
}
total / max
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn value_noise_is_deterministic() {
let a = value_noise_2d(123, 1.5, -3.2);
let b = value_noise_2d(123, 1.5, -3.2);
assert_eq!(a, b);
}
#[test]
fn value_noise_stays_in_unit_range() {
for x in -50..50 {
for y in -50..50 {
let v = value_noise_2d(7, x as f64 * 0.31, y as f64 * 0.17);
assert!((0.0..1.0).contains(&v), "out of range at ({x},{y}): {v}");
}
}
}
#[test]
fn value_noise_changes_with_coord() {
let center = value_noise_2d(0, 0.0, 0.0);
let near = value_noise_2d(0, 5.0, 5.0);
assert_ne!(center, near);
}
#[test]
fn fbm_stays_in_unit_range() {
for x in -20..20 {
for y in -20..20 {
let v = fbm_2d(99, x as f64 * 0.7, y as f64 * 0.7);
assert!((0.0..=1.0).contains(&v), "out of range: {v}");
}
}
}
}