aboutsummaryrefslogblamecommitdiffstats
path: root/src/interpolation.rs
blob: ecb2c85842a7fa1521d7e0453745c17766a5943b (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
}
pub fn raw_bezier<T>(v: Vec<T>, t: f64) -> T
where
  T: Copy + Add<Output = T> + Sub<Output = T> + Mul<f64, Output = T>,
{
  todo!()
}

#[derive(Clone, Copy)]
pub enum TwoValueTimingFunction {
  Lerp,
}
#[derive(Clone, Copy)]
pub enum ManyValueTimingFunction {
  Bezier,
}

#[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: TwoValueTimingFunction,
  ) -> 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 {
      TwoValueTimingFunction::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>)> {
    let length = AnimationTrack::length((
      *sorted_keyframes.first().unwrap(),
      *sorted_keyframes.last().unwrap(),
    ));
    // This can be removed if size restrictions call for it
    match if time > length {
      if time > length * f64::from(self.loop_count) {
        None
      } else {
        Some(time % length)
      }
    } else {
      Some(time)
    } {
      None => None,
      Some(time) => {
        // 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: TwoValueTimingFunction,
  ) -> 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,
    }
  }
}