noise_functions/base/
open_simplex_2.rs

1use core::num::Wrapping;
2#[cfg(feature = "nightly-simd")]
3use core::simd::{f32x2, f32x4, num::SimdFloat};
4
5use crate::{
6    from_open_simplex_2::fast_luts::{GRADIENTS_2D, GRADIENTS_3D, GRADIENTS_4D},
7    open_simplex_2::impl_open_simplex_noise,
8    OpenSimplexNoise,
9};
10
11#[cfg(feature = "nightly-simd")]
12use crate::math::splat;
13
14/// 2/3/4 dimensional OpenSimplex2 noise. Fast variant.
15///
16/// You can improve the visual isotropy for certain orientations using the `improve_*` methods
17/// provided by the [`OpenSimplexNoise`] trait.
18#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
19pub struct OpenSimplex2;
20
21impl_open_simplex_noise!(234 OpenSimplex2);
22
23impl OpenSimplexNoise for OpenSimplex2 {
24    #[inline]
25    fn raw_sample2(&self, [xs, ys]: [f32; 2], seed: i32) -> f32 {
26        let seed = Wrapping(seed as i64);
27
28        // Get base points and offsets.
29        let xsb = fast_floor(xs);
30        let ysb = fast_floor(ys);
31        let xi = (xs - xsb as f32) as f32;
32        let yi = (ys - ysb as f32) as f32;
33
34        // Prime pre-multiplication for hash.
35        let xsbp = Wrapping(xsb as i64) * Wrapping(PRIME_X);
36        let ysbp = Wrapping(ysb as i64) * Wrapping(PRIME_Y);
37
38        // Unskew.
39        let t = (xi + yi) * UNSKEW_2D;
40        let dx0 = xi + t;
41        let dy0 = yi + t;
42
43        // First vertex.
44        let mut value = 0.0;
45        let a0 = RSQUARED_2D - dx0 * dx0 - dy0 * dy0;
46        if a0 > 0.0 {
47            value = (a0 * a0) * (a0 * a0) * grad2(seed, xsbp, ysbp, dx0, dy0);
48        }
49
50        // Second vertex.
51        let a1 = (2.0 * (1.0 + 2.0 * UNSKEW_2D) * (1.0 / UNSKEW_2D + 2.0)) * t + ((-2.0 * (1.0 + 2.0 * UNSKEW_2D) * (1.0 + 2.0 * UNSKEW_2D)) + a0);
52        if a1 > 0.0 {
53            let dx1 = dx0 - (1.0 + 2.0 * UNSKEW_2D);
54            let dy1 = dy0 - (1.0 + 2.0 * UNSKEW_2D);
55            value += (a1 * a1) * (a1 * a1) * grad2(seed, xsbp + Wrapping(PRIME_X), ysbp + Wrapping(PRIME_Y), dx1, dy1);
56        }
57
58        // Third vertex.
59        if dy0 > dx0 {
60            let dx2 = dx0 - UNSKEW_2D;
61            let dy2 = dy0 - (UNSKEW_2D + 1.0);
62            let a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;
63            if a2 > 0.0 {
64                value += (a2 * a2) * (a2 * a2) * grad2(seed, xsbp, ysbp + Wrapping(PRIME_Y), dx2, dy2);
65            }
66        } else {
67            let dx2 = dx0 - (UNSKEW_2D + 1.0);
68            let dy2 = dy0 - UNSKEW_2D;
69            let a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;
70            if a2 > 0.0 {
71                value += (a2 * a2) * (a2 * a2) * grad2(seed, xsbp + Wrapping(PRIME_X), ysbp, dx2, dy2);
72            }
73        }
74
75        value
76    }
77
78    #[inline]
79    #[cfg(feature = "nightly-simd")]
80    fn raw_sample2a(&self, point: f32x2, seed: i32) -> f32 {
81        self.raw_sample2(point.into(), seed)
82    }
83
84    #[inline]
85    fn raw_sample3(&self, [xr, yr, zr]: [f32; 3], seed: i32) -> f32 {
86        let mut seed = Wrapping(seed as i64);
87
88        // Get base points and offsets.
89        let xrb = fast_round(xr);
90        let yrb = fast_round(yr);
91        let zrb = fast_round(zr);
92        let mut xri = (xr - xrb as f32) as f32;
93        let mut yri = (yr - yrb as f32) as f32;
94        let mut zri = (zr - zrb as f32) as f32;
95
96        // -1 if positive, 1 if negative.
97        let mut x_nsign = (-1.0 - xri) as i32 | 1;
98        let mut y_nsign = (-1.0 - yri) as i32 | 1;
99        let mut z_nsign = (-1.0 - zri) as i32 | 1;
100
101        // Compute absolute values, using the above as a shortcut. This was faster in my tests for some reason.
102        let mut ax0 = x_nsign as f32 * -xri;
103        let mut ay0 = y_nsign as f32 * -yri;
104        let mut az0 = z_nsign as f32 * -zri;
105
106        // Prime pre-multiplication for hash.
107        let mut xrbp = Wrapping(xrb as i64) * Wrapping(PRIME_X);
108        let mut yrbp = Wrapping(yrb as i64) * Wrapping(PRIME_Y);
109        let mut zrbp = Wrapping(zrb as i64) * Wrapping(PRIME_Z);
110
111        // Loop: Pick an edge on each lattice copy.
112        let mut value = 0.0;
113        let mut a = (RSQUARED_3D - xri * xri) - (yri * yri + zri * zri);
114        for l in 0.. {
115            // Closest point on cube.
116            if a > 0.0 {
117                value += (a * a) * (a * a) * grad3(seed, xrbp, yrbp, zrbp, xri, yri, zri);
118            }
119
120            // Second-closest point.
121            if ax0 >= ay0 && ax0 >= az0 {
122                let mut b = a + ax0 + ax0;
123                if b > 1.0 {
124                    b -= 1.0;
125                    value += (b * b) * (b * b) * grad3(seed, xrbp - Wrapping(x_nsign as i64) * Wrapping(PRIME_X), yrbp, zrbp, xri + x_nsign as f32, yri, zri);
126                }
127            } else if ay0 > ax0 && ay0 >= az0 {
128                let mut b = a + ay0 + ay0;
129                if b > 1.0 {
130                    b -= 1.0;
131                    value += (b * b) * (b * b) * grad3(seed, xrbp, yrbp - Wrapping(y_nsign as i64) * Wrapping(PRIME_Y), zrbp, xri, yri + y_nsign as f32, zri);
132                }
133            } else {
134                let mut b = a + az0 + az0;
135                if b > 1.0 {
136                    b -= 1.0;
137                    value += (b * b) * (b * b) * grad3(seed, xrbp, yrbp, zrbp - Wrapping(z_nsign as i64) * Wrapping(PRIME_Z), xri, yri, zri + z_nsign as f32);
138                }
139            }
140
141            // Break from loop if we're done, skipping updates below.
142            if l == 1 {
143                break;
144            }
145
146            // Update absolute value.
147            ax0 = 0.5 - ax0;
148            ay0 = 0.5 - ay0;
149            az0 = 0.5 - az0;
150
151            // Update relative coordinate.
152            xri = x_nsign as f32 * ax0;
153            yri = y_nsign as f32 * ay0;
154            zri = z_nsign as f32 * az0;
155
156            // Update falloff.
157            a += (0.75 - ax0) - (ay0 + az0);
158
159            // Update prime for hash.
160            xrbp += (x_nsign as i64 >> 1) & PRIME_X;
161            yrbp += (y_nsign as i64 >> 1) & PRIME_Y;
162            zrbp += (z_nsign as i64 >> 1) & PRIME_Z;
163
164            // Update the reverse sign indicators.
165            x_nsign = -x_nsign;
166            y_nsign = -y_nsign;
167            z_nsign = -z_nsign;
168
169            // And finally update the seed for the other lattice copy.
170            seed ^= SEED_FLIP_3D;
171        }
172
173        value
174    }
175
176    #[inline]
177    #[cfg(feature = "nightly-simd")]
178    fn raw_sample3a(&self, point: f32x4, seed: i32) -> f32 {
179        self.raw_sample3(*crate::array_4_take_3(point.as_array()), seed)
180    }
181
182    #[inline]
183    fn raw_sample4(&self, [xs, ys, zs, ws]: [f32; 4], seed: i32) -> f32 {
184        let mut seed = Wrapping(seed as i64);
185
186        // Get base points and offsets
187        let xsb = fast_floor(xs);
188        let ysb = fast_floor(ys);
189        let zsb = fast_floor(zs);
190        let wsb = fast_floor(ws);
191        let mut xsi = (xs - xsb as f32) as f32;
192        let mut ysi = (ys - ysb as f32) as f32;
193        let mut zsi = (zs - zsb as f32) as f32;
194        let mut wsi = (ws - wsb as f32) as f32;
195
196        // Determine which lattice we can be confident has a contributing point its corresponding cell's base simplex.
197        // We only look at the spaces between the diagonal planes. This proved effective in all of my tests.
198        let si_sum = (xsi + ysi) + (zsi + wsi);
199        let starting_lattice = (si_sum * 1.25) as i32;
200
201        // Offset for seed based on first lattice copy.
202        seed += Wrapping(starting_lattice as i64) * Wrapping(SEED_OFFSET_4D);
203
204        // Offset for lattice point relative positions (skewed)
205        let starting_lattice_offset = starting_lattice as f32 * -LATTICE_STEP_4D;
206        xsi += starting_lattice_offset;
207        ysi += starting_lattice_offset;
208        zsi += starting_lattice_offset;
209        wsi += starting_lattice_offset;
210
211        // Prep for vertex contributions.
212        let mut ssi = (si_sum + starting_lattice_offset * 4.0) * UNSKEW_4D;
213
214        // Prime pre-multiplication for hash.
215        let mut xsvp = Wrapping(xsb as i64) * Wrapping(PRIME_X);
216        let mut ysvp = Wrapping(ysb as i64) * Wrapping(PRIME_Y);
217        let mut zsvp = Wrapping(zsb as i64) * Wrapping(PRIME_Z);
218        let mut wsvp = Wrapping(wsb as i64) * Wrapping(PRIME_W);
219
220        // Five points to add, total, from five copies of the A4 lattice.
221        let mut value = 0.0;
222        for i in 0.. {
223            // Next point is the closest vertex on the 4-simplex whose base vertex is the aforementioned vertex.
224            let score0 = 1.0 + ssi * (-1.0 / UNSKEW_4D); // Seems slightly faster than 1.0-xsi-ysi-zsi-wsi
225            if xsi >= ysi && xsi >= zsi && xsi >= wsi && xsi >= score0 {
226                xsvp += PRIME_X;
227                xsi -= 1.0;
228                ssi -= UNSKEW_4D;
229            } else if ysi > xsi && ysi >= zsi && ysi >= wsi && ysi >= score0 {
230                ysvp += PRIME_Y;
231                ysi -= 1.0;
232                ssi -= UNSKEW_4D;
233            } else if zsi > xsi && zsi > ysi && zsi >= wsi && zsi >= score0 {
234                zsvp += PRIME_Z;
235                zsi -= 1.0;
236                ssi -= UNSKEW_4D;
237            } else if wsi > xsi && wsi > ysi && wsi > zsi && wsi >= score0 {
238                wsvp += PRIME_W;
239                wsi -= 1.0;
240                ssi -= UNSKEW_4D;
241            }
242
243            // gradient contribution with falloff.
244            let dx = xsi + ssi;
245            let dy = ysi + ssi;
246            let dz = zsi + ssi;
247            let dw = wsi + ssi;
248            let mut a = (dx * dx + dy * dy) + (dz * dz + dw * dw);
249            if a < RSQUARED_4D {
250                a -= RSQUARED_4D;
251                a *= a;
252                value += a * a * grad4(seed, xsvp, ysvp, zsvp, wsvp, dx, dy, dz, dw);
253            }
254
255            // Break from loop if we're done, skipping updates below.
256            if i == 4 {
257                break;
258            }
259
260            // Update for next lattice copy shifted down by <-0.2, -0.2, -0.2, -0.2>.
261            xsi += LATTICE_STEP_4D;
262            ysi += LATTICE_STEP_4D;
263            zsi += LATTICE_STEP_4D;
264            wsi += LATTICE_STEP_4D;
265            ssi += LATTICE_STEP_4D * 4.0 * UNSKEW_4D;
266            seed -= SEED_OFFSET_4D;
267
268            // Because we don't always start on the same lattice copy, there's a special reset case.
269            if i == starting_lattice {
270                xsvp -= PRIME_X;
271                ysvp -= PRIME_Y;
272                zsvp -= PRIME_Z;
273                wsvp -= PRIME_W;
274                seed += SEED_OFFSET_4D * 5;
275            }
276        }
277
278        value
279    }
280
281    #[inline]
282    #[cfg(feature = "nightly-simd")]
283    fn raw_sample4a(&self, point: f32x4, seed: i32) -> f32 {
284        self.raw_sample4(point.into(), seed)
285    }
286
287    #[inline(always)]
288    fn raw_improve2_x(&self, [x, y]: [f32; 2]) -> [f32; 2] {
289        // Skew transform and rotation baked into one.
290        let xx = x * ROOT2OVER2;
291        let yy = y * (ROOT2OVER2 * (1.0 + 2.0 * SKEW_2D));
292
293        [yy + xx, yy - xx]
294    }
295
296    #[inline(always)]
297    #[cfg(feature = "nightly-simd")]
298    fn raw_improve2a_x(&self, point: f32x2) -> f32x2 {
299        self.raw_improve2_x(point.into()).into()
300    }
301
302    #[doc(hidden)]
303    fn raw_improve3_xy(&self, [x, y, z]: [f32; 3]) -> [f32; 3] {
304        // Re-orient the cubic lattices without skewing, so Z points up the main lattice diagonal,
305        // and the planes formed by XY are moved far out of alignment with the cube faces.
306        // Orthonormal rotation. Not a skew transform.
307        let xy = x + y;
308        let s2 = xy * ROTATE_3D_ORTHOGONALIZER;
309        let zz = z * ROOT3OVER3;
310        let xr = x + s2 + zz;
311        let yr = y + s2 + zz;
312        let zr = xy * -ROOT3OVER3 + zz;
313
314        // Evaluate both lattices to form a BCC lattice.
315        [xr, yr, zr]
316    }
317
318    #[doc(hidden)]
319    #[cfg(feature = "nightly-simd")]
320    fn raw_improve3a_xy(&self, point: f32x4) -> f32x4 {
321        let &[x, y, z, _] = point.as_array();
322        let [x, y, z] = self.raw_improve3_xy([x, y, z]);
323        f32x4::from_array([x, y, z, z])
324    }
325
326    #[doc(hidden)]
327    fn raw_improve3_xz(&self, [x, y, z]: [f32; 3]) -> [f32; 3] {
328        // Re-orient the cubic lattices without skewing, so Y points up the main lattice diagonal,
329        // and the planes formed by XZ are moved far out of alignment with the cube faces.
330        // Orthonormal rotation. Not a skew transform.
331        let xz = x + z;
332        let s2 = xz * ROTATE_3D_ORTHOGONALIZER;
333        let yy = y * ROOT3OVER3;
334        let xr = x + s2 + yy;
335        let zr = z + s2 + yy;
336        let yr = xz * -ROOT3OVER3 + yy;
337
338        // Evaluate both lattices to form a BCC lattice.
339        [xr, yr, zr]
340    }
341
342    #[doc(hidden)]
343    #[cfg(feature = "nightly-simd")]
344    fn raw_improve3a_xz(&self, point: f32x4) -> f32x4 {
345        let &[x, y, z, _] = point.as_array();
346        let [x, y, z] = self.raw_improve3_xz([x, y, z]);
347        f32x4::from_array([x, y, z, z])
348    }
349
350    #[inline]
351    fn raw_improve4_xyz(&self, [x, y, z, w]: [f32; 4]) -> [f32; 4] {
352        let xyz = x + y + z;
353        let ww = w * 0.2236067977499788;
354        let s2 = xyz * -0.16666666666666666 + ww;
355        let xs = x + s2;
356        let ys = y + s2;
357        let zs = z + s2;
358        let ws = -0.5 * xyz + ww;
359        [xs, ys, zs, ws]
360    }
361
362    #[inline]
363    #[cfg(feature = "nightly-simd")]
364    fn raw_improve4a_xyz(&self, point: f32x4) -> f32x4 {
365        self.raw_improve4_xyz(*point.as_array()).into()
366    }
367
368    #[inline]
369    fn raw_improve4_xyz_xy(&self, [x, y, z, w]: [f32; 4]) -> [f32; 4] {
370        let xy = x + y;
371        let s2 = xy * -0.21132486540518699998;
372        let zz = z * 0.28867513459481294226;
373        let ww = w * 0.2236067977499788;
374        let xr = x + (zz + ww + s2);
375        let yr = y + (zz + ww + s2);
376        let zr = xy * -0.57735026918962599998 + (zz + ww);
377        let wr = z * -0.866025403784439 + ww;
378        [xr, yr, zr, wr]
379    }
380
381    #[inline]
382    #[cfg(feature = "nightly-simd")]
383    fn raw_improve4a_xyz_xy(&self, point: f32x4) -> f32x4 {
384        self.raw_improve4_xyz_xy(*point.as_array()).into()
385    }
386
387    #[inline]
388    fn raw_improve4_xyz_xz(&self, [x, y, z, w]: [f32; 4]) -> [f32; 4] {
389        let xz = x + z;
390        let s2 = xz * -0.21132486540518699998;
391        let yy = y * 0.28867513459481294226;
392        let ww = w * 0.2236067977499788;
393        let xr = x + (yy + ww + s2);
394        let zr = z + (yy + ww + s2);
395        let yr = xz * -0.57735026918962599998 + (yy + ww);
396        let wr = y * -0.866025403784439 + ww;
397        [xr, yr, zr, wr]
398    }
399
400    #[inline]
401    #[cfg(feature = "nightly-simd")]
402    fn raw_improve4a_xyz_xz(&self, point: f32x4) -> f32x4 {
403        self.raw_improve4_xyz_xz(*point.as_array()).into()
404    }
405
406    #[inline]
407    fn raw_improve4_xy_zw(&self, [x, y, z, w]: [f32; 4]) -> [f32; 4] {
408        let s2 = (x + y) * -0.178275657951399372 + (z + w) * 0.215623393288842828;
409        let t2 = (z + w) * -0.403949762580207112 + (x + y) * -0.375199083010075342;
410        let xs = x + s2;
411        let ys = y + s2;
412        let zs = z + t2;
413        let ws = w + t2;
414        [xs, ys, zs, ws]
415    }
416
417    #[inline]
418    #[cfg(feature = "nightly-simd")]
419    fn raw_improve4a_xy_zw(&self, point: f32x4) -> f32x4 {
420        self.raw_improve4_xy_zw(*point.as_array()).into()
421    }
422}
423
424#[inline]
425pub(crate) fn improve2([x, y]: [f32; 2]) -> [f32; 2] {
426    // Get points for A2* lattice
427    let s = SKEW_2D * (x + y);
428    let xs = x + s;
429    let ys = y + s;
430    [xs, ys]
431}
432
433#[inline]
434#[cfg(feature = "nightly-simd")]
435pub(crate) fn improve2a(point: f32x2) -> f32x2 {
436    improve2(point.into()).into()
437}
438
439#[inline]
440pub(crate) fn improve3([x, y, z]: [f32; 3]) -> [f32; 3] {
441    // Re-orient the cubic lattices via rotation, to produce a familiar look.
442    // Orthonormal rotation. Not a skew transform.
443    let r = FALLBACK_ROTATE_3D * (x + y + z);
444    let xr = r - x;
445    let yr = r - y;
446    let zr = r - z;
447
448    // Evaluate both lattices to form a BCC lattice.
449    [xr, yr, zr]
450}
451
452#[inline]
453#[cfg(feature = "nightly-simd")]
454pub(crate) fn improve3a(point: f32x4) -> f32x4 {
455    const R3: f32 = 2.0 / 3.0;
456    let r: f32 = (point[0] + point[1] + point[2]) * R3; // Rotation, not skew
457    f32x4::splat(r) - point
458}
459
460#[inline]
461pub(crate) fn improve4([mut x, mut y, mut z, mut w]: [f32; 4]) -> [f32; 4] {
462    let s = SKEW_4D * (x + y + z + w);
463    x += s;
464    y += s;
465    z += s;
466    w += s;
467    [x, y, z, w]
468}
469
470#[inline]
471#[cfg(feature = "nightly-simd")]
472pub(crate) fn improve4a(point: f32x4) -> f32x4 {
473    let s = SKEW_4D * point.reduce_sum();
474    point + splat(s)
475}
476
477fn grad2(seed: Wrapping<i64>, xsvp: Wrapping<i64>, ysvp: Wrapping<i64>, dx: f32, dy: f32) -> f32 {
478    let mut hash = seed ^ xsvp ^ ysvp;
479    hash *= HASH_MULTIPLIER;
480    hash ^= hash.0 >> (64 - N_GRADS_2D_EXPONENT + 1);
481    let [grad_x, grad_y] = *GRADIENTS_2D[hash.0].as_array();
482    grad_x * dx + grad_y * dy
483}
484
485fn grad3(seed: Wrapping<i64>, xrvp: Wrapping<i64>, yrvp: Wrapping<i64>, zrvp: Wrapping<i64>, dx: f32, dy: f32, dz: f32) -> f32 {
486    let mut hash = (seed ^ xrvp) ^ (yrvp ^ zrvp);
487    hash *= HASH_MULTIPLIER;
488    hash ^= hash.0 >> (64 - N_GRADS_3D_EXPONENT + 2);
489    let [grad_x, grad_y, grad_z, ..] = *GRADIENTS_3D[hash.0].as_array();
490    grad_x * dx + grad_y * dy + grad_z * dz
491}
492
493fn grad4(seed: Wrapping<i64>, xsvp: Wrapping<i64>, ysvp: Wrapping<i64>, zsvp: Wrapping<i64>, wsvp: Wrapping<i64>, dx: f32, dy: f32, dz: f32, dw: f32) -> f32 {
494    let mut hash = seed ^ (xsvp ^ ysvp) ^ (zsvp ^ wsvp);
495    hash *= HASH_MULTIPLIER;
496    hash ^= hash.0 >> (64 - N_GRADS_4D_EXPONENT + 2);
497    let [grad_x, grad_y, grad_z, grad_w] = *GRADIENTS_4D[hash.0].as_array();
498    grad_x * dx + grad_y * dy + grad_z * dz + grad_w * dw
499}
500
501fn fast_floor(x: f32) -> i32 {
502    let xi = x as i32;
503    if x < xi as f32 {
504        xi - 1
505    } else {
506        xi
507    }
508}
509
510fn fast_round(x: f32) -> i32 {
511    if x < 0.0 {
512        (x - 0.5) as i32
513    } else {
514        (x + 0.5) as i32
515    }
516}
517
518const PRIME_X: i64 = 0x5205402B9270C86F;
519const PRIME_Y: i64 = 0x598CD327003817B5;
520const PRIME_Z: i64 = 0x5BCC226E9FA0BACB;
521const PRIME_W: i64 = 0x56CC5227E58F554B;
522const HASH_MULTIPLIER: i64 = 0x53A3F72DEEC546F5;
523const SEED_FLIP_3D: i64 = -0x52D547B2E96ED629;
524const SEED_OFFSET_4D: i64 = 0xE83DC3E0DA7164D;
525
526const ROOT2OVER2: f32 = 0.7071067811865476;
527const SKEW_2D: f32 = 0.366025403784439;
528const UNSKEW_2D: f32 = -0.21132486540518713;
529
530const ROOT3OVER3: f32 = 0.577350269189626;
531const FALLBACK_ROTATE_3D: f32 = 2.0 / 3.0;
532const ROTATE_3D_ORTHOGONALIZER: f32 = UNSKEW_2D;
533
534const SKEW_4D: f32 = -0.138196601125011;
535const UNSKEW_4D: f32 = 0.309016994374947;
536const LATTICE_STEP_4D: f32 = 0.2;
537
538const N_GRADS_2D_EXPONENT: i32 = 7;
539const N_GRADS_3D_EXPONENT: i32 = 8;
540const N_GRADS_4D_EXPONENT: i32 = 9;
541
542const RSQUARED_2D: f32 = 0.5;
543const RSQUARED_3D: f32 = 0.6;
544const RSQUARED_4D: f32 = 0.6;