20 Commits

Author SHA1 Message Date
1bfd9a0e7c Merge pull request #29 from phaazon/release/2.0.0
2.0.0.
2019-09-24 10:59:00 +02:00
7846177471 Fix CI. 2019-09-24 10:44:45 +02:00
6f65be125b 2.0.0. 2019-09-24 10:42:03 +02:00
5d0ebc0777 Merge pull request #28 from phaazon/feature/mutation
Feature/mutation
2019-09-23 21:12:06 +02:00
4fdbfa6189 Fix 1.1. 2019-09-23 20:56:56 +02:00
7dbc85a312 Add key getters (immutable & mutable). 2019-09-23 20:34:39 +02:00
03031a1e92 Yank notation. 2019-09-23 19:53:52 +02:00
54eb89ae96 Merge pull request #27 from phaazon/feature/extra-splines
Feature/extra splines
2019-09-23 17:13:22 +02:00
51ab8022f9 Fix CI. 2019-09-23 17:10:40 +02:00
b78be8cba3 Prepare 1.1. 2019-09-23 17:09:09 +02:00
fd05dd0419 Update readme. 2019-09-23 17:08:32 +02:00
b05582d653 Add Bézier curves. 2019-09-23 17:06:32 +02:00
e76f18ac5b 1.0.0. 2019-09-22 19:15:57 +02:00
8e6af2cee9 Merge pull request #26 from phaazon/feature/add-key
Implement Spline::add.
2019-09-22 19:05:15 +02:00
a6e77a3d09 Remove Travis CI. 2019-09-22 18:22:12 +02:00
510881b5c6 Implement Spline::add.
Fixes #23.
2019-09-22 18:21:20 +02:00
1eed163277 Doc typo. 2019-09-22 18:13:52 +02:00
311efa5b26 Synchronize README. 2019-09-21 14:42:08 +02:00
c98b493993 Add support for removing a key. #24 2019-09-21 14:42:08 +02:00
c818b4c810 Add GitHub CI. 2019-09-21 14:19:21 +02:00
13 changed files with 412 additions and 109 deletions

46
.github/workflows/ci.yaml vendored Normal file
View File

@ -0,0 +1,46 @@
name: CI
on: [push]
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Build
run: |
cargo build --verbose --all-features
- name: Test
run: |
cargo test --verbose --all-features
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- name: Build
run: |
cargo build --verbose --all-features
- name: Test
run: |
cargo test --verbose --all-features
build-macosx:
runs-on: macosx-latest
steps:
- uses: actions/checkout@v1
- name: Build
run: |
cargo build --verbose --all-features
- name: Test
run: |
cargo test --verbose --all-features
check-readme:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install cargo-sync-readme
run: cargo install --force cargo-sync-readme
- name: Check
run: cargo sync-readme -c

View File

@ -1,29 +0,0 @@
language: rust
rust:
- stable
- beta
- nightly
os:
- linux
- osx
script:
- rustc --version
- cargo --version
- echo "Testing default crate configuration"
- cargo build --verbose
- cargo test --verbose
- cd examples && cargo check --verbose
- echo "Testing feature serialization"
- cargo build --verbose --features serialization
- cargo test --verbose --features serialization
- echo "Building without std"
- cargo build --verbose --no-default-features
- echo "Testing with cgmath"
- cargo build --verbose --features impl-cgmath
- cargo test --verbose --features impl-cgmath
- echo "Testing with nalgebra"
- cargo build --verbose --features impl-nalgebra
- cargo test --verbose --features impl-nalgebra

View File

