funutd/
noise.rs

1//! Isotropic value and gradient noises.
2
3use super::ease::*;
4use super::hash::*;
5use super::map3base::*;
6use super::math::*;
7use super::*;
8extern crate alloc;
9use alloc::{boxed::Box, string::String};
10
11/// Roughly isotropic value noise.
12#[derive(Clone)]
13pub struct VNoise<H: Hasher> {
14    seed: u64,
15    frequency: f32,
16    hasher: H,
17    ease: Ease,
18}
19
20pub fn vnoise<H: 'static + Hasher>(
21    seed: u64,
22    frequency: f32,
23    ease: Ease,
24    hasher: H,
25) -> Box<dyn Texture> {
26    Box::new(VNoise {
27        seed,
28        frequency,
29        ease,
30        hasher,
31    })
32}
33
34pub fn vnoise_basis<H: 'static + Hasher>(seed: u64, ease: Ease, hasher: H) -> Box<dyn Texture> {
35    Box::new(VNoise {
36        seed,
37        frequency: 1.0,
38        ease,
39        hasher,
40    })
41}
42
43impl<H: Hasher> Texture for VNoise<H> {
44    fn at_frequency(&self, point: Vec3a, frequency: Option<f32>) -> Vec3a {
45        let frequency = frequency.unwrap_or(self.frequency);
46        let basis = self.hasher.query(self.seed, frequency, point);
47        let mut result = Vec3a::zero();
48
49        for dx in -1..=1 {
50            let hx = self.hasher.hash_x(&basis, 0, dx);
51            for dy in -1..=1 {
52                let hxy = self.hasher.hash_y(&basis, hx, dy);
53                let mut offset = Vec3a::new(dx as f32, dy as f32, 0.0) - basis.d;
54                for dz in -1..=1 {
55                    let mut hash = self.hasher.hash_z(&basis, hxy, dz);
56                    // Pick number of cells as a rough approximation to a Poisson distribution.
57                    let n = match hash & 7 {
58                        0 => 0,
59                        1 | 2 | 3 => 1,
60                        4 | 5 | 6 => 2,
61                        _ => 3,
62                    };
63                    // Offset points from cell corner to queried point.
64                    offset = vec3a(offset.x, offset.y, dz as f32 - basis.d.z);
65                    for i in 0..n {
66                        // Feature location.
67                        let p = hash_01(hash);
68                        let distance2: f32 = (p + offset).length_squared();
69                        // Feature radius is always 1 here, which is the maximum.
70                        let radius: f32 = 1.0;
71                        if distance2 < radius * radius {
72                            let distance = sqrt(distance2) / radius;
73                            let color = hash_11(hash);
74                            let blend = self.ease.at(1.0 - distance);
75                            result += color * blend;
76                        }
77                        if i + 1 < n {
78                            hash = hash64c(hash);
79                        }
80                    }
81                }
82            }
83        }
84        result
85    }
86
87    fn get_code(&self) -> String {
88        format!(
89            "vnoise({}, {}, {}, {})",
90            self.seed,
91            self.frequency,
92            self.ease.get_code(),
93            self.hasher.get_code()
94        )
95    }
96
97    fn get_basis_code(&self) -> String {
98        format!(
99            "vnoise_basis({}, {}, {})",
100            self.seed,
101            self.ease.get_code(),
102            self.hasher.get_code()
103        )
104    }
105}
106
107/// Roughly isotropic gradient noise.
108#[derive(Clone)]
109pub struct Noise<H: Hasher> {
110    seed: u64,
111    frequency: f32,
112    hasher: H,
113}
114
115pub fn noise<H: 'static + Hasher>(seed: u64, frequency: f32, hasher: H) -> Box<dyn Texture> {
116    Box::new(Noise {
117        seed,
118        frequency,
119        hasher,
120    })
121}
122
123pub fn noise_basis<H: 'static + Hasher>(seed: u64, hasher: H) -> Box<dyn Texture> {
124    Box::new(Noise {
125        seed,
126        frequency: 1.0,
127        hasher,
128    })
129}
130
131impl<H: Hasher> Texture for Noise<H> {
132    fn at_frequency(&self, point: Vec3a, frequency: Option<f32>) -> Vec3a {
133        let frequency = frequency.unwrap_or(self.frequency);
134        let basis = self.hasher.query(self.seed, frequency, point);
135        let mut result = Vec3a::zero();
136
137        for dx in -1..=1 {
138            let hx = self.hasher.hash_x(&basis, 0, dx);
139            for dy in -1..=1 {
140                let hxy = self.hasher.hash_y(&basis, hx, dy);
141                let mut offset = Vec3a::new(dx as f32, dy as f32, 0.0) - basis.d;
142                for dz in -1..=1 {
143                    let mut hash = self.hasher.hash_z(&basis, hxy, dz);
144                    // Pick number of cells as a rough approximation to a Poisson distribution.
145                    let n = match hash & 7 {
146                        0 | 1 | 2 => 1,
147                        3 | 4 | 5 => 2,
148                        _ => 3,
149                    };
150                    // Offset points from cell corner to queried point.
151                    offset = vec3a(offset.x, offset.y, dz as f32 - basis.d.z);
152                    for i in 0..n {
153                        // Feature location.
154                        let p = hash_01(hash);
155                        let distance2: f32 = (p + offset).length_squared();
156                        // Feature radius is always 1 here, which is the maximum.
157                        let radius: f32 = 1.0;
158                        if distance2 < radius * radius {
159                            let distance = sqrt(distance2) / radius;
160                            let color = hash_11(hash);
161                            let gradient = hash_unit(hash64d(hash));
162                            let blend = 1.0 - smooth5(distance);
163                            result += color * blend * gradient.dot(p + offset);
164                        }
165                        if i + 1 < n {
166                            hash = hash64c(hash);
167                        }
168                    }
169                }
170            }
171        }
172        result * 3.0
173    }
174
175    fn get_code(&self) -> String {
176        format!(
177            "noise({}, {}, {})",
178            self.seed,
179            self.frequency,
180            self.hasher.get_code()
181        )
182    }
183
184    fn get_basis_code(&self) -> String {
185        format!("noise_basis({}, {})", self.seed, self.hasher.get_code())
186    }
187}