From 22e75c69016a10dbffe224f84320fa389153e580 Mon Sep 17 00:00:00 2001 From: Dimitri Sabadie Date: Sun, 20 Oct 2019 20:52:15 +0200 Subject: [PATCH 1/5] =?UTF-8?q?Fix=20B=C3=A9zier=20interpolation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 13 +++++++++++++ Cargo.toml | 2 +- src/interpolate.rs | 7 ++----- src/spline.rs | 28 +++++++++++++--------------- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 358802f..88910d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 3.0.0 + +> Sun Oct 20th 2019 + +## Major changes + +- Sampling now requires the value of the key to be `Linear` for `Interpolate`. That is needed + to ease some interpolation mode (especially Bézier). + +## Patch changes + +- Fix Bézier interpolation when the next key is Bézier too. + # 2.2.0 > Mon Oct 17th 2019 diff --git a/Cargo.toml b/Cargo.toml index 471fbea..787b54c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "splines" -version = "2.2.0" +version = "3.0.0" license = "BSD-3-Clause" authors = ["Dimitri Sabadie "] description = "Spline interpolation made easy" diff --git a/src/interpolate.rs b/src/interpolate.rs index 2f1b3c2..8c79f28 100644 --- a/src/interpolate.rs +++ b/src/interpolate.rs @@ -45,7 +45,7 @@ /// instance to know which trait your type must implement to be usable. /// /// [`Spline::sample`]: crate::spline::Spline::sample -pub trait Interpolate: Sized + Copy { +pub trait Interpolate: Sized + Copy + Linear { /// Linear interpolation. fn lerp(a: Self, b: Self, t: T) -> Self; @@ -240,10 +240,7 @@ where V: Linear, let one_t_3 = one_t_2 * one_t; let three = T::one() + T::one() + T::one(); - // mirror the “output” tangent based on the next key “input” tangent - let v_ = b + b - v; - - a.outer_mul(one_t_3) + u.outer_mul(three * one_t_2 * t) + v_.outer_mul(three * one_t * t * t) + b.outer_mul(t * t * t) + a.outer_mul(one_t_3) + u.outer_mul(three * one_t_2 * t) + v.outer_mul(three * one_t * t * t) + b.outer_mul(t * t * t) } macro_rules! impl_interpolate_simple { diff --git a/src/spline.rs b/src/spline.rs index 2051dea..b12b4a1 100644 --- a/src/spline.rs +++ b/src/spline.rs @@ -7,7 +7,7 @@ #[cfg(not(feature = "std"))] use core::ops::{Div, Mul}; #[cfg(not(feature = "std"))] use core::cmp::Ordering; -use crate::interpolate::{Interpolate, Additive, One, Trigo}; +use crate::interpolate::{Additive, Interpolate, One, Trigo}; use crate::interpolation::Interpolation; use crate::key::Key; @@ -86,7 +86,7 @@ impl Spline { /// the sampling. pub fn sample_with_key(&self, t: T) -> Option<(V, &Key, Option<&Key>)> where T: Additive + One + Trigo + Mul + Div + PartialOrd, - V: Interpolate { + V: Additive + Interpolate { let keys = &self.0; let i = search_lower_cp(keys, t)?; let cp0 = &keys[i]; @@ -134,29 +134,27 @@ impl Spline { } } - Interpolation::Bezier(u) => { + 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 = normalize_time(t, cp0, cp1); let value = - if let Interpolation::Bezier(v) = cp1.interpolation { - Interpolate::cubic_bezier(cp0.value, u, v, cp1.value, nt) - } else { - Interpolate::quadratic_bezier(cp0.value, u, cp1.value, nt) + 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::StrokeBezier(input, output) => { - let cp1 = &keys[i + 1]; - let nt = normalize_time(t, cp0, cp1); - let value = Interpolate::cubic_bezier(cp0.value, input, output, cp1.value, nt); - - Some((value, cp0, Some(cp1))) - } - Interpolation::__NonExhaustive => unreachable!(), } } From 955050eceebed0b4e20a0fd260482d127db34431 Mon Sep 17 00:00:00 2001 From: Dimitri Sabadie Date: Tue, 22 Oct 2019 13:25:55 +0200 Subject: [PATCH 2/5] Fix examples. --- Cargo.toml | 10 ++++++++++ examples/01-hello-world/Cargo.toml | 7 ------- examples/02-serialization/Cargo.toml | 8 -------- examples/Cargo.toml | 9 --------- .../{01-hello-world/src/main.rs => hello-world.rs} | 0 .../{02-serialization/src/main.rs => serialization.rs} | 0 tests/mod.rs | 8 ++++---- 7 files changed, 14 insertions(+), 28 deletions(-) delete mode 100644 examples/01-hello-world/Cargo.toml delete mode 100644 examples/02-serialization/Cargo.toml delete mode 100644 examples/Cargo.toml rename examples/{01-hello-world/src/main.rs => hello-world.rs} (100%) rename examples/{02-serialization/src/main.rs => serialization.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index 787b54c..26d2944 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,5 +34,15 @@ num-traits = { version = "0.2", optional = true } serde = { version = "1", optional = true } serde_derive = { version = "1", optional = true } +[dev-dependencies] +serde_json = "1" + [package.metadata.docs.rs] all-features = true + +[[example]] +name = "hello-world" + +[[example]] +name = "serialization" +required-features = ["serialization"] diff --git a/examples/01-hello-world/Cargo.toml b/examples/01-hello-world/Cargo.toml deleted file mode 100644 index 7dc3fa2..0000000 --- a/examples/01-hello-world/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "hello-world" -version = "0.2.0" -authors = ["Dimitri Sabadie "] - -[dependencies] -splines = "1.0.0-rc.2" diff --git a/examples/02-serialization/Cargo.toml b/examples/02-serialization/Cargo.toml deleted file mode 100644 index 5aad2fd..0000000 --- a/examples/02-serialization/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "serialization" -version = "0.2.0" -authors = ["Dimitri Sabadie "] - -[dependencies] -serde_json = "1" -splines = { version = "1.0.0-rc.2", features = ["serialization"] } diff --git a/examples/Cargo.toml b/examples/Cargo.toml deleted file mode 100644 index 62a55c0..0000000 --- a/examples/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[workspace] - -members = [ - "01-hello-world", - "02-serialization" -] - -[patch.crates-io] -splines = { path = ".." } diff --git a/examples/01-hello-world/src/main.rs b/examples/hello-world.rs similarity index 100% rename from examples/01-hello-world/src/main.rs rename to examples/hello-world.rs diff --git a/examples/02-serialization/src/main.rs b/examples/serialization.rs similarity index 100% rename from examples/02-serialization/src/main.rs rename to examples/serialization.rs diff --git a/tests/mod.rs b/tests/mod.rs index 91007a6..ab98ebb 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -1,7 +1,7 @@ use splines::{Interpolation, Key, Spline}; -#[cfg(feature = "impl-cgmath")] use cgmath as cg; -#[cfg(feature = "impl-nalgebra")] use nalgebra as na; +#[cfg(feature = "cgmath")] use cgmath as cg; +#[cfg(feature = "nalgebra")] use nalgebra as na; #[test] fn step_interpolation_f32() { @@ -149,7 +149,7 @@ fn several_interpolations_several_keys() { assert_eq!(spline.clamped_sample(11.), Some(4.)); } -#[cfg(feature = "impl-cgmath")] +#[cfg(feature = "cgmath")] #[test] fn cgmath_vector_interpolation() { use splines::Interpolate; @@ -163,7 +163,7 @@ fn cgmath_vector_interpolation() { assert_eq!(Interpolate::lerp(start, end, 0.5), mid); } -#[cfg(feature = "impl-nalgebra")] +#[cfg(feature = "nalgebra")] #[test] fn nalgebra_vector_interpolation() { use splines::Interpolate; From f2b356b78d8da558f7cf1685d10f05671c6659ce Mon Sep 17 00:00:00 2001 From: Dimitri Sabadie Date: Tue, 22 Oct 2019 18:13:51 +0200 Subject: [PATCH 3/5] Working on tests. --- tests/mod.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/tests/mod.rs b/tests/mod.rs index ab98ebb..f9aa8e6 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -1,8 +1,55 @@ -use splines::{Interpolation, Key, Spline}; +use splines::{ Interpolate, Interpolation, Key, Spline}; +use splines::interpolate::{Additive, Linear}; +use std::ops::{Add, Div, Sub, Mul}; #[cfg(feature = "cgmath")] use cgmath as cg; #[cfg(feature = "nalgebra")] use nalgebra as na; +// Small utility 2D-point. +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +struct P2 { + x: T, + y: T +} + +impl Add for P2 where T: Add { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + P2 { + x: self.x + rhs.x, + y: self.y + rhs.y + } + } +} + +impl Sub for P2 where T: Sub { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + P2 { + x: self.x - rhs.x, + y: self.y - rhs.y + } + } +} + +impl Linear for P2 where Self: Additive, T: Mul + Div { + fn outer_mul(self, t: T) -> Self { + P2 { + x: self.x * t, + y: self.y * t, + } + } + + fn outer_div(self, t: T) -> Self { + P2 { + x: self.x / t, + y: self.y / t, + } + } +} + #[test] fn step_interpolation_f32() { let start = Key::new(0., 0., Interpolation::Step(0.)); @@ -149,6 +196,22 @@ fn several_interpolations_several_keys() { assert_eq!(spline.clamped_sample(11.), Some(4.)); } +#[test] +fn stroke_bezier_straight() { + let keys = vec![ + Key::new(0.0, [0., 1.], Interpolation::StrokeBezier([0., 1.], [0., 1.])), + Key::new(5.0, [5., 1.], Interpolation::StrokeBezier([5., 1.], [5., 1.])) + ]; + let spline = Spline::from_vec(keys); + + assert_eq!(spline.clamped_sample(0.0)[1], 1.); + assert_eq!(spline.clamped_sample(1.0)[1], 1.); + assert_eq!(spline.clamped_sample(2.0)[1], 1.); + assert_eq!(spline.clamped_sample(3.0)[1], 1.); + assert_eq!(spline.clamped_sample(4.0)[1], 1.); + assert_eq!(spline.clamped_sample(5.0)[1], 1.); +} + #[cfg(feature = "cgmath")] #[test] fn cgmath_vector_interpolation() { From 824afef513666b1a7044bfd2d59ff594c2f05aee Mon Sep 17 00:00:00 2001 From: Dimitri Sabadie Date: Sun, 20 Oct 2019 20:52:15 +0200 Subject: [PATCH 4/5] =?UTF-8?q?Fix=20B=C3=A9zier=20interpolation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/spline.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/spline.rs b/src/spline.rs index b12b4a1..5bc2674 100644 --- a/src/spline.rs +++ b/src/spline.rs @@ -163,7 +163,7 @@ impl Spline { /// pub fn sample(&self, t: T) -> Option where T: Additive + One + Trigo + Mul + Div + PartialOrd, - V: Interpolate { + V: Additive + Interpolate { self.sample_with_key(t).map(|(v, _, _)| v) } @@ -180,7 +180,7 @@ impl Spline { /// This function returns [`None`] if you have no key. pub fn clamped_sample_with_key(&self, t: T) -> Option<(V, &Key, Option<&Key>)> where T: Additive + One + Trigo + Mul + Div + PartialOrd, - V: Interpolate { + V: Additive + Interpolate { if self.0.is_empty() { return None; } @@ -205,7 +205,7 @@ impl Spline { /// Sample a spline at a given time with clamping. pub fn clamped_sample(&self, t: T) -> Option where T: Additive + One + Trigo + Mul + Div + PartialOrd, - V: Interpolate { + V: Additive + Interpolate { self.clamped_sample_with_key(t).map(|(v, _, _)| v) } From 3b6ddc5ea68429e560e7f0add182ccff64bd3e78 Mon Sep 17 00:00:00 2001 From: Dimitri Sabadie Date: Tue, 22 Oct 2019 20:59:46 +0200 Subject: [PATCH 5/5] =?UTF-8?q?Update=20integration=20tests=20for=20stroke?= =?UTF-8?q?=20B=C3=A9zier.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + tests/mod.rs | 67 +++++++++------------------------------------------- 2 files changed, 12 insertions(+), 56 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 26d2944..ac0ca65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ serde = { version = "1", optional = true } serde_derive = { version = "1", optional = true } [dev-dependencies] +float-cmp = "0.5" serde_json = "1" [package.metadata.docs.rs] diff --git a/tests/mod.rs b/tests/mod.rs index f9aa8e6..dddeca0 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -1,55 +1,9 @@ -use splines::{ Interpolate, Interpolation, Key, Spline}; -use splines::interpolate::{Additive, Linear}; -use std::ops::{Add, Div, Sub, Mul}; +use float_cmp::approx_eq; +use splines::{Interpolation, Key, Spline}; #[cfg(feature = "cgmath")] use cgmath as cg; #[cfg(feature = "nalgebra")] use nalgebra as na; -// Small utility 2D-point. -#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] -struct P2 { - x: T, - y: T -} - -impl Add for P2 where T: Add { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - P2 { - x: self.x + rhs.x, - y: self.y + rhs.y - } - } -} - -impl Sub for P2 where T: Sub { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - P2 { - x: self.x - rhs.x, - y: self.y - rhs.y - } - } -} - -impl Linear for P2 where Self: Additive, T: Mul + Div { - fn outer_mul(self, t: T) -> Self { - P2 { - x: self.x * t, - y: self.y * t, - } - } - - fn outer_div(self, t: T) -> Self { - P2 { - x: self.x / t, - y: self.y / t, - } - } -} - #[test] fn step_interpolation_f32() { let start = Key::new(0., 0., Interpolation::Step(0.)); @@ -196,20 +150,21 @@ fn several_interpolations_several_keys() { assert_eq!(spline.clamped_sample(11.), Some(4.)); } +#[cfg(feature = "cgmath")] #[test] fn stroke_bezier_straight() { let keys = vec![ - Key::new(0.0, [0., 1.], Interpolation::StrokeBezier([0., 1.], [0., 1.])), - Key::new(5.0, [5., 1.], Interpolation::StrokeBezier([5., 1.], [5., 1.])) + Key::new(0.0, cg::Vector2::new(0., 1.), Interpolation::StrokeBezier(cg::Vector2::new(0., 1.), cg::Vector2::new(0., 1.))), + Key::new(5.0, cg::Vector2::new(5., 1.), Interpolation::StrokeBezier(cg::Vector2::new(5., 1.), cg::Vector2::new(5., 1.))) ]; let spline = Spline::from_vec(keys); - assert_eq!(spline.clamped_sample(0.0)[1], 1.); - assert_eq!(spline.clamped_sample(1.0)[1], 1.); - assert_eq!(spline.clamped_sample(2.0)[1], 1.); - assert_eq!(spline.clamped_sample(3.0)[1], 1.); - assert_eq!(spline.clamped_sample(4.0)[1], 1.); - assert_eq!(spline.clamped_sample(5.0)[1], 1.); + assert!(approx_eq!(f32, spline.clamped_sample(0.0).unwrap().y, 1.)); + assert!(approx_eq!(f32, spline.clamped_sample(1.0).unwrap().y, 1.)); + assert!(approx_eq!(f32, spline.clamped_sample(2.0).unwrap().y, 1.)); + assert!(approx_eq!(f32, spline.clamped_sample(3.0).unwrap().y, 1.)); + assert!(approx_eq!(f32, spline.clamped_sample(4.0).unwrap().y, 1.)); + assert!(approx_eq!(f32, spline.clamped_sample(5.0).unwrap().y, 1.)); } #[cfg(feature = "cgmath")]