use glam::Vec3;
#[inline(always)]
pub const fn pcg(v: u32) -> u32 {
let v = v.wrapping_mul(747_796_405).wrapping_add(2_891_336_453);
let w = ((v >> ((v >> 28) + 4)) ^ v).wrapping_mul(277_803_737);
(w >> 22) ^ w
}
#[inline(always)]
fn hash3(i: Vec3, seed: u32) -> f32 {
let h = pcg(i.x.to_bits() ^ pcg(i.y.to_bits() ^ pcg(i.z.to_bits() ^ seed)));
(h as f32) * (1.0 / 4_294_967_295.0)
}
#[inline(always)]
pub fn hash_noise_3d(p: Vec3, seed: u32) -> f32 {
let i = p.floor();
let f = p - i;
let u = f * f * (Vec3::splat(3.0) - f * 2.0);
let n000 = hash3(i, seed);
let n100 = hash3(i + Vec3::X, seed);
let n010 = hash3(i + Vec3::Y, seed);
let n110 = hash3(i + Vec3::new(1.0, 1.0, 0.0), seed);
let n001 = hash3(i + Vec3::Z, seed);
let n101 = hash3(i + Vec3::new(1.0, 0.0, 1.0), seed);
let n011 = hash3(i + Vec3::new(0.0, 1.0, 1.0), seed);
let n111 = hash3(i + Vec3::ONE, seed);
let c00 = (n100 - n000).mul_add(u.x, n000);
let c10 = (n110 - n010).mul_add(u.x, n010);
let c01 = (n101 - n001).mul_add(u.x, n001);
let c11 = (n111 - n011).mul_add(u.x, n011);
let c0 = (c10 - c00).mul_add(u.y, c00);
let c1 = (c11 - c01).mul_add(u.y, c01);
(c1 - c0).mul_add(u.z, c0).mul_add(2.0, -1.0)
}
pub const SURFACE_ROUGHNESS_SEED: u32 = 42;
#[inline(always)]
pub fn fbm(p: Vec3, octaves: u32) -> f32 {
let mut value = 0.0_f32;
let mut amplitude = 1.0_f32;
for i in 0..octaves {
let scale = (1u32 << i) as f32;
value = amplitude.mul_add(hash_noise_3d(p * scale, SURFACE_ROUGHNESS_SEED), value);
amplitude *= 0.5;
}
value
}
#[inline(always)]
pub fn fbm_bound(octaves: u32) -> f32 {
2.0 - 0.5_f32.powi(octaves as i32 - 1)
}
#[inline(always)]
pub fn surface_roughness(
p: Vec3,
distance: f32,
frequency: f32,
amplitude: f32,
octaves: u32,
) -> f32 {
let noise = fbm(p * frequency, octaves);
noise.mul_add(amplitude, distance)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fbm_bounded() {
let p = Vec3::new(1.0, 2.0, 3.0);
let n = fbm(p, 4);
assert!(n.abs() < 2.0, "FBM should be bounded: got {}", n);
}
#[test]
fn test_roughness_preserves_sign() {
let p = Vec3::new(0.0, 0.0, 0.0);
let d = surface_roughness(p, 10.0, 1.0, 0.01, 3);
assert!(
d > 0.0,
"Large positive distance should stay positive with small roughness"
);
}
}