//! Spline curves and operations. #[cfg(feature = "std")] use crate::interpolate::{Interpolate, Interpolator}; use crate::interpolation::Interpolation; use crate::key::Key; #[cfg(not(feature = "std"))] use alloc::vec::Vec; #[cfg(not(feature = "std"))] use core::cmp::Ordering; #[cfg(not(feature = "std"))] use core::ops::{Div, Mul}; #[cfg(any(feature = "serialization", feature = "serde"))] use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] use std::cmp::Ordering; /// Spline curve used to provide interpolation between control points (keys). /// /// Splines are made out of control points ([`Key`]). When creating a [`Spline`] with /// [`Spline::from_vec`] or [`Spline::from_iter`], the keys don’t have to be sorted (they are sorted /// automatically by the sampling value). /// /// You can sample from a spline with several functions: /// /// - [`Spline::sample`]: allows you to sample from a spline. If not enough keys are available /// for the required interpolation mode, you get `None`. /// - [`Spline::clamped_sample`]: behaves like [`Spline::sample`] but will return either the first /// or last key if out of bound; it will return `None` if not enough key. #[derive(Debug, Clone)] #[cfg_attr( any(feature = "serialization", feature = "serde"), derive(Deserialize, Serialize) )] pub struct Spline(pub(crate) Vec>); impl Spline { /// Internal sort to ensure invariant of sorting keys is valid. fn internal_sort(&mut self) where T: PartialOrd, { self .0 .sort_by(|k0, k1| k0.t.partial_cmp(&k1.t).unwrap_or(Ordering::Less)); } /// 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(keys: Vec>) -> Self where T: PartialOrd, { let mut spline = Spline(keys); spline.internal_sort(); spline } /// 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`]. pub fn from_iter(iter: I) -> Self where I: Iterator>, T: PartialOrd, { Self::from_vec(iter.collect()) } /// Retrieve the keys of a spline. pub fn keys(&self) -> &[Key] { &self.0 } /// Number of keys. #[inline(always)] pub fn len(&self) -> usize { self.0.len() } /// Check whether the spline has no key. #[inline(always)] pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Sample a spline at a given time, returning the interpolated value along with its associated /// key. /// /// 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 /// happen if you try to sample between two keys with a specific interpolation mode that makes the /// sampling impossible. For instance, [`Interpolation::CatmullRom`] requires *four* keys. If /// you’re near the beginning of the spline or its end, ensure you have enough keys around to make /// the sampling. pub fn sample_with_key(&self, t: T) -> Option> where T: Interpolator, V: Interpolate, { let keys = &self.0; let i = search_lower_cp(keys, t)?; let cp0 = &keys[i]; let value = match cp0.interpolation { Interpolation::Step(threshold) => { let cp1 = &keys[i + 1]; let nt = t.normalize(cp0.t, cp1.t); let value = V::step(nt, threshold, cp0.value, cp1.value); Some(value) } Interpolation::Linear => { let cp1 = &keys[i + 1]; let nt = t.normalize(cp0.t, cp1.t); let value = V::lerp(nt, cp0.value, cp1.value); Some(value) } Interpolation::Cosine => { let cp1 = &keys[i + 1]; let nt = t.normalize(cp0.t, cp1.t); let value = V::cosine(nt, cp0.value, cp1.value); Some(value) } Interpolation::CatmullRom => { // We need at least four points for Catmull Rom; ensure we have them, otherwise, return // None. if i == 0 || i >= keys.len() - 2 { None } else { let cp1 = &keys[i + 1]; let cpm0 = &keys[i - 1]; let cpm1 = &keys[i + 2]; let nt = t.normalize(cp0.t, cp1.t); let value = V::cubic_hermite( nt, (cpm0.t, cpm0.value), (cp0.t, cp0.value), (cp1.t, cp1.value), (cpm1.t, cpm1.value), ); Some(value) } } Interpolation::Bezier(u) | Interpolation::StrokeBezier(_, u) => { // We need to check the next control point to see whether we want quadratic or cubic Bezier. let cp1 = &keys[i + 1]; let nt = t.normalize(cp0.t, cp1.t); let value = match cp1.interpolation { Interpolation::Bezier(v) => V::cubic_bezier_mirrored(nt, cp0.value, u, v, cp1.value), Interpolation::StrokeBezier(v, _) => V::cubic_bezier(nt, cp0.value, u, v, cp1.value), _ => V::quadratic_bezier(nt, cp0.value, u, cp1.value), }; Some(value) } }; value.map(|value| SampledWithKey { value, key: i }) } /// Sample a spline at a given time. /// pub fn sample(&self, t: T) -> Option where T: Interpolator, V: Interpolate, { self.sample_with_key(t).map(|sampled| sampled.value) } /// Sample a spline at a given time with clamping, returning the interpolated value along with its /// associated key. /// /// # Return /// /// If you sample before the first key or after the last one, return the first key or the last /// one, respectively. Otherwise, behave the same way as [`Spline::sample`]. /// /// # Error /// /// This function returns [`None`] if you have no key. pub fn clamped_sample_with_key(&self, t: T) -> Option> where T: Interpolator, V: Interpolate, { if self.0.is_empty() { return None; } self.sample_with_key(t).or_else(move || { let first = self.0.first().unwrap(); if t <= first.t { let sampled = SampledWithKey { value: first.value, key: 0, }; Some(sampled) } else { let last = self.0.last().unwrap(); if t >= last.t { let sampled = SampledWithKey { value: last.value, key: self.0.len() - 1, }; Some(sampled) } else { None } } }) } /// Sample a spline at a given time with clamping. pub fn clamped_sample(&self, t: T) -> Option where T: Interpolator, V: Interpolate, { self.clamped_sample_with_key(t).map(|sampled| sampled.value) } /// Add a key into the spline. pub fn add(&mut self, key: Key) where T: PartialOrd, { self.0.push(key); self.internal_sort(); } /// Remove a key from the spline. pub fn remove(&mut self, index: usize) -> Option> { if index >= self.0.len() { None } else { Some(self.0.remove(index)) } } /// Update a key and return the key already present. /// /// The key is updated — if present — with the provided function. /// /// # Notes /// /// That function makes sense only if you want to change the interpolator (i.e. [`Key::t`]) of /// your key. If you just want to change the interpolation mode or the carried value, consider /// using the [`Spline::get_mut`] method instead as it will be way faster. pub fn replace(&mut self, index: usize, f: F) -> Option> where F: FnOnce(&Key) -> Key, T: PartialOrd, { let key = self.remove(index)?; self.add(f(&key)); Some(key) } /// Get a key at a given index. pub fn get(&self, index: usize) -> Option<&Key> { self.0.get(index) } /// Mutably get a key at a given index. pub fn get_mut(&mut self, index: usize) -> Option> { self.0.get_mut(index).map(|key| KeyMut { value: &mut key.value, interpolation: &mut key.interpolation, }) } } /// A sampled value along with its key index. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct SampledWithKey { /// Sampled value. pub value: V, /// Key index. pub key: usize, } /// A mutable [`Key`]. /// /// Mutable keys allow to edit the carried values and the interpolation mode but not the actual /// interpolator value as it would invalidate the internal structure of the [`Spline`]. If you /// want to achieve this, you’re advised to use [`Spline::replace`]. #[derive(Debug)] pub struct KeyMut<'a, T, V> { /// Carried value. pub value: &'a mut V, /// Interpolation mode to use for that key. pub interpolation: &'a mut Interpolation, } // Find the lower control point corresponding to a given time. fn search_lower_cp(cps: &[Key], t: T) -> Option where T: PartialOrd, { 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) }