Merge pull request #32 from phaazon/feature/sample-key

Add Spline::sample_with_key and Spline::clamped_sample_with_key.
This commit is contained in:
Dimitri Sabadie 2019-09-30 12:59:11 +02:00 committed by GitHub
commit ebc6e16aef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 56 additions and 17 deletions

View File

@ -1,3 +1,12 @@
# 2.1
> Mon Sep 30th 2019
- Add `Spline::sample_with_key` and `Spline::clamped_sample_with_key`. Those methods allow one to
perform the regular `Spline::sample` and `Spline::clamped_sample` but also retreive the base
key that was used to perform the interpolation. The key can be inspected to get the base time,
interpolation, etc. The next key is also returned, if present.
# 2.0.1 # 2.0.1
> Tue Sep 24th 2019 > Tue Sep 24th 2019

View File

@ -1,6 +1,6 @@
[package] [package]
name = "splines" name = "splines"
version = "2.0.1" version = "2.1.0"
license = "BSD-3-Clause" license = "BSD-3-Clause"
authors = ["Dimitri Sabadie <dimitri.sabadie@gmail.com>"] authors = ["Dimitri Sabadie <dimitri.sabadie@gmail.com>"]
description = "Spline interpolation made easy" description = "Spline interpolation made easy"

View File

