aboutsummaryrefslogblamecommitdiffstats
path: root/src/interpolation.rs
blob: d088bf8a3a25a91f7a37d9dacd13fd204527dc45 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14













                                                                                                                                  







                                                                     
                      


                         

                                                                                                                 
                                















                                                                


                                                                               


















                                                                   



























































































                                                                                       
// NOTE: This entire file would need to be reworked to handle functions like bezier curves/any other fn involving multiple points.
//       Someone should do this at some point.
//
//       This likely would only be slightly breaking; ie the most commonly used portions of AnimationTrack would remain in-tact
//       however, the internals of a KeyFrame would change immensely and be (more or less) impossible to keep

use std::ops::{Add, Index, Mul, Sub};

// For when RFC1733 finally passes
// trait AcceptableValueType<ValueType> = PartialOrd
//   + Copys
//   + Add<Output = ValueType>
//   + Sub<Output = ValueType>
//   + Mul<f64, Output = ValueType>;

pub fn raw_lerp<T>(a: T, b: T, t: f64) -> T
where
  T: Copy + Add<Output = T> + Sub<Output = T> + Mul<f64, Output = T>,
{
  a + (b - a) * t
}

#[derive(Clone, Copy)]
pub enum TimingFunction {
  Lerp,
}
#[derive(Clone, Copy, PartialEq, PartialOrd)]
// We could make a TimeType generic (I initially did), however it's safe to assume we use a 64-bit float for this
pub struct KeyFrame<ValueType> {
  pub time: f64,
  pub val: ValueType,
}
impl<ValueType> KeyFrame<ValueType> {
  pub fn new(time: f64, val: ValueType) -> KeyFrame<ValueType> {
    KeyFrame { time, val }
  }
}
impl<
    ValueType: PartialOrd
      + Copy
      + Add<Output = ValueType>
      + Sub<Output = ValueType>
      + Mul<f64, Output = ValueType>,
  > KeyFrame<ValueType>
{
  /**
  Simply passes the data to the timing function involved. Does not do bounding.
  */
  pub fn raw_value_at(
    from: &KeyFrame<ValueType>,
    to: &KeyFrame<ValueType>,
    time: f64,
    timing_function: TimingFunction,
  ) -> ValueType {
    // Order them so `from` is always the lower bound
    let (from, to) = if from.time < to.time {
      (from, to)
    } else {
      (to, from)
    };
    let length = to.time - from.time;
    let position = (time - from.time) / length;
    match timing_function {
      TimingFunction::Lerp => raw_lerp(from.val, to.val, position),
    }
  }
}

#[derive(Clone)]
pub struct AnimationTrack<ValueType> {
  pub keyframes: Vec<KeyFrame<ValueType>>,
  pub loop_count: u32,
}
impl<ValueType: Copy> AnimationTrack<ValueType> {
  fn get_sorted_keyframes(&self) -> Vec<KeyFrame<ValueType>> {
    let mut kf = self.keyframes.clone();
    kf.sort_by(|a, b| a.time.total_cmp(&b.time));
    kf
  }
  fn get_first_last(&self) -> Option<(KeyFrame<ValueType>, KeyFrame<ValueType>)> {
    let okf = self.get_sorted_keyframes();
    if okf.len() == 0 {
      None
    } else {
      Some((*okf.first().unwrap(), *okf.last().unwrap()))
    }
  }
  fn length(fl: (KeyFrame<ValueType>, KeyFrame<ValueType>)) -> f64 {
    fl.1.time - fl.0.time
  }
}
impl<
    ValueType: PartialOrd
      + Copy
      + Add<Output = ValueType>
      + Sub<Output = ValueType>
      + Mul<f64, Output = ValueType>,
  > AnimationTrack<ValueType>
{
  /** Get the 2 keyframes surrounding the current position in time. */
  fn get_current_keyframes(
    &self,
    // We have the user pass this, as to prevent needing to constantly re-calculate it.
    sorted_keyframes: Vec<KeyFrame<ValueType>>,
    time: f64,
  ) -> Option<(KeyFrame<ValueType>, KeyFrame<ValueType>)> {
    // TODO: maybe refactor the internals of this to be faster
    let idx = {
      let mut idx = 0;
      let mut found = false;
      for f in &sorted_keyframes {
        if f.time > time {
          found = true;
          break;
        } else {
          idx += 1;
        }
      }
      if found {
        Some(idx)
      } else {
        None
      }
    };
    match idx {
      None => None,
      Some(idx) => {
        // If it's the last item, we don't have a 2nd
        if idx + 1 == sorted_keyframes.len() {
          None
        } else {
          Some((
            *sorted_keyframes.index(idx),
            *sorted_keyframes.index(idx + 1),
          ))
        }
      }
    }
  }
  /** Gets the current value */
  pub fn get_current_value(
    &self,
    // We have the user pass this, as to prevent needing to constantly re-calculate it.
    sorted_keyframes: Vec<KeyFrame<ValueType>>,
    time: f64,
    timing_function: TimingFunction,
  ) -> Option<ValueType> {
    let frames = self.get_current_keyframes(sorted_keyframes, time);
    match frames {
      Some(frames) => Some(KeyFrame::raw_value_at(
        &frames.0,
        &frames.1,
        time,
        timing_function,
      )),
      None => None,
    }
  }
}