aboutsummaryrefslogtreecommitdiffstats
path: root/src/interpolation.rs
blob: 17e4a48087f93d41f6eebd67c9a48db1272b9f51 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// 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>)> {
    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: 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,
    }
  }
}