noise 0.8.2

Procedural noise generation library.
Documentation
use crate::{
    gradient,
    math::vectors::{Vector, Vector2, Vector3, Vector4},
    permutationtable::NoiseHasher,
};
use num_traits::{Float, NumCast};

fn grad1(hash: u8) -> f64 {
    let h = hash & 15;
    let gx = (1 + (h & 7)) as f64; // Gradient value is one of 1.0, 2.0, ..., 8.0
    match h & 8 {
        0 => -gx,
        1 => gx, // Make half of the gradients negative
        _ => unreachable!(),
    }
}

// Skew Value
//
//     sqrt(n + 1) - 1
// F = ---------------
//            n
fn skew_factor<F>(n: usize) -> F
where
    F: Float,
{
    let n: F = NumCast::from(n).unwrap();

    ((n + F::one()).sqrt() - F::one()) / n
}

//  Unskew Value
//
//     1 - 1 / sqrt(n + 1)
// G = -------------------
//             n
fn unskew_factor<F>(n: usize) -> F
where
    F: Float,
{
    let n: F = NumCast::from(n).unwrap();

    (F::one() - (F::one() / (n + F::one()).sqrt())) / n
}

/// The simplex noise code was adapted from code by Stefan Gustavson,
/// http://staffwww.itn.liu.se/~stegu/aqsis/aqsis-newnoise/sdnoise1234.c
///
/// This is Stefan Gustavson's original copyright notice:
///
/// /* sdnoise1234, Simplex noise with true analytic
///  * derivative in 1D to 4D.
///  *
///  * Copyright © 2003-2011, Stefan Gustavson
///  *
///  * Contact: stefan.gustavson@gmail.com
///  *
///  * This library is public domain software, released by the author
///  * into the public domain in February 2011. You may do anything
///  * you like with it. You may even remove all attributions,
///  * but of course I'd appreciate it if you kept my name somewhere.
///  *
///  * This library is distributed in the hope that it will be useful,
///  * but WITHOUT ANY WARRANTY; without even the implied warranty of
///  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
///  * General Public License for more details.
///  */
///
/// 1D Simplex Noise with Derivative
#[inline(always)]
pub fn simplex_1d<NH>(x: f64, hasher: &NH) -> (f64, f64)
where
    NH: NoiseHasher + ?Sized,
{
    let cell = x.floor() as isize;

    let near_distance = x - cell as f64;
    let far_distance = near_distance - 1.0;

    // Calculate gradient indexes for each corner
    let gi0 = hasher.hash(&[cell]);
    let gi1 = hasher.hash(&[cell + 1]);

    struct SurfletComponents {
        value: f64,
        t: f64,
        t2: f64,
        t4: f64,
        gradient: f64,
        x2: f64,
    }

    fn surflet(gradient_index: usize, x: f64) -> SurfletComponents {
        let x2 = x * x;
        let t = 1.0 - x2;

        // if t <= 0.0 { Never happens in 1D: x is always <= 1.
        // No influence
        // t0 = 0.0;
        // } else {
        let gradient = grad1((gradient_index % 0xff) as u8);
        let t2 = t * t;
        let t4 = t2 * t2;

        let value = t4 * gradient * x;

        SurfletComponents {
            value,
            t,
            t2,
            t4,
            gradient,
            x2,
        }
    }

    let corner0 = surflet(gi0, near_distance);
    let corner1 = surflet(gi1, far_distance);

    // The maximum value of this noise is 8*(3/4)^4 = 2.53125
    // A factor of 0.395 would scale to fit exactly within [-1,1], but
    // to better match classic Perlin noise, we scale it down some more.
    // ^-- Original note from Gustavson.

    // Since the objective of this library is to be as close to [-1, 1] as possible, we'll use the
    // 0.395 scale instead.
    let noise = 0.395 * (corner0.value + corner1.value);

    /* Compute derivative according to:
     *  dnoise_dx = -8.0 * t20 * t0 * x0 * (gx0 * x0) + t40 * gx0;
     *  dnoise_dx += -8.0 * t21 * t1 * x1 * (gx1 * x1) + t41 * gx1;
     */
    let mut dnoise_dx = corner0.t2 * corner0.t * corner0.gradient * corner0.x2;
    dnoise_dx += corner1.t2 * corner1.t * corner1.gradient * corner1.x2;
    dnoise_dx *= -8.0;
    dnoise_dx += corner0.t4 * corner0.gradient + corner1.t4 * corner1.gradient;
    dnoise_dx *= 0.395; /* Scale derivative to match the noise scaling */

    (noise, dnoise_dx)
}

