// 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 = PartialOrd // + Copys // + Add // + Sub // + Mul; pub fn raw_lerp(a: T, b: T, t: f64) -> T where T: Copy + Add + Sub + Mul, { 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 { pub time: f64, pub val: ValueType, } impl KeyFrame { pub fn new(time: f64, val: ValueType) -> KeyFrame { KeyFrame { time, val } } } impl< ValueType: PartialOrd + Copy + Add + Sub + Mul, > KeyFrame { /** Simply passes the data to the timing function involved. Does not do bounding. */ pub fn raw_value_at( from: &KeyFrame, to: &KeyFrame, 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 { pub keyframes: Vec>, pub loop_count: u32, } impl AnimationTrack { fn get_sorted_keyframes(&self) -> Vec> { 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, KeyFrame)> { let okf = self.get_sorted_keyframes(); if okf.len() == 0 { None } else { Some((*okf.first().unwrap(), *okf.last().unwrap())) } } fn length(fl: (KeyFrame, KeyFrame)) -> f64 { fl.1.time - fl.0.time } } impl< ValueType: PartialOrd + Copy + Add + Sub + Mul, > AnimationTrack { /** 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>, time: f64, ) -> Option<(KeyFrame, KeyFrame)> { // 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>, time: f64, timing_function: TimingFunction, ) -> Option { 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, } } }