From 44154d9728521c73944a12f7a76e829fc309847d Mon Sep 17 00:00:00 2001 From: Dimitri Sabadie Date: Sun, 5 Aug 2018 17:11:44 +0200 Subject: [PATCH] Add some unit tests. --- src/lib.rs | 96 +++++++++++++++++++++++++++++++++++++++-------- tests/mod.rs | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 15 deletions(-) create mode 100644 tests/mod.rs diff --git a/src/lib.rs b/src/lib.rs index 14fdba5..6c19754 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,14 +21,14 @@ //! use splines::{Interpolation, Key, Spline}; //! //! let start = Key::new(0., 0., Interpolation::Linear); -//! let end = Key::new(1., 10., Interpolation::Linear); -//! let spline = Spline::from_keys(vec![start, end]); +//! let end = Key::new(1., 10., Interpolation::default()); +//! let spline = Spline::from_vec(vec![start, end]); //! ``` //! -//! You will notice that we used `Interpolation::Linear` for both the keys. The first key `start`’s +//! You will notice that we used `Interpolation::Linear` for the first key. The first key `start`’s //! interpolation will be used for the whole segment defined by those two keys. The `end`’s //! interpolation won’t be used. You can in theory use any [`Interpolation`] you want for the last -//! key. +//! key. We use the default one because we don’t care. //! //! # Interpolate values //! @@ -43,11 +43,26 @@ //! # use splines::{Interpolation, Key, Spline}; //! # let start = Key::new(0., 0., Interpolation::Linear); //! # let end = Key::new(1., 10., Interpolation::Linear); -//! # let spline = Spline::from_keys(vec![start, end]); +//! # let spline = Spline::from_vec(vec![start, end]); //! assert_eq!(spline.sample(0.), Some(0.)); -//! assert_eq!(spline.sample(1.), Some(10.)); +//! assert_eq!(spline.clamped_sample(1.), 10.); //! assert_eq!(spline.sample(1.1), None); //! ``` +//! +//! It’s possible that you want to get a value even if you’re out-of-bounds. This is especially +//! important for simulations / animations. Feel free to use the `Spline::clamped_interpolation` for +//! that purpose. +//! +//! ``` +//! # use splines::{Interpolation, Key, Spline}; +//! # let start = Key::new(0., 0., Interpolation::Linear); +//! # let end = Key::new(1., 10., Interpolation::Linear); +//! # let spline = Spline::from_vec(vec![start, end]); +//! assert_eq!(spline.clamped_sample(-0.9), 0.); // clamped to the first key +//! assert_eq!(spline.clamped_sample(1.1), 10.); // clamped to the last key +//! ``` +//! +//! Feel free to have a look at the rest of the documentation for advanced usage. use std::cmp::Ordering; use std::f32::consts; @@ -55,13 +70,14 @@ use std::ops::{Add, Div, Mul, Sub}; /// A spline control point. /// -/// This type associates a value at a given time. It also contains an interpolation object used to -/// determine how to interpolate values on the segment defined by this key and the next one. +/// This type associates a value at a given interpolation parameter value. It also contains an +/// interpolation hint used to determine how to interpolate values on the segment defined by this +/// key and the next one – if existing. #[derive(Copy, Clone, Debug)] pub struct Key { - /// f32 at which the [`Key`] should be reached. + /// Interpolation parameter at which the [`Key`] should be reached. pub t: f32, - /// Actual value. + /// Held value. pub value: T, /// Interpolation mode. pub interpolation: Interpolation @@ -109,14 +125,25 @@ impl Default for Interpolation { pub struct Spline(Vec>); impl Spline { - /// Create a new spline out of keys. The keys don’t have to be sorted because they’re sorted by - /// this function. - pub fn from_keys(mut keys: Vec>) -> Self { + /// Create a new spline out of keys. The keys don’t have to be sorted even though it’s recommended + /// to provide ascending sorted ones (for performance purposes). + pub fn from_vec(mut keys: Vec>) -> Self { keys.sort_by(|k0, k1| k0.t.partial_cmp(&k1.t).unwrap_or(Ordering::Less)); Spline(keys) } + /// Create a new spline by consuming an `Iterater>`. They keys don’t have to be + /// sorted. + /// + /// # Note on iterators + /// + /// It’s valid to use any iterator that implements `Iterator>`. However, you should + /// use `Spline::from_vec` if you are passing a `Vec<_>`. This will remove dynamic allocations. + pub fn from_iter(iter: I) -> Self where I: Iterator> { + Self::from_vec(iter.collect()) + } + /// Retrieve the keys of a spline. pub fn keys(&self) -> &[Key] { &self.0 @@ -124,6 +151,11 @@ impl Spline { /// Sample a spline at a given time. /// + /// The current implementation, based on immutability, cannot perform in constant time. This means + /// that sampling’s processing complexity is currently *O(log n)*. It’s possible to achieve *O(1)* + /// performance by using a slightly different spline type. If you are interested by this feature, + /// an implementation for a dedicated type is foreseen yet not started yet. + /// /// # Return /// /// `None` if you try to sample a value at a time that has no key associated with. That can also @@ -133,8 +165,7 @@ impl Spline { /// sampling. pub fn sample(&self, t: f32) -> Option where T: Interpolate { let keys = &self.0; - let i = keys.binary_search_by(|key| key.t.partial_cmp(&t).unwrap_or(Ordering::Less)).ok()?; - + let i = search_lower_cp(keys, t)?; let cp0 = &keys[i]; match cp0.interpolation { @@ -198,6 +229,8 @@ impl Spline { } /// Iterator over spline keys. +/// +/// This iterator type assures you to iterate over sorted keys. pub struct Iter<'a, T> where T: 'a { anim_param: &'a Spline, i: usize @@ -275,3 +308,36 @@ pub(crate) fn normalize_time(t: f32, cp: &Key, cp1: &Key) -> f32 { (t - cp.t) / (cp1.t - cp.t) } + +// Find the lower control point corresponding to a given time. +fn search_lower_cp(cps: &[Key], t: f32) -> Option { + let mut i = 0; + let len = cps.len(); + + if len < 2 { + return None; + } + + loop { + let cp = &cps[i]; + let cp1 = &cps[i+1]; + + if t >= cp1.t { + if i >= len - 2 { + return None; + } + + i += 1; + } else if t < cp.t { + if i == 0 { + return None; + } + + i -= 1; + } else { + break; // found + } + } + + Some(i) +} diff --git a/tests/mod.rs b/tests/mod.rs new file mode 100644 index 0000000..d08aa09 --- /dev/null +++ b/tests/mod.rs @@ -0,0 +1,103 @@ +extern crate splines; + +use splines::{Interpolation, Key, Spline}; + +#[test] +fn step_interpolation_0() { + let start = Key::new(0., 0., Interpolation::Step(0.)); + let end = Key::new(1., 10., Interpolation::default()); + let spline = Spline::from_vec(vec![start, end]); + + assert_eq!(spline.sample(0.), Some(10.)); + assert_eq!(spline.sample(0.1), Some(10.)); + assert_eq!(spline.sample(0.2), Some(10.)); + assert_eq!(spline.sample(0.5), Some(10.)); + assert_eq!(spline.sample(0.9), Some(10.)); + assert_eq!(spline.sample(1.), None); + assert_eq!(spline.clamped_sample(1.), 10.); +} + +#[test] +fn step_interpolation_0_5() { + let start = Key::new(0., 0., Interpolation::Step(0.5)); + let end = Key::new(1., 10., Interpolation::default()); + let spline = Spline::from_vec(vec![start, end]); + + assert_eq!(spline.sample(0.), Some(0.)); + assert_eq!(spline.sample(0.1), Some(0.)); + assert_eq!(spline.sample(0.2), Some(0.)); + assert_eq!(spline.sample(0.5), Some(10.)); + assert_eq!(spline.sample(0.9), Some(10.)); + assert_eq!(spline.sample(1.), None); + assert_eq!(spline.clamped_sample(1.), 10.); +} + +#[test] +fn step_interpolation_0_75() { + let start = Key::new(0., 0., Interpolation::Step(0.75)); + let end = Key::new(1., 10., Interpolation::default()); + let spline = Spline::from_vec(vec![start, end]); + + assert_eq!(spline.sample(0.), Some(0.)); + assert_eq!(spline.sample(0.1), Some(0.)); + assert_eq!(spline.sample(0.2), Some(0.)); + assert_eq!(spline.sample(0.5), Some(0.)); + assert_eq!(spline.sample(0.9), Some(10.)); + assert_eq!(spline.sample(1.), None); + assert_eq!(spline.clamped_sample(1.), 10.); +} + +#[test] +fn step_interpolation_1() { + let start = Key::new(0., 0., Interpolation::Step(1.)); + let end = Key::new(1., 10., Interpolation::default()); + let spline = Spline::from_vec(vec![start, end]); + + assert_eq!(spline.sample(0.), Some(0.)); + assert_eq!(spline.sample(0.1), Some(0.)); + assert_eq!(spline.sample(0.2), Some(0.)); + assert_eq!(spline.sample(0.5), Some(0.)); + assert_eq!(spline.sample(0.9), Some(0.)); + assert_eq!(spline.sample(1.), None); + assert_eq!(spline.clamped_sample(1.), 10.); +} + +#[test] +fn linear_interpolation() { + let start = Key::new(0., 0., Interpolation::Linear); + let end = Key::new(1., 10., Interpolation::default()); + let spline = Spline::from_vec(vec![start, end]); + + assert_eq!(spline.sample(0.), Some(0.)); + assert_eq!(spline.sample(0.1), Some(1.)); + assert_eq!(spline.sample(0.2), Some(2.)); + assert_eq!(spline.sample(0.5), Some(5.)); + assert_eq!(spline.sample(0.9), Some(9.)); + assert_eq!(spline.sample(1.), None); + assert_eq!(spline.clamped_sample(1.), 10.); +} + +#[test] +fn linear_interpolation_several_keys() { + let start = Key::new(0., 0., Interpolation::Linear); + let k1 = Key::new(1., 5., Interpolation::Linear); + let k2 = Key::new(2., 0., Interpolation::Linear); + let k3 = Key::new(3., 1., Interpolation::Linear); + let k4 = Key::new(10., 2., Interpolation::Linear); + let end = Key::new(11., 4., Interpolation::default()); + let spline = Spline::from_vec(vec![start, k1, k2, k3, k4, end]); + + assert_eq!(spline.sample(0.), Some(0.)); + assert_eq!(spline.sample(0.1), Some(0.5)); + assert_eq!(spline.sample(0.2), Some(1.)); + assert_eq!(spline.sample(0.5), Some(2.5)); + assert_eq!(spline.sample(0.9), Some(4.5)); + assert_eq!(spline.sample(1.), Some(5.)); + assert_eq!(spline.sample(1.5), Some(2.5)); + assert_eq!(spline.sample(2.), Some(0.)); + assert_eq!(spline.sample(2.75), Some(0.75)); + assert_eq!(spline.sample(3.), Some(1.)); + assert_eq!(spline.sample(6.5), Some(1.5)); + assert_eq!(spline.sample(10.), Some(2.)); + assert_eq!(spline.clamped_sample(11.), 4.); +}