///|
/// Clamp a value to the normalized animation interval.
pub fn clamp01(value : Double) -> Double {
  if value < 0.0 {
    0.0
  } else if value > 1.0 {
    1.0
  } else {
    value
  }
}

///|
/// Map normalized motion progress onto an application value range.
pub fn interpolate(
  start : Double,
  end : Double,
  t : Double,
  motion : MotionFn,
) -> Double {
  start + (end - start) * motion(t)
}

///|
/// Sample a motion curve at evenly spaced points, including both endpoints.
pub fn sample(motion : MotionFn, count : Int) -> Array[Double] {
  let result : Array[Double] = Array::new()
  if count <= 0 {
    return result
  }
  if count == 1 {
    result.push(motion(0.0))
    return result
  }
  for i in 0.. Array[Double] {
  let result : Array[Double] = Array::new()
  if count <= 0 {
    return result
  }
  if count == 1 {
    result.push(start)
    return result
  }
  for i in 0.. MotionFn {
  fn(t : Double) -> Double {
    if split <= 0.0 {
      second(t)
    } else if split >= 1.0 {
      first(t)
    } else if t < split {
      first(t / split) * split
    } else {
      split + second((t - split) / (1.0 - split)) * (1.0 - split)
    }
  }
}

///|
/// Estimate local velocity with a centered finite difference.
pub fn velocity(motion : MotionFn, t : Double, epsilon : Double) -> Double {
  let step = if epsilon <= 0.0 { 0.000001 } else { epsilon }
  (motion(t + step) - motion(t - step)) / (2.0 * step)
}

///|
/// Check whether sampled values never decrease.
pub fn is_monotonic(motion : MotionFn, samples : Int) -> Bool {
  let values = sample(motion, samples)
  for i in 1.. Double {
  let mut largest = 0.0
  for value in sample(motion, samples) {
    let excess = if value < 0.0 {
      -value
    } else if value > 1.0 {
      value - 1.0
    } else {
      0.0
    }
    if excess > largest {
      largest = excess
    }
  }
  largest
}