///|
priv struct Vec3 {
  x : Double
  y : Double
  z : Double
}

///|
fn vec3(x : Double, y : Double, z : Double) -> Vec3 {
  { x, y, z }
}

///|
fn Vec3::add(self : Vec3, other : Vec3) -> Vec3 {
  { x: self.x + other.x, y: self.y + other.y, z: self.z + other.z }
}

///|
fn Vec3::sub(self : Vec3, other : Vec3) -> Vec3 {
  { x: self.x - other.x, y: self.y - other.y, z: self.z - other.z }
}

///|
fn Vec3::mul(self : Vec3, s : Double) -> Vec3 {
  { x: self.x * s, y: self.y * s, z: self.z * s }
}

///|
fn Vec3::dot(self : Vec3, other : Vec3) -> Double {
  self.x * other.x + self.y * other.y + self.z * other.z
}

///|
fn Vec3::cross(self : Vec3, other : Vec3) -> Vec3 {
  {
    x: self.y * other.z - self.z * other.y,
    y: self.z * other.x - self.x * other.z,
    z: self.x * other.y - self.y * other.x,
  }
}

///|
fn Vec3::length(self : Vec3) -> Double {
  self.dot(self).sqrt()
}

///|
fn Vec3::normalize(self : Vec3) -> Vec3 {
  let len = self.length()
  if len <= 0.0 {
    self
  } else {
    self.mul(1.0 / len)
  }
}

///|
fn clamp(x : Double, min_val : Double, max_val : Double) -> Double {
  if x < min_val {
    min_val
  } else if x > max_val {
    max_val
  } else {
    x
  }
}

///|
fn mat4_perspective(
  fov_y : Double,
  aspect : Double,
  near : Double,
  far : Double,
) -> Array[Double] {
  let f = 1.0 / @math.tan(fov_y / 2.0)
  let nf = 1.0 / (near - far)
  [
    f / aspect,
    0.0,
    0.0,
    0.0,
    0.0,
    f,
    0.0,
    0.0,
    0.0,
    0.0,
    (far + near) * nf,
    -1.0,
    0.0,
    0.0,
    2.0 * far * near * nf,
    0.0,
  ]
}

///|
fn mat4_look_at(eye : Vec3, center : Vec3, up : Vec3) -> Array[Double] {
  let f = center.sub(eye).normalize()
  let s = f.cross(up).normalize()
  let u = s.cross(f)
  [
    s.x,
    u.x,
    -f.x,
    0.0,
    s.y,
    u.y,
    -f.y,
    0.0,
    s.z,
    u.z,
    -f.z,
    0.0,
    -s.dot(eye),
    -u.dot(eye),
    f.dot(eye),
    1.0,
  ]
}

///|
fn mat4_mul(a : Array[Double], b : Array[Double]) -> Array[Double] {
  let out = Array::make(16, 0.0)
  for c = 0; c < 4; c = c + 1 {
    for r = 0; r < 4; r = r + 1 {
      let i = c * 4 + r
      let a0 = a[0 * 4 + r] * b[c * 4 + 0]
      let a1 = a[1 * 4 + r] * b[c * 4 + 1]
      let a2 = a[2 * 4 + r] * b[c * 4 + 2]
      let a3 = a[3 * 4 + r] * b[c * 4 + 3]
      out[i] = a0 + a1 + a2 + a3
    }
  }
  out
}