noise/core/
simplex.rs

1use crate::{
2    gradient,
3    math::vectors::{Vector2, Vector3, Vector4},
4    permutationtable::NoiseHasher,
5};
6use num_traits::{Float, NumCast};
7
8// Skew Value
9//
10//     sqrt(n + 1) - 1
11// F = ---------------
12//            n
13fn skew_factor<F>(n: usize) -> F
14where
15    F: Float,
16{
17    let n: F = NumCast::from(n).unwrap();
18
19    ((n + F::one()).sqrt() - F::one()) / n
20}
21
22//  Unskew Value
23//
24//     1 - 1 / sqrt(n + 1)
25// G = -------------------
26//             n
27fn unskew_factor<F>(n: usize) -> F
28where
29    F: Float,
30{
31    let n: F = NumCast::from(n).unwrap();
32
33    (F::one() - (F::one() / (n + F::one()).sqrt())) / n
34}
35
36/// The simplex noise code was adapted from code by Stefan Gustavson,
37/// http://staffwww.itn.liu.se/~stegu/aqsis/aqsis-newnoise/sdnoise1234.c
38///
39/// This is Stefan Gustavson's original copyright notice:
40///
41/// /* sdnoise1234, Simplex noise with true analytic
42///  * derivative in 1D to 4D.
43///  *
44///  * Copyright © 2003-2011, Stefan Gustavson
45///  *
46///  * Contact: stefan.gustavson@gmail.com
47///  *
48///  * This library is public domain software, released by the author
49///  * into the public domain in February 2011. You may do anything
50///  * you like with it. You may even remove all attributions,
51///  * but of course I'd appreciate it if you kept my name somewhere.
52///  *
53///  * This library is distributed in the hope that it will be useful,
54///  * but WITHOUT ANY WARRANTY; without even the implied warranty of
55///  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
56///  * General Public License for more details.
57///  */
58
59#[inline(always)]
60pub fn simplex_2d<NH>(point: Vector2<f64>, hasher: &NH) -> (f64, [f64; 2])
61where
62    NH: NoiseHasher + ?Sized,
63{
64    let skew_factor: f64 = skew_factor(2);
65    let unskew_factor: f64 = unskew_factor(2);
66
67    // Skew the input space to determine which simplex cell we're in
68    let skew = point.sum() * skew_factor;
69    let skewed = point + skew;
70    let cell = skewed.floor_to_isize();
71    let floor = cell.numcast().unwrap();
72
73    let unskew: f64 = floor.sum() * unskew_factor;
74    // Unskew the cell origin back to (x,y) space
75    let unskewed = floor - unskew;
76    // The x,y distances from the cell origin
77    let offset1 = point - unskewed;
78
79    // For the 2D case, the simplex shape is an equilateral triangle.
80    // Determine which simplex we are in.
81    let order = if offset1.x > offset1.y {
82        // Offsets for second (middle) corner of simplex in (i,j) coords
83        // lower triangle, XY order: (0,0)->(1,0)->(1,1)
84        Vector2::new(1.0, 0.0)
85    } else {
86        // upper triangle, YX order: (0,0)->(0,1)->(1,1)
87        Vector2::new(0.0, 1.0)
88    };
89
90    // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
91    // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
92    // c = (3-sqrt(3))/6
93
94    // Offsets for middle corner in (x,y) unskewed coords
95    let offset2 = offset1 - order + unskew_factor;
96    // Offsets for last corner in (x,y) unskewed coords
97    let offset3 = offset1 - 1.0 + 2.0 * unskew_factor;
98
99    // Calculate gradient indexes for each corner
100    let gi0 = hasher.hash(&cell.into_array());
101    let gi1 = hasher.hash(&(cell + order.numcast().unwrap()).into_array());
102    let gi2 = hasher.hash(&(cell + 1).into_array());
103
104    struct SurfletComponents {
105        value: f64,
106        t: f64,
107        t2: f64,
108        t4: f64,
109        gradient: Vector2<f64>,
110    }
111
112    #[inline(always)]
113    fn surflet(gradient_index: usize, point: Vector2<f64>) -> SurfletComponents {
114        let t = 1.0 - point.magnitude_squared() * 2.0;
115
116        if t > 0.0 {
117            let gradient: Vector2<f64> = gradient::grad2(gradient_index).into();
118            let t2 = t * t;
119            let t4 = t2 * t2;
120
121            SurfletComponents {
122                value: (2.0 * t2 + t4) * point.dot(gradient),
123                t,
124                t2,
125                t4,
126                gradient,
127            }
128        } else {
129            // No influence
130            SurfletComponents {
131                value: 0.0,
132                t: 0.0,
133                t2: 0.0,
134                t4: 0.0,
135                gradient: Vector2::zero(),
136            }
137        }
138    }
139
140    // Calculate the contribution from the three corners
141    let corner0 = surflet(gi0, offset1);
142    let corner1 = surflet(gi1, offset2);
143    let corner2 = surflet(gi2, offset3);
144
145    // Add contributions from each corner to get the final noise value.
146    // The result is scaled to return values in the interval [-1, 1].
147    let noise = corner0.value + corner1.value + corner2.value;
148
149    // A straight, unoptimised calculation would be like:
150    //   dnoise_dx = -8.0 * t20 * t0 * x0 * ( gx0 * x0 + gy0 * y0 ) + t40 * gx0;
151    //   dnoise_dy = -8.0 * t20 * t0 * y0 * ( gx0 * x0 + gy0 * y0 ) + t40 * gy0;
152    //   dnoise_dx += -8.0 * t21 * t1 * x1 * ( gx1 * x1 + gy1 * y1 ) + t41 * gx1;
153    //   dnoise_dy += -8.0 * t21 * t1 * y1 * ( gx1 * x1 + gy1 * y1 ) + t41 * gy1;
154    //   dnoise_dx += -8.0 * t22 * t2 * x2 * ( gx2 * x2 + gy2 * y2 ) + t42 * gx2;
155    //   dnoise_dy += -8.0 * t22 * t2 * y2 * ( gx2 * x2 + gy2 * y2 ) + t42 * gy2;
156    //
157    let mut dnoise = offset1 + corner0.t2 * corner0.t * corner0.gradient.dot(offset1);
158    dnoise += offset2 * corner1.t2 * corner1.t * corner1.gradient.dot(offset2);
159    dnoise += offset3 * corner2.t2 * corner2.t * corner2.gradient.dot(offset3);
160
161    dnoise *= -8.0;
162
163    dnoise += corner0.gradient * corner0.t4
164        + corner1.gradient * corner1.t4
165        + corner2.gradient * corner2.t4;
166
167    (noise, dnoise.into())
168}
169
170#[inline(always)]
171pub fn simplex_3d<NH>(point: Vector3<f64>, hasher: &NH) -> (f64, [f64; 3])
172where
173    NH: NoiseHasher + ?Sized,
174{
175    let skew_factor: f64 = skew_factor(3);
176    let unskew_factor: f64 = unskew_factor(3);
177
178    /* Skew the input space to determine which simplex cell we're in */
179    // let skew = (x + y + z) * f3; /* Very nice and simple skew factor for 3D */
180    let skew = point.sum() * skew_factor;
181    let skewed = point + skew;
182    let cell = skewed.floor_to_isize();
183    let floor = cell.numcast().unwrap();
184
185    let unskew: f64 = floor.sum() * unskew_factor;
186    /* Unskew the cell origin back to (x,y,z) space */
187    let unskewed = floor - unskew;
188    /* The x,y,z distances from the cell origin */
189    let offset1 = point - unskewed;
190
191    /* For the 3D case, the simplex shape is a slightly irregular tetrahedron.
192     * Determine which simplex we are in. */
193    /* TODO: This code would benefit from a backport from the GLSL version! */
194    let (order1, order2): (Vector3<isize>, Vector3<isize>) = if offset1.x >= offset1.y {
195        if offset1.y >= offset1.z {
196            /* X Y Z order */
197            (Vector3::new(1, 0, 0), Vector3::new(1, 1, 0))
198        } else if offset1.x >= offset1.z {
199            /* X Z Y order */
200            (Vector3::new(1, 0, 0), Vector3::new(1, 0, 1))
201        } else {
202            /* Z X Y order */
203            (Vector3::new(0, 0, 1), Vector3::new(1, 0, 1))
204        }
205    } else {
206        // x0<y0
207        if offset1.y < offset1.z {
208            /* Z Y X order */
209            (Vector3::new(0, 0, 1), Vector3::new(0, 1, 1))
210        } else if offset1.x < offset1.z {
211            /* Y Z X order */
212            (Vector3::new(0, 1, 0), Vector3::new(0, 1, 1))
213        } else {
214            /* Y X Z order */
215            (Vector3::new(0, 1, 0), Vector3::new(1, 1, 0))
216        }
217    };
218
219    /* A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
220     * a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
221     * a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
222     * c = 1/6.   */
223
224    let offset2 = offset1 - order1.numcast().unwrap() + unskew_factor;
225    let offset3 = offset1 - order2.numcast().unwrap() + 2.0 * unskew_factor;
226    let offset4 = offset1 - Vector3::one() + 3.0 * unskew_factor;
227
228    // Calculate gradient indexes for each corner
229    let gi0 = hasher.hash(&cell.into_array());
230    let gi1 = hasher.hash(&(cell + order1).into_array());
231    let gi2 = hasher.hash(&(cell + order2).into_array());
232    let gi3 = hasher.hash(&(cell + 1).into_array());
233
234    struct SurfletComponents {
235        value: f64,
236        t: f64,
237        t2: f64,
238        t4: f64,
239        gradient: Vector3<f64>,
240    }
241
242    fn surflet(gradient_index: usize, point: Vector3<f64>) -> SurfletComponents {
243        let t = 1.0 - point.magnitude_squared() * 2.0;
244
245        if t > 0.0 {
246            let gradient = gradient::grad3(gradient_index).into();
247            let t2 = t * t;
248            let t4 = t2 * t2;
249
250            SurfletComponents {
251                value: (2.0 * t2 + t4) * point.dot(gradient),
252                t,
253                t2,
254                t4,
255                gradient,
256            }
257        } else {
258            // No influence
259            SurfletComponents {
260                value: 0.0,
261                t: 0.0,
262                t2: 0.0,
263                t4: 0.0,
264                gradient: Vector3::zero(),
265            }
266        }
267    }
268
269    /* Calculate the contribution from the four corners */
270    let corner0 = surflet(gi0, offset1);
271    let corner1 = surflet(gi1, offset2);
272    let corner2 = surflet(gi2, offset3);
273    let corner3 = surflet(gi3, offset4);
274
275    /*  Add contributions from each corner to get the final noise value.
276     * The result is scaled to return values in the range [-1,1] */
277    let noise = corner0.value + corner1.value + corner2.value + corner3.value;
278
279    /*  A straight, unoptimised calculation would be like:
280     *    dnoise_dx = -8.0 * t20 * t0 * x0 * dot(gx0, gy0, gz0, x0, y0, z0) + t40 * gx0;
281     *    dnoise_dy = -8.0 * t20 * t0 * y0 * dot(gx0, gy0, gz0, x0, y0, z0) + t40 * gy0;
282     *    dnoise_dz = -8.0 * t20 * t0 * z0 * dot(gx0, gy0, gz0, x0, y0, z0) + t40 * gz0;
283     *    dnoise_dx += -8.0 * t21 * t1 * x1 * dot(gx1, gy1, gz1, x1, y1, z1) + t41 * gx1;
284     *    dnoise_dy += -8.0 * t21 * t1 * y1 * dot(gx1, gy1, gz1, x1, y1, z1) + t41 * gy1;
285     *    dnoise_dz += -8.0 * t21 * t1 * z1 * dot(gx1, gy1, gz1, x1, y1, z1) + t41 * gz1;
286     *    dnoise_dx += -8.0 * t22 * t2 * x2 * dot(gx2, gy2, gz2, x2, y2, z2) + t42 * gx2;
287     *    dnoise_dy += -8.0 * t22 * t2 * y2 * dot(gx2, gy2, gz2, x2, y2, z2) + t42 * gy2;
288     *    dnoise_dz += -8.0 * t22 * t2 * z2 * dot(gx2, gy2, gz2, x2, y2, z2) + t42 * gz2;
289     *    dnoise_dx += -8.0 * t23 * t3 * x3 * dot(gx3, gy3, gz3, x3, y3, z3) + t43 * gx3;
290     *    dnoise_dy += -8.0 * t23 * t3 * y3 * dot(gx3, gy3, gz3, x3, y3, z3) + t43 * gy3;
291     *    dnoise_dz += -8.0 * t23 * t3 * z3 * dot(gx3, gy3, gz3, x3, y3, z3) + t43 * gz3;
292     */
293    let mut dnoise = offset1 * corner0.t2 * corner0.t * corner0.gradient.dot(offset1);
294    dnoise += offset2 * corner1.t2 * corner1.t * corner1.gradient.dot(offset2);
295    dnoise += offset3 * corner2.t2 * corner2.t * corner2.gradient.dot(offset3);
296    dnoise += offset4 * corner3.t2 * corner3.t * corner3.gradient.dot(offset4);
297
298    dnoise *= -8.0;
299
300    dnoise += corner0.gradient * corner0.t4
301        + corner1.gradient * corner1.t4
302        + corner2.gradient * corner2.t4
303        + corner3.gradient * corner3.t4;
304
305    (noise, dnoise.into())
306}
307
308#[inline(always)]
309pub fn simplex_4d<NH>(point: Vector4<f64>, hasher: &NH) -> (f64, [f64; 4])
310where
311    NH: NoiseHasher + ?Sized,
312{
313    let skew_factor: f64 = skew_factor(4);
314    let unskew_factor: f64 = unskew_factor(4);
315
316    // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
317    // Factor for 4D skewing
318    let skew = point.sum() * skew_factor;
319    let skewed = point + skew;
320    let cell: Vector4<isize> = skewed.floor_to_isize();
321    let floor = cell.numcast().unwrap();
322
323    // Factor for 4D unskewing
324    let unskew: f64 = floor.sum() * unskew_factor;
325    // Unskew the cell origin back to (x,y,z,w) space
326    let unskewed = floor - unskew;
327    // The x,y,z,w distances from the cell origin
328    let offset1 = point - unskewed;
329
330    // For the 4D case, the simplex is a 4D shape I won't even try to describe.
331    // To find out which of the 24 possible simplices we're in, we need to
332    // determine the magnitude ordering of x0, y0, z0 and w0.
333    // The method below is a reasonable way of finding the ordering of x,y,z,w
334    // and then find the correct traversal order for the simplex we're in.
335    // First, six pair-wise comparisons are performed between each possible pair
336    // of the four coordinates, and then the results are used to add up binary
337    // bits for an integer index into a precomputed lookup table, simplex[].
338    let c1 = (offset1.x > offset1.y) as usize * 32;
339    let c2 = (offset1.x > offset1.z) as usize * 16;
340    let c3 = (offset1.y > offset1.z) as usize * 8;
341    let c4 = (offset1.x > offset1.w) as usize * 4;
342    let c5 = (offset1.y > offset1.w) as usize * 2;
343    let c6 = (offset1.z > offset1.w) as usize;
344    let c = c1 | c2 | c3 | c4 | c5 | c6; // '|' is mostly faster than '+'
345
346    // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
347    // Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
348    // impossible. Only the 24 indices which have non-zero entries make any sense.
349    // We use a thresholding to set the coordinates in turn from the largest magnitude.
350    // The number 3 in the "simplex" array is at the position of the largest coordinate.
351    let order1 = Vector4::from(SIMPLEX[c]).map(|n| if n >= 3 { 1 } else { 0 });
352    // The number 2 in the "simplex" array is at the second largest coordinate.
353    let order2 = Vector4::from(SIMPLEX[c]).map(|n| if n >= 2 { 1 } else { 0 });
354    // The number 1 in the "simplex" array is at the second smallest coordinate.
355    let order3 = Vector4::from(SIMPLEX[c]).map(|n| if n >= 1 { 1 } else { 0 });
356    // The fifth corner has all coordinate offsets = 1, so no need to look that up.
357
358    // Offsets for second corner in (x,y,z,w) coords
359    let offset2 = offset1 - order1.numcast().unwrap() + unskew_factor;
360    // Offsets for third corner in (x,y,z,w) coords
361    let offset3 = offset1 - order2.numcast().unwrap() + 2.0 * unskew_factor;
362    // Offsets for fourth corner in (x,y,z,w) coords
363    let offset4 = offset1 - order3.numcast().unwrap() + 3.0 * unskew_factor;
364    // Offsets for last corner in (x,y,z,w) coords
365    let offset5 = offset1 - 1.0 + 4.0 * unskew_factor;
366
367    // Calculate gradient indexes for each corner
368    let gi0 = hasher.hash(&cell.into_array());
369    let gi1 = hasher.hash(&(cell + order1).into_array());
370    let gi2 = hasher.hash(&(cell + order2).into_array());
371    let gi3 = hasher.hash(&(cell + order3).into_array());
372    let gi4 = hasher.hash(&(cell + 1).into_array());
373
374    struct SurfletComponents {
375        value: f64,
376        t: f64,
377        t2: f64,
378        t4: f64,
379        gradient: Vector4<f64>,
380    }
381
382    fn surflet(gradient_index: usize, point: Vector4<f64>) -> SurfletComponents {
383        let t = 1.0 - point.magnitude_squared() * 2.0;
384
385        if t > 0.0 {
386            let gradient = gradient::grad4(gradient_index).into();
387            let t2 = t * t;
388            let t4 = t2 * t2;
389
390            SurfletComponents {
391                value: (2.0 * t2 + t4) * point.dot(gradient),
392                t,
393                t2,
394                t4,
395                gradient,
396            }
397        } else {
398            // No influence
399            SurfletComponents {
400                value: 0.0,
401                t: 0.0,
402                t2: 0.0,
403                t4: 0.0,
404                gradient: Vector4::zero(),
405            }
406        }
407    }
408
409    /* Calculate the contribution from the five corners */
410    let corner1 = surflet(gi0, offset1);
411    let corner2 = surflet(gi1, offset2);
412    let corner3 = surflet(gi2, offset3);
413    let corner4 = surflet(gi3, offset4);
414    let corner5 = surflet(gi4, offset5);
415
416    // Sum up and scale the result to cover the range [-1,1]
417    let noise = corner1.value + corner2.value + corner3.value + corner4.value + corner5.value;
418
419    /*  A straight, unoptimised calculation would be like:
420     *    dnoise_dx = -8.0 * t20 * t0 * x0 * dot(gx0, gy0, gz0, gw0, x0, y0, z0, w0) + t40 * gx0;
421     *    dnoise_dy = -8.0 * t20 * t0 * y0 * dot(gx0, gy0, gz0, gw0, x0, y0, z0, w0) + t40 * gy0;
422     *    dnoise_dz = -8.0 * t20 * t0 * z0 * dot(gx0, gy0, gz0, gw0, x0, y0, z0, w0) + t40 * gz0;
423     *    dnoise_dw = -8.0 * t20 * t0 * w0 * dot(gx0, gy0, gz0, gw0, x0, y0, z0, w0) + t40 * gw0;
424     *    dnoise_dx += -8.0 * t21 * t1 * x1 * dot(gx1, gy1, gz1, gw1, x1, y1, z1, w1) + t41 * gx1;
425     *    dnoise_dy += -8.0 * t21 * t1 * y1 * dot(gx1, gy1, gz1, gw1, x1, y1, z1, w1) + t41 * gy1;
426     *    dnoise_dz += -8.0 * t21 * t1 * z1 * dot(gx1, gy1, gz1, gw1, x1, y1, z1, w1) + t41 * gz1;
427     *    dnoise_dw += -8.0 * t21 * t1 * w1 * dot(gx1, gy1, gz1, gw1, x1, y1, z1, w1) + t41 * gw1;
428     *    dnoise_dx += -8.0 * t22 * t2 * x2 * dot(gx2, gy2, gz2, gw2, x2, y2, z2, w2) + t42 * gx2;
429     *    dnoise_dy += -8.0 * t22 * t2 * y2 * dot(gx2, gy2, gz2, gw2, x2, y2, z2, w2) + t42 * gy2;
430     *    dnoise_dz += -8.0 * t22 * t2 * z2 * dot(gx2, gy2, gz2, gw2, x2, y2, z2, w2) + t42 * gz2;
431     *    dnoise_dw += -8.0 * t22 * t2 * w2 * dot(gx2, gy2, gz2, gw2, x2, y2, z2, w2) + t42 * gw2;
432     *    dnoise_dx += -8.0 * t23 * t3 * x3 * dot(gx3, gy3, gz3, gw3, x3, y3, z3, w3) + t43 * gx3;
433     *    dnoise_dy += -8.0 * t23 * t3 * y3 * dot(gx3, gy3, gz3, gw3, x3, y3, z3, w3) + t43 * gy3;
434     *    dnoise_dz += -8.0 * t23 * t3 * z3 * dot(gx3, gy3, gz3, gw3, x3, y3, z3, w3) + t43 * gz3;
435     *    dnoise_dw += -8.0 * t23 * t3 * w3 * dot(gx3, gy3, gz3, gw3, x3, y3, z3, w3) + t43 * gw3;
436     *    dnoise_dx += -8.0 * t24 * t4 * x4 * dot(gx4, gy4, gz4, gw4, x4, y4, z4, w4) + t44 * gx4;
437     *    dnoise_dy += -8.0 * t24 * t4 * y4 * dot(gx4, gy4, gz4, gw4, x4, y4, z4, w4) + t44 * gy4;
438     *    dnoise_dz += -8.0 * t24 * t4 * z4 * dot(gx4, gy4, gz4, gw4, x4, y4, z4, w4) + t44 * gz4;
439     *    dnoise_dw += -8.0 * t24 * t4 * w4 * dot(gx4, gy4, gz4, gw4, x4, y4, z4, w4) + t44 * gw4;
440     */
441    let mut dnoise = offset1 * corner1.t2 * corner1.t * corner1.gradient.dot(offset1);
442    dnoise += offset2 * corner2.t2 * corner2.t * corner2.gradient.dot(offset2);
443    dnoise += offset3 * corner3.t2 * corner3.t * corner3.gradient.dot(offset3);
444    dnoise += offset4 * corner4.t2 * corner4.t * corner4.gradient.dot(offset4);
445    dnoise += offset5 * corner5.t2 * corner5.t * corner5.gradient.dot(offset5);
446
447    dnoise *= -8.0;
448
449    dnoise += corner1.gradient * corner1.t4
450        + corner2.gradient * corner2.t4
451        + corner3.gradient * corner3.t4
452        + corner4.gradient * corner4.t4
453        + corner5.gradient * corner5.t4;
454
455    (noise, dnoise.into())
456}
457
458// A lookup table to traverse the simplex around a given point in 4D.
459// Details can be found where this table is used, in the 4D noise method.
460/* TODO: This should not be required, backport it from Bill's GLSL code! */
461#[rustfmt::skip]
462const SIMPLEX: [[u8; 4]; 64] = [
463    [0, 1, 2, 3], [0, 1, 3, 2], [0, 0, 0, 0], [0, 2, 3, 1], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [1, 2, 3, 0],
464    [0, 2, 1, 3], [0, 0, 0, 0], [0, 3, 1, 2], [0, 3, 2, 1], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [1, 3, 2, 0],
465    [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0],
466    [1, 2, 0, 3], [0, 0, 0, 0], [1, 3, 0, 2], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [2, 3, 0, 1], [2, 3, 1, 0],
467    [1, 0, 2, 3], [1, 0, 3, 2], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [2, 0, 3, 1], [0, 0, 0, 0], [2, 1, 3, 0],
468    [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0],
469    [2, 0, 1, 3], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [3, 0, 1, 2], [3, 0, 2, 1], [0, 0, 0, 0], [3, 1, 2, 0],
470    [2, 1, 0, 3], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [3, 1, 0, 2], [0, 0, 0, 0], [3, 2, 0, 1], [3, 2, 1, 0],
471];