//! Deterministic hashing, random numbers and smooth noise for `wiggle`, `random` and `noise`. //! //! Everything is a pure function of its seeds, so a frame renders identically on any thread, //! in any order, on native and on the web. /// SplitMix64 finaliser: a good 64-bit mixing function. pub fn hash64(mut x: u64) -> u64 { x = x.wrapping_add(0x9e37_79b9_7f4a_7c15); x = (x ^ (x >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9); x = (x ^ (x >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb); x ^ (x >> 31) } /// Combine several seeds into one. pub fn mix(parts: &[u64]) -> u64 { parts.iter().fold(0x2545_f491_4f6c_dd1d, |h, p| hash64(h ^ hash64(*p))) } /// Uniform in `[0, 1)`. pub fn unit(seed: u64) -> f64 { (hash64(seed) >> 11) as f64 / (1u64 << 53) as f64 } /// Lattice value in `[-1, 1]`. fn lattice(seed: u64, i: i64) -> f64 { unit(seed ^ hash64(i as u64)) * 2.0 - 1.0 } fn fade(t: f64) -> f64 { t * t * t * (t * (t * 6.0 - 15.0) + 10.0) } /// Smooth 1D value noise in `[-1, 1]` (C2-continuous, bounded by the lattice values). pub fn noise1(seed: u64, x: f64) -> f64 { if !x.is_finite() { return 0.0; } let i = x.floor(); let u = fade(x - i); let i = i as i64; let (a, b) = (lattice(seed, i), lattice(seed, i + 1)); a + (b - a) * u } /// Smooth 3D value noise in `[-1, 1]` (AE's `noise()`: nearby inputs give nearby outputs). pub fn noise3(x: f64, y: f64, z: f64) -> f64 { if !(x.is_finite() && y.is_finite() && z.is_finite()) { return 0.0; } let (fx, fy, fz) = (x.floor(), y.floor(), z.floor()); let (u, v, w) = (fade(x - fx), fade(y - fy), fade(z - fz)); let (ix, iy, iz) = (fx as i64, fy as i64, fz as i64); let g = |dx: i64, dy: i64, dz: i64| unit(mix(&[(ix + dx) as u64, (iy + dy) as u64, (iz + dz) as u64])) * 2.0 - 1.0; let l = |a: f64, b: f64, t: f64| a + (b - a) * t; let x00 = l(g(0, 0, 0), g(1, 0, 0), u); let x10 = l(g(0, 1, 0), g(1, 1, 0), u); let x01 = l(g(0, 0, 1), g(1, 0, 1), u); let x11 = l(g(0, 1, 1), g(1, 1, 1), u); l(l(x00, x10, v), l(x01, x11, v), w) } /// Per-dimension wiggle offsets: `Σ amp·mult^k · noise(freq·2^k·t)` over `octaves`. /// With one octave every offset lies in `[-amp, amp]`. pub fn wiggle(seed: u64, dims: usize, freq: f64, amp: f64, octaves: f64, amp_mult: f64, t: f64) -> Vec { let octaves = if octaves.is_finite() { octaves.clamp(1.0, 16.0) as usize } else { 1 }; (0..dims) .map(|d| { let mut a = amp; let mut f = freq; let mut sum = 0.0; for k in 0..octaves { let s = mix(&[seed, d as u64, k as u64]); // Offset each octave's phase so octaves don't share lattice points. sum += a * noise1(s, f * t + unit(s) * 1000.0); a *= amp_mult; f *= 2.0; } sum }) .collect() } #[cfg(test)] mod tests { use super::*; #[test] fn noise_is_bounded_smooth_and_deterministic() { let mut prev = noise1(7, 0.0); for i in 1..10_000 { let x = i as f64 * 0.001; let v = noise1(7, x); assert!((-1.0..=1.0).contains(&v)); assert!((v - prev).abs() < 0.01, "jump at {x}"); prev = v; } assert_eq!(noise1(3, 1.25), noise1(3, 1.25)); assert_ne!(noise1(3, 1.25), noise1(4, 1.25)); assert!((-1.0..=1.0).contains(&noise3(0.3, 4.2, -7.5))); } #[test] fn wiggle_bounds() { for i in 0..2000 { let w = wiggle(99, 2, 3.0, 50.0, 1.0, 0.5, i as f64 / 30.0); assert!(w.iter().all(|v| v.abs() <= 50.0)); } } }