solstice_2d/shared/
noise_texture.rs

1// ported from https://github.com/blackears/PerlinNoiseMaker/blob/master/noiseMaker.js
2
3struct Random {
4    seed: i32,
5    m: i32,
6    a: i32,
7    q: i32,
8    r: i32,
9}
10
11impl Random {
12    fn with_seed(seed: i32) -> Self {
13        let seed = Self::validate_seed(seed);
14        let m = i32::max_value();
15        let a = 16807; //7^5; primitive root of m
16        let q = 127773; // m / a
17        let r = 2836; // m % a
18        Self { seed, m, a, q, r }
19    }
20
21    fn validate_seed(seed: i32) -> i32 {
22        if seed <= 0 {
23            -(seed % (i32::max_value() - 1)) + 1
24        } else if seed > i32::max_value() - 1 {
25            i32::max_value() - 1
26        } else {
27            seed
28        }
29    }
30
31    fn next_long(&mut self) -> i32 {
32        let res = self.a * (self.seed % self.q) - self.r * (self.seed / self.q);
33        let res = if res <= 0 { res + self.m } else { res };
34        self.seed = res;
35        res
36    }
37
38    fn next(&mut self) -> f32 {
39        self.next_long() as f32 / self.m as f32
40    }
41}
42
43struct PerlinSampler {
44    width: usize,
45    height: usize,
46    gradients: Vec<f32>,
47}
48
49impl PerlinSampler {
50    pub fn new(width: usize, height: usize, seed: i32) -> Self {
51        let mut rng = Random::with_seed(seed);
52        let mut gradients = Vec::with_capacity(width * height * 2);
53        const TAU: f32 = std::f32::consts::PI * 2.;
54        for _i in (0..(width * height * 2)).step_by(2) {
55            let phi = rng.next() * TAU;
56            let (x, y) = phi.sin_cos();
57            gradients.push(x);
58            gradients.push(y);
59        }
60        Self {
61            width,
62            height,
63            gradients,
64        }
65    }
66
67    pub fn dot(&self, x_cell: usize, y_cell: usize, vx: f32, vy: f32) -> f32 {
68        let offset = (x_cell + y_cell * self.width) * 2;
69        let wx = self.gradients[offset];
70        let wy = self.gradients[offset + 1];
71        wx * vx + wy * vy
72    }
73
74    pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
75        a + t * (b - a)
76    }
77
78    pub fn s_curve(t: f32) -> f32 {
79        t * t * (3. - 2. * t)
80    }
81
82    pub fn get(&self, x: f32, y: f32) -> f32 {
83        let x_cell = x.trunc() as usize;
84        let y_cell = y.trunc() as usize;
85        let x_fract = x.fract();
86        let y_fract = y.fract();
87
88        let x0 = x_cell;
89        let y0 = y_cell;
90        let x1 = if x_cell == (self.width - 1) {
91            0
92        } else {
93            x_cell + 1
94        };
95        let y1 = if y_cell == (self.height - 1) {
96            0
97        } else {
98            y_cell + 1
99        };
100
101        let v00 = self.dot(x0, y0, x_fract, y_fract);
102        let v10 = self.dot(x1, y0, x_fract - 1., y_fract);
103        let v01 = self.dot(x0, y1, x_fract, y_fract - 1.);
104        let v11 = self.dot(x1, y1, x_fract - 1., y_fract - 1.);
105
106        let vx0 = Self::lerp(v00, v10, Self::s_curve(x_fract));
107        let vx1 = Self::lerp(v01, v11, Self::s_curve(x_fract));
108
109        return Self::lerp(vx0, vx1, Self::s_curve(y_fract));
110    }
111}
112
113#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Default)]
114pub struct PerlinTextureSettings {
115    pub seed: i32,
116    pub width: usize,
117    pub height: usize,
118    pub period: u32,
119    pub levels: u32,
120    pub attenuation: ordered_float::NotNan<f32>,
121    pub color: bool,
122}
123
124fn raster_to_bytes(raster: Vec<f32>) -> Vec<u8> {
125    let mut bytes = vec![0u8; raster.len()];
126    for (p, f) in bytes.iter_mut().zip(raster.into_iter()) {
127        *p = (((f + 1.) / 2.) * 255.).round() as u8;
128    }
129    bytes
130}
131
132fn bytes(settings: PerlinTextureSettings) -> Vec<u8> {
133    let PerlinTextureSettings {
134        seed,
135        width,
136        height,
137        period,
138        levels,
139        attenuation,
140        color,
141    } = settings;
142    let attenuation = attenuation.into_inner();
143    let num_channels = if color { 3 } else { 1 };
144    let mut raster = vec![0f32; width * height * num_channels];
145    for channel in 0..num_channels {
146        let mut local_period_inv = 1. / period as f32;
147        let mut freq_inv = 1f32;
148        let mut atten = 1.;
149        let mut weight = 0f32;
150
151        for level in 0..levels {
152            let sampler = PerlinSampler::new(
153                (width as f32 * local_period_inv).ceil() as usize,
154                (height as f32 * local_period_inv).ceil() as usize,
155                seed * 100 + channel as i32 * 10 + level as i32,
156            );
157            for y in 0..height {
158                for x in 0..width {
159                    let val = sampler.get(x as f32 * local_period_inv, y as f32 * local_period_inv);
160                    raster[(x + y * width) * num_channels + channel] += val * freq_inv.powf(atten);
161                }
162            }
163            weight += freq_inv.powf(atten);
164            freq_inv *= 0.5;
165            local_period_inv *= 2.;
166            atten *= attenuation;
167        }
168
169        let weight_inv = 1. / weight;
170        for y in 0..height {
171            for x in 0..width {
172                raster[(x + y * width) * num_channels + channel] *= weight_inv;
173            }
174        }
175    }
176
177    raster_to_bytes(raster)
178}
179
180pub fn create_perlin_texture(
181    gl: &mut solstice::Context,
182    settings: PerlinTextureSettings,
183) -> crate::ImageResult {
184    let width = settings.width;
185    let height = settings.height;
186    let format = if settings.color {
187        solstice::PixelFormat::RGB8
188    } else {
189        solstice::PixelFormat::LUMINANCE
190    };
191    let bytes = bytes(settings);
192
193    use solstice::image::*;
194    use solstice::texture::*;
195    Image::with_data(
196        gl,
197        TextureType::Tex2D,
198        format,
199        width as u32,
200        height as u32,
201        bytes.as_slice(),
202        Settings {
203            mipmaps: false,
204            filter: FilterMode::Linear,
205            wrap: WrapMode::Repeat,
206            ..Settings::default()
207        },
208    )
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn random_test() {
217        let mut rng = Random::with_seed(1);
218        assert_eq!(rng.next_long(), 16807);
219        assert_eq!(rng.next_long(), 282475249);
220        assert_eq!(rng.next_long(), 1622650073);
221
222        assert_eq!(rng.next(), 0.4586501319234493);
223        assert_eq!(rng.next(), 0.5327672374121692);
224        assert_eq!(rng.next(), 0.21895918632809036);
225    }
226
227    #[test]
228    fn sampler_test() {
229        let sampler = PerlinSampler::new(2, 2, 0);
230
231        assert_eq!(CONTROL_GRADIENTS_4X4.len(), sampler.gradients.len());
232        for (a, b) in CONTROL_GRADIENTS_4X4.iter().zip(sampler.gradients.iter()) {
233            // epsilon doesn't work. the sin/cos operations are too variable i think
234            assert!((a - b).abs() <= 0.00001, "{} != {}", a, b);
235        }
236
237        assert_eq!(sampler.get(0., 0.), 0.);
238        assert_eq!(sampler.get(0.5, 0.0), -0.18387488976327257);
239        assert_eq!(sampler.get(1., 0.), 0.);
240        assert_eq!(sampler.get(1.5, 0.0), 0.18387488976327257);
241
242        assert!((sampler.get(0., 0.5) - 0.24119700031647284).abs() <= std::f32::EPSILON);
243        assert!((sampler.get(1.5, 1.0) - 0.31406892987757684).abs() <= std::f32::EPSILON);
244    }
245
246    #[test]
247    fn black_and_white_raster_test() {
248        fn dup_channel(bytes: Vec<u8>) -> Vec<u8> {
249            let mut new_bytes = Vec::with_capacity(bytes.len() * 4);
250            for byte in bytes {
251                new_bytes.push(byte);
252                new_bytes.push(byte);
253                new_bytes.push(byte);
254                new_bytes.push(255);
255            }
256            new_bytes
257        }
258
259        let settings = PerlinTextureSettings {
260            seed: 0,
261            width: 4,
262            height: 4,
263            period: 2,
264            levels: 1,
265            attenuation: std::convert::TryInto::try_into(0.0).unwrap(),
266            color: false,
267        };
268        let bytes = dup_channel(bytes(settings));
269        assert_eq!(&SEED0_CELL2_LEVEL1_4X4_BW[..], bytes.as_slice());
270    }
271
272    #[test]
273    fn color_raster_test() {
274        fn add_alpha(bytes: Vec<u8>) -> Vec<u8> {
275            let mut new_bytes = Vec::with_capacity(bytes.len() / 3);
276            for byte in bytes.chunks_exact(3) {
277                new_bytes.extend_from_slice(byte);
278                new_bytes.push(255);
279            }
280            new_bytes
281        }
282
283        let settings = PerlinTextureSettings {
284            seed: 0,
285            width: 4,
286            height: 4,
287            period: 2,
288            levels: 1,
289            attenuation: std::convert::TryInto::try_into(0.0).unwrap(),
290            color: true,
291        };
292        let bytes = add_alpha(bytes(settings));
293        assert_eq!(&SEED0_CELL2_LEVEL1_4X4_COLOR[..], bytes.as_slice());
294    }
295
296    const CONTROL_GRADIENTS_4X4: [f32; 8] = [
297        0.00004917452831956654,
298        0.9999999987909329,
299        0.7355487335814098,
300        0.6774718153006694,
301        -0.9993798653316448,
302        0.03521199752504148,
303        0.25689585417866245,
304        -0.9664390928071026,
305    ];
306
307    const SEED0_CELL2_LEVEL1_4X4_BW: [u8; 64] = [
308        128, 128, 128, 255, 104, 104, 104, 255, 128, 128, 128, 255, 151, 151, 151, 255, 158, 158,
309        158, 255, 137, 137, 137, 255, 180, 180, 180, 255, 201, 201, 201, 255, 128, 128, 128, 255,
310        87, 87, 87, 255, 128, 128, 128, 255, 168, 168, 168, 255, 97, 97, 97, 255, 54, 54, 54, 255,
311        75, 75, 75, 255, 118, 118, 118, 255,
312    ];
313
314    const SEED0_CELL2_LEVEL1_4X4_COLOR: [u8; 64] = [
315        128, 128, 128, 255, 104, 98, 151, 255, 128, 128, 128, 255, 151, 157, 104, 255, 158, 189,
316        135, 255, 137, 154, 121, 255, 180, 142, 91, 255, 201, 178, 105, 255, 128, 128, 128, 255,
317        87, 133, 120, 255, 128, 128, 128, 255, 168, 122, 135, 255, 97, 66, 120, 255, 54, 77, 150,
318        255, 75, 113, 164, 255, 118, 101, 134, 255,
319    ];
320}