///|
/// Camera model with configurable position, look-at, and defocus blur.

pub(all) struct Camera {
  origin : Vec3
  lower_left_corner : Vec3
  horizontal : Vec3
  vertical : Vec3
  u : Vec3
  v : Vec3
  w : Vec3
  lens_radius : Double
  time0 : Double
  time1 : Double
} derive(Debug)

pub fn Camera::new(
  lookfrom~ : Vec3,
  lookat~ : Vec3,
  vup~ : Vec3,
  vfov~ : Double,
  aspect_ratio~ : Double,
  aperture~ : Double,
  focus_dist~ : Double,
  time0~ : Double,
  time1~ : Double
) -> Camera {
  let theta = vfov * @math.PI / 180.0
  let h = @math.tan(theta / 2.0)
  let viewport_height = 2.0 * h
  let viewport_width = aspect_ratio * viewport_height

  let w_dir = (lookfrom - lookat).normalize()
  let u_dir = vup.cross(w_dir).normalize()
  let v_dir = w_dir.cross(u_dir)

  let origin = lookfrom
  let horizontal = u_dir.mul_scalar(viewport_width * focus_dist)
  let vertical = v_dir.mul_scalar(viewport_height * focus_dist)
  let lower_left_corner = origin - horizontal.div_scalar(2.0) - vertical.div_scalar(2.0) - w_dir.mul_scalar(focus_dist)

  {
    origin, lower_left_corner, horizontal, vertical,
    u: u_dir, v: v_dir, w: w_dir,
    lens_radius: aperture / 2.0,
    time0, time1,
  }
}

pub fn Camera::default(aspect_ratio~ : Double) -> Camera {
  Camera::new(
    lookfrom={ x: 0.0, y: 0.0, z: 0.0 },
    lookat={ x: 0.0, y: 0.0, z: -1.0 },
    vup={ x: 0.0, y: 1.0, z: 0.0 },
    vfov=90.0, aspect_ratio=aspect_ratio, aperture=0.0, focus_dist=1.0,
    time0=0.0, time1=0.0,
  )
}

pub fn Camera::get_ray(self : Camera, s : Double, t : Double) -> Ray {
  let (rd, _) = default_rng().random_in_unit_disk()
  let offset = self.u.mul_scalar(rd.x * self.lens_radius) + self.v.mul_scalar(rd.y * self.lens_radius)
  let (tm, _) = default_rng().random_double_range(min=self.time0, max=self.time1)
  Ray::new(
    orig=self.origin + offset,
    dir=self.lower_left_corner + self.horizontal.mul_scalar(s) + self.vertical.mul_scalar(t) - self.origin - offset,
    tm=tm,
  )
}