#[inline(always)]
pub fn simplex_2d<NH>(point: [f64; 2], hasher: &NH) -> (f64, [f64; 2])
where
    NH: NoiseHasher + ?Sized,
{
    let f2: f64 = skew_factor(2);
    let g2: f64 = unskew_factor(2);

    let point = Vector2::from(point);

    /* Skew the input space to determine which simplex cell we're in */
    let skew = point.sum() * f2; /* Hairy factor for 2D */
    let skewed = point + Vector2::broadcast(skew);
    let cell: Vector2<isize> = skewed.floor().numcast().unwrap();

    let unskew: f64 = cell.sum() as f64 * g2;
    // Unskew the cell origin back to (x,y) space
    let unskewed = cell.numcast().unwrap() - Vector2::broadcast(unskew);
    // The x,y distances from the cell origin
    let distance = point - unskewed;

    // For the 2D case, the simplex shape is an equilateral triangle.
    // Determine which simplex we are in.
    let offset = if distance.x > distance.y {
        /* Offsets for second (middle) corner of simplex in (i,j) coords */
        // lower triangle, XY order: (0,0)->(1,0)->(1,1)
        Vector2::from([1, 0])
    } else {
        // upper triangle, YX order: (0,0)->(0,1)->(1,1)
        Vector2::from([0, 1])
    };

    /* A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
     * a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
     * c = (3-sqrt(3))/6   */
    // Offsets for middle corner in (x,y) unskewed coords */
    let distance1 = distance - offset.numcast().unwrap() + Vector2::broadcast(g2);
    /* Offsets for last corner in (x,y) unskewed coords */
    let distance2 = distance - Vector2::broadcast(1.0 + 2.0 * g2);

    // Calculate gradient indexes for each corner
    let gi0 = hasher.hash(&cell.into_array());
    let gi1 = hasher.hash(&(cell + distance1.numcast().unwrap()).into_array());
    let gi2 = hasher.hash(&(cell + Vector2::one()).into_array());

    struct SurfletComponents {
        value: f64,
        t: f64,
        t2: f64,
        t4: f64,
        gradient: Vector2<f64>,
    }

    impl SurfletComponents {
        fn zeros() -> Self {
            Self {
                value: 0.0,
                t: 0.0,
                t2: 0.0,
                t4: 0.0,
                gradient: Vector2::zero(),
            }
        }
    }

    fn surflet(gradient_index: usize, point: Vector2<f64>) -> SurfletComponents {
        // let t = 0.5 - (x * x + y * y);
        let t = 0.5 - point.magnitude_squared();

        if t > 0.0 {
            let gradient = Vector2::from(gradient::grad2(gradient_index));
            let t2 = t * t;
            let t4 = t2 * t2;

            SurfletComponents {
                value: t4 * gradient.dot(point),
                t,
                t2,
                t4,
                gradient,
            }
        } else {
            // No influence
            SurfletComponents::zeros()
        }
    }

    let corner0 = surflet(gi0, distance);
    let corner1 = surflet(gi1, distance1);
    let corner2 = surflet(gi2, distance2);

    /* Add contributions from each corner to get the final noise value.
     * The result is scaled to return values in the interval [-1, 1]. */
    let noise = 40.0 * (corner0.value + corner1.value + corner2.value);

    /*  A straight, unoptimised calculation would be like:
     *    dnoise_dx = -8.0 * t20 * t0 * x0 * ( gx0 * x0 + gy0 * y0 ) + t40 * gx0;
     *    dnoise_dy = -8.0 * t20 * t0 * y0 * ( gx0 * x0 + gy0 * y0 ) + t40 * gy0;
     *    dnoise_dx += -8.0 * t21 * t1 * x1 * ( gx1 * x1 + gy1 * y1 ) + t41 * gx1;
     *    dnoise_dy += -8.0 * t21 * t1 * y1 * ( gx1 * x1 + gy1 * y1 ) + t41 * gy1;
     *    dnoise_dx += -8.0 * t22 * t2 * x2 * ( gx2 * x2 + gy2 * y2 ) + t42 * gx2;
     *    dnoise_dy += -8.0 * t22 * t2 * y2 * ( gx2 * x2 + gy2 * y2 ) + t42 * gy2;
     */
    let temp0 = corner0.t2 * corner0.t * corner0.gradient.dot(distance);
    let mut dnoise = distance + Vector2::broadcast(temp0);

    let temp1 = corner1.t2 * corner1.t * corner1.gradient.dot(distance1);
    dnoise += distance1 * temp1;

    let temp2 = corner2.t2 * corner2.t * corner2.gradient.dot(distance2);
    dnoise += distance2 * temp2;

    dnoise *= -8.0;

    dnoise += corner0.gradient * corner0.t4
        + corner1.gradient * corner1.t4
        + corner2.gradient * corner2.t4;

    dnoise *= 40.0; /* Scale derivative to match the noise scaling */

    (noise, dnoise.into())
}

