Skip to main content

pfx_materials/
noise.rs

1use crate::math::{fade, fract, lerp};
2
3pub fn pcg(v: u32) -> u32 {
4    let s = v.wrapping_mul(747796405).wrapping_add(2891336453);
5    let w = ((s >> ((s >> 28) + 4)) ^ s).wrapping_mul(277803737);
6    (w >> 22) ^ w
7}
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
10pub enum Hash {
11    #[default]
12    Xor,
13    Nested,
14}
15
16pub fn hash3(p: [i32; 3], hash: Hash) -> f32 {
17    match hash {
18        Hash::Xor => hash3_xor(p),
19        Hash::Nested => hash3_nested(p),
20    }
21}
22
23fn hash3_xor(p: [i32; 3]) -> f32 {
24    let h = (p[0] as u32).wrapping_mul(73856093)
25        ^ (p[1] as u32).wrapping_mul(19349663)
26        ^ (p[2] as u32).wrapping_mul(83492791);
27    (pcg(h) >> 8) as f32 / 16777216.0
28}
29
30fn hash3_nested(p: [i32; 3]) -> f32 {
31    let h = pcg((p[0] as u32).wrapping_mul(73856093)
32        ^ pcg((p[1] as u32).wrapping_mul(19349663) ^ pcg((p[2] as u32).wrapping_mul(83492791))));
33    (h >> 8) as f32 / 16777216.0
34}
35
36pub fn hash2(p: [i32; 2], seed: u32) -> f32 {
37    let mut h = (p[0] as u32).wrapping_mul(0x8da6_b343) ^ (p[1] as u32).wrapping_mul(0xd816_3841);
38    h ^= seed;
39    h ^= h >> 15;
40    h = h.wrapping_mul(0x2c1b_3c6d);
41    h ^= h >> 12;
42    h = h.wrapping_mul(0x297a_2d39);
43    h ^= h >> 15;
44    (h >> 8) as f32 / 16777216.0
45}
46
47pub fn value_noise3(p: [f32; 3], hash: Hash) -> f32 {
48    match hash {
49        Hash::Xor => lattice_noise(p, hash3_xor),
50        Hash::Nested => lattice_noise(p, hash3_nested),
51    }
52}
53
54fn lattice_noise(p: [f32; 3], hash: fn([i32; 3]) -> f32) -> f32 {
55    let i = [
56        p[0].floor() as i32,
57        p[1].floor() as i32,
58        p[2].floor() as i32,
59    ];
60    let f = [p[0] - i[0] as f32, p[1] - i[1] as f32, p[2] - i[2] as f32];
61    let u = [fade(f[0]), fade(f[1]), fade(f[2])];
62    let h = |x, y, z| hash([i[0] + x, i[1] + y, i[2] + z]);
63    let a = lerp(
64        lerp(h(0, 0, 0), h(1, 0, 0), u[0]),
65        lerp(h(0, 1, 0), h(1, 1, 0), u[0]),
66        u[1],
67    );
68    let b = lerp(
69        lerp(h(0, 0, 1), h(1, 0, 1), u[0]),
70        lerp(h(0, 1, 1), h(1, 1, 1), u[0]),
71        u[1],
72    );
73    lerp(a, b, u[2])
74}
75
76pub fn fbm3(p: [f32; 3], octaves: i32, hash: Hash) -> f32 {
77    fbm3_filtered(p, octaves, 0.0, hash)
78}
79
80pub fn octave_weight(footprint: f32) -> f32 {
81    let t = ((footprint - 0.5) * 2.0).clamp(0.0, 1.0);
82    1.0 - t * t * (3.0 - 2.0 * t)
83}
84
85pub fn fbm3_filtered(p: [f32; 3], octaves: i32, footprint: f32, hash: Hash) -> f32 {
86    let mut sum = 0.0;
87    let mut amp = 0.5;
88    let mut q = p;
89    let mut width = footprint;
90    for _ in 0..octaves.max(0) {
91        let weight = octave_weight(width);
92        if weight == 1.0 {
93            sum += amp * value_noise3(q, hash);
94        } else if weight == 0.0 {
95            sum += amp * 0.5;
96        } else {
97            sum += amp * (weight * value_noise3(q, hash) + (1.0 - weight) * 0.5);
98        }
99        q = [q[0] * 2.03 + 1.7, q[1] * 2.03 + 9.2, q[2] * 2.03 + 3.1];
100        amp *= 0.5;
101        width *= 2.03;
102    }
103    sum
104}
105
106pub fn noise2(p: [f32; 2], seed: u32) -> f32 {
107    let i = [p[0].floor() as i32, p[1].floor() as i32];
108    let f = [p[0] - i[0] as f32, p[1] - i[1] as f32];
109    let u = [fade(f[0]), fade(f[1])];
110    let a = lerp(hash2(i, seed), hash2([i[0] + 1, i[1]], seed), u[0]);
111    let b = lerp(
112        hash2([i[0], i[1] + 1], seed),
113        hash2([i[0] + 1, i[1] + 1], seed),
114        u[0],
115    );
116    lerp(a, b, u[1])
117}
118
119pub fn hash21(p: [f32; 2]) -> f32 {
120    let mut q = [fract(p[0] * 123.34), fract(p[1] * 456.21)];
121    let d = q[0] * (q[0] + 45.32) + q[1] * (q[1] + 45.32);
122    q[0] += d;
123    q[1] += d;
124    fract(q[0] * q[1])
125}
126
127pub fn value_noise2_signed(p: [f32; 2]) -> f32 {
128    let i = [p[0].floor(), p[1].floor()];
129    let f = [p[0] - i[0], p[1] - i[1]];
130    let u = [fade(f[0]), fade(f[1])];
131    let h = |x: f32, y: f32| hash21([i[0] + x, i[1] + y]);
132    let a = lerp(h(0.0, 0.0), h(1.0, 0.0), u[0]);
133    let b = lerp(h(0.0, 1.0), h(1.0, 1.0), u[0]);
134    lerp(a, b, u[1]) * 2.0 - 1.0
135}
136
137pub fn flow(p: [f32; 2], time: f32) -> f32 {
138    let warp = [
139        value_noise2_signed([p[0] * 0.006 + time * 0.010, p[1] * 0.006 + 3.1]),
140        value_noise2_signed([p[0] * 0.006 + 7.7, p[1] * 0.006 - time * 0.008]),
141    ];
142    value_noise2_signed([
143        p[0] * 0.009 + warp[0] * 1.6 + time * 0.006,
144        p[1] * 0.009 + warp[1] * 1.6,
145    ]) * 0.65
146        + value_noise2_signed([
147            p[0] * 0.021 - warp[0],
148            p[1] * 0.021 - warp[1] + time * 0.009,
149        ]) * 0.35
150}
151
152fn lattice_plane_x(i: [i32; 3]) -> [f32; 4] {
153    [
154        hash3_xor(i),
155        hash3_xor([i[0], i[1] + 1, i[2]]),
156        hash3_xor([i[0], i[1], i[2] + 1]),
157        hash3_xor([i[0], i[1] + 1, i[2] + 1]),
158    ]
159}
160
161fn lattice_plane_y(i: [i32; 3]) -> [f32; 4] {
162    [
163        hash3_xor(i),
164        hash3_xor([i[0] + 1, i[1], i[2]]),
165        hash3_xor([i[0], i[1], i[2] + 1]),
166        hash3_xor([i[0] + 1, i[1], i[2] + 1]),
167    ]
168}
169
170fn lattice_plane_z(i: [i32; 3]) -> [f32; 4] {
171    [
172        hash3_xor(i),
173        hash3_xor([i[0] + 1, i[1], i[2]]),
174        hash3_xor([i[0], i[1] + 1, i[2]]),
175        hash3_xor([i[0] + 1, i[1] + 1, i[2]]),
176    ]
177}
178
179fn bilerp4(p: [f32; 4], u: f32, v: f32) -> f32 {
180    lerp(lerp(p[0], p[1], u), lerp(p[2], p[3], u), v)
181}
182
183pub fn value_noise3_gradient(p: [f32; 3], scale: f32) -> [f32; 3] {
184    let q = p.map(|v| v * scale);
185    let i = q.map(|v| v.floor() as i32);
186    let f = q.map(|v| v - v.floor());
187    let [u, v, w] = f.map(fade);
188    let left = q.map(|v| (v - 0.35).floor() != v.floor());
189    let right = q.map(|v| (v + 0.35).floor() != v.floor());
190    let x0 = lattice_plane_x(i);
191    let x1 = lattice_plane_x([i[0] + 1, i[1], i[2]]);
192    let xlo = if left[0] {
193        lattice_plane_x([i[0] - 1, i[1], i[2]])
194    } else {
195        x0
196    };
197    let xhi = if right[0] {
198        lattice_plane_x([i[0] + 2, i[1], i[2]])
199    } else {
200        x1
201    };
202    let xminus = lerp(
203        bilerp4(xlo, v, w),
204        bilerp4(if left[0] { x0 } else { x1 }, v, w),
205        fade(fract(q[0] - 0.35)),
206    );
207    let xplus = lerp(
208        bilerp4(if right[0] { x1 } else { x0 }, v, w),
209        bilerp4(xhi, v, w),
210        fade(fract(q[0] + 0.35)),
211    );
212    let y0 = [x0[0], x1[0], x0[2], x1[2]];
213    let y1 = [x0[1], x1[1], x0[3], x1[3]];
214    let ylo = if left[1] {
215        lattice_plane_y([i[0], i[1] - 1, i[2]])
216    } else {
217        y0
218    };
219    let yhi = if right[1] {
220        lattice_plane_y([i[0], i[1] + 2, i[2]])
221    } else {
222        y1
223    };
224    let yminus = lerp(
225        bilerp4(ylo, u, w),
226        bilerp4(if left[1] { y0 } else { y1 }, u, w),
227        fade(fract(q[1] - 0.35)),
228    );
229    let yplus = lerp(
230        bilerp4(if right[1] { y1 } else { y0 }, u, w),
231        bilerp4(yhi, u, w),
232        fade(fract(q[1] + 0.35)),
233    );
234    let z0 = [x0[0], x1[0], x0[1], x1[1]];
235    let z1 = [x0[2], x1[2], x0[3], x1[3]];
236    let zlo = if left[2] {
237        lattice_plane_z([i[0], i[1], i[2] - 1])
238    } else {
239        z0
240    };
241    let zhi = if right[2] {
242        lattice_plane_z([i[0], i[1], i[2] + 2])
243    } else {
244        z1
245    };
246    let zminus = lerp(
247        bilerp4(zlo, u, v),
248        bilerp4(if left[2] { z0 } else { z1 }, u, v),
249        fade(fract(q[2] - 0.35)),
250    );
251    let zplus = lerp(
252        bilerp4(if right[2] { z1 } else { z0 }, u, v),
253        bilerp4(zhi, u, v),
254        fade(fract(q[2] + 0.35)),
255    );
256    [
257        (xplus - xminus) / 0.7,
258        (yplus - yminus) / 0.7,
259        (zplus - zminus) / 0.7,
260    ]
261}
262
263#[cfg(test)]
264mod tests {
265    use super::{Hash, fbm3, fbm3_filtered, octave_weight, value_noise3, value_noise3_gradient};
266
267    #[test]
268    fn wobble_reuses_hashes_without_changing_the_noise() {
269        for sample in 0..1000 {
270            let p = [
271                sample as f32 * 0.013 - 6.0,
272                sample as f32 * 0.007 - 2.0,
273                sample as f32 * 0.031 - 9.0,
274            ];
275            let q = p.map(|v| v * 640.0);
276            let n =
277                |x: f32, y: f32, z: f32| value_noise3([q[0] + x, q[1] + y, q[2] + z], Hash::Xor);
278            let expected = [
279                (n(0.35, 0.0, 0.0) - n(-0.35, 0.0, 0.0)) / 0.7,
280                (n(0.0, 0.35, 0.0) - n(0.0, -0.35, 0.0)) / 0.7,
281                (n(0.0, 0.0, 0.35) - n(0.0, 0.0, -0.35)) / 0.7,
282            ];
283            let actual = value_noise3_gradient(p, 640.0);
284            for axis in 0..3 {
285                assert!(
286                    (actual[axis] - expected[axis]).abs() < 0.0001,
287                    "sample {sample} axis {axis}: actual {} expected {} q {q:?}",
288                    actual[axis],
289                    expected[axis]
290                );
291            }
292        }
293    }
294
295    #[test]
296    fn filtered_octaves_are_continuous_and_deterministic_across_zoom() {
297        let p = [0.212, -0.354, 0.477];
298        assert_eq!(
299            fbm3_filtered(p, 4, 0.0, Hash::Nested),
300            fbm3(p, 4, Hash::Nested)
301        );
302        let mut previous = 1.0;
303        for step in 0..=1000 {
304            let footprint = step as f32 * 0.002;
305            let weight = octave_weight(footprint);
306            assert!(weight <= previous);
307            previous = weight;
308            let left = fbm3_filtered(p, 4, footprint, Hash::Nested);
309            let right = fbm3_filtered(p, 4, footprint + 0.00001, Hash::Nested);
310            assert!((left - right).abs() < 0.0001);
311            assert_eq!(left, fbm3_filtered(p, 4, footprint, Hash::Nested));
312        }
313        assert_eq!(octave_weight(0.5), 1.0);
314        assert_eq!(octave_weight(1.0), 0.0);
315    }
316}