1mod advanced;
35mod film;
36
37pub use advanced::{DomainWarp, domain_warp, perlin3, ridged2, simplex2, turbulence2, worley2};
38
39pub use film::{film_grain, film_grain_multi};
40
41const GRADIENTS: [[f64; 2]; 12] = [
42 [1.0, 0.0],
43 [0.8660254037844386, 0.5],
44 [0.5, 0.8660254037844386],
45 [0.0, 1.0],
46 [-0.5, 0.8660254037844386],
47 [-0.8660254037844386, 0.5],
48 [-1.0, 0.0],
49 [-0.8660254037844386, -0.5],
50 [-0.5, -0.8660254037844386],
51 [0.0, -1.0],
52 [0.5, -0.8660254037844386],
53 [0.8660254037844386, -0.5],
54];
55
56pub fn hash32(mut value: u32) -> u32 {
58 value ^= value >> 16;
59 value = value.wrapping_mul(0x7feb_352d);
60 value ^= value >> 15;
61 value = value.wrapping_mul(0x846c_a68b);
62 value ^ (value >> 16)
63}
64
65pub fn hash2(horizontal: i32, vertical: i32, seed: u32) -> u32 {
67 hash32(
68 (horizontal as u32).wrapping_mul(0x9e37_79b9)
69 ^ (vertical as u32).wrapping_mul(0x85eb_ca6b)
70 ^ seed,
71 )
72}
73
74pub fn value2(point: [f64; 2], seed: u32) -> f64 {
77 if !lattice_supported(point) {
78 return 0.0;
79 }
80 let cell = [point[0].floor(), point[1].floor()];
81 let amount = [point[0] - cell[0], point[1] - cell[1]]
82 .map(|value| value * value * value * (value * (value * 6.0 - 15.0) + 10.0));
83 let value = |horizontal: i32, vertical: i32| {
84 f64::from(hash2(horizontal, vertical, seed)) / f64::from(u32::MAX) * 2.0 - 1.0
85 };
86 let horizontal = cell[0] as i32;
87 let vertical = cell[1] as i32;
88 let top = value(horizontal, vertical) * (1.0 - amount[0])
89 + value(horizontal.wrapping_add(1), vertical) * amount[0];
90 let bottom = value(horizontal, vertical.wrapping_add(1)) * (1.0 - amount[0])
91 + value(horizontal.wrapping_add(1), vertical.wrapping_add(1)) * amount[0];
92 top * (1.0 - amount[1]) + bottom * amount[1]
93}
94
95pub fn hash3(horizontal: i32, vertical: i32, depth: i32, seed: u32) -> u32 {
97 hash32(
98 (horizontal as u32).wrapping_mul(0x9e37_79b9)
99 ^ (vertical as u32).wrapping_mul(0x85eb_ca6b)
100 ^ (depth as u32).wrapping_mul(0xc2b2_ae35)
101 ^ seed,
102 )
103}
104
105fn fade(value: f64) -> (f64, f64) {
106 (
107 value * value * value * (value * (value * 6.0 - 15.0) + 10.0),
108 30.0 * value * value * (value - 1.0) * (value - 1.0),
109 )
110}
111
112pub fn value2_d(point: [f64; 2], seed: u32) -> (f64, [f64; 2]) {
115 if !lattice_supported(point) {
116 return (0.0, [0.0; 2]);
117 }
118 let cell = point.map(f64::floor);
119 let (horizontal, dh) = fade(point[0] - cell[0]);
120 let (vertical, dv) = fade(point[1] - cell[1]);
121 let sample = |dx: i32, dy: i32| {
122 f64::from(hash2(
123 (cell[0] as i32).wrapping_add(dx),
124 (cell[1] as i32).wrapping_add(dy),
125 seed,
126 )) / f64::from(u32::MAX)
127 * 2.0
128 - 1.0
129 };
130 let samples = [sample(0, 0), sample(1, 0), sample(0, 1), sample(1, 1)];
131 let top = samples[0] * (1.0 - horizontal) + samples[1] * horizontal;
132 let bottom = samples[2] * (1.0 - horizontal) + samples[3] * horizontal;
133 (
134 value2(point, seed),
135 [
136 dh * ((samples[1] - samples[0]) * (1.0 - vertical)
137 + (samples[3] - samples[2]) * vertical),
138 dv * (bottom - top),
139 ],
140 )
141}
142
143pub fn lattice_supported(point: [f64; 2]) -> bool {
145 point.into_iter().all(|value| {
146 value.is_finite() && value >= f64::from(i32::MIN) && value < f64::from(i32::MAX)
147 })
148}
149
150pub fn perlin2(point: [f64; 2], seed: u32) -> f64 {
153 perlin2_d(point, seed).0
154}
155
156pub fn curl2(point: [f64; 2], seed: u32) -> [f64; 2] {
159 let derivative = perlin2_d(point, seed).1;
160 [derivative[1], -derivative[0]]
161}
162
163pub fn perlin2_d(point: [f64; 2], seed: u32) -> (f64, [f64; 2]) {
166 if !lattice_supported(point) {
167 return (0.0, [0.0; 2]);
168 }
169 let cell = point.map(f64::floor);
170 let fraction = [point[0] - cell[0], point[1] - cell[1]];
171 let (horizontal, dh) = fade(fraction[0]);
172 let (vertical, dv) = fade(fraction[1]);
173 let corner = |dx: i32, dy: i32| {
174 let gradient = GRADIENTS[(hash2(
175 (cell[0] as i32).wrapping_add(dx),
176 (cell[1] as i32).wrapping_add(dy),
177 seed,
178 ) % 12) as usize];
179 (
180 gradient[0] * (fraction[0] - f64::from(dx))
181 + gradient[1] * (fraction[1] - f64::from(dy)),
182 gradient,
183 )
184 };
185 let corners = [corner(0, 0), corner(1, 0), corner(0, 1), corner(1, 1)];
186 let row = |left: usize, right: usize| {
187 let value = corners[left].0 * (1.0 - horizontal) + corners[right].0 * horizontal;
188 let derivative = [
189 corners[left].1[0] * (1.0 - horizontal)
190 + corners[right].1[0] * horizontal
191 + dh * (corners[right].0 - corners[left].0),
192 corners[left].1[1] * (1.0 - horizontal) + corners[right].1[1] * horizontal,
193 ];
194 (value, derivative)
195 };
196 let top = row(0, 1);
197 let bottom = row(2, 3);
198 (
199 top.0 * (1.0 - vertical) + bottom.0 * vertical,
200 [
201 top.1[0] * (1.0 - vertical) + bottom.1[0] * vertical,
202 top.1[1] * (1.0 - vertical) + bottom.1[1] * vertical + dv * (bottom.0 - top.0),
203 ],
204 )
205}
206
207#[derive(Clone, Copy, Debug, PartialEq)]
209pub struct Fractal {
210 pub octaves: u8,
212 pub lacunarity: f64,
214 pub gain: f64,
216}
217
218impl Default for Fractal {
219 fn default() -> Self {
220 Self {
221 octaves: 4,
222 lacunarity: 2.0,
223 gain: 0.5,
224 }
225 }
226}
227
228impl Fractal {
229 pub fn validate(self) -> Result<(), crate::ValidationError> {
231 if !(1..=8).contains(&self.octaves)
232 || !self.lacunarity.is_finite()
233 || !(1.0..=8.0).contains(&self.lacunarity)
234 || !self.gain.is_finite()
235 || !(0.0..=1.0).contains(&self.gain)
236 {
237 return Err(crate::ValidationError::invalid(
238 "noise",
239 "fractal",
240 "Require 1..=8 octaves, lacunarity 1..=8 and gain 0..=1",
241 ));
242 }
243 Ok(())
244 }
245}
246
247pub fn fbm(
250 sample: impl Fn([f64; 2]) -> f64,
251 mut point: [f64; 2],
252 fractal: Fractal,
253) -> Result<f64, crate::ValidationError> {
254 fractal.validate()?;
255 let mut amplitude = 1.0;
256 let mut total = 0.0;
257 let mut weight = 0.0;
258 for _ in 0..fractal.octaves {
259 if !lattice_supported(point) {
260 return Err(crate::ValidationError::invalid(
261 "noise",
262 "point",
263 "Fractal coordinates exceed signed 32-bit lattice support",
264 ));
265 }
266 let value = sample(point);
267 if !value.is_finite() {
268 return Err(crate::ValidationError::invalid(
269 "noise",
270 "sample",
271 "Noise samples must be finite",
272 ));
273 }
274 total += value * amplitude;
275 weight += amplitude;
276 amplitude *= fractal.gain;
277 point = point.map(|value| value * fractal.lacunarity);
278 }
279 let value = total / weight;
280 if !value.is_finite() {
281 return Err(crate::ValidationError::invalid(
282 "noise",
283 "sample",
284 "Fractal accumulation must remain finite",
285 ));
286 }
287 Ok(value)
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn hashes_and_value_noise_are_stable() {
296 assert_eq!(hash32(0), 0);
297 assert_eq!(hash32(1), 0x6889_90c0);
298 assert_eq!(hash2(-3, 7, 19), 1_084_314_139);
299 assert_eq!(hash3(-3, 7, 1, 19), 3_845_583_026);
300 assert!((value2([-3.4, 2.7], 42) - 0.0184252545877103).abs() < 1e-15);
301 assert!((value2([10.125, -2.75], 42) + 0.4827757548364509).abs() < 1e-15);
302 assert_eq!(hash3(-3, 7, 0, 19), hash2(-3, 7, 19));
303 assert_ne!(hash3(-3, 7, 1, 19), hash3(-3, 7, 0, 19));
304 for point in [[-3.4, 2.7], [0.0, 0.0], [10.125, -2.75]] {
305 assert_eq!(value2(point, 42).to_bits(), value2_d(point, 42).0.to_bits());
306 }
307 }
308
309 #[test]
310 fn analytic_derivatives_match_central_differences_and_join_cells() {
311 for (sample, derivative) in [
312 (
313 value2 as fn([f64; 2], u32) -> f64,
314 value2_d as fn([f64; 2], u32) -> (f64, [f64; 2]),
315 ),
316 (perlin2, perlin2_d),
317 ] {
318 for point in [[-3.4, 2.7], [0.0, 0.0], [10.125, -2.75], [1.0, -2.0]] {
319 let actual = derivative(point, 42).1;
320 for axis in 0..2 {
321 let mut before = point;
322 let mut after = point;
323 before[axis] -= 1e-5;
324 after[axis] += 1e-5;
325 let expected = (sample(after, 42) - sample(before, 42)) / 2e-5;
326 assert!(
327 (actual[axis] - expected).abs() < 1e-7,
328 "{point:?} {axis}: {actual:?} {expected}"
329 );
330 }
331 }
332 let before = derivative([1.0 - 1e-8, 0.37], 17);
333 let after = derivative([1.0 + 1e-8, 0.37], 17);
334 assert!((before.0 - after.0).abs() < 1e-7);
335 for axis in 0..2 {
336 assert!((before.1[axis] - after.1[axis]).abs() < 1e-6);
337 }
338 }
339 }
340
341 #[test]
342 fn noise_statistics_and_fractal_limits() {
343 for sample in [value2 as fn([f64; 2], u32) -> f64, perlin2] {
344 let mut sum = 0.0;
345 let mut squared = 0.0;
346 for index in 0..8192 {
347 let value = sample(
348 [f64::from(index % 128) * 0.37, f64::from(index / 128) * 0.41],
349 733,
350 );
351 assert!(value.is_finite() && value.abs() <= 1.0);
352 sum += value;
353 squared += value * value;
354 }
355 assert!((sum / 8192.0).abs() < 0.06);
356 assert!(squared / 8192.0 > 0.01);
357 }
358 assert_eq!(fbm(|_| 0.75, [0.0; 2], Fractal::default()).unwrap(), 0.75);
359 for octaves in [0, 9, 255] {
360 assert!(
361 fbm(
362 |_| 0.0,
363 [0.0; 2],
364 Fractal {
365 octaves,
366 ..Fractal::default()
367 }
368 )
369 .is_err()
370 );
371 }
372 assert!(fbm(|_| f64::NAN, [0.0; 2], Fractal::default()).is_err());
373 assert!(fbm(|_| 0.0, [f64::INFINITY, 0.0], Fractal::default()).is_err());
374 assert!(fbm(|_| 0.0, [1e9; 2], Fractal::default()).is_err());
375 }
376}