///|
/// Perlin noise generator for procedural texturing.

pub(all) struct Perlin {
  randfloat : Array[Double]
  perm_x : Array[Int]
  perm_y : Array[Int]
  perm_z : Array[Int]
} derive(Debug)

pub fn Perlin::new() -> Perlin {
  let point_count = 256
  let randfloat = Array::new(capacity=point_count)
  for _ in 0.. Array[Int] {
  let p = Array::new(capacity=256)
  for i in 0..<256 {
    p.push(i)
  }
  perlin_permute(p)
  p
}

fn perlin_permute(p : Array[Int]) -> Unit {
  let mut i = p.length() - 1
  while i >= 1 {
    let target = default_rng().random_int(i + 1).0
    let tmp = p[i]
    p[i] = p[target]
    p[target] = tmp
    i = i - 1
  }
}

fn Perlin::perm_index(self : Perlin, i : Int, j : Int, k : Int) -> Int {
  let xi = i & 255
  let yi = j & 255
  let zi = k & 255
  self.perm_x[xi] ^ self.perm_y[yi] ^ self.perm_z[zi]
}

fn trilinear_interp(c000 : Double, c001 : Double, c010 : Double, c011 : Double,
                    c100 : Double, c101 : Double, c110 : Double, c111 : Double,
                    u : Double, v : Double, w : Double) -> Double {
  let uu = u * u * (3.0 - 2.0 * u)
  let vv = v * v * (3.0 - 2.0 * v)
  let ww = w * w * (3.0 - 2.0 * w)

  let x00 = c000 + uu * (c100 - c000)
  let x01 = c001 + uu * (c101 - c001)
  let x10 = c010 + uu * (c110 - c010)
  let x11 = c011 + uu * (c111 - c011)

  let y0 = x00 + vv * (x10 - x00)
  let y1 = x01 + vv * (x11 - x01)

  y0 + ww * (y1 - y0)
}

fn Perlin::noise(self : Perlin, p : Vec3) -> Double {
  let u = p.x - p.x.floor()
  let v = p.y - p.y.floor()
  let w = p.z - p.z.floor()

  let i = p.x.floor().to_int()
  let j = p.y.floor().to_int()
  let k = p.z.floor().to_int()

  let corner_values = Array::new(capacity=8)

  for di in 0..<2 {
    for dj in 0..<2 {
      for dk in 0..<2 {
        let idx = self.randfloat[self.perm_index(i + di, j + dj, k + dk) % 256]
        corner_values.push(idx)
      }
    }
  }

  trilinear_interp(
    corner_values[0], corner_values[1], corner_values[2], corner_values[3],
    corner_values[4], corner_values[5], corner_values[6], corner_values[7],
    u, v, w,
  )
}

pub fn Perlin::turbulence(self : Perlin, p : Vec3, depth~ : Int) -> Double {
  let mut accum = 0.0
  let mut temp_p = p
  let mut weight = 1.0

  for _ in 0..