fn simplex3d(
  p: Array[Float], 
  perm_table: Array[Int], 
  grads: Array[Array[Float]], 
  scale: Float 
) -> Float {
  let f3 = 1.0 / 3.0
  let g3 = 1.0 / 6.0

  let nx = p[0] * scale
  let ny = p[1] * scale
  let nz = p[2] * scale

  let s = (nx + ny + nz).to_double() * f3
  let i = @math.floor(nx.to_double() + s).to_int()
  let j = @math.floor(ny.to_double() + s).to_int()
  let k = @math.floor(nz.to_double() + s).to_int()

  let t = (i.to_float() + j.to_float() + k.to_float()) * g3.to_float()
  let x0 = nx - (i.to_float() - t)
  let y0 = ny - (j.to_float() - t)
  let z0 = nz - (k.to_float() - t)

  let mut ijk1 = [0, 0, 0]
  let mut ijk2 = [0, 0, 0]

  if x0 >= y0 {
    if y0 >= z0 {
      ijk1 = [1, 0, 0]
      ijk2 = [1, 1, 0]
    } else if x0 >= z0 {
      ijk1 = [1, 0, 0]
      ijk2 = [1, 0, 1]
    } else {
      ijk1 = [0, 0, 1]
      ijk2 = [1, 0, 1]
    }
  } else {
    if y0 < z0 {
      ijk1 = [0, 0, 1]
      ijk2 = [0, 1, 1]
    } else if x0 < z0 {
      ijk1 = [0, 1, 0]
      ijk2 = [0, 1, 1]
    } else {
      ijk1 = [0, 1, 0]
      ijk2 = [1, 1, 0]
    }
  }

  let x1 = x0 - (ijk1[0].to_double() + g3).to_float()
  let y1 = y0 - (ijk1[1].to_double() + g3).to_float()
  let z1 = z0 - (ijk1[2].to_double() + g3).to_float()

  let x2 = x0 - (ijk2[0].to_double() + 2.0 * g3).to_float()
  let y2 = y0 - (ijk2[1].to_double() + 2.0 * g3).to_float()
  let z2 = z0 - (ijk2[2].to_double() + 2.0 * g3).to_float()

  let x3 = x0 - 1.0 + 3.0 * g3.to_float()
  let y3 = y0 - 1.0 + 3.0 * g3.to_float()
  let z3 = z0 - 1.0 + 3.0 * g3.to_float()

  let gi0 = hash3d(i, j, k, perm_table, grads)
  let gi1 = hash3d(i + ijk1[0], j + ijk1[1], k + ijk1[2], perm_table, grads)
  let gi2 = hash3d(i + ijk2[0], j + ijk2[1], k + ijk2[2], perm_table, grads)
  let gi3 = hash3d(i + 1, j + 1, k + 1, perm_table, grads)

  let mut n0 = 0.0
  let mut n1 = 0.0
  let mut n2 = 0.0
  let mut n3 = 0.0

  let t0 = 0.6 - (x0 * x0 + y0 * y0 + z0 * z0).to_double()
  if t0 >= 0.0 {
    let t0_2 = t0 * t0
    n0 = t0_2 * t0_2 * (gi0[0] * x0 + gi0[1] * y0 + gi0[2] * z0).to_double()
  }

  let t1 = 0.6 - (x1 * x1 + y1 * y1 + z1 * z1).to_double()
  if t1 >= 0.0 {
    let t1_2 = t1 * t1
    n1 = t1_2 * t1_2 * (gi1[0] * x1 + gi1[1] * y1 + gi1[2] * z1).to_double()
  }

  let t2 = 0.6 - (x2 * x2 + y2 * y2 + z2 * z2).to_double()
  if t2 >= 0.0 {
    let t2_2 = t2 * t2
    n2 = t2_2 * t2_2 * (gi2[0] * x2 + gi2[1] * y2 + gi2[2] * z2).to_double()
  }

  let t3 = 0.6 - (x3 * x3 + y3 * y3 + z3 * z3).to_double()
  if t3 >= 0.0 {
    let t3_2 = t3 * t3
    n3 = t3_2 * t3_2 * (gi3[0] * x3 + gi3[1] * y3 + gi3[2] * z3).to_double()
  }

  32.0 * (n0 + n1 + n2 + n3).to_float() 
}