///|
/// Dot product for planar tangent and normal calculations.
pub fn point2_dot(first : Point2, second : Point2) -> Double {
  first.x() * second.x() + first.y() * second.y()
}

///|
pub fn point2_cross(first : Point2, second : Point2) -> Double {
  first.x() * second.y() - first.y() * second.x()
}

///|
pub fn point2_length(value : Point2) -> Double {
  @math.hypot(value.x(), value.y())
}

///|
pub fn point2_normalized(value : Point2) -> Point2 {
  let length = point2_length(value)
  if length == 0.0 {
    point2(x=0.0, y=0.0)
  } else {
    point2(x=value.x() / length, y=value.y() / length)
  }
}

///|
pub fn point2_normal(value : Point2) -> Point2 {
  point2(x=-value.y(), y=value.x())
}

///|
pub fn point2_reflect(value : Point2, normal : Point2) -> Point2 {
  let unit = point2_normalized(normal)
  let scale = 2.0 * point2_dot(value, unit)
  point2(x=value.x() - scale * unit.x(), y=value.y() - scale * unit.y())
}

///|
pub fn point2_angle(first : Point2, second : Point2) -> Double {
  @math.atan2(point2_cross(first, second), point2_dot(first, second))
}

///|
pub fn point2_from_polar(radius : Double, angle : Double) -> Point2 {
  point2(x=radius * @math.cos(angle), y=radius * @math.sin(angle))
}

///|
pub fn point2_lerp_clamped(
  first : Point2,
  second : Point2,
  ratio : Double,
) -> Point2 {
  first.lerp(second, clamp01(ratio))
}

///|
pub fn point3_dot(first : Point3, second : Point3) -> Double {
  first.x() * second.x() + first.y() * second.y() + first.z() * second.z()
}

///|
pub fn point3_length(value : Point3) -> Double {
  @math.pow(point3_dot(value, value), 0.5)
}

///|
pub fn point3_normalized(value : Point3) -> Point3 {
  let length = point3_length(value)
  if length == 0.0 {
    point3(x=0.0, y=0.0, z=0.0)
  } else {
    point3(x=value.x() / length, y=value.y() / length, z=value.z() / length)
  }
}

///|
pub fn point3_cross(first : Point3, second : Point3) -> Point3 {
  point3(
    x=first.y() * second.z() - first.z() * second.y(),
    y=first.z() * second.x() - first.x() * second.z(),
    z=first.x() * second.y() - first.y() * second.x(),
  )
}

///|
pub fn point3_distance(first : Point3, second : Point3) -> Double {
  point3_length(
    point3(
      x=second.x() - first.x(),
      y=second.y() - first.y(),
      z=second.z() - first.z(),
    ),
  )
}

///|
pub fn point3_lerp_clamped(
  first : Point3,
  second : Point3,
  ratio : Double,
) -> Point3 {
  first.lerp(second, clamp01(ratio))
}

///|
pub fn remap_value(
  value : Double,
  source_min : Double,
  source_max : Double,
  target_min : Double,
  target_max : Double,
) -> Double {
  let span = source_max - source_min
  if span == 0.0 {
    target_min
  } else {
    target_min + (target_max - target_min) * (value - source_min) / span
  }
}

///|
pub fn smooth_damp(
  current : Double,
  target : Double,
  velocity : Double,
  smooth_time : Double,
  delta_time : Double,
) -> (Double, Double) {
  let time = smooth_time.max(0.000001)
  let step = delta_time.max(0.0)
  let omega = 2.0 / time
  let x = omega * step
  let factor = 1.0 / (1.0 + x + 0.48 * x * x + 0.235 * x * x * x)
  let difference = current - target
  let temporary = (velocity + omega * difference) * step
  let next_velocity = (velocity - omega * temporary) * factor
  let next = target + (difference + temporary) * factor
  (next, next_velocity)
}

///|
pub fn critically_damped_step(
  current : Double,
  target : Double,
  velocity : Double,
  response : Double,
  delta_time : Double,
) -> (Double, Double) {
  smooth_damp(
    current,
    target,
    velocity,
    1.0 / response.max(0.000001),
    delta_time,
  )
}