@ -69,7 +69,8 @@ impl<T, V> Spline<T, V> {
self.0.is_empty() self.0.is_empty()
} }
/// Sample a spline at a given time. /// 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 /// The current implementation, based on immutability, cannot perform in constant time. This means
/// that samplings processing complexity is currently *O(log n)*. Its possible to achieve *O(1)* /// that samplings processing complexity is currently *O(log n)*. Its possible to achieve *O(1)*
@ -84,7 +85,7 @@ impl<T, V> Spline<T, V> {
/// youre near the beginning of the spline or its end, ensure you have enough keys around to make /// youre near the beginning of the spline or its end, ensure you have enough keys around to make
/// the sampling. /// the sampling.
/// ///
pub fn sample(&self, t: T) -> Option<V> 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, where T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
V: Interpolate<T> { V: Interpolate<T> {
let keys = &self.0; let keys = &self.0;
@ -95,14 +96,17 @@ impl<T, V> Spline<T, V> {
Interpolation::Step(threshold) => { Interpolation::Step(threshold) => {
let cp1 = &keys[i + 1]; let cp1 = &keys[i + 1];
let nt = normalize_time(t, cp0, cp1); let nt = normalize_time(t, cp0, cp1);
Some(if nt < threshold { cp0.value } else { cp1.value }) let value = if nt < threshold { cp0.value } else { cp1.value };
Some((value, cp0, Some(cp1)))
} }
Interpolation::Linear => { Interpolation::Linear => {
let cp1 = &keys[i + 1]; let cp1 = &keys[i + 1];
let nt = normalize_time(t, cp0, cp1); let nt = normalize_time(t, cp0, cp1);
let value = Interpolate::lerp(cp0.value, cp1.value, nt);
Some(Interpolate::lerp(cp0.value, cp1.value, nt)) Some((value, cp0, Some(cp1)))
} }
Interpolation::Cosine => { Interpolation::Cosine => {
@ -110,8 +114,9 @@ impl<T, V> Spline<T, V> {
let cp1 = &keys[i + 1]; let cp1 = &keys[i + 1];
let nt = normalize_time(t, cp0, cp1); let nt = normalize_time(t, cp0, cp1);
let cos_nt = (T::one() - (nt * T::pi()).cos()) / two_t; let cos_nt = (T::one() - (nt * T::pi()).cos()) / two_t;
let value = Interpolate::lerp(cp0.value, cp1.value, cos_nt);
Some(Interpolate::lerp(cp0.value, cp1.value, cos_nt)) Some((value, cp0, Some(cp1)))
} }
Interpolation::CatmullRom => { Interpolation::CatmullRom => {
@ -124,8 +129,9 @@ impl<T, V> Spline<T, V> {
let cpm0 = &keys[i - 1]; let cpm0 = &keys[i - 1];
let cpm1 = &keys[i + 2]; let cpm1 = &keys[i + 2];
let nt = normalize_time(t, cp0, cp1); 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(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)))
} }
} }
@ -134,18 +140,30 @@ impl<T, V> Spline<T, V> {
let cp1 = &keys[i + 1]; let cp1 = &keys[i + 1];
let nt = normalize_time(t, cp0, cp1); let nt = normalize_time(t, cp0, cp1);
if let Interpolation::Bezier(v) = cp1.interpolation { let value =
Some(Interpolate::cubic_bezier(cp0.value, u, v, cp1.value, nt)) if let Interpolation::Bezier(v) = cp1.interpolation {
} else { Interpolate::cubic_bezier(cp0.value, u, v, cp1.value, nt)
Some(Interpolate::quadratic_bezier(cp0.value, u, cp1.value, nt)) } else {
} Interpolate::quadratic_bezier(cp0.value, u, cp1.value, nt)
};
Some((value, cp0, Some(cp1)))
} }
Interpolation::__NonExhaustive => unreachable!(), Interpolation::__NonExhaustive => unreachable!(),
} }
} }
/// Sample a spline at a given time with clamping. /// Sample a spline at a given time.
///
pub fn sample(&self, t: T) -> Option<V>
where T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
V: Interpolate<T> {
self.sample_with_key(t).map(|(v, _, _)| v)
}
/// Sample a spline at a given time with clamping, returning the interpolated value along with its
/// associated key.
/// ///
/// # Return /// # Return
/// ///
@ -155,22 +173,23 @@ impl<T, V> Spline<T, V> {
/// # Error /// # Error
/// ///
/// This function returns [`None`] if you have no key. /// This function returns [`None`] if you have no key.
pub fn clamped_sample(&self, t: T) -> Option<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, where T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
V: Interpolate<T> { V: Interpolate<T> {
if self.0.is_empty() { if self.0.is_empty() {
return None; return None;
} }
self.sample(t).or_else(move || { self.sample_with_key(t).or_else(move || {
let first = self.0.first().unwrap(); let first = self.0.first().unwrap();
if t <= first.t { if t <= first.t {
Some(first.value) let second = if self.0.len() >= 2 { Some(&self.0[1]) } else { None };
Some((first.value, &first, second))
} else { } else {
let last = self.0.last().unwrap(); let last = self.0.last().unwrap();
if t >= last.t { if t >= last.t {
Some(last.value) Some((last.value, &last, None))
} else { } else {
None None
} }
@ -178,6 +197,13 @@ impl<T, V> Spline<T, V> {
}) })
} }
/// Sample a spline at a given time with clamping.
pub fn clamped_sample(&self, t: T) -> Option<V>
where T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
V: Interpolate<T> {
self.clamped_sample_with_key(t).map(|(v, _, _)| v)
}
/// Add a key into the spline. /// Add a key into the spline.
pub fn add(&mut self, key: Key<T, V>) where T: PartialOrd { pub fn add(&mut self, key: Key<T, V>) where T: PartialOrd {
self.0.push(key); self.0.push(key);

View File

@ -16,6 +16,8 @@ fn step_interpolation_f32() {
assert_eq!(spline.sample(0.9), Some(10.)); assert_eq!(spline.sample(0.9), Some(10.));
assert_eq!(spline.sample(1.), None); assert_eq!(spline.sample(1.), None);
assert_eq!(spline.clamped_sample(1.), Some(10.)); assert_eq!(spline.clamped_sample(1.), Some(10.));
assert_eq!(spline.sample_with_key(0.2), Some((10., &start, Some(&end))));
assert_eq!(spline.clamped_sample_with_key(1.), Some((10., &end, None)));
} }
#[test] #[test]
@ -31,6 +33,8 @@ fn step_interpolation_f64() {
assert_eq!(spline.sample(0.9), Some(10.)); assert_eq!(spline.sample(0.9), Some(10.));
assert_eq!(spline.sample(1.), None); assert_eq!(spline.sample(1.), None);
assert_eq!(spline.clamped_sample(1.), Some(10.)); assert_eq!(spline.clamped_sample(1.), Some(10.));
assert_eq!(spline.sample_with_key(0.2), Some((10., &start, Some(&end))));
assert_eq!(spline.clamped_sample_with_key(1.), Some((10., &end, None)));
} }
#[test] #[test]