@ -1,43 +1,79 @@
## 0.2.3 # 2.0.0
> Mon Sep 24th 2019
## Major changes
- Add support for [Bézier curves](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
- Because of Bézier curves, the `Interpolation` type now has one more type variable to know how we
should interpolate with Bézier.
## Minor changes
- Add `Spline::get`, `Spline::get_mut` and `Spline::replace`.
# 1.0
> Sun Sep 22nd 2019
## Major changes
- Make `Spline::clamped_sample` failible via `Option` instead of panicking.
- Add support for polymorphic sampling type.
## Minor changes
- Add the `std` feature (and hence support for `no_std`).
- Add `impl-nalgebra` feature.
- Add `impl-cgmath` feature.
- Add support for adding keys to splines.
- Add support for removing keys from splines.
## Patch changes
- Migrate to Rust 2018.
- Documentation typo fixes.
# 0.2.3
> Sat 13th October 2018 > Sat 13th October 2018
- Add the `"impl-nalgebra"` feature gate. It gives access to some implementors for the `nalgebra` - Add the `"impl-nalgebra"` feature gate. It gives access to some implementors for the `nalgebra`
crate. crate.
- Enhance the documentation. - Enhance the documentation.
## 0.2.2 # 0.2.2
> Sun 30th September 2018 > Sun 30th September 2018
- Bump version numbers (`splines-0.2`) in examples. - Bump version numbers (`splines-0.2`) in examples.
- Fix several typos in the documentation. - Fix several typos in the documentation.
## 0.2.1 # 0.2.1
> Thu 20th September 2018 > Thu 20th September 2018
- Enhance the features documentation. - Enhance the features documentation.
# 0.2 # 0.2
> Thu 6th September 2018 > Thu 6th September 2018
- Add the `"std"` feature gate, that can be used to compile with the standard library. - Add the `"std"` feature gate, that can be used to compile with the standard library.
- Add the `"impl-cgmath"` feature gate in order to make optional, if wanted, the `cgmath` - Add the `"impl-cgmath"` feature gate in order to make optional, if wanted, the `cgmath`
dependency. dependency.
- Enhance the documentation. - Enhance the documentation.
## 0.1.1 # 0.1.1
> Wed 8th August 2018 > Wed 8th August 2018
- Add a feature gate, `"serialization"`, that can be used to automatically derive `Serialize` and - Add a feature gate, `"serialization"`, that can be used to automatically derive `Serialize` and
`Deserialize` from the [serde](https://crates.io/crates/serde) crate. `Deserialize` from the [serde](https://crates.io/crates/serde) crate.
- Enhance the documentation. - Enhance the documentation.
# 0.1 # 0.1
> Sunday 5th August 2018 > Sunday 5th August 2018
- Initial revision. - Initial revision.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "splines" name = "splines"
version = "1.0.0-rc.3" version = "2.0.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"
@ -33,3 +33,6 @@ nalgebra = { version = ">=0.14, <0.19", optional = true }
num-traits = { version = "0.2", optional = true } num-traits = { version = "0.2", optional = true }
serde = { version = "1", optional = true } serde = { version = "1", optional = true }
serde_derive = { version = "1", optional = true } serde_derive = { version = "1", optional = true }
[package.metadata.docs.rs]
all-features = true

View File

@ -13,9 +13,9 @@ switch to a cubic Hermite interpolator for the next section.
Most of the crate consists of three types: Most of the crate consists of three types:
- [`Key`], which represents the control points by which the spline must pass. - [`Key`], which represents the control points by which the spline must pass.
- [`Interpolation`], the type of possible interpolation for each segment. - [`Interpolation`], the type of possible interpolation for each segment.
- [`Spline`], a spline from which you can *sample* points by interpolation. - [`Spline`], a spline from which you can *sample* points by interpolation.
When adding control points, you add new sections. Two control points define a section i.e. When adding control points, you add new sections. Two control points define a section i.e.
its not possible to define a spline without at least two control points. Every time you add a its not possible to define a spline without at least two control points. Every time you add a
@ -40,17 +40,13 @@ key. We use the default one because we dont care.
# Interpolate values # Interpolate values
The whole purpose of splines is to interpolate discrete values to yield continuous ones. This is The whole purpose of splines is to interpolate discrete values to yield continuous ones. This is
usually done with the `Spline::sample` method. This method expects the interpolation parameter usually done with the [`Spline::sample`] method. This method expects the sampling parameter
(often, this will be the time of your simulation) as argument and will yield an interpolated (often, this will be the time of your simulation) as argument and will yield an interpolated
value. value.
If you try to sample in out-of-bounds interpolation parameter, youll get no value. If you try to sample in out-of-bounds sampling parameter, youll get no value.
``` ```
# use splines::{Interpolation, Key, Spline};
# let start = Key::new(0., 0., Interpolation::Linear);
# let end = Key::new(1., 10., Interpolation::Linear);
# let spline = Spline::from_vec(vec![start, end]);
assert_eq!(spline.sample(0.), Some(0.)); assert_eq!(spline.sample(0.), Some(0.));
assert_eq!(spline.clamped_sample(1.), Some(10.)); assert_eq!(spline.clamped_sample(1.), Some(10.));
assert_eq!(spline.sample(1.1), None); assert_eq!(spline.sample(1.1), None);
@ -61,14 +57,17 @@ important for simulations / animations. Feel free to use the `Spline::clamped_in
that purpose. that purpose.
``` ```
# use splines::{Interpolation, Key, Spline};
# let start = Key::new(0., 0., Interpolation::Linear);
# let end = Key::new(1., 10., Interpolation::Linear);
# let spline = Spline::from_vec(vec![start, end]);
assert_eq!(spline.clamped_sample(-0.9), Some(0.)); // clamped to the first key assert_eq!(spline.clamped_sample(-0.9), Some(0.)); // clamped to the first key
assert_eq!(spline.clamped_sample(1.1), Some(10.)); // clamped to the last key assert_eq!(spline.clamped_sample(1.1), Some(10.)); // clamped to the last key
``` ```
# Polymorphic sampling types
[`Spline`] curves are parametered both by the carried value (being interpolated) but also the
sampling type. Its very typical to use `f32` or `f64` but really, you can in theory use any
kind of type; that type must, however, implement a contract defined by a set of traits to
implement. See [the documentation of this module](crate::interpolate) for further details.
# Features and customization # Features and customization
This crate was written with features baked in and hidden behind feature-gates. The idea is that This crate was written with features baked in and hidden behind feature-gates. The idea is that
@ -84,20 +83,22 @@ not. Its especially important to see how it copes with the documentation.
So heres a list of currently supported features and how to enable them: So heres a list of currently supported features and how to enable them:
- **Serialization / deserialization.** - **Serialization / deserialization.**
+ This feature implements both the `Serialize` and `Deserialize` traits from `serde` for all - This feature implements both the `Serialize` and `Deserialize` traits from `serde` for all
types exported by this crate. types exported by this crate.
+ Enable with the `"serialization"` feature. - Enable with the `"serialization"` feature.
- **[cgmath](https://crates.io/crates/cgmath) implementors.** - **[cgmath](https://crates.io/crates/cgmath) implementors.**
+ Adds some useful implementations of `Interpolate` for some cgmath types. - Adds some useful implementations of `Interpolate` for some cgmath types.
+ Enable with the `"impl-cgmath"` feature. - Enable with the `"impl-cgmath"` feature.
- **[nalgebra](https://crates.io/crates/nalgebra) implementors.** - **[nalgebra](https://crates.io/crates/nalgebra) implementors.**
+ Adds some useful implementations of `Interpolate` for some nalgebra types. - Adds some useful implementations of `Interpolate` for some nalgebra types.
+ Enable with the `"impl-nalgebra"` feature. - Enable with the `"impl-nalgebra"` feature.
- **Standard library / no standard library.** - **Standard library / no standard library.**
+ Its possible to compile against the standard library or go on your own without it. - Its possible to compile against the standard library or go on your own without it.
+ Compiling with the standard library is enabled by default. - Compiling with the standard library is enabled by default.
+ Use `default-features = []` in your `Cargo.toml` to disable. - Use `default-features = []` in your `Cargo.toml` to disable.
+ Enable explicitly with the `"std"` feature. - Enable explicitly with the `"std"` feature.
[`Interpolation`]: crate::interpolation::Interpolation
<!-- cargo-sync-readme end --> <!-- cargo-sync-readme end -->

View File

@ -2,7 +2,9 @@ use cgmath::{
BaseFloat, BaseNum, InnerSpace, Quaternion, Vector1, Vector2, Vector3, Vector4, VectorSpace 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 { macro_rules! impl_interpolate_vec {
($($t:tt)*) => { ($($t:tt)*) => {
@ -29,6 +31,16 @@ macro_rules! impl_interpolate_vec {
fn cubic_hermite(x: (Self, T), a: (Self, T), b: (Self, T), y: (Self, T), t: T) -> Self { 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) cubic_hermite_def(x, a, b, y, t)
} }
#[inline(always)]
fn quadratic_bezier(a: Self, u: Self, b: Self, t: T) -> Self {
quadratic_bezier_def(a, u, b, t)
}
#[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 +73,14 @@ where Self: InnerSpace<Scalar = T>, T: Additive + BaseFloat + One {
fn cubic_hermite(x: (Self, T), a: (Self, T), b: (Self, T), y: (Self, T), t: T) -> Self { 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) cubic_hermite_def(x, a, b, y, t)
} }
#[inline(always)]
fn quadratic_bezier(a: Self, u: Self, b: Self, t: T) -> Self {
quadratic_bezier_def(a, u, b, t)
}
#[inline(always)]
fn cubic_bezier(a: Self, u: Self, v: Self, b: Self, t: T) -> Self {
cubic_bezier_def(a, u, v, b, t)
}
} }

View File

@ -57,6 +57,12 @@ pub trait Interpolate<T>: Sized + Copy {
fn cubic_hermite(_: (Self, T), a: (Self, T), b: (Self, T), _: (Self, T), t: T) -> Self { fn cubic_hermite(_: (Self, T), a: (Self, T), b: (Self, T), _: (Self, T), t: T) -> Self {
Self::lerp(a.0, b.0, t) Self::lerp(a.0, b.0, t)
} }
/// Quadratic Bézier interpolation.
fn quadratic_bezier(a: Self, u: Self, b: Self, t: T) -> Self;
/// Cubic Bézier interpolation.
fn cubic_bezier(a: Self, u: Self, v: Self, b: Self, t: T) -> Self;
} }
/// Set of types that support additions and subtraction. /// Set of types that support additions and subtraction.
@ -212,6 +218,31 @@ where V: Linear<T>,
a.0.outer_mul(two_t3 - three_t2 + one_t) + m0.outer_mul(t3 - t2 * two_t + t) + b.0.outer_mul(three_t2 - two_t3) + m1.outer_mul(t3 - t2) a.0.outer_mul(two_t3 - three_t2 + one_t) + m0.outer_mul(t3 - t2 * two_t + t) + b.0.outer_mul(three_t2 - two_t3) + m1.outer_mul(t3 - t2)
} }
/// Default implementation of [`Interpolate::quadratic_bezier`].
///
/// `V` is the value being interpolated. `T` is the sampling value (also sometimes called time).
pub fn quadratic_bezier_def<V, T>(a: V, u: V, b: V, t: T) -> V
where V: Linear<T>,
T: Additive + Mul<T, Output = T> + One {
let one_t = T::one() - t;
let one_t_2 = one_t * one_t;
u + (a - u).outer_mul(one_t_2) + (b - u).outer_mul(t * t)
}
/// Default implementation of [`Interpolate::cubic_bezier`].
///
/// `V` is the value being interpolated. `T` is the sampling value (also sometimes called time).
pub fn cubic_bezier_def<V, T>(a: V, u: V, v: V, b: V, t: T) -> V
where V: Linear<T>,
T: Additive + Mul<T, Output = T> + One {
let one_t = T::one() - t;
let one_t_2 = one_t * one_t;
let one_t_3 = one_t_2 * one_t;
let three = T::one() + T::one() + T::one();
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 { macro_rules! impl_interpolate_simple {
($t:ty) => { ($t:ty) => {
impl Interpolate<$t> for $t { impl Interpolate<$t> for $t {
@ -222,6 +253,14 @@ macro_rules! impl_interpolate_simple {
fn cubic_hermite(x: (Self, $t), a: (Self, $t), b: (Self, $t), y: (Self, $t), t: $t) -> Self { 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) cubic_hermite_def(x, a, b, y, t)
} }
fn quadratic_bezier(a: Self, u: Self, b: Self, t: $t) -> Self {
quadratic_bezier_def(a, u, b, t)
}
fn cubic_bezier(a: Self, u: Self, v: Self, b: Self, t: $t) -> Self {
cubic_bezier_def(a, u, v, b, t)
}
} }
} }
} }
@ -239,6 +278,14 @@ macro_rules! impl_interpolate_via {
fn cubic_hermite((x, xt): (Self, $t), (a, at): (Self, $t), (b, bt): (Self, $t), (y, yt): (Self, $t), t: $t) -> Self { 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) 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 {
quadratic_bezier_def(a, u, b, t as $v)
}
fn cubic_bezier(a: Self, u: Self, v: Self, b: Self, t: $t) -> Self {
cubic_bezier_def(a, u, v, b, t as $v)
}
} }
} }
} }

View File

@ -5,11 +5,11 @@
/// Available kind of interpolations. /// Available kind of interpolations.
/// ///
/// Feel free to visit each variant for more documentation. /// Feel free to visit each variant for more documentation.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "serialization", serde(rename_all = "snake_case"))] #[cfg_attr(feature = "serialization", serde(rename_all = "snake_case"))]
pub enum Interpolation<T> { pub enum Interpolation<T, V> {
/// Hold a [`Key<T, _>`] 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. /// 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 /// > Note: if you set the threshold to `0.5`, the first key will be used until half the time
@ -17,20 +17,36 @@ pub enum Interpolation<T> {
/// > first key will be kept until the next key. Set it to `0.` and the first key will never be /// > first key will be kept until the next key. Set it to `0.` and the first key will never be
/// > used. /// > used.
/// ///
/// [`Key<T, _>`]: crate::key::Key /// [`Key`]: crate::key::Key
Step(T), Step(T),
/// Linear interpolation between a key and the next one. /// Linear interpolation between a key and the next one.
Linear, Linear,
/// Cosine interpolation between a key and the next one. /// Cosine interpolation between a key and the next one.
Cosine, Cosine,
/// Catmull-Rom interpolation, performing a cubic Hermite interpolation using four keys. /// Catmull-Rom interpolation, performing a cubic Hermite interpolation using four keys.
CatmullRom CatmullRom,
/// Bézier interpolation.
///
/// A control point that uses such an interpolation is associated with an extra point. The segmant
/// connecting both is called the _tangent_ of this point. The part of the spline defined between
/// this control point and the next one will be interpolated across with Bézier interpolation. Two
/// cases are possible:
///
/// - The next control point also has a Bézier interpolation mode. In this case, its tangent is
/// used for the interpolation process. This is called _cubic Bézier interpolation_ and it
/// kicks ass.
/// - The next control point doesnt have a Bézier interpolation mode set. In this case, the
/// tangent used for the next control point is defined as the segment connecting that control
/// point and the current control points associated point. This is called _quadratic Bézer
/// interpolation_ and it kicks ass too, but a bit less than cubic.
Bezier(V),
#[doc(hidden)]
__NonExhaustive
} }
impl<T> Default for Interpolation<T> { impl<T, V> Default for Interpolation<T, V> {
/// [`Interpolation::Linear`] is the default. /// [`Interpolation::Linear`] is the default.
fn default() -> Self { fn default() -> Self {
Interpolation::Linear Interpolation::Linear
} }
} }

View File

@ -17,7 +17,7 @@ use crate::interpolation::Interpolation;
/// key and the next one if existing. Have a look at [`Interpolation`] for further details. /// key and the next one if existing. Have a look at [`Interpolation`] for further details.
/// ///
/// [`Interpolation`]: crate::interpolation::Interpolation /// [`Interpolation`]: crate::interpolation::Interpolation
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "serialization", serde(rename_all = "snake_case"))] #[cfg_attr(feature = "serialization", serde(rename_all = "snake_case"))]
pub struct Key<T, V> { pub struct Key<T, V> {
@ -26,12 +26,12 @@ pub struct Key<T, V> {
/// Carried value. /// Carried value.
pub value: V, pub value: V,
/// Interpolation mode. /// Interpolation mode.
pub interpolation: Interpolation<T> pub interpolation: Interpolation<T, V>
} }
impl<T, V> Key<T, V> { impl<T, V> Key<T, V> {
/// Create a new key. /// Create a new key.
pub fn new(t: T, value: V, interpolation: Interpolation<T>) -> Self { pub fn new(t: T, value: V, interpolation: Interpolation<T, V>) -> Self {
Key { t, value, interpolation } Key { t, value, interpolation }
} }
} }

View File

@ -85,20 +85,22 @@
//! So heres a list of currently supported features and how to enable them: //! So heres a list of currently supported features and how to enable them:
//! //!
//! - **Serialization / deserialization.** //! - **Serialization / deserialization.**
//! + This feature implements both the `Serialize` and `Deserialize` traits from `serde` for all //! - This feature implements both the `Serialize` and `Deserialize` traits from `serde` for all
//! types exported by this crate. //! types exported by this crate.
//! + Enable with the `"serialization"` feature. //! - Enable with the `"serialization"` feature.
//! - **[cgmath](https://crates.io/crates/cgmath) implementors.** //! - **[cgmath](https://crates.io/crates/cgmath) implementors.**
//! + Adds some useful implementations of `Interpolate` for some cgmath types. //! - Adds some useful implementations of `Interpolate` for some cgmath types.
//! + Enable with the `"impl-cgmath"` feature. //! - Enable with the `"impl-cgmath"` feature.
//! - **[nalgebra](https://crates.io/crates/nalgebra) implementors.** //! - **[nalgebra](https://crates.io/crates/nalgebra) implementors.**
//! + Adds some useful implementations of `Interpolate` for some nalgebra types. //! - Adds some useful implementations of `Interpolate` for some nalgebra types.
//! + Enable with the `"impl-nalgebra"` feature. //! - Enable with the `"impl-nalgebra"` feature.
//! - **Standard library / no standard library.** //! - **Standard library / no standard library.**
//! + Its possible to compile against the standard library or go on your own without it. //! - Its possible to compile against the standard library or go on your own without it.
//! + Compiling with the standard library is enabled by default. //! - Compiling with the standard library is enabled by default.
//! + Use `default-features = []` in your `Cargo.toml` to disable. //! - Use `default-features = []` in your `Cargo.toml` to disable.
//! + Enable explicitly with the `"std"` feature. //! - Enable explicitly with the `"std"` feature.
//!
//! [`Interpolation`]: crate::interpolation::Interpolation
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(feature = "std"), feature(alloc))] #![cfg_attr(not(feature = "std"), feature(alloc))]

View File

@ -3,7 +3,9 @@ use nalgebra::{Scalar, Vector, Vector1, Vector2, Vector3, Vector4, Vector5, Vect
use num_traits as nt; use num_traits as nt;
use std::ops::Mul; 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 { macro_rules! impl_interpolate_vector {
($($t:tt)*) => { ($($t:tt)*) => {
@ -40,6 +42,16 @@ macro_rules! impl_interpolate_vector {
fn cubic_hermite(x: (Self, T), a: (Self, T), b: (Self, T), y: (Self, T), t: T) -> Self { 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) cubic_hermite_def(x, a, b, y, t)
} }
#[inline(always)]
fn quadratic_bezier(a: Self, u: Self, b: Self, t: T) -> Self {
quadratic_bezier_def(a, u, b, t)
}
#[inline(always)]
fn cubic_bezier(a: Self, u: Self, v: Self, b: Self, t: T) -> Self {
cubic_bezier_def(a, u, v, b, t)
}
} }
} }
} }

View File

@ -28,12 +28,17 @@ use crate::key::Key;
pub struct Spline<T, V>(pub(crate) Vec<Key<T, V>>); pub struct Spline<T, V>(pub(crate) Vec<Key<T, V>>);
impl<T, V> Spline<T, V> { impl<T, V> Spline<T, V> {
/// 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 dont have to be sorted even though its recommended /// Create a new spline out of keys. The keys dont have to be sorted even though its recommended
/// to provide ascending sorted ones (for performance purposes). /// to provide ascending sorted ones (for performance purposes).
pub fn from_vec(mut keys: Vec<Key<T, V>>) -> Self where T: PartialOrd { pub fn from_vec(keys: Vec<Key<T, V>>) -> Self where T: PartialOrd {
keys.sort_by(|k0, k1| k0.t.partial_cmp(&k1.t).unwrap_or(Ordering::Less)); let mut spline = Spline(keys);
spline.internal_sort();
Spline(keys) spline
} }
/// Create a new spline by consuming an `Iterater<Item = Key<T>>`. They keys dont have to be /// Create a new spline by consuming an `Iterater<Item = Key<T>>`. They keys dont have to be
@ -42,7 +47,7 @@ impl<T, V> Spline<T, V> {
/// # Note on iterators /// # Note on iterators
/// ///
/// Its valid to use any iterator that implements `Iterator<Item = Key<T>>`. However, you should /// Its valid to use any iterator that implements `Iterator<Item = Key<T>>`. However, you should
/// use [`Spline::from_vec`] if you are passing a [`Vec`]. This will remove dynamic allocations. /// use [`Spline::from_vec`] if you are passing a [`Vec`].
pub fn from_iter<I>(iter: I) -> Self where I: Iterator<Item = Key<T, V>>, T: PartialOrd { pub fn from_iter<I>(iter: I) -> Self where I: Iterator<Item = Key<T, V>>, T: PartialOrd {
Self::from_vec(iter.collect()) Self::from_vec(iter.collect())
} }
@ -52,6 +57,18 @@ impl<T, V> Spline<T, V> {
&self.0 &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. /// Sample a spline at a given time.
/// ///
/// 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
@ -76,13 +93,13 @@ impl<T, V> Spline<T, V> {
match cp0.interpolation { match cp0.interpolation {
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 }) Some(if nt < threshold { cp0.value } else { cp1.value })
} }
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);
Some(Interpolate::lerp(cp0.value, cp1.value, nt)) Some(Interpolate::lerp(cp0.value, cp1.value, nt))
@ -90,7 +107,7 @@ impl<T, V> Spline<T, V> {
Interpolation::Cosine => { Interpolation::Cosine => {
let two_t = T::one() + T::one(); let two_t = T::one() + T::one();
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;
@ -103,14 +120,33 @@ impl<T, V> Spline<T, V> {
if i == 0 || i >= keys.len() - 2 { if i == 0 || i >= keys.len() - 2 {
None None
} else { } else {
let cp1 = &keys[i+1]; let cp1 = &keys[i + 1];
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);
Some(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))
} }
} }
Interpolation::Bezier(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);
if let Interpolation::Bezier(v) = cp1.interpolation {
Some(Interpolate::cubic_bezier(cp0.value, u, v, cp1.value, nt))
//let one_nt = T::one() - nt;
//let one_nt_2 = one_nt * one_nt;
//let one_nt_3 = one_nt_2 * one_nt;
//let three_one_nt_2 = one_nt_2 + one_nt_2 + one_nt_2; // one_nt_2 * 3
//let r = cp0.value * one_nt_3;
} else {
Some(Interpolate::quadratic_bezier(cp0.value, u, cp1.value, nt))
}
}
Interpolation::__NonExhaustive => unreachable!(),
} }
} }
@ -146,6 +182,69 @@ impl<T, V> Spline<T, V> {
} }
}) })
} }
/// Add a key into the spline.
pub fn add(&mut self, key: Key<T, V>) where T: PartialOrd {
self.0.push(key);
self.internal_sort();
}
/// Remove a key from the spline.
pub fn remove(&mut self, index: usize) -> Option<Key<T, V>> {
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<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)
}
/// Get a key at a given index.
pub fn get(&self, index: usize) -> Option<&Key<T, V>> {
self.0.get(index)
}
/// Mutably get a key at a given 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
})
}
}
/// 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, youre 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<T, V>,
} }
// Normalize a time ([0;1]) given two control points. // Normalize a time ([0;1]) given two control points.

View File

@ -172,3 +172,51 @@ fn nalgebra_vector_interpolation() {
assert_eq!(Interpolate::lerp(start, end, 1.0), end); assert_eq!(Interpolate::lerp(start, end, 1.0), end);
assert_eq!(Interpolate::lerp(start, end, 0.5), mid); assert_eq!(Interpolate::lerp(start, end, 0.5), mid);
} }
#[test]
fn add_key_empty() {
let mut spline: Spline<f32, f32> = Spline::from_vec(vec![]);
spline.add(Key::new(0., 0., Interpolation::Linear));
assert_eq!(spline.keys(), &[Key::new(0., 0., Interpolation::Linear)]);
}
#[test]
fn add_key() {
let start = Key::new(0., 0., Interpolation::Step(0.5));
let k1 = Key::new(1., 5., Interpolation::Linear);
let k2 = Key::new(2., 0., Interpolation::Step(0.1));
let k3 = Key::new(3., 1., Interpolation::Linear);
let k4 = Key::new(10., 2., Interpolation::Linear);
let end = Key::new(11., 4., Interpolation::default());
let new = Key::new(2.4, 40., Interpolation::Linear);
let mut spline = Spline::from_vec(vec![start, k1, k2.clone(), k3, k4, end]);
assert_eq!(spline.keys(), &[start, k1, k2, k3, k4, end]);
spline.add(new);
assert_eq!(spline.keys(), &[start, k1, k2, new, k3, k4, end]);
}
#[test]
fn remove_element_empty() {
let mut spline: Spline<f32, f32> = Spline::from_vec(vec![]);
let removed = spline.remove(0);
assert_eq!(removed, None);
assert!(spline.is_empty());
}
#[test]
fn remove_element() {
let start = Key::new(0., 0., Interpolation::Step(0.5));
let k1 = Key::new(1., 5., Interpolation::Linear);
let k2 = Key::new(2., 0., Interpolation::Step(0.1));
let k3 = Key::new(3., 1., Interpolation::Linear);
let k4 = Key::new(10., 2., Interpolation::Linear);
let end = Key::new(11., 4., Interpolation::default());
let mut spline = Spline::from_vec(vec![start, k1, k2.clone(), k3, k4, end]);
let removed = spline.remove(2);
assert_eq!(removed, Some(k2));
assert_eq!(spline.len(), 5);
}