Skip to main content

fx/
perlin.rs

1// perlin -- deterministic 2-D gradient noise for the fx kernels (Clouds, Turbulence, Dents,
2// Frosted Glass)                                                            [src/fx/fx_perlin.s]
3//
4//   perlin(x, y, seed, stream) -> f64        gradient noise, about -1 .. 1
5//   perlin_row(rec, acc)                     acc[j] += amp * noise(x_j, y) along a row
6//
7// Improved Perlin noise (quintic fade 6t^5 - 15t^4 + 10t^3) on the integer lattice; the gradient
8// of lattice point (ix, iy) is G[h & 15], h = hash32(ix, iy, seed, stream), G_k = sqrt(2)
9// (cos, sin)(2 pi (k + 0.5) / 16).  g00 = G00.x fx + G00.y fy, g10 = G10.x (fx - 1) + G10.y fy,
10// g01 = G01.x fx + G01.y (fy - 1), g11 = G11.x (fx - 1) + G11.y (fy - 1); a = g00 + u (g10 -
11// g00), b = g01 + u (g11 - g01), noise = a + v (b - a), u / v = fade(fx / fy) computed as
12// ((((f 6) - 15) f + 10) f) f f.  Coordinates are clamped to +-2^30; fixed operation order:
13// perlin and perlin_row give bit-identical values.
14
15use rt::fx::{cvttd64, maxsd, minsd};
16
17const LO: f64 = -1073741824.0;
18const HI: f64 = 1073741824.0;
19const C1: u32 = 0x8DA6B343;
20const C2: u32 = 0xD8163841;
21const C3: u32 = 0xCB1AB31F;
22const C4: u32 = 0x165667B1;
23
24static GTAB: [[f64; 2]; 16] = [
25    [1.3870398453221475, 0.275899379282943],
26    [1.1758756024193588, 0.78569495838710213],
27    [0.78569495838710235, 1.1758756024193588],
28    [0.27589937928294311, 1.3870398453221475],
29    [-0.27589937928294295, 1.3870398453221475],
30    [-0.7856949583871019, 1.1758756024193588],
31    [-1.1758756024193588, 0.78569495838710213],
32    [-1.3870398453221475, 0.2758993792829435],
33    [-1.3870398453221475, -0.27589937928294317],
34    [-1.1758756024193591, -0.7856949583871019],
35    [-0.78569495838710213, -1.1758756024193588],
36    [-0.27589937928294361, -1.3870398453221473],
37    [0.27589937928294306, -1.3870398453221475],
38    [0.78569495838710168, -1.1758756024193591],
39    [1.1758756024193588, -0.78569495838710213],
40    [1.3870398453221473, -0.27589937928294367],
41];
42
43#[inline(always)]
44fn fmix(mut h: u32) -> u32 {
45    h ^= h >> 16;
46    h = h.wrapping_mul(0x85EBCA6B);
47    h ^= h >> 13;
48    h = h.wrapping_mul(0xC2B2AE35);
49    h ^ (h >> 16)
50}
51
52/// floor (i64) and fraction of a clamped coordinate  [FLOOR64]
53#[inline(always)]
54fn floor64(x: f64) -> (i64, f64) {
55    let mut t = cvttd64(x);
56    if t as f64 > x {
57        t -= 1;
58    }
59    (t, x - t as f64)
60}
61
62#[inline(always)]
63fn fade(f: f64) -> f64 {
64    (((f * 6.0 - 15.0) * f + 10.0) * f) * f * f
65}
66
67#[inline(always)]
68fn grad(h: u32, dx: f64, dy: f64) -> f64 {
69    let g = &GTAB[(h & 15) as usize];
70    g[0] * dx + g[1] * dy
71}
72
73/// gradient noise at (x, y)  [fxn_perlin]
74pub fn perlin(x: f64, y: f64, seed: u32, stream: u32) -> f64 {
75    let (ix, fx) = floor64(minsd(maxsd(x, LO), HI));
76    let (iy, fy) = floor64(minsd(maxsd(y, LO), HI));
77    let k = seed.wrapping_mul(C3) ^ stream.wrapping_mul(C4);
78    let x0 = (ix as u32).wrapping_mul(C1);
79    let x1 = x0.wrapping_add(C1);
80    let y0 = (iy as u32).wrapping_mul(C2);
81    let y1 = y0.wrapping_add(C2);
82    let u = fade(fx);
83    let v = fade(fy);
84    let g00 = grad(fmix(x0 ^ y0 ^ k), fx, fy);
85    let g10 = grad(fmix(x1 ^ y0 ^ k), fx - 1.0, fy - 0.0);
86    let a = g00 + (g10 - g00) * u;
87    let g01 = grad(fmix(x0 ^ y1 ^ k), fx - 0.0, fy - 1.0);
88    let g11 = grad(fmix(x1 ^ y1 ^ k), fx - 1.0, fy - 1.0);
89    let b = g01 + (g11 - g01) * u;
90    a + (b - a) * v
91}
92
93/// a row of noise samples (PERLIN_ROW): x_j = (x0 + j step) * inv + ofsx
94pub struct PerlinRow {
95    pub n: usize,
96    pub x0: i32,
97    pub step: i32,
98    pub abs: bool,
99    pub seed: u32,
100    pub stream: u32,
101    pub inv: f64,
102    pub ofsx: f64,
103    pub y: f64,
104    pub amp: f64,
105}
106
107/// acc[j] += amp * noise(x_j, y) (|noise| when abs); the lattice gradients are reused while
108/// consecutive x_j stay in one cell  [fxn_perlin_row]
109pub fn perlin_row(r: &PerlinRow, acc: &mut [f64]) {
110    let (iy, fy) = floor64(minsd(maxsd(r.y, LO), HI));
111    let fy1 = fy - 1.0;
112    let v = fade(fy);
113    let y0 = (iy as u32).wrapping_mul(C2);
114    let y1 = y0.wrapping_add(C2);
115    let k = r.seed.wrapping_mul(C3) ^ r.stream.wrapping_mul(C4);
116    let (mut a0, mut b0, mut a1, mut b1) = ([0f64; 2], [0f64; 2], [0f64; 2], [0f64; 2]);
117    let mut cached: Option<u32> = None;
118    let mut xi = r.x0;
119    for a in acc.iter_mut().take(r.n) {
120        let x = minsd(maxsd(xi as f64 * r.inv + r.ofsx, LO), HI);
121        let (ix, fx) = floor64(x);
122        if cached != Some(ix as u32) {
123            cached = Some(ix as u32);
124            let x0 = (ix as u32).wrapping_mul(C1);
125            let x1 = x0.wrapping_add(C1);
126            let g00 = &GTAB[(fmix(x0 ^ y0 ^ k) & 15) as usize];
127            let g01 = &GTAB[(fmix(x0 ^ y1 ^ k) & 15) as usize];
128            let g10 = &GTAB[(fmix(x1 ^ y0 ^ k) & 15) as usize];
129            let g11 = &GTAB[(fmix(x1 ^ y1 ^ k) & 15) as usize];
130            a0 = [g00[0], g01[0]];
131            b0 = [g00[1] * fy, g01[1] * fy1];
132            a1 = [g10[0], g11[0]];
133            b1 = [g10[1] * fy, g11[1] * fy1];
134        }
135        let p = [fx * a0[0] + b0[0], fx * a0[1] + b0[1]];
136        let fxm = fx - 1.0;
137        let q = [fxm * a1[0] + b1[0], fxm * a1[1] + b1[1]];
138        let u = fade(fx);
139        let ab = [p[0] + (q[0] - p[0]) * u, p[1] + (q[1] - p[1]) * u];
140        let mut nv = ab[0] + (ab[1] - ab[0]) * v;
141        if r.abs {
142            nv = f64::from_bits(nv.to_bits() & 0x7FFF_FFFF_FFFF_FFFF);
143        }
144        *a += nv * r.amp;
145        xi = xi.wrapping_add(r.step);
146    }
147}