#[inline(always)]
pub fn simplex_3d<NH>(point: [f64; 3], hasher: &NH) -> (f64, [f64; 3])
where
    NH: NoiseHasher + ?Sized,
{
    let f3: f64 = skew_factor(3);
    let g3: f64 = unskew_factor(3);

    let point = Vector3::from(point);

    /* Skew the input space to determine which simplex cell we're in */
    // let skew = (x + y + z) * f3; /* Very nice and simple skew factor for 3D */
    let skew = point.sum() * f3;
    let skewed = point + Vector3::broadcast(skew);
    let cell: Vector3<isize> = skewed.floor().numcast().unwrap();

    // let unskew = (cell_x + cell_y + cell_z) as f64 * g3;
    let unskew = cell.sum() as f64 * g3;
    /* Unskew the cell origin back to (x,y,z) space */
    let unskewed = cell.numcast().unwrap() - Vector3::broadcast(unskew);
    /* The x,y,z distances from the cell origin */
    let distance = point - unskewed;

    /* For the 3D case, the simplex shape is a slightly irregular tetrahedron.
     * Determine which simplex we are in. */
    /* TODO: This code would benefit from a backport from the GLSL version! */
    let (order1, order2): (Vector3<isize>, Vector3<isize>) = if distance.x >= distance.y {
        if distance.y >= distance.z {
            /* X Y Z order */
            (Vector3::new(1, 0, 0), Vector3::new(1, 1, 0))
        } else if distance.x >= distance.z {
            /* X Z Y order */
            (Vector3::new(1, 0, 0), Vector3::new(1, 0, 1))
        } else {
            /* Z X Y order */
            (Vector3::new(0, 0, 1), Vector3::new(1, 0, 1))
        }
    } else {
        // x0<y0
        if distance.y < distance.z {
            /* Z Y X order */
            (Vector3::new(0, 0, 1), Vector3::new(0, 1, 1))
        } else if distance.x < distance.z {
            /* Y Z X order */
            (Vector3::new(0, 1, 0), Vector3::new(0, 1, 1))
        } else {
            /* Y X Z order */
            (Vector3::new(0, 1, 0), Vector3::new(1, 1, 0))
        }
    };

    /* A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
     * a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
     * a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
     * c = 1/6.   */

    let offset1 = distance - order1.numcast().unwrap() + Vector3::broadcast(g3);
    let offset2 = distance - order2.numcast().unwrap() + Vector3::broadcast(2.0 * g3);
    let offset3 = distance - Vector3::one() + Vector3::broadcast(3.0 * g3);

    // Calculate gradient indexes for each corner
    let gi0 = hasher.hash(&cell.into_array());
    let gi1 = hasher.hash(&(cell + order1).into_array());
    let gi2 = hasher.hash(&(cell + order2).into_array());
    let gi3 = hasher.hash(&(cell + Vector3::one()).into_array());

    struct SurfletComponents {
        value: f64,
        t: f64,
        t2: f64,
        t4: f64,
        gradient: Vector3<f64>,
    }

    impl SurfletComponents {
        fn zeros() -> Self {
            Self {
                value: 0.0,
                t: 0.0,
                t2: 0.0,
                t4: 0.0,
                gradient: Vector3::zero(),
            }
        }
    }

    fn surflet(gradient_index: usize, point: Vector3<f64>) -> SurfletComponents {
        let t = 0.5 - point.magnitude_squared();

        if t > 0.0 {
            let gradient = Vector3::from(gradient::grad3(gradient_index));
            let t2 = t * t;
            let t4 = t2 * t2;

            SurfletComponents {
                value: t4 * gradient.dot(point),
                t,
                t2,
                t4,
                gradient,
            }
        } else {
            // No influence
            SurfletComponents::zeros()
        }
    }

    /* Calculate the contribution from the four corners */
    let corner0 = surflet(gi0, distance);
    let corner1 = surflet(gi1, offset1);
    let corner2 = surflet(gi2, offset2);
    let corner3 = surflet(gi3, offset3);

    /*  Add contributions from each corner to get the final noise value.
     * The result is scaled to return values in the range [-1,1] */
    let noise = 28.0 * (corner0.value + corner1.value + corner2.value + corner3.value);

    /*  A straight, unoptimised calculation would be like:
     *    dnoise_dx = -8.0 * t20 * t0 * x0 * dot(gx0, gy0, gz0, x0, y0, z0) + t40 * gx0;
     *    dnoise_dy = -8.0 * t20 * t0 * y0 * dot(gx0, gy0, gz0, x0, y0, z0) + t40 * gy0;
     *    dnoise_dz = -8.0 * t20 * t0 * z0 * dot(gx0, gy0, gz0, x0, y0, z0) + t40 * gz0;
     *    dnoise_dx += -8.0 * t21 * t1 * x1 * dot(gx1, gy1, gz1, x1, y1, z1) + t41 * gx1;
     *    dnoise_dy += -8.0 * t21 * t1 * y1 * dot(gx1, gy1, gz1, x1, y1, z1) + t41 * gy1;
     *    dnoise_dz += -8.0 * t21 * t1 * z1 * dot(gx1, gy1, gz1, x1, y1, z1) + t41 * gz1;
     *    dnoise_dx += -8.0 * t22 * t2 * x2 * dot(gx2, gy2, gz2, x2, y2, z2) + t42 * gx2;
     *    dnoise_dy += -8.0 * t22 * t2 * y2 * dot(gx2, gy2, gz2, x2, y2, z2) + t42 * gy2;
     *    dnoise_dz += -8.0 * t22 * t2 * z2 * dot(gx2, gy2, gz2, x2, y2, z2) + t42 * gz2;
     *    dnoise_dx += -8.0 * t23 * t3 * x3 * dot(gx3, gy3, gz3, x3, y3, z3) + t43 * gx3;
     *    dnoise_dy += -8.0 * t23 * t3 * y3 * dot(gx3, gy3, gz3, x3, y3, z3) + t43 * gy3;
     *    dnoise_dz += -8.0 * t23 * t3 * z3 * dot(gx3, gy3, gz3, x3, y3, z3) + t43 * gz3;
     */
    let temp0 = corner0.t2 * corner0.t * corner0.gradient.dot(distance);
    let mut dnoise = distance * temp0;

    let temp1 = corner1.t2 * corner1.t * corner1.gradient.dot(offset1);
    dnoise += offset1 * temp1;

    let temp2 = corner2.t2 * corner2.t * corner2.gradient.dot(offset2);
    dnoise += offset2 * temp2;

    let temp3 = corner3.t2 * corner3.t * corner3.gradient.dot(offset3);
    dnoise += offset3 * temp3;

    dnoise *= -8.0;

    dnoise += corner0.gradient * corner0.t4
        + corner1.gradient * corner1.t4
        + corner2.gradient * corner2.t4
        + corner3.gradient * corner3.t4;

    /* Scale derivative to match the noise scaling */
    dnoise *= 28.0;

    (noise, dnoise.into())
}

