#[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(feature = "serialization")]
use serde_derive::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::cmp::Ordering;
#[cfg(feature = "std")]
use std::ops::{Div, Mul};
use crate::interpolate::{Additive, Interpolate, One, Trigo};
use crate::interpolation::Interpolation;
use crate::key::Key;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))]
pub struct Spline<T, V>(pub(crate) Vec<Key<T, V>>);
impl<T, V> Spline<T, V> {
fn internal_sort(&mut self)
where
T: PartialOrd,
{
self
.0
.sort_by(|k0, k1| k0.t.partial_cmp(&k1.t).unwrap_or(Ordering::Less));
}
pub fn from_vec(keys: Vec<Key<T, V>>) -> Self
where
T: PartialOrd,
{
let mut spline = Spline(keys);
spline.internal_sort();
spline
}
pub fn from_iter<I>(iter: I) -> Self
where
I: Iterator<Item = Key<T, V>>,
T: PartialOrd,
{
Self::from_vec(iter.collect())
}
pub fn keys(&self) -> &[Key<T, V>] {
&self.0
}
#[inline(always)]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn sample_with_key(&self, t: T) -> Option<(V, &Key<T, V>, Option<&Key<T, V>>)>
where
T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
V: Additive + Interpolate<T>,
{
let keys = &self.0;
let i = search_lower_cp(keys, t)?;
let cp0 = &keys[i];
match cp0.interpolation {
Interpolation::Step(threshold) => {
let cp1 = &keys[i + 1];
let nt = normalize_time(t, cp0, cp1);
let value = if nt < threshold { cp0.value } else { cp1.value };
Some((value, cp0, Some(cp1)))
}
Interpolation::Linear => {
let cp1 = &keys[i + 1];
let nt = normalize_time(t, cp0, cp1);
let value = Interpolate::lerp(cp0.value, cp1.value, nt);
Some((value, cp0, Some(cp1)))
}
Interpolation::Cosine => {
let two_t = T::one() + T::one();
let cp1 = &keys[i + 1];
let nt = normalize_time(t, cp0, cp1);
let cos_nt = (T::one() - (nt * T::pi()).cos()) / two_t;
let value = Interpolate::lerp(cp0.value, cp1.value, cos_nt);
Some((value, cp0, Some(cp1)))
}
Interpolation::CatmullRom => {
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 = normalize_time(t, cp0, cp1);
let value = Interpolate::cubic_hermite(
(cpm0.value, cpm0.t),
(cp0.value, cp0.t),
(cp1.value, cp1.t),
(cpm1.value, cpm1.t),
nt,
);
Some((value, cp0, Some(cp1)))
}
}
Interpolation::Bezier(u) | Interpolation::StrokeBezier(_, u) => {
let cp1 = &keys[i + 1];
let nt = normalize_time(t, cp0, cp1);
let value = match cp1.interpolation {
Interpolation::Bezier(v) => {
Interpolate::cubic_bezier(cp0.value, u, cp1.value + cp1.value - v, cp1.value, nt)
}
Interpolation::StrokeBezier(v, _) => {
Interpolate::cubic_bezier(cp0.value, u, v, cp1.value, nt)
}
_ => Interpolate::quadratic_bezier(cp0.value, u, cp1.value, nt),
};
Some((value, cp0, Some(cp1)))
}
Interpolation::__NonExhaustive => unreachable!(),
}
}
pub fn sample(&self, t: T) -> Option<V>
where
T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
V: Additive + Interpolate<T>,
{
self.sample_with_key(t).map(|(v, _, _)| v)
}
pub fn clamped_sample_with_key(&self, t: T) -> Option<(V, &Key<T, V>, Option<&Key<T, V>>)>
where
T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
V: Additive + Interpolate<T>,
{
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 second = if self.0.len() >= 2 {
Some(&self.0[1])
} else {
None
};
Some((first.value, &first, second))
} else {
let last = self.0.last().unwrap();
if t >= last.t {
Some((last.value, &last, None))
} else {
None
}
}
})
}
pub fn clamped_sample(&self, t: T) -> Option<V>
where
T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
V: Additive + Interpolate<T>,
{
self.clamped_sample_with_key(t).map(|(v, _, _)| v)
}
pub fn add(&mut self, key: Key<T, V>)
where
T: PartialOrd,
{
self.0.push(key);
self.internal_sort();
}
pub fn remove(&mut self, index: usize) -> Option<Key<T, V>> {
if index >= self.0.len() {
None
} else {
Some(self.0.remove(index))
}
}
pub fn replace<F>(&mut self, index: usize, f: F) -> Option<Key<T, V>>
where
F: FnOnce(&Key<T, V>) -> Key<T, V>,
T: PartialOrd,
{
let key = self.remove(index)?;
self.add(f(&key));
Some(key)
}
pub fn get(&self, index: usize) -> Option<&Key<T, V>> {
self.0.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<KeyMut<T, V>> {
self.0.get_mut(index).map(|key| KeyMut {
value: &mut key.value,
interpolation: &mut key.interpolation,
})
}
}
pub struct KeyMut<'a, T, V> {
pub value: &'a mut V,
pub interpolation: &'a mut Interpolation<T, V>,
}
#[inline(always)]
pub(crate) fn normalize_time<T, V>(t: T, cp: &Key<T, V>, cp1: &Key<T, V>) -> T
where
T: Additive + Div<T, Output = T> + PartialEq,
{
assert!(cp1.t != cp.t, "overlapping keys");
(t - cp.t) / (cp1.t - cp.t)
}
fn search_lower_cp<T, V>(cps: &[Key<T, V>], t: T) -> Option<usize>
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;
}
}
Some(i)
}