From 7dbc85a3127f1354db3e1695ba55086bbd68c5cd Mon Sep 17 00:00:00 2001 From: Dimitri Sabadie Date: Mon, 23 Sep 2019 20:34:39 +0200 Subject: [PATCH 1/2] Add key getters (immutable & mutable). --- Cargo.toml | 2 +- src/spline.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3dae0ce..577627f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "splines" -version = "1.1.0" +version = "1.2.0" license = "BSD-3-Clause" authors = ["Dimitri Sabadie "] description = "Spline interpolation made easy" diff --git a/src/spline.rs b/src/spline.rs index 1e20e95..6a1665e 100644 --- a/src/spline.rs +++ b/src/spline.rs @@ -199,6 +199,54 @@ impl Spline { 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 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`]. +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, } // Normalize a time ([0;1]) given two control points. From 4fdbfa6189aebf28b59ed121854796d17dc5e294 Mon Sep 17 00:00:00 2001 From: Dimitri Sabadie Date: Mon, 23 Sep 2019 20:56:56 +0200 Subject: [PATCH 2/2] Fix 1.1. --- CHANGELOG.md | 7 ++++-- Cargo.toml | 2 +- src/cgmath.rs | 28 +++++++++++++++++++++- src/interpolate.rs | 56 +++++++++++++++++++++++++------------------- src/interpolation.rs | 42 +++++++++++++++++++++++++++++---- src/key.rs | 28 ++++++++++++++++++++++ src/nalgebra.rs | 16 ++++++++++++- src/spline.rs | 6 ++--- 8 files changed, 148 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c5e6f..76305e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ -# 1.1 +# 1.1.1 > Mon Sep 22rd 2019 -- Yanked. +- Add support for [Bézier curves](https://en.wikipedia.org/wiki/B%C3%A9zier_curve). This is + normally a breaking change so it’s currently disabled by default and available via the + `"bezier"` feature-gate. +- Add `Spline::get`, `Spline::get_mut` and `Spline::replace`. # 1.0 diff --git a/Cargo.toml b/Cargo.toml index 577627f..d989696 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "splines" -version = "1.2.0" +version = "1.1.1" license = "BSD-3-Clause" authors = ["Dimitri Sabadie "] description = "Spline interpolation made easy" diff --git a/src/cgmath.rs b/src/cgmath.rs index 5d46b4b..d6f2733 100644 --- a/src/cgmath.rs +++ b/src/cgmath.rs @@ -2,7 +2,9 @@ use cgmath::{ BaseFloat, BaseNum, InnerSpace, Quaternion, Vector1, Vector2, Vector3, Vector4, VectorSpace }; -use crate::interpolate::{Additive, Interpolate, Linear, One, cubic_hermite_def}; +use crate::interpolate::{ + Additive, Interpolate, Linear, One, cubic_bezier_def, cubic_hermite_def, quadratic_bezier_def +}; macro_rules! impl_interpolate_vec { ($($t:tt)*) => { @@ -29,6 +31,18 @@ macro_rules! impl_interpolate_vec { fn cubic_hermite(x: (Self, T), a: (Self, T), b: (Self, T), y: (Self, T), t: T) -> Self { cubic_hermite_def(x, a, b, y, t) } + + #[cfg(feature = "bezier")] + #[inline(always)] + fn quadratic_bezier(a: Self, u: Self, b: Self, t: T) -> Self { + quadratic_bezier_def(a, u, b, t) + } + + #[cfg(feature = "bezier")] + #[inline(always)] + fn cubic_bezier(a: Self, u: Self, v: Self, b: Self, t: T) -> Self { + cubic_bezier_def(a, u, v, b, t) + } } } } @@ -61,4 +75,16 @@ where Self: InnerSpace, T: Additive + BaseFloat + One { fn cubic_hermite(x: (Self, T), a: (Self, T), b: (Self, T), y: (Self, T), t: T) -> Self { cubic_hermite_def(x, a, b, y, t) } + + #[cfg(feature = "bezier")] + #[inline(always)] + fn quadratic_bezier(a: Self, u: Self, b: Self, t: T) -> Self { + quadratic_bezier_def(a, u, b, t) + } + + #[cfg(feature = "bezier")] + #[inline(always)] + fn cubic_bezier(a: Self, u: Self, v: Self, b: Self, t: T) -> Self { + cubic_bezier_def(a, u, v, b, t) + } } diff --git a/src/interpolate.rs b/src/interpolate.rs index ed4a9ee..1a6242f 100644 --- a/src/interpolate.rs +++ b/src/interpolate.rs @@ -59,9 +59,11 @@ pub trait Interpolate: Sized + Copy { } /// Quadratic Bézier interpolation. + #[cfg(feature = "bezier")] fn quadratic_bezier(a: Self, u: Self, b: Self, t: T) -> Self; /// Cubic Bézier interpolation. + #[cfg(feature = "bezier")] fn cubic_bezier(a: Self, u: Self, v: Self, b: Self, t: T) -> Self; } @@ -221,6 +223,7 @@ where V: Linear, /// Default implementation of [`Interpolate::quadratic_bezier`]. /// /// `V` is the value being interpolated. `T` is the sampling value (also sometimes called time). +#[cfg(feature = "bezier")] pub fn quadratic_bezier_def(a: V, u: V, b: V, t: T) -> V where V: Linear, T: Additive + Mul + One { @@ -232,6 +235,7 @@ where V: Linear, /// Default implementation of [`Interpolate::cubic_bezier`]. /// /// `V` is the value being interpolated. `T` is the sampling value (also sometimes called time). +#[cfg(feature = "bezier")] pub fn cubic_bezier_def(a: V, u: V, v: V, b: V, t: T) -> V where V: Linear, T: Additive + Mul + One { @@ -254,10 +258,12 @@ macro_rules! impl_interpolate_simple { cubic_hermite_def(x, a, b, y, t) } + #[cfg(feature = "bezier")] fn quadratic_bezier(a: Self, u: Self, b: Self, t: $t) -> Self { quadratic_bezier_def(a, u, b, t) } + #[cfg(feature = "bezier")] fn cubic_bezier(a: Self, u: Self, v: Self, b: Self, t: $t) -> Self { cubic_bezier_def(a, u, v, b, t) } @@ -268,27 +274,29 @@ macro_rules! impl_interpolate_simple { impl_interpolate_simple!(f32); impl_interpolate_simple!(f64); -//macro_rules! impl_interpolate_via { -// ($t:ty, $v:ty) => { -// impl Interpolate<$t> for $v { -// fn lerp(a: Self, b: Self, t: $t) -> Self { -// a * (1. - t as $v) + b * t as $v -// } -// -// fn cubic_hermite((x, xt): (Self, $t), (a, at): (Self, $t), (b, bt): (Self, $t), (y, yt): (Self, $t), t: $t) -> Self { -// cubic_hermite_def((x, xt as $v), (a, at as $v), (b, bt as $v), (y, yt as $v), t as $v) -// } -// -// fn quadratic_bezier(a: Self, u: Self, b: Self, t: $t) -> Self { -// $t::quadratic_bezier(a as $t, u as $t, b as $t, t) -// } -// -// fn cubic_bezier(a: Self, u: Self, v: Self, b: Self, t: $t) -> Self { -// $t::cubic_bezier(a as $t, u as $t, v as $t, b as $t, t) -// } -// } -// } -//} -// -//impl_interpolate_via!(f32, f64); -//impl_interpolate_via!(f64, f32); +macro_rules! impl_interpolate_via { + ($t:ty, $v:ty) => { + impl Interpolate<$t> for $v { + fn lerp(a: Self, b: Self, t: $t) -> Self { + a * (1. - t as $v) + b * t as $v + } + + fn cubic_hermite((x, xt): (Self, $t), (a, at): (Self, $t), (b, bt): (Self, $t), (y, yt): (Self, $t), t: $t) -> Self { + cubic_hermite_def((x, xt as $v), (a, at as $v), (b, bt as $v), (y, yt as $v), t as $v) + } + + #[cfg(feature = "bezier")] + fn quadratic_bezier(a: Self, u: Self, b: Self, t: $t) -> Self { + quadratic_bezier_def(a, u, b, t as $v) + } + + #[cfg(feature = "bezier")] + fn cubic_bezier(a: Self, u: Self, v: Self, b: Self, t: $t) -> Self { + cubic_bezier_def(a, u, v, b, t as $v) + } + } + } +} + +impl_interpolate_via!(f32, f64); +impl_interpolate_via!(f64, f32); diff --git a/src/interpolation.rs b/src/interpolation.rs index b02022e..7974832 100644 --- a/src/interpolation.rs +++ b/src/interpolation.rs @@ -5,11 +5,12 @@ /// Available kind of interpolations. /// /// Feel free to visit each variant for more documentation. +#[cfg(feature = "bezier")] #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serialization", serde(rename_all = "snake_case"))] pub enum Interpolation { - /// Hold a [`Key`] until the sampling value passes the normalized step threshold, in which + /// Hold a [`Key`] until the sampling value passes the normalized step threshold, in which /// case the next key is used. /// /// > Note: if you set the threshold to `0.5`, the first key will be used until half the time @@ -17,7 +18,7 @@ pub enum Interpolation { /// > first key will be kept until the next key. Set it to `0.` and the first key will never be /// > used. /// - /// [`Key`]: crate::key::Key + /// [`Key`]: crate::key::Key Step(T), /// Linear interpolation between a key and the next one. Linear, @@ -41,11 +42,35 @@ pub enum Interpolation { /// interpolation_ and it kicks ass too, but a bit less than cubic. #[cfg(feature = "bezier")] Bezier(V), - #[cfg(not(any(feature = "bezier")))] - #[doc(hidden)] - _V(std::marker::PhantomData), } +/// Available kind of interpolations. +/// +/// Feel free to visit each variant for more documentation. +#[cfg(not(feature = "bezier"))] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serialization", serde(rename_all = "snake_case"))] +pub enum Interpolation { + /// Hold a [`Key`] until the sampling value passes the normalized step threshold, in which + /// case the next key is used. + /// + /// > Note: if you set the threshold to `0.5`, the first key will be used until half the time + /// > between the two keys; the second key will be in used afterwards. If you set it to `1.0`, the + /// > first key will be kept until the next key. Set it to `0.` and the first key will never be + /// > used. + /// + /// [`Key`]: crate::key::Key + Step(T), + /// Linear interpolation between a key and the next one. + Linear, + /// Cosine interpolation between a key and the next one. + Cosine, + /// Catmull-Rom interpolation, performing a cubic Hermite interpolation using four keys. + CatmullRom, +} + +#[cfg(feature = "bezier")] impl Default for Interpolation { /// [`Interpolation::Linear`] is the default. fn default() -> Self { @@ -53,3 +78,10 @@ impl Default for Interpolation { } } +#[cfg(not(feature = "bezier"))] +impl Default for Interpolation { + /// [`Interpolation::Linear`] is the default. + fn default() -> Self { + Interpolation::Linear + } +} diff --git a/src/key.rs b/src/key.rs index 76f09f6..983ee76 100644 --- a/src/key.rs +++ b/src/key.rs @@ -17,6 +17,7 @@ use crate::interpolation::Interpolation; /// key and the next one – if existing. Have a look at [`Interpolation`] for further details. /// /// [`Interpolation`]: crate::interpolation::Interpolation +#[cfg(feature = "bezier")] #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serialization", serde(rename_all = "snake_case"))] @@ -29,9 +30,36 @@ pub struct Key { pub interpolation: Interpolation } +/// A spline control point. +/// +/// This type associates a value at a given interpolation parameter value. It also contains an +/// interpolation mode used to determine how to interpolate values on the segment defined by this +/// key and the next one – if existing. Have a look at [`Interpolation`] for further details. +/// +/// [`Interpolation`]: crate::interpolation::Interpolation +#[cfg(not(feature = "bezier"))] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serialization", serde(rename_all = "snake_case"))] +pub struct Key { + /// Interpolation parameter at which the [`Key`] should be reached. + pub t: T, + /// Carried value. + pub value: V, + /// Interpolation mode. + pub interpolation: Interpolation +} + impl Key { /// Create a new key. + #[cfg(feature = "bezier")] pub fn new(t: T, value: V, interpolation: Interpolation) -> Self { Key { t, value, interpolation } } + + /// Create a new key. + #[cfg(not(feature = "bezier"))] + pub fn new(t: T, value: V, interpolation: Interpolation) -> Self { + Key { t, value, interpolation } + } } diff --git a/src/nalgebra.rs b/src/nalgebra.rs index c13f4c7..ccb261f 100644 --- a/src/nalgebra.rs +++ b/src/nalgebra.rs @@ -3,7 +3,9 @@ use nalgebra::{Scalar, Vector, Vector1, Vector2, Vector3, Vector4, Vector5, Vect use num_traits as nt; use std::ops::Mul; -use crate::interpolate::{Interpolate, Linear, Additive, One, cubic_hermite_def}; +use crate::interpolate::{ + Interpolate, Linear, Additive, One, cubic_bezier_def, cubic_hermite_def, quadratic_bezier_def +}; macro_rules! impl_interpolate_vector { ($($t:tt)*) => { @@ -40,6 +42,18 @@ macro_rules! impl_interpolate_vector { fn cubic_hermite(x: (Self, T), a: (Self, T), b: (Self, T), y: (Self, T), t: T) -> Self { cubic_hermite_def(x, a, b, y, t) } + + #[cfg(feature = "bezier")] + #[inline(always)] + fn quadratic_bezier(a: Self, u: Self, b: Self, t: T) -> Self { + quadratic_bezier_def(a, u, b, t) + } + + #[cfg(feature = "bezier")] + #[inline(always)] + fn cubic_bezier(a: Self, u: Self, v: Self, b: Self, t: T) -> Self { + cubic_bezier_def(a, u, v, b, t) + } } } } diff --git a/src/spline.rs b/src/spline.rs index 6a1665e..ac0ed72 100644 --- a/src/spline.rs +++ b/src/spline.rs @@ -146,9 +146,6 @@ impl Spline { Some(Interpolate::quadratic_bezier(cp0.value, u, cp1.value, nt)) } } - - #[cfg(not(any(feature = "bezier")))] - Interpolation::_V(_) => unreachable!() } } @@ -246,7 +243,10 @@ pub struct KeyMut<'a, T, V> { /// Carried value. pub value: &'a mut V, /// Interpolation mode to use for that key. + #[cfg(feature = "bezier")] pub interpolation: &'a mut Interpolation, + #[cfg(not(feature = "bezier"))] + pub interpolation: &'a mut Interpolation, } // Normalize a time ([0;1]) given two control points.