use glam::Vec3;
#[inline(always)]
pub fn modifier_noise_perlin(
distance: f32,
point: Vec3,
amplitude: f32,
frequency: f32,
seed: u32,
) -> f32 {
let noise = perlin_noise_3d(
point.x * frequency,
point.y * frequency,
point.z * frequency,
seed,
);
noise * amplitude + distance
}
#[inline(always)]
pub fn modifier_noise_simplex(
distance: f32,
point: Vec3,
amplitude: f32,
frequency: f32,
seed: u32,
) -> f32 {
modifier_noise_perlin(distance, point, amplitude, frequency, seed)
}
#[inline(always)]
pub fn perlin_noise_3d(x: f32, y: f32, z: f32, seed: u32) -> f32 {
let xi = x.floor() as i32;
let yi = y.floor() as i32;
let zi = z.floor() as i32;
let (xi1, yi1, zi1) = (xi.wrapping_add(1), yi.wrapping_add(1), zi.wrapping_add(1));
let xf = x - x.floor();
let yf = y - y.floor();
let zf = z - z.floor();
let u = fade(xf);
let v = fade(yf);
let w = fade(zf);
let aaa = grad3d(hash3d(xi, yi, zi, seed), xf, yf, zf);
let aba = grad3d(hash3d(xi, yi1, zi, seed), xf, yf - 1.0, zf);
let aab = grad3d(hash3d(xi, yi, zi1, seed), xf, yf, zf - 1.0);
let abb = grad3d(hash3d(xi, yi1, zi1, seed), xf, yf - 1.0, zf - 1.0);
let baa = grad3d(hash3d(xi1, yi, zi, seed), xf - 1.0, yf, zf);
let bba = grad3d(hash3d(xi1, yi1, zi, seed), xf - 1.0, yf - 1.0, zf);
let bab = grad3d(hash3d(xi1, yi, zi1, seed), xf - 1.0, yf, zf - 1.0);
let bbb = grad3d(hash3d(xi1, yi1, zi1, seed), xf - 1.0, yf - 1.0, zf - 1.0);
lerp(
lerp(lerp(aaa, baa, u), lerp(aba, bba, u), v),
lerp(lerp(aab, bab, u), lerp(abb, bbb, u), v),
w,
)
}
#[inline(always)]
pub fn fbm_noise_3d(
x: f32,
y: f32,
z: f32,
seed: u32,
octaves: u32,
lacunarity: f32,
persistence: f32,
) -> f32 {
let mut value = 0.0;
let mut amplitude = 1.0;
let mut frequency = 1.0;
let mut max_value = 0.0;
for i in 0..octaves {
let octave_seed = seed.wrapping_add(i);
value +=
amplitude * perlin_noise_3d(x * frequency, y * frequency, z * frequency, octave_seed);
max_value += amplitude;
amplitude *= persistence;
frequency *= lacunarity;
}
value * (1.0 / max_value)
}
#[inline(always)]
fn fade(t: f32) -> f32 {
t * t * t * (t * (t * 6.0 + -15.0) + 10.0)
}
#[inline(always)]
fn lerp(a: f32, b: f32, t: f32) -> f32 {
t * (b - a) + a
}
#[inline(always)]
const fn hash3d(x: i32, y: i32, z: i32, seed: u32) -> u32 {
let mut h = seed;
h ^= x as u32;
h = h.wrapping_mul(0x85EBCA6B);
h ^= y as u32;
h = h.wrapping_mul(0xC2B2AE35);
h ^= z as u32;
h = h.wrapping_mul(0x27D4EB2D);
h ^= h >> 16;
h
}
#[inline(always)]
fn grad3d(hash: u32, x: f32, y: f32, z: f32) -> f32 {
const LUT: [u8; 16] = [
0b_00_01_00, 0b_00_01_01, 0b_00_01_10, 0b_00_01_11, 0b_00_10_00, 0b_00_10_01, 0b_00_10_10, 0b_00_10_11, 0b_01_10_00, 0b_01_10_01, 0b_01_10_10, 0b_01_10_11, 0b_01_00_00, 0b_00_10_10, 0b_01_00_10, 0b_00_10_01, ];
let entry = LUT[(hash & 15) as usize];
let coords = [x, y, z];
let u_src = ((entry >> 4) & 3) as usize;
let v_src = ((entry >> 2) & 3) as usize;
let u_neg = (entry >> 1) & 1;
let v_neg = entry & 1;
let u = coords[u_src] * (2.0f32 * -(u_neg as f32) + 1.0);
let v = coords[v_src] * (2.0f32 * -(v_neg as f32) + 1.0);
u + v
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_perlin_range() {
let mut min = f32::MAX;
let mut max = f32::MIN;
for i in 0..1000 {
let x = (i as f32) * 0.1;
let y = (i as f32) * 0.13;
let z = (i as f32) * 0.17;
let n = perlin_noise_3d(x, y, z, 42);
min = min.min(n);
max = max.max(n);
}
assert!(min >= -1.5);
assert!(max <= 1.5);
}
#[test]
fn test_perlin_deterministic() {
let n1 = perlin_noise_3d(1.5, 2.5, 3.5, 42);
let n2 = perlin_noise_3d(1.5, 2.5, 3.5, 42);
assert!((n1 - n2).abs() < 0.0001);
}
#[test]
fn test_modifier_noise() {
let original = 1.0;
let noisy = modifier_noise_perlin(original, Vec3::new(1.0, 2.0, 3.0), 0.1, 1.0, 42);
assert!((noisy - original).abs() <= 0.15);
}
#[test]
fn test_fbm() {
let n = fbm_noise_3d(1.5, 2.5, 3.5, 42, 4, 2.0, 0.5);
assert!((-1.0..=1.0).contains(&n));
}
}