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];