///|
/// Post-process camera samples with screen-safe and optical policies.
pub struct CameraPostSettings2 {
  safe_margin : Double
  shake : Point2
  exposure : Double
  focus_depth : Double
  vignette : Double
} derive(Debug)

///|
pub fn camera_post_settings2() -> CameraPostSettings2 {
  {
    safe_margin: 0.05,
    shake: point2(x=0.0, y=0.0),
    exposure: 0.0,
    focus_depth: 1.0,
    vignette: 0.0,
  }
}

///|
pub fn CameraPostSettings2::safe_margin(self : CameraPostSettings2) -> Double {
  self.safe_margin
}

///|
pub fn CameraPostSettings2::shake(self : CameraPostSettings2) -> Point2 {
  self.shake
}

///|
pub fn CameraPostSettings2::exposure(self : CameraPostSettings2) -> Double {
  self.exposure
}

///|
pub fn CameraPostSettings2::focus_depth(self : CameraPostSettings2) -> Double {
  self.focus_depth
}

///|
pub fn CameraPostSettings2::vignette(self : CameraPostSettings2) -> Double {
  self.vignette
}

///|
pub fn CameraPostSettings2::with_safe_margin(
  self : CameraPostSettings2,
  value : Double,
) -> CameraPostSettings2 {
  {
    safe_margin: clamp01(value),
    shake: self.shake,
    exposure: self.exposure,
    focus_depth: self.focus_depth,
    vignette: self.vignette,
  }
}

///|
pub fn CameraPostSettings2::with_shake(
  self : CameraPostSettings2,
  value : Point2,
) -> CameraPostSettings2 {
  {
    safe_margin: self.safe_margin,
    shake: value,
    exposure: self.exposure,
    focus_depth: self.focus_depth,
    vignette: self.vignette,
  }
}

///|
pub fn CameraPostSettings2::with_exposure(
  self : CameraPostSettings2,
  value : Double,
) -> CameraPostSettings2 {
  {
    safe_margin: self.safe_margin,
    shake: self.shake,
    exposure: value,
    focus_depth: self.focus_depth,
    vignette: self.vignette,
  }
}

///|
pub fn CameraPostSettings2::with_focus(
  self : CameraPostSettings2,
  value : Double,
) -> CameraPostSettings2 {
  {
    safe_margin: self.safe_margin,
    shake: self.shake,
    exposure: self.exposure,
    focus_depth: value.max(0.000001),
    vignette: self.vignette,
  }
}

///|
pub fn CameraPostSettings2::with_vignette(
  self : CameraPostSettings2,
  value : Double,
) -> CameraPostSettings2 {
  {
    safe_margin: self.safe_margin,
    shake: self.shake,
    exposure: self.exposure,
    focus_depth: self.focus_depth,
    vignette: clamp01(value),
  }
}

///|
pub fn apply_camera_post(
  camera : CameraRig,
  settings : CameraPostSettings2,
) -> CameraRig {
  camera.with_jitter(settings.shake)
}

///|
pub fn screen_safe_bounds(viewport : Viewport, margin : Double) -> Bounds2D {
  let horizontal = viewport.width() * clamp01(margin)
  let vertical = viewport.height() * clamp01(margin)
  {
    minimum: point2(x=horizontal, y=vertical),
    maximum: point2(
      x=viewport.width() - horizontal,
      y=viewport.height() - vertical,
    ),
  }
}

///|
pub fn screen_safe_contains(
  viewport : Viewport,
  point : Point2,
  margin : Double,
) -> Bool {
  let bounds = screen_safe_bounds(viewport, margin)
  point.x() >= bounds.minimum().x() &&
  point.x() <= bounds.maximum().x() &&
  point.y() >= bounds.minimum().y() &&
  point.y() <= bounds.maximum().y()
}

///|
pub fn camera_depth_of(camera : CameraRig, point : Point3) -> Double {
  let position = camera.pose().position()
  let delta = point3(
    x=point.x() - position.x(),
    y=point.y() - position.y(),
    z=point.z() - position.z(),
  )
  point3_dot(delta, camera.pose().forward())
}

///|
pub fn camera_focus_weight(
  camera : CameraRig,
  point : Point3,
  focus : Double,
  falloff : Double,
) -> Double {
  let distance = (camera_depth_of(camera, point) - focus).abs()
  if falloff <= 0.0 {
    if distance == 0.0 {
      1.0
    } else {
      0.0
    }
  } else {
    @math.pow(clamp01(1.0 - distance / falloff), 2.0)
  }
}

///|
pub fn project_safe(
  camera : CameraRig,
  point : Point3,
  margin : Double,
) -> (Point2, Bool) {
  let screen = camera.project(point)
  (
    screen,
    camera.visible(point) &&
    screen_safe_contains(camera.viewport(), screen, margin),
  )
}

///|
pub fn camera_motion_delta(
  first : CameraRig,
  second : CameraRig,
) -> (Double, Double) {
  (
    point3_distance(first.pose().position(), second.pose().position()),
    first.pose().forward().lerp(second.pose().forward(), 0.5).x(),
  )
}