#[inline(always)]
pub fn simplex_4d<NH>(point: [f64; 4], hasher: &NH) -> (f64, [f64; 4])
where
    NH: NoiseHasher + ?Sized,
{
    let f4: f64 = skew_factor(4);
    let g4: f64 = unskew_factor(4);

    let point = Vector4::from(point);

    // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
    // Factor for 4D skewing
    let skew = point.sum() * f4;
    let skewed = point + Vector4::broadcast(skew);
    let cell: Vector4<isize> = skewed.numcast().unwrap();

    // Factor for 4D unskewing
    let unskew = cell.sum() as f64 * g4;
    // Unskew the cell origin back to (x,y,z,w) space
    let unskewed = cell.numcast().unwrap() - Vector4::broadcast(unskew);

    // let distance_x = x - unskewed_x; // The x,y,z,w distances from the cell origin
    // let distance_y = y - unskewed_y;
    // let distance_z = z - unskewed_z;
    // let distance_w = w - unskewed_w;
    let distance = point - unskewed;

    // For the 4D case, the simplex is a 4D shape I won't even try to describe.
    // To find out which of the 24 possible simplices we're in, we need to
    // determine the magnitude ordering of x0, y0, z0 and w0.
    // The method below is a reasonable way of finding the ordering of x,y,z,w
    // and then find the correct traversal order for the simplex we're in.
    // First, six pair-wise comparisons are performed between each possible pair
    // of the four coordinates, and then the results are used to add up binary
    // bits for an integer index into a precomputed lookup table, simplex[].
    let c1 = if distance.x > distance.y { 32 } else { 0 };
    let c2 = if distance.x > distance.z { 16 } else { 0 };
    let c3 = if distance.y > distance.z { 8 } else { 0 };
    let c4 = if distance.x > distance.w { 4 } else { 0 };
    let c5 = if distance.y > distance.w { 2 } else { 0 };
    let c6 = if distance.z > distance.w { 1 } else { 0 };
    let c = c1 | c2 | c3 | c4 | c5 | c6; // '|' is mostly faster than '+'

    let [i1, j1, k1, l1]: [isize; 4]; // The integer offsets for the second simplex corner
    let [i2, j2, k2, l2]: [isize; 4]; // The integer offsets for the third simplex corner
    let [i3, j3, k3, l3]: [isize; 4]; // The integer offsets for the fourth simplex corner

    // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
    // Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
    // impossible. Only the 24 indices which have non-zero entries make any sense.
    // We use a thresholding to set the coordinates in turn from the largest magnitude.
    // The number 3 in the "simplex" array is at the position of the largest coordinate.
    i1 = if SIMPLEX[c][0] >= 3 { 1 } else { 0 };
    j1 = if SIMPLEX[c][1] >= 3 { 1 } else { 0 };
    k1 = if SIMPLEX[c][2] >= 3 { 1 } else { 0 };
    l1 = if SIMPLEX[c][3] >= 3 { 1 } else { 0 };
    // The number 2 in the "simplex" array is at the second largest coordinate.
    i2 = if SIMPLEX[c][0] >= 2 { 1 } else { 0 };
    j2 = if SIMPLEX[c][1] >= 2 { 1 } else { 0 };
    k2 = if SIMPLEX[c][2] >= 2 { 1 } else { 0 };
    l2 = if SIMPLEX[c][3] >= 2 { 1 } else { 0 };
    // The number 1 in the "simplex" array is at the second smallest coordinate.
    i3 = if SIMPLEX[c][0] >= 1 { 1 } else { 0 };
    j3 = if SIMPLEX[c][1] >= 1 { 1 } else { 0 };
    k3 = if SIMPLEX[c][2] >= 1 { 1 } else { 0 };
    l3 = if SIMPLEX[c][3] >= 1 { 1 } else { 0 };
    // The fifth corner has all coordinate offsets = 1, so no need to look that up.

    let order1 = Vector4::new(i1, j1, k1, l1);
    let order2 = Vector4::new(i2, j2, k2, l2);
    let order3 = Vector4::new(i3, j3, k3, l3);

    // Offsets for second corner in (x,y,z,w) coords
    let offset1 = distance - order1.numcast().unwrap() + Vector4::broadcast(g4);
    // Offsets for third corner in (x,y,z,w) coords
    let offset2 = distance - order2.numcast().unwrap() + Vector4::broadcast(2.0 * g4);
    // Offsets for fourth corner in (x,y,z,w) coords
    let offset3 = distance - order3.numcast().unwrap() + Vector4::broadcast(3.0 * g4);
    // Offsets for last corner in (x,y,z,w) coords
    let offset4 = distance - Vector4::one() + Vector4::broadcast(4.0 * g4);

    // Calculate gradient indexes for each corner
    let gi0 = hasher.hash(&cell.into_array());
    let gi1 = hasher.hash(&(cell + order1).into_array());
    let gi2 = hasher.hash(&(cell + order2).into_array());
    let gi3 = hasher.hash(&(cell + order2).into_array());
    let gi4 = hasher.hash(&(cell + Vector4::one()).into_array());

    struct SurfletComponents {
        value: f64,
        t: f64,
        t2: f64,
        t4: f64,
        gradient: Vector4<f64>,
    }

    impl SurfletComponents {
        fn zeros() -> Self {
            Self {
                value: 0.0,
                t: 0.0,
                t2: 0.0,
                t4: 0.0,
                gradient: Vector4::zero(),
            }
        }
    }

    fn surflet(gradient_index: usize, point: Vector4<f64>) -> SurfletComponents {
        let t = 0.6 - point.magnitude_squared();

        if t > 0.0 {
            let gradient = Vector4::from(gradient::grad4(gradient_index));
            let t2 = t * t;
            let t4 = t2 * t2;

            SurfletComponents {
                value: t4 * gradient.dot(point),
                t,
                t2,
                t4,
                gradient,
            }
        } else {
            // No influence
            SurfletComponents::zeros()
        }
    }

    /* Calculate the contribution from the five corners */
    let corner0 = surflet(gi0, distance);
    let corner1 = surflet(gi1, offset1);
    let corner2 = surflet(gi2, offset2);
    let corner3 = surflet(gi3, offset3);
    let corner4 = surflet(gi4, offset4);

    // Sum up and scale the result to cover the range [-1,1]
    let noise =
        27.0 * (corner0.value + corner1.value + corner2.value + corner3.value + corner4.value); // TODO: The scale factor is preliminary!

    /*  A straight, unoptimised calculation would be like:
     *    dnoise_dx = -8.0 * t20 * t0 * x0 * dot(gx0, gy0, gz0, gw0, x0, y0, z0, w0) + t40 * gx0;
     *    dnoise_dy = -8.0 * t20 * t0 * y0 * dot(gx0, gy0, gz0, gw0, x0, y0, z0, w0) + t40 * gy0;
     *    dnoise_dz = -8.0 * t20 * t0 * z0 * dot(gx0, gy0, gz0, gw0, x0, y0, z0, w0) + t40 * gz0;
     *    dnoise_dw = -8.0 * t20 * t0 * w0 * dot(gx0, gy0, gz0, gw0, x0, y0, z0, w0) + t40 * gw0;
     *    dnoise_dx += -8.0 * t21 * t1 * x1 * dot(gx1, gy1, gz1, gw1, x1, y1, z1, w1) + t41 * gx1;
     *    dnoise_dy += -8.0 * t21 * t1 * y1 * dot(gx1, gy1, gz1, gw1, x1, y1, z1, w1) + t41 * gy1;
     *    dnoise_dz += -8.0 * t21 * t1 * z1 * dot(gx1, gy1, gz1, gw1, x1, y1, z1, w1) + t41 * gz1;
     *    dnoise_dw += -8.0 * t21 * t1 * w1 * dot(gx1, gy1, gz1, gw1, x1, y1, z1, w1) + t41 * gw1;
     *    dnoise_dx += -8.0 * t22 * t2 * x2 * dot(gx2, gy2, gz2, gw2, x2, y2, z2, w2) + t42 * gx2;
     *    dnoise_dy += -8.0 * t22 * t2 * y2 * dot(gx2, gy2, gz2, gw2, x2, y2, z2, w2) + t42 * gy2;
     *    dnoise_dz += -8.0 * t22 * t2 * z2 * dot(gx2, gy2, gz2, gw2, x2, y2, z2, w2) + t42 * gz2;
     *    dnoise_dw += -8.0 * t22 * t2 * w2 * dot(gx2, gy2, gz2, gw2, x2, y2, z2, w2) + t42 * gw2;
     *    dnoise_dx += -8.0 * t23 * t3 * x3 * dot(gx3, gy3, gz3, gw3, x3, y3, z3, w3) + t43 * gx3;
     *    dnoise_dy += -8.0 * t23 * t3 * y3 * dot(gx3, gy3, gz3, gw3, x3, y3, z3, w3) + t43 * gy3;
     *    dnoise_dz += -8.0 * t23 * t3 * z3 * dot(gx3, gy3, gz3, gw3, x3, y3, z3, w3) + t43 * gz3;
     *    dnoise_dw += -8.0 * t23 * t3 * w3 * dot(gx3, gy3, gz3, gw3, x3, y3, z3, w3) + t43 * gw3;
     *    dnoise_dx += -8.0 * t24 * t4 * x4 * dot(gx4, gy4, gz4, gw4, x4, y4, z4, w4) + t44 * gx4;
     *    dnoise_dy += -8.0 * t24 * t4 * y4 * dot(gx4, gy4, gz4, gw4, x4, y4, z4, w4) + t44 * gy4;
     *    dnoise_dz += -8.0 * t24 * t4 * z4 * dot(gx4, gy4, gz4, gw4, x4, y4, z4, w4) + t44 * gz4;
     *    dnoise_dw += -8.0 * t24 * t4 * w4 * dot(gx4, gy4, gz4, gw4, x4, y4, z4, w4) + t44 * gw4;
     */
    let temp0 = corner0.t2 * corner0.t * (corner0.gradient.dot(distance));
    let mut dnoise = distance * temp0;

    let temp1 = corner1.t2 * corner1.t * (corner1.gradient.dot(offset1));
    dnoise += offset1 * temp1;

    let temp2 = corner2.t2 * corner2.t * (corner2.gradient.dot(offset2));
    dnoise += offset2 * temp2;

    let temp3 = corner3.t2 * corner3.t * (corner3.gradient.dot(offset3));
    dnoise += offset3 * temp3;

    let temp4 = corner4.t2 * corner4.t * (corner4.gradient.dot(offset4));
    dnoise += offset4 * temp4;

    dnoise *= -8.0;

    dnoise += corner0.gradient * corner0.t4
        + corner1.gradient * corner1.t4
        + corner2.gradient * corner2.t4
        + corner3.gradient * corner3.t4
        + corner4.gradient * corner4.t4;

    // Scale derivative to match the noise scaling
    dnoise *= 28.0;

    (noise, dnoise.into())
}

// A lookup table to traverse the simplex around a given point in 4D.
// Details can be found where this table is used, in the 4D noise method.
/* TODO: This should not be required, backport it from Bill's GLSL code! */
#[rustfmt::skip]
const SIMPLEX: [[u8; 4]; 64] = [
    [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],
    [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],
    [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],
    [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],
    [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],
    [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],
    [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],
    [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],
];