Compare commits

..

No commits in common. "master" and "1.0.0-rc.1" have entirely different histories.

25 changed files with 586 additions and 1240 deletions

View File

@ -1,25 +0,0 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/."
schedule:
interval: daily
time: "04:00"
open-pull-requests-limit: 10
target-branch: master
reviewers:
- phaazon
assignees:
- phaazon
labels:
- dependency-update
ignore:
- dependency-name: glam
versions:
- 0.13.0
- dependency-name: nalgebra
versions:
- 0.25.0
- dependency-name: cgmath
versions:
- 0.18.0

View File

@ -1,38 +0,0 @@
name: CI
on: [push, pull_request]
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Test
run: cargo test --verbose --all-features
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- name: Test
run: cargo test --verbose --all-features
build-macosx:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- name: Test
run: cargo test --verbose --all-features
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install dependencies
run: |
cargo install --force cargo-sync-readme
rustup component add rustfmt
- name: cargo sync-readme
run: |
cargo sync-readme -c
- name: rustfmt
run: cargo fmt -- --check

23
.travis.yml Normal file
View File

@ -0,0 +1,23 @@
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

View File

@ -1,322 +1,43 @@
# Changelog ## 0.2.3
* [4.3.1](#431)
* [4.3](#43)
* [4.2](#42)
* [4.1.1](#411)
* [4.1](#41)
* [4.0.3](#403)
* [4.0.2](#402)
* [4.0.1](#401)
* [4.0](#40)
* [Major changes](#major-changes)
* [Patch changes](#patch-changes)
* [3.5.4](#354)
* [3.5.3](#353)
* [3.5.2](#352)
* [3.5.1](#351)
* [3.5](#35)
* [3.4.2](#342)
* [3.4.1](#341)
* [3.4](#34)
* [3.3](#33)
* [3.2](#32)
* [3.1](#31)
* [3.0](#30)
* [Major changes](#major-changes-1)
* [Patch changes](#patch-changes-1)
* [2.2](#22)
* [2.1.1](#211)
* [2.1](#21)
* [2.0.1](#201)
* [2.0](#20)
* [Major changes](#major-changes-2)
* [Minor changes](#minor-changes)
* [1.0](#10)
* [Major changes](#major-changes-3)
* [Minor changes](#minor-changes-1)
* [Patch changes](#patch-changes-2)
* [0.2.3](#023)
* [0.2.2](#022)
* [0.2.1](#021)
* [0.2](#02)
* [0.1.1](#011)
* [0.1](#01)
# 4.3.1
> Nov 22, 2023
- Add `Default` implementation for `Spline`. [c6ba847](https://github.com/phaazon/splines/commit/c6ba847)
# 4.3
> Sep 23, 2023
- Add support for `glam-0.23` and `glam-0.24`. [cdc48a4](https://github.com/phaazon/splines/commit/cdc48a4)
- Add `Spline::clear` to clear a spline keys without deallocating its internal storage. [eca09f1](https://github.com/phaazon/splines/commit/eca09f1)
# 4.2
> Feb 1, 2023
- Add support for `glam-0.22`.
- Add support for `nalgebra-0.32`.
- Add deprecation lints for `impl-*` feature gates. Those shouldnt be used anymore and the `*` variant should be
preferred. For instance, if you used `impl-cgmath`, you should just use the `cgmath` feature gate now.
# 4.1.1
> Jul 27, 2022
- Internal enhancement of sampling splines by looking for control points. That brings the lookup from _O(N)_ to
_O(log(N))_. That is super embarassing because it should have been the default from the very first commit. Sorry
about that.
- Fix hermite cubic interpolation.
- Add support for `glam-0.21`.
- Add support for `nalgebra-0.31`.
# 4.1
> Mar 28, 2022
- Support for edition 2021.
- Bump `float-cmp` dependency.
- Bump `glam` dependency.
- Bump `nalgebra` dependency.
- Simplify the CI.
# 4.0.3
> Jul 11, 2021
- Add more implementors for `Interpolate`.
# 4.0.2
> Jul 11, 2021
- **Yanked.**
# 4.0.1
> Jul 11, 2021
- Add support up to `glam-0.17`.
- Add support up to `nalgebra-0.27`.
- Replace the name of some feature gates:
- `serialization` becomes `serde`.
- `impl-*` becomes `*`.
- The previous feature gates are kept around to prevent a breaking change but will eventually be removed in the next
major update.
# 4.0
> Mar 05, 2021
## Major changes
- Switch the `Interpolation` enum to `#[non_exhaustive]` to allow adding more interpolation modes (if any) in the
future.
- Introduce `SampledWithKey`, which is a more elegant / typed way to access a sample along with its associated key
index.
- Refactor the `Interpolate` trait and add the `Interpolator` trait.
## Patch changes
- Highly simplify the various implementors (`cgmath`, `nalgebra` and `glam`) so that maintenance is easy.
- Expose the `impl_Interpolate` macro, allowing to implement the API all at once if a type implements the various
`std::ops:*` traits. Since most of the crates do, this macro makes it really easy to add support for a crate.
- Drop `simba` as a direct dependency.
- Drop `num-traits` as a direct dependency.
# 3.5.4
> Feb 27, 2021
- Support of `cgmath-0.18`.
# 3.5.3
> Jan 16, 2021
- Resynchronize and fix links in the README (fix in `cargo sync-readme`).
# 3.5.2
> Fri Jan 01, 2021
- Support of `nalgebra-0.24`.
# 3.5.1
> Dec 5th, 2020
- Support of `glam-0.11`.
# 3.5
> Nov 23rd, 2020
- Add support for [glam](https://crates.io/crates/glam) via the `"impl-glam"` feature gate.
- Support of `nalgebra-0.23`.
# 3.4.2
> Oct 24th, 2020
- Support of `simba-0.3`.
# 3.4.1
> Sep 5th, 2020
- Support of `simba-0.2`.
- Support of `nalgebra-0.22`.
# 3.4
> Thu May 21st 2020
- Add support for `float-cmp-0.7` and `float-cmp-0.8`. Because this uses a SemVer range, if you
already have a `Cargo.lock`, dont forget to update `splines` with `cargo update --aggressive`.
# 3.3
> Thu Apr 10th 2020
- Add support for `nalgebra-0.21`.
# 3.2
> Thu Mar 19th 2020
- Add support for `nalgebra-0.20`.
- Add support for `float-cmp-0.6`.
# 3.1
> Sat Jan 26th 2020
- Add support for `nalgebra-0.19`.
# 3.0
> Tue Oct 22th 2019
## Major changes
- Sampling now requires the value of the key to be `Linear<T>` for `Interpolate<T>`. 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
> Mon Oct 17th 2019
- Add `Interpolation::StrokeBezier`.
# 2.1.1
> Mon Oct 17th 2019
- Licensing support in the crate.
# 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
> Tue Sep 24th 2019
- Fix the cubic Bézier curve interpolation. The “output” tangent is now taken by mirroring the
next keys tangent around its control point.
# 2.0
> Mon Sep 23rd 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 = "4.3.1" version = "1.0.0-rc.1"
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"
@ -11,32 +11,25 @@ repository = "https://github.com/phaazon/splines"
documentation = "https://docs.rs/splines" documentation = "https://docs.rs/splines"
readme = "README.md" readme = "README.md"
edition = "2021" edition = "2018"
[badges]
travis-ci = { repository = "phaazon/splines", branch = "master" }
is-it-maintained-issue-resolution = { repository = "phaazon/splines" }
is-it-maintained-open-issues = { repository = "phaazon/splines" }
maintenance = { status = "actively-developed" }
[features] [features]
default = ["std"] default = ["std"]
impl-cgmath = ["cgmath"] impl-cgmath = ["cgmath"]
impl-glam = ["glam"] impl-nalgebra = ["alga", "nalgebra", "num-traits"]
impl-nalgebra = ["nalgebra"] serialization = ["serde", "serde_derive"]
serialization = ["serde"] std = []
std = ["nalgebra/std"]
[dependencies] [dependencies]
cgmath = { version = ">=0.17, <0.19", optional = true } alga = { version = "0.9", optional = true }
glam = { version = ">=0.10, <0.25", optional = true } cgmath = { version = "0.17", optional = true }
nalgebra = { version = ">=0.21, <0.33", default-features = false, optional = true } nalgebra = { version = ">=0.14, <0.19", optional = true }
serde = { version = "1", features = ["derive"], optional = true } num-traits = { version = "0.2", optional = true }
serde = { version = "1", optional = true }
[dev-dependencies] serde_derive = { version = "1", optional = true }
float-cmp = ">=0.6, < 0.10"
serde_json = "1"
[package.metadata.docs.rs]
features = ["std", "cgmath", "glam", "nalgebra", "serde"]
[[example]]
name = "hello-world"
[[example]]
name = "serialization"
required-features = ["serde"]

30
LICENSE
View File

@ -1,30 +0,0 @@
Copyright (c) 2019, Dimitri Sabadie <dimitri.sabadie@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Dimitri Sabadie <dimitri.sabadie@gmail.com> nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

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
@ -24,7 +24,7 @@ is picked from its lower control point.
# Quickly create splines # Quickly create splines
```rust ```
use splines::{Interpolation, Key, Spline}; use splines::{Interpolation, Key, Spline};
let start = Key::new(0., 0., Interpolation::Linear); let start = Key::new(0., 0., Interpolation::Linear);
@ -40,13 +40,17 @@ 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 sampling parameter usually done with the `Spline::sample` method. This method expects the interpolation 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 sampling parameter, youll get no value. If you try to sample in out-of-bounds interpolation parameter, youll get no value.
```rust ```
# 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);
@ -56,18 +60,15 @@ Its possible that you want to get a value even if youre out-of-bounds. Thi
important for simulations / animations. Feel free to use the `Spline::clamped_interpolation` for important for simulations / animations. Feel free to use the `Spline::clamped_interpolation` for
that purpose. that purpose.
```rust ```
# 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](https://docs.rs/splines/latest/splines/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
@ -83,25 +84,20 @@ 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:
- **Serde.** - **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 `"serde"` 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 `"cgmath"` feature. + Enable with the `"impl-cgmath"` feature.
- **[glam](https://crates.io/crates/glam) implementors.** - **[nalgebra](https://crates.io/crates/nalgebra) implementors.**
- Adds some useful implementations of `Interpolate` for some glam types. + Adds some useful implementations of `Interpolate` for some nalgebra types.
- Enable with the `"glam"` feature. + Enable with the `"impl-nalgebra"` feature.
- **[nalgebra](https://crates.io/crates/nalgebra) implementors.** - **Standard library / no standard library.**
- Adds some useful implementations of `Interpolate` for some nalgebra types. + Its possible to compile against the standard library or go on your own without it.
- Enable with the `"nalgebra"` feature. + Compiling with the standard library is enabled by default.
- **Standard library / no standard library.** + Use `default-features = []` in your `Cargo.toml` to disable.
- Its possible to compile against the standard library or go on your own without it. + Enable explicitly with the `"std"` feature.
- Compiling with the standard library is enabled by default.
- Use `default-features = []` in your `Cargo.toml` to disable.
- Enable explicitly with the `"std"` feature.
[`Interpolation`]: crate::interpolation::Interpolation
<!-- cargo-sync-readme end --> <!-- cargo-sync-readme end -->

View File

@ -0,0 +1,7 @@
[package]
name = "hello-world"
version = "0.2.0"
authors = ["Dimitri Sabadie <dimitri.sabadie@gmail.com>"]
[dependencies]
splines = "0.2"

View File

@ -3,10 +3,7 @@ extern crate splines;
use splines::{Interpolation, Key, Spline}; use splines::{Interpolation, Key, Spline};
fn main() { fn main() {
let keys = vec![ let keys = vec![Key::new(0., 0., Interpolation::default()), Key::new(5., 1., Interpolation::default())];
Key::new(0., 0., Interpolation::default()),
Key::new(5., 1., Interpolation::default()),
];
let spline = Spline::from_vec(keys); let spline = Spline::from_vec(keys);
println!("value at 0: {:?}", spline.clamped_sample(0.)); println!("value at 0: {:?}", spline.clamped_sample(0.));

View File

@ -0,0 +1,11 @@
[package]
name = "serialization"
version = "0.2.0"
authors = ["Dimitri Sabadie <dimitri.sabadie@gmail.com>"]
[dependencies]
serde_json = "1"
[dependencies.splines]
version = "0.2"
features = ["serialization"]

View File

@ -1,12 +1,11 @@
#[macro_use] #[macro_use] extern crate serde_json;
extern crate serde_json;
extern crate splines; extern crate splines;
use serde_json::from_value; use serde_json::from_value;
use splines::Spline; use splines::Spline;
fn main() { fn main() {
let value = json! { let value = json!{
[ [
{ {
"t": 0, "t": 0,

9
examples/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[workspace]
members = [
"01-hello-world",
"02-serialization"
]
[patch.crates-io]
splines = { path = ".." }

View File

@ -1,15 +0,0 @@
edition = "2018"
fn_params_layout = "Tall"
force_explicit_abi = true
hard_tabs = false
max_width = 100
merge_derives = true
newline_style = "Unix"
remove_nested_parens = true
reorder_imports = true
reorder_modules = true
tab_spaces = 2
use_field_init_shorthand = true
use_small_heuristics = "Default"
use_try_shorthand = true

View File

@ -1,15 +1,64 @@
use crate::impl_Interpolate; use cgmath::{
BaseFloat, BaseNum, InnerSpace, Quaternion, Vector1, Vector2, Vector3, Vector4, VectorSpace
};
use cgmath::{Quaternion, Vector1, Vector2, Vector3, Vector4}; use crate::interpolate::{Additive, Interpolate, Linear, One, cubic_hermite_def};
impl_Interpolate!(f32, Vector1<f32>, std::f32::consts::PI); macro_rules! impl_interpolate_vec {
impl_Interpolate!(f32, Vector2<f32>, std::f32::consts::PI); ($($t:tt)*) => {
impl_Interpolate!(f32, Vector3<f32>, std::f32::consts::PI); impl<T> Linear<T> for $($t)*<T> where T: BaseNum {
impl_Interpolate!(f32, Vector4<f32>, std::f32::consts::PI); #[inline(always)]
impl_Interpolate!(f32, Quaternion<f32>, std::f32::consts::PI); fn outer_mul(self, t: T) -> Self {
self * t
}
impl_Interpolate!(f64, Vector1<f64>, std::f64::consts::PI); #[inline(always)]
impl_Interpolate!(f64, Vector2<f64>, std::f64::consts::PI); fn outer_div(self, t: T) -> Self {
impl_Interpolate!(f64, Vector3<f64>, std::f64::consts::PI); self / t
impl_Interpolate!(f64, Vector4<f64>, std::f64::consts::PI); }
impl_Interpolate!(f64, Quaternion<f64>, std::f64::consts::PI); }
impl<T> Interpolate<T> for $($t)*<T>
where Self: InnerSpace<Scalar = T>, T: Additive + BaseFloat + One {
#[inline(always)]
fn lerp(a: Self, b: Self, t: T) -> Self {
a.lerp(b, t)
}
#[inline(always)]
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)
}
}
}
}
impl_interpolate_vec!(Vector1);
impl_interpolate_vec!(Vector2);
impl_interpolate_vec!(Vector3);
impl_interpolate_vec!(Vector4);
impl<T> Linear<T> for Quaternion<T> where T: BaseFloat {
#[inline(always)]
fn outer_mul(self, t: T) -> Self {
self * t
}
#[inline(always)]
fn outer_div(self, t: T) -> Self {
self / t
}
}
impl<T> Interpolate<T> for Quaternion<T>
where Self: InnerSpace<Scalar = T>, T: Additive + BaseFloat + One {
#[inline(always)]
fn lerp(a: Self, b: Self, t: T) -> Self {
a.nlerp(b, t)
}
#[inline(always)]
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)
}
}

View File

@ -1,8 +0,0 @@
use crate::impl_Interpolate;
use glam::{Quat, Vec2, Vec3, Vec3A, Vec4};
impl_Interpolate!(f32, Vec2, std::f32::consts::PI);
impl_Interpolate!(f32, Vec3, std::f32::consts::PI);
impl_Interpolate!(f32, Vec3A, std::f32::consts::PI);
impl_Interpolate!(f32, Vec4, std::f32::consts::PI);
impl_Interpolate!(f32, Quat, std::f32::consts::PI);

View File

@ -1,247 +1,210 @@
//! The [`Interpolate`] trait and associated symbols. #[cfg(feature = "std")] use std::f32;
//! #[cfg(not(feature = "std"))] use core::f32;
//! The [`Interpolate`] trait is the central concept of the crate. It enables a spline to be #[cfg(not(feature = "std"))] use core::intrinsics::cosf32;
//! sampled at by interpolating in between control points. #[cfg(feature = "std")] use std::f64;
//! #[cfg(not(feature = "std"))] use core::f64;
//! In order for a type to be used in [`Spline<K, V>`], some properties must be met about the `K` #[cfg(not(feature = "std"))] use core::intrinsics::cosf64;
//! type must implementing several traits: #[cfg(feature = "std")] use std::ops::{Add, Mul, Sub};
//! #[cfg(not(feature = "std"))] use core::ops::{Add, Mul, Sub};
//! - [`One`], giving a neutral element for the multiplication monoid.
//! - [`Additive`], making the type additive (i.e. one can add or subtract with it).
//! - [`Linear`], unlocking linear combinations, required for interpolating.
//! - [`Trigo`], a trait giving *π* and *cosine*, required for e.g. cosine interpolation.
//!
//! Feel free to have a look at current implementors for further help.
//!
//! > *Why doesnt this crate use [num-traits] instead of
//! > defining its own traits?*
//!
//! The reason for this is quite simple: this crate provides a `no_std` support, which is not
//! currently available easily with [num-traits]. Also, if something changes in [num-traits] with
//! those traits, it would make this whole crate unstable.
//!
//! [`Interpolate`]: crate::interpolate::Interpolate
//! [`Spline<K, V>`]: crate::spline::Spline
//! [`One`]: crate::interpolate::One
//! [`Additive`]: crate::interpolate::Additive
//! [`Linear`]: crate::interpolate::Linear
//! [`Trigo`]: crate::interpolate::Trigo
//! [num-traits]: https://crates.io/crates/num-traits
#[cfg(not(feature = "std"))] /// Keys that can be interpolated in between. Implementing this trait is required to perform
use core::f32; /// sampling on splines.
#[cfg(not(feature = "std"))]
use core::f64;
#[cfg(not(feature = "std"))]
use core::intrinsics::cosf32;
#[cfg(not(feature = "std"))]
use core::intrinsics::cosf64;
#[cfg(not(feature = "std"))]
use core::ops::{Add, Mul, Sub};
#[cfg(feature = "std")]
use std::f32;
#[cfg(feature = "std")]
use std::f64;
/// Types that can be used as interpolator in splines.
/// ///
/// An interpolator value is like the fabric on which control keys (and sampled values) live on. /// `T` is the variable used to sample with. Typical implementations use `f32` or `f64`, but youre
pub trait Interpolator: Sized + Copy + PartialOrd { /// free to use the ones you like.
/// Normalize the interpolator.
fn normalize(self, start: Self, end: Self) -> Self;
}
macro_rules! impl_Interpolator {
($t:ty) => {
impl Interpolator for $t {
fn normalize(self, start: Self, end: Self) -> Self {
(self - start) / (end - start)
}
}
};
}
impl_Interpolator!(f32);
impl_Interpolator!(f64);
/// Values that can be interpolated. Implementing this trait is required to perform sampling on splines.
///
/// `T` is the interpolator used to sample with. Typical implementations use [`f32`] or [`f64`], but
/// youre free to use the ones you like.
pub trait Interpolate<T>: Sized + Copy { pub trait Interpolate<T>: Sized + Copy {
/// Step interpolation.
fn step(t: T, threshold: T, a: Self, b: Self) -> Self;
/// Linear interpolation. /// Linear interpolation.
fn lerp(t: T, a: Self, b: Self) -> Self; fn lerp(a: Self, b: Self, t: T) -> Self;
/// Cosine interpolation.
fn cosine(t: T, a: Self, b: Self) -> Self;
/// Cubic hermite interpolation. /// Cubic hermite interpolation.
fn cubic_hermite(t: T, x: (T, Self), a: (T, Self), b: (T, Self), y: (T, Self)) -> Self;
/// Quadratic Bézier interpolation.
/// ///
/// `a` is the first point; `b` is the second point and `u` is the tangent of `a` to the curve. /// Default to `Self::lerp`.
fn quadratic_bezier(t: T, a: Self, u: Self, b: Self) -> Self; fn cubic_hermite(_: (Self, T), a: (Self, T), b: (Self, T), _: (Self, T), t: T) -> Self {
Self::lerp(a.0, b.0, t)
/// Cubic Bézier interpolation. }
///
/// `a` is the first point; `b` is the second point; `u` is the output tangent of `a` to the curve and `v` is the
/// input tangent of `b` to the curve.
fn cubic_bezier(t: T, a: Self, u: Self, v: Self, b: Self) -> Self;
/// Cubic Bézier interpolation special case for non-explicit second tangent.
///
/// This version does the same computation as [`Interpolate::cubic_bezier`] but computes the second tangent by
/// inversing it (typical when the next point uses a Bézier interpolation, where input and output tangents are
/// mirrored for the same key).
fn cubic_bezier_mirrored(t: T, a: Self, u: Self, v: Self, b: Self) -> Self;
} }
#[macro_export] /// A trait for anything that supports additions, subtraction, multiplication and division.
macro_rules! impl_Interpolate { pub trait Additive:
($t:ty, $v:ty, $pi:expr) => { Copy +
impl $crate::interpolate::Interpolate<$t> for $v { Add<Self, Output = Self> +
fn step(t: $t, threshold: $t, a: Self, b: Self) -> Self { Sub<Self, Output = Self> {
if t < threshold { }
a
} else { impl<T> Additive for T
b where T: Copy +
} Add<Self, Output = Self> +
Sub<Self, Output = Self> {
}
/// Linear combination.
pub trait Linear<T> {
/// Apply an outer multiplication law.
fn outer_mul(self, t: T) -> Self;
/// Apply an outer division law.
fn outer_div(self, t: T) -> Self;
}
macro_rules! impl_linear_simple {
($t:ty) => {
impl Linear<$t> for $t {
fn outer_mul(self, t: $t) -> Self {
self * t
} }
#[cfg(feature = "std")] /// Apply an outer division law.
fn cosine(t: $t, a: Self, b: Self) -> Self { fn outer_div(self, t: $t) -> Self {
let cos_nt = (1. - (t * $pi).cos()) * 0.5; self / t
<Self as $crate::interpolate::Interpolate<$t>>::lerp(cos_nt, a, b)
} }
#[cfg(not(feature = "std"))] }
fn cosine(t: $t, a: Self, b: Self) -> Self { }
unimplemented!(); }
impl_linear_simple!(f32);
impl_linear_simple!(f64);
macro_rules! impl_linear_cast {
($t:ty, $q:ty) => {
impl Linear<$t> for $q {
fn outer_mul(self, t: $t) -> Self {
self * t as $q
} }
fn lerp(t: $t, a: Self, b: Self) -> Self { /// Apply an outer division law.
fn outer_div(self, t: $t) -> Self {
self / t as $q
}
}
}
}
impl_linear_cast!(f32, f64);
impl_linear_cast!(f64, f32);
/// Types with a neutral element for multiplication.
pub trait One {
/// Return the neutral element for the multiplicative monoid.
fn one() -> Self;
}
macro_rules! impl_one_float {
($t:ty) => {
impl One for $t {
#[inline(always)]
fn one() -> Self {
1.
}
}
}
}
impl_one_float!(f32);
impl_one_float!(f64);
/// Types with a sane definition of π and cosine.
pub trait Trigo {
/// π.
fn pi() -> Self;
/// Cosine of the argument.
fn cos(self) -> Self;
}
impl Trigo for f32 {
#[inline(always)]
fn pi() -> Self {
f32::consts::PI
}
#[inline(always)]
fn cos(self) -> Self {
#[cfg(feature = "std")]
{
self.cos()
}
#[cfg(not(feature = "std"))]
{
unsafe { cosf32(self) }
}
}
}
impl Trigo for f64 {
#[inline(always)]
fn pi() -> Self {
f64::consts::PI
}
#[inline(always)]
fn cos(self) -> Self {
#[cfg(feature = "std")]
{
self.cos()
}
#[cfg(not(feature = "std"))]
{
unsafe { cosf64(self) }
}
}
}
// Default implementation of Interpolate::cubic_hermite.
//
// `V` is the value being interpolated. `T` is the sampling value (also sometimes called time).
pub(crate) fn cubic_hermite_def<V, T>(x: (V, T), a: (V, T), b: (V, T), y: (V, T), t: T) -> V
where V: Additive + Linear<T>,
T: Additive + Mul<T, Output = T> + One {
// some stupid generic constants, because Rust doesnt have polymorphic literals…
let one_t = T::one();
let two_t = one_t + one_t; // lolololol
let three_t = two_t + one_t; // megalol
// sampler stuff
let t2 = t * t;
let t3 = t2 * t;
let two_t3 = t3 * two_t;
let three_t2 = t2 * three_t;
// tangents
let m0 = (b.0 - x.0).outer_div(b.1 - x.1);
let m1 = (y.0 - a.0).outer_div(y.1 - a.1);
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)
}
macro_rules! impl_interpolate_simple {
($t:ty) => {
impl Interpolate<$t> for $t {
fn lerp(a: Self, b: Self, t: $t) -> Self {
a * (1. - t) + b * t a * (1. - t) + b * t
} }
fn cubic_hermite(t: $t, x: ($t, Self), a: ($t, Self), b: ($t, Self), y: ($t, Self)) -> Self { fn cubic_hermite(x: (Self, $t), a: (Self, $t), b: (Self, $t), y: (Self, $t), t: $t) -> Self {
// sampler stuff cubic_hermite_def(x, a, b, y, t)
let two_t = t * 2.;
let three_t = t * 3.;
let t2 = t * t;
let t3 = t2 * t;
let two_t3 = t2 * two_t;
let two_t2 = t * two_t;
let three_t2 = t * three_t;
// tangents
let m0 = (b.1 - x.1) / (b.0 - x.0) * (b.0 - a.0);
let m1 = (y.1 - a.1) / (y.0 - a.0) * (b.0 - a.0);
a.1 * (two_t3 - three_t2 + 1.)
+ m0 * (t3 - two_t2 + t)
+ b.1 * (three_t2 - two_t3)
+ m1 * (t3 - t2)
}
fn quadratic_bezier(t: $t, a: Self, u: Self, b: Self) -> Self {
let one_t = 1. - t;
let one_t2 = one_t * one_t;
u + (a - u) * one_t2 + (b - u) * t * t
}
fn cubic_bezier(t: $t, a: Self, u: Self, v: Self, b: Self) -> Self {
let one_t = 1. - t;
let one_t2 = one_t * one_t;
let one_t3 = one_t2 * one_t;
let t2 = t * t;
a * one_t3 + (u * one_t2 * t + v * one_t * t2) * 3. + b * t2 * t
}
fn cubic_bezier_mirrored(t: $t, a: Self, u: Self, v: Self, b: Self) -> Self {
<Self as $crate::interpolate::Interpolate<$t>>::cubic_bezier(t, a, u, b + b - v, b)
} }
} }
}; }
} }
#[macro_export] impl_interpolate_simple!(f32);
macro_rules! impl_InterpolateT { impl_interpolate_simple!(f64);
($t:ty, $v:ty, $pi:expr) => {
impl $crate::interpolate::Interpolate<$t> for $v { macro_rules! impl_interpolate_via {
fn step(t: $t, threshold: $t, a: Self, b: Self) -> Self { ($t:ty, $v:ty) => {
if t < threshold { impl Interpolate<$t> for $v {
a fn lerp(a: Self, b: Self, t: $t) -> Self {
} else { a * (1. - t as $v) + b * t as $v
b
}
} }
#[cfg(feature = "std")] fn cubic_hermite((x, xt): (Self, $t), (a, at): (Self, $t), (b, bt): (Self, $t), (y, yt): (Self, $t), t: $t) -> Self {
fn cosine(t: $t, a: Self, b: Self) -> Self { cubic_hermite_def((x, xt as $v), (a, at as $v), (b, bt as $v), (y, yt as $v), t as $v)
let cos_nt = (1. - (t * $pi).cos()) * 0.5;
<Self as $crate::interpolate::Interpolate<$t>>::lerp(cos_nt, a, b)
}
#[cfg(not(feature = "std"))]
fn cosine(t: $t, a: Self, b: Self) -> Self {
unimplemented!()
}
fn lerp(t: $t, a: Self, b: Self) -> Self {
let t = Self::from(t);
a * (1. - t) + b * t
}
fn cubic_hermite(t: $t, x: ($t, Self), a: ($t, Self), b: ($t, Self), y: ($t, Self)) -> Self {
// sampler stuff
let t = Self::from(t);
let two_t = t * 2.;
let three_t = t * 3.;
let t2 = t * t;
let t3 = t2 * t;
let two_t3 = t2 * two_t;
let two_t2 = t * two_t;
let three_t2 = t * three_t;
// tangents
let m0 = (b.1 - x.1) / (Self::from(b.0 - x.0)) * (Self::from(b.0 - a.0));
let m1 = (y.1 - a.1) / (Self::from(y.0 - a.0)) * (Self::from(b.0 - a.0));
a.1 * (two_t3 - three_t2 + 1.)
+ m0 * (t3 - two_t2 + t)
+ b.1 * (three_t2 - two_t3)
+ m1 * (t3 - t2)
}
fn quadratic_bezier(t: $t, a: Self, u: Self, b: Self) -> Self {
let t = Self::from(t);
let one_t = 1. - t;
let one_t2 = one_t * one_t;
u + (a - u) * one_t2 + (b - u) * t * t
}
fn cubic_bezier(t: $t, a: Self, u: Self, v: Self, b: Self) -> Self {
let t = Self::from(t);
let one_t = 1. - t;
let one_t2 = one_t * one_t;
let one_t3 = one_t2 * one_t;
let t2 = t * t;
a * one_t3 + (u * one_t2 * t + v * one_t * t2) * 3. + b * t2 * t
}
fn cubic_bezier_mirrored(t: $t, a: Self, u: Self, v: Self, b: Self) -> Self {
<Self as $crate::interpolate::Interpolate<$t>>::cubic_bezier(t, a, u, b + b - v, b)
} }
} }
}; }
} }
impl_Interpolate!(f32, f32, f32::consts::PI); impl_interpolate_via!(f32, f64);
impl_Interpolate!(f64, f64, f64::consts::PI); impl_interpolate_via!(f64, f32);
impl_InterpolateT!(f32, f64, f32::consts::PI);

View File

@ -1,72 +1,30 @@
//! Available interpolation modes. #[cfg(feature = "serialization")] use serde_derive::{Deserialize, Serialize};
#[cfg(any(feature = "serialization", feature = "serde"))] /// Interpolation mode.
use serde::{Deserialize, Serialize}; #[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))]
/// Available kind of interpolations. #[cfg_attr(feature = "serialization", serde(rename_all = "snake_case"))]
/// pub enum Interpolation<T> {
/// Feel free to visit each variant for more documentation. /// Hold a [`Key`] until the interpolator value passes the normalized step threshold, in which
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
any(feature = "serialization", feature = "serde"),
derive(Deserialize, Serialize),
serde(rename_all = "snake_case")
)]
pub enum Interpolation<T, V> {
/// 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
/// > between the two keys; the second key will be in used afterwards. If you set it to `1.0`, the /// > 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 /// > first key will be kept until the next key. Set it to `0.` and the first key will never be
/// > used. /// > used.
///
/// [`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),
/// A special Bézier interpolation using an _input tangent_ and an _output tangent_.
///
/// With this kind of interpolation, a control point has an input tangent, which has the same role
/// as the one defined by [`Interpolation::Bezier`], and an output tangent, which has the same
/// role defined by the next keys [`Interpolation::Bezier`] if present, normally.
///
/// What it means is that instead of setting the output tangent as the next keys Bézier tangent,
/// this interpolation mode allows you to manually set the output tangent. That will yield more
/// control on the tangents but might generate discontinuities. Use with care.
///
/// Stroke Bézier interpolation is always a cubic Bézier interpolation by default.
StrokeBezier(V, V),
} }
impl<T, V> Default for Interpolation<T, V> { impl<T> Default for Interpolation<T> {
/// [`Interpolation::Linear`] is the default. /// `Interpolation::Linear` is the default.
fn default() -> Self { fn default() -> Self {
Interpolation::Linear Interpolation::Linear
} }
} }

View File

@ -1,30 +1,18 @@
//! Spline [`Iterator`], in a nutshell.
//!
//! You can iterate over a [`Spline<K, V>`]s keys with the [`IntoIterator`] trait on
//! `&Spline<K, V>`. This gives you iterated [`Key<K, V>`] keys.
//!
//! [`Spline<K, V>`]: crate::spline::Spline
//! [`Key<K, V>`]: crate::key::Key
use crate::{Key, Spline}; use crate::{Key, Spline};
/// Iterator over spline keys. /// Iterator over spline keys.
/// ///
/// This iterator type is guaranteed to iterate over sorted keys. /// This iterator type assures you to iterate over sorted keys.
pub struct Iter<'a, T, V> pub struct Iter<'a, T, V> where T: 'a, V: 'a {
where anim_param: &'a Spline<T, V>,
T: 'a, i: usize
V: 'a,
{
spline: &'a Spline<T, V>,
i: usize,
} }
impl<'a, T, V> Iterator for Iter<'a, T, V> { impl<'a, T, V> Iterator for Iter<'a, T, V> {
type Item = &'a Key<T, V>; type Item = &'a Key<T, V>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let r = self.spline.0.get(self.i); let r = self.anim_param.0.get(self.i);
if let Some(_) = r { if let Some(_) = r {
self.i += 1; self.i += 1;
@ -39,6 +27,10 @@ impl<'a, T, V> IntoIterator for &'a Spline<T, V> {
type IntoIter = Iter<'a, T, V>; type IntoIter = Iter<'a, T, V>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
Iter { spline: self, i: 0 } Iter {
anim_param: self,
i: 0
}
} }
} }

View File

@ -1,44 +1,28 @@
//! Spline control points. #[cfg(feature = "serialization")] use serde_derive::{Deserialize, Serialize};
//!
//! A control point associates to a “sampling value” (a.k.a. time) a carried value that can be
//! interpolated along the curve made by the control points.
//!
//! Splines constructed with this crate have the property that its possible to change the
//! interpolation mode on a key-based way, allowing you to implement and encode complex curves.
use crate::interpolation::Interpolation; use crate::interpolation::Interpolation;
#[cfg(any(feature = "serialization", feature = "serde"))]
use serde::{Deserialize, Serialize};
/// A spline control point. /// A spline control point.
/// ///
/// This type associates a value at a given interpolation parameter value. It also contains an /// 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 /// interpolation hint 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. /// key and the next one if existing.
/// #[derive(Copy, Clone, Debug)]
/// [`Interpolation`]: crate::interpolation::Interpolation #[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serialization", serde(rename_all = "snake_case"))]
#[cfg_attr(
any(feature = "serialization", feature = "serde"),
derive(Deserialize, Serialize),
serde(rename_all = "snake_case")
)]
pub struct Key<T, V> { pub struct Key<T, V> {
/// Interpolation parameter at which the [`Key`] should be reached. /// Interpolation parameter at which the [`Key`] should be reached.
pub t: T, pub t: T,
/// Carried value. /// Held value.
pub value: V, pub value: V,
/// Interpolation mode. /// Interpolation mode.
pub interpolation: Interpolation<T, V>, pub interpolation: Interpolation<T>
} }
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, V>) -> Self { pub fn new(t: T, value: V, interpolation: Interpolation<T>) -> Self {
Key { Key { t, value, interpolation }
t,
value,
interpolation,
}
} }
} }

View File

@ -33,11 +33,11 @@
//! # 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 sampling parameter //! usually done with the `Spline::sample` method. This method expects the interpolation 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 sampling parameter, youll get no value. //! If you try to sample in out-of-bounds interpolation parameter, youll get no value.
//! //!
//! ``` //! ```
//! # use splines::{Interpolation, Key, Spline}; //! # use splines::{Interpolation, Key, Spline};
@ -62,13 +62,6 @@
//! 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,55 +77,34 @@
//! //!
//! 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:
//! //!
//! - **Serde.** //! - **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 `"serde"` 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 `"cgmath"` feature. //! + Enable with the `"impl-cgmath"` feature.
//! - **[glam](https://crates.io/crates/glam) implementors.**
//! - Adds some useful implementations of `Interpolate` for some glam types.
//! - Enable with the `"glam"` 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 `"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))]
#![cfg_attr(not(feature = "std"), feature(core_intrinsics))] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))]
#![cfg_attr(
any(
feature = "impl-cgmath",
feature = "impl-glam",
feature = "impl-nalgebra"
),
deprecated(
since = "4.2.0",
note = "you are using an impl-* feature gate; please switch to * (e.g. impl-cgmath becomes cgmath)"
)
)]
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))] extern crate alloc;
extern crate alloc;
#[cfg(any(feature = "impl-cgmath", feature = "cgmath"))] #[cfg(feature = "impl-cgmath")] mod cgmath;
mod cgmath;
#[cfg(any(feature = "impl-glam", feature = "glam"))]
mod glam;
pub mod interpolate; pub mod interpolate;
pub mod interpolation; pub mod interpolation;
pub mod iter; pub mod iter;
pub mod key; pub mod key;
#[cfg(any(feature = "impl-nalgebra", feature = "nalgebra"))] #[cfg(feature = "impl-nalgebra")] mod nalgebra;
mod nalgebra;
pub mod spline; pub mod spline;
pub use crate::interpolate::Interpolate; pub use crate::interpolate::Interpolate;

View File

@ -1,27 +1,72 @@
#[cfg(not(feature = "std"))] use alga::general::{ClosedAdd, ClosedDiv, ClosedMul, ClosedSub};
use core::f32; use nalgebra::{
#[cfg(not(feature = "std"))] DefaultAllocator, DimName, Point, Scalar, Vector, Vector1, Vector2, Vector3, Vector4, Vector5,
use core::f64; Vector6
#[cfg(feature = "std")] };
use std::f32; use nalgebra::allocator::Allocator;
#[cfg(feature = "std")] use num_traits as nt;
use std::f64; use std::ops::Mul;
use crate::impl_Interpolate; use crate::interpolate::{Interpolate, Linear, Additive, One, cubic_hermite_def};
use nalgebra::{Quaternion, Vector1, Vector2, Vector3, Vector4, Vector5, Vector6};
impl_Interpolate!(f32, Vector1<f32>, f32::consts::PI); macro_rules! impl_interpolate_vector {
impl_Interpolate!(f32, Vector2<f32>, f32::consts::PI); ($($t:tt)*) => {
impl_Interpolate!(f32, Vector3<f32>, f32::consts::PI); // implement Linear
impl_Interpolate!(f32, Vector4<f32>, f32::consts::PI); impl<T> Linear<T> for $($t)*<T> where T: Scalar + ClosedMul + ClosedDiv {
impl_Interpolate!(f32, Vector5<f32>, f32::consts::PI); #[inline(always)]
impl_Interpolate!(f32, Vector6<f32>, f32::consts::PI); fn outer_mul(self, t: T) -> Self {
impl_Interpolate!(f32, Quaternion<f32>, f32::consts::PI); self * t
}
impl_Interpolate!(f64, Vector1<f64>, f64::consts::PI); #[inline(always)]
impl_Interpolate!(f64, Vector2<f64>, f64::consts::PI); fn outer_div(self, t: T) -> Self {
impl_Interpolate!(f64, Vector3<f64>, f64::consts::PI); self / t
impl_Interpolate!(f64, Vector4<f64>, f64::consts::PI); }
impl_Interpolate!(f64, Vector5<f64>, f64::consts::PI); }
impl_Interpolate!(f64, Vector6<f64>, f64::consts::PI);
impl_Interpolate!(f64, Quaternion<f64>, f64::consts::PI); impl<T, V> Interpolate<T> for $($t)*<V>
where Self: Linear<T>,
T: Additive + One + Mul<T, Output = T>,
V: nt::One +
nt::Zero +
Additive +
Scalar +
ClosedAdd +
ClosedMul +
ClosedSub +
Interpolate<T> {
#[inline(always)]
fn lerp(a: Self, b: Self, t: T) -> Self {
Vector::zip_map(&a, &b, |c1, c2| Interpolate::lerp(c1, c2, t))
}
#[inline(always)]
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)
}
}
}
}
impl_interpolate_vector!(Vector1);
impl_interpolate_vector!(Vector2);
impl_interpolate_vector!(Vector3);
impl_interpolate_vector!(Vector4);
impl_interpolate_vector!(Vector5);
impl_interpolate_vector!(Vector6);
impl<T, D> Linear<T> for Point<T, D>
where D: DimName,
DefaultAllocator: Allocator<T, D>,
<DefaultAllocator as Allocator<T, D>>::Buffer: Copy,
T: Scalar + ClosedDiv + ClosedMul {
#[inline(always)]
fn outer_mul(self, t: T) -> Self {
self * t
}
#[inline(always)]
fn outer_div(self, t: T) -> Self {
self / t
}
}

View File

@ -1,66 +1,26 @@
//! Spline curves and operations. #[cfg(feature = "serialization")] use serde_derive::{Deserialize, Serialize};
#[cfg(not(feature = "std"))] use alloc::vec::Vec;
#[cfg(feature = "std")] use std::cmp::Ordering;
#[cfg(feature = "std")] use std::ops::{Div, Mul};
#[cfg(not(feature = "std"))] use core::ops::{Div, Mul};
#[cfg(not(feature = "std"))] use core::cmp::Ordering;
// #[cfg(feature = "std")] use crate::interpolate::{Interpolate, Additive, One, Trigo};
use crate::interpolate::{Interpolate, Interpolator};
use crate::interpolation::Interpolation; use crate::interpolation::Interpolation;
use crate::key::Key; use crate::key::Key;
#[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(any(feature = "serialization", feature = "serde"))]
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::cmp::Ordering;
/// Spline curve used to provide interpolation between control points (keys). /// Spline curve used to provide interpolation between control points (keys).
/// #[derive(Debug, Clone)]
/// Splines are made out of control points ([`Key`]). When creating a [`Spline`] with #[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))]
/// [`Spline::from_vec`] or [`Spline::from_iter`], the keys dont have to be sorted (they are sorted
/// automatically by the sampling value).
///
/// You can sample from a spline with several functions:
///
/// - [`Spline::sample`]: allows you to sample from a spline. If not enough keys are available
/// for the required interpolation mode, you get `None`.
/// - [`Spline::clamped_sample`]: behaves like [`Spline::sample`] but will return either the first
/// or last key if out of bound; it will return `None` if not enough key.
#[derive(Debug, Clone, Default)]
#[cfg_attr(
any(feature = "serialization", feature = "serde"),
derive(Deserialize, Serialize)
)]
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(keys: Vec<Key<T, V>>) -> Self pub fn from_vec(mut keys: Vec<Key<T, V>>) -> Self where T: PartialOrd {
where keys.sort_by(|k0, k1| k0.t.partial_cmp(&k1.t).unwrap_or(Ordering::Less));
T: PartialOrd,
{
let mut spline = Spline(keys);
spline.internal_sort();
spline
}
/// Clear the spline by removing all keys. Keeps the underlying allocated storage, so adding Spline(keys)
/// new keys should be faster than creating a new [`Spline`]
#[inline]
pub fn clear(&mut self) {
self.0.clear()
} }
/// 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
@ -69,12 +29,8 @@ 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`]. /// use `Spline::from_vec` if you are passing a `Vec<_>`. This will remove dynamic allocations.
pub fn from_iter<I>(iter: I) -> Self pub fn from_iter<I>(iter: I) -> Self where I: Iterator<Item = Key<T, V>>, T: PartialOrd {
where
I: Iterator<Item = Key<T, V>>,
T: PartialOrd,
{
Self::from_vec(iter.collect()) Self::from_vec(iter.collect())
} }
@ -83,20 +39,7 @@ impl<T, V> Spline<T, V> {
&self.0 &self.0
} }
/// Number of keys. /// Sample a spline at a given time.
#[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, 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)*
@ -107,41 +50,37 @@ impl<T, V> Spline<T, V> {
/// ///
/// `None` if you try to sample a value at a time that has no key associated with. That can also /// `None` if you try to sample a value at a time that has no key associated with. That can also
/// happen if you try to sample between two keys with a specific interpolation mode that makes the /// happen if you try to sample between two keys with a specific interpolation mode that makes the
/// sampling impossible. For instance, [`Interpolation::CatmullRom`] requires *four* keys. If /// sampling impossible. For instance, `Interpolate::CatmullRom` requires *four* keys. If youre
/// youre near the beginning of the spline or its end, ensure you have enough keys around to make /// near the beginning of the spline or its end, ensure you have enough keys around to make the
/// the sampling. /// sampling.
pub fn sample_with_key(&self, t: T) -> Option<SampledWithKey<V>> pub fn sample(&self, t: T) -> Option<V>
where where T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
T: Interpolator, V: Interpolate<T> {
V: Interpolate<T>,
{
let keys = &self.0; let keys = &self.0;
let i = search_lower_cp(keys, t)?; let i = search_lower_cp(keys, t)?;
let cp0 = &keys[i]; let cp0 = &keys[i];
let value = match cp0.interpolation { match cp0.interpolation {
Interpolation::Step(threshold) => { Interpolation::Step(threshold) => {
let cp1 = &keys[i + 1]; let cp1 = &keys[i+1];
let nt = t.normalize(cp0.t, cp1.t); let nt = normalize_time(t, cp0, cp1);
let value = V::step(nt, threshold, cp0.value, cp1.value); Some(if nt < threshold { cp0.value } else { cp1.value })
Some(value)
} }
Interpolation::Linear => { Interpolation::Linear => {
let cp1 = &keys[i + 1]; let cp1 = &keys[i+1];
let nt = t.normalize(cp0.t, cp1.t); let nt = normalize_time(t, cp0, cp1);
let value = V::lerp(nt, cp0.value, cp1.value);
Some(value) Some(Interpolate::lerp(cp0.value, cp1.value, nt))
} }
Interpolation::Cosine => { Interpolation::Cosine => {
let cp1 = &keys[i + 1]; let two_t = T::one() + T::one();
let nt = t.normalize(cp0.t, cp1.t); let cp1 = &keys[i+1];
let value = V::cosine(nt, cp0.value, cp1.value); let nt = normalize_time(t, cp0, cp1);
let cos_nt = (T::one() - (nt * T::pi()).cos()) / two_t;
Some(value) Some(Interpolate::lerp(cp0.value, cp1.value, cos_nt))
} }
Interpolation::CatmullRom => { Interpolation::CatmullRom => {
@ -150,195 +89,91 @@ 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 = t.normalize(cp0.t, cp1.t); let nt = normalize_time(t, cp0, cp1);
let value = V::cubic_hermite(
nt,
(cpm0.t, cpm0.value),
(cp0.t, cp0.value),
(cp1.t, cp1.value),
(cpm1.t, cpm1.value),
);
Some(value) Some(Interpolate::cubic_hermite((cpm0.value, cpm0.t), (cp0.value, cp0.t), (cp1.value, cp1.t), (cpm1.value, cpm1.t), nt))
} }
} }
}
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 = t.normalize(cp0.t, cp1.t);
let value = match cp1.interpolation {
Interpolation::Bezier(v) => V::cubic_bezier_mirrored(nt, cp0.value, u, v, cp1.value),
Interpolation::StrokeBezier(v, _) => V::cubic_bezier(nt, cp0.value, u, v, cp1.value),
_ => V::quadratic_bezier(nt, cp0.value, u, cp1.value),
};
Some(value)
}
};
value.map(|value| SampledWithKey { value, key: i })
} }
/// Sample a spline at a given time. /// Sample a spline at a given time with clamping.
///
pub fn sample(&self, t: T) -> Option<V>
where
T: Interpolator,
V: Interpolate<T>,
{
self.sample_with_key(t).map(|sampled| sampled.value)
}
/// Sample a spline at a given time with clamping, returning the interpolated value along with its
/// associated key.
/// ///
/// # Return /// # Return
/// ///
/// If you sample before the first key or after the last one, return the first key or the last /// If you sample before the first key or after the last one, return the first key or the last
/// one, respectively. Otherwise, behave the same way as [`Spline::sample`]. /// one, respectively. Otherwise, behave the same way as `Spline::sample`.
/// ///
/// # Error /// # Error
/// ///
/// This function returns [`None`] if you have no key. /// This function returns `None` if you have no key.
pub fn clamped_sample_with_key(&self, t: T) -> Option<SampledWithKey<V>> pub fn clamped_sample(&self, t: T) -> Option<V>
where where T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
T: Interpolator, V: Interpolate<T> {
V: Interpolate<T>,
{
if self.0.is_empty() { if self.0.is_empty() {
return None; return None;
} }
self.sample_with_key(t).or_else(move || { self.sample(t).or_else(move || {
let first = self.0.first().unwrap(); let first = self.0.first().unwrap();
if t <= first.t { if t <= first.t {
let sampled = SampledWithKey { Some(first.value)
value: first.value,
key: 0,
};
Some(sampled)
} else { } else {
let last = self.0.last().unwrap(); let last = self.0.last().unwrap();
if t >= last.t { if t >= last.t {
let sampled = SampledWithKey { Some(last.value)
value: last.value,
key: self.0.len() - 1,
};
Some(sampled)
} else { } else {
None None
} }
} }
}) })
} }
/// Sample a spline at a given time with clamping.
pub fn clamped_sample(&self, t: T) -> Option<V>
where
T: Interpolator,
V: Interpolate<T>,
{
self.clamped_sample_with_key(t).map(|sampled| sampled.value)
}
/// 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 sampled value along with its key index. // Normalize a time ([0;1]) given two control points.
#[derive(Clone, Debug, Eq, Hash, PartialEq)] #[inline(always)]
pub struct SampledWithKey<V> { pub(crate) fn normalize_time<T, V>(
/// Sampled value. t: T,
pub value: V, cp: &Key<T, V>,
cp1: &Key<T, V>
/// Key index. ) -> T where T: Additive + Div<T, Output = T> + PartialEq {
pub key: usize, assert!(cp1.t != cp.t, "overlapping keys");
} (t - cp.t) / (cp1.t - cp.t)
/// 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`].
#[derive(Debug)]
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>,
} }
// Find the lower control point corresponding to a given time. // Find the lower control point corresponding to a given time.
// It has the property to have a timestamp smaller or equal to t fn search_lower_cp<T, V>(cps: &[Key<T, V>], t: T) -> Option<usize> where T: PartialOrd {
fn search_lower_cp<T, V>(cps: &[Key<T, V>], t: T) -> Option<usize> let mut i = 0;
where
T: PartialOrd,
{
let len = cps.len(); let len = cps.len();
if len < 2 { if len < 2 {
return None; return None;
} }
match cps.binary_search_by(|key| key.t.partial_cmp(&t).unwrap()) {
Err(i) if i >= len => None, loop {
Err(i) if i == 0 => None, let cp = &cps[i];
Err(i) => Some(i - 1), let cp1 = &cps[i+1];
Ok(i) if i == len - 1 => None,
Ok(i) => Some(i), 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; // found
}
} }
Some(i)
} }

View File

@ -1,43 +0,0 @@
#![cfg(feature = "cgmath")]
use cgmath as cg;
use splines::{Interpolation, Key, Spline};
#[test]
fn cgmath_vector_interpolation() {
use splines::Interpolate;
let start = cg::Vector2::new(0.0, 0.0);
let mid = cg::Vector2::new(0.5, 0.5);
let end = cg::Vector2::new(1.0, 1.0);
assert_eq!(Interpolate::lerp(0., start, end), start);
assert_eq!(Interpolate::lerp(1., start, end), end);
assert_eq!(Interpolate::lerp(0.5, start, end), mid);
}
#[test]
fn stroke_bezier_straight() {
use float_cmp::approx_eq;
let keys = vec![
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!(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.));
}

View File

@ -1,4 +1,7 @@
use splines::{spline::SampledWithKey, Interpolation, Key, Spline}; use splines::{Interpolation, Key, Spline};
#[cfg(feature = "impl-cgmath")] use cgmath as cg;
#[cfg(feature = "impl-nalgebra")] use nalgebra as na;
#[test] #[test]
fn step_interpolation_f32() { fn step_interpolation_f32() {
@ -13,14 +16,6 @@ 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(SampledWithKey { value: 10., key: 0 })
);
assert_eq!(
spline.clamped_sample_with_key(1.),
Some(SampledWithKey { value: 10., key: 1 })
);
} }
#[test] #[test]
@ -36,14 +31,6 @@ 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(SampledWithKey { value: 10., key: 0 })
);
assert_eq!(
spline.clamped_sample_with_key(1.),
Some(SampledWithKey { value: 10., key: 1 })
);
} }
#[test] #[test]
@ -158,50 +145,30 @@ fn several_interpolations_several_keys() {
assert_eq!(spline.clamped_sample(11.), Some(4.)); assert_eq!(spline.clamped_sample(11.), Some(4.));
} }
#[cfg(feature = "impl-cgmath")]
#[test] #[test]
fn add_key_empty() { fn cgmath_vector_interpolation() {
let mut spline: Spline<f32, f32> = Spline::from_vec(vec![]); use splines::Interpolate;
spline.add(Key::new(0., 0., Interpolation::Linear));
assert_eq!(spline.keys(), &[Key::new(0., 0., Interpolation::Linear)]); let start = cg::Vector2::new(0.0, 0.0);
let mid = cg::Vector2::new(0.5, 0.5);
let end = cg::Vector2::new(1.0, 1.0);
assert_eq!(Interpolate::lerp(start, end, 0.0), start);
assert_eq!(Interpolate::lerp(start, end, 1.0), end);
assert_eq!(Interpolate::lerp(start, end, 0.5), mid);
} }
#[cfg(feature = "impl-nalgebra")]
#[test] #[test]
fn add_key() { fn nalgebra_vector_interpolation() {
let start = Key::new(0., 0., Interpolation::Step(0.5)); use splines::Interpolate;
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]); let start = na::Vector2::new(0.0, 0.0);
spline.add(new); let mid = na::Vector2::new(0.5, 0.5);
assert_eq!(spline.keys(), &[start, k1, k2, new, k3, k4, end]); let end = na::Vector2::new(1.0, 1.0);
}
assert_eq!(Interpolate::lerp(start, end, 0.0), start);
#[test] assert_eq!(Interpolate::lerp(start, end, 1.0), end);
fn remove_element_empty() { assert_eq!(Interpolate::lerp(start, end, 0.5), mid);
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);
} }

View File

@ -1,16 +0,0 @@
#![cfg(feature = "nalgebra")]
use nalgebra as na;
#[test]
fn nalgebra_vector_interpolation() {
use splines::Interpolate;
let start = na::Vector2::new(0.0, 0.0);
let mid = na::Vector2::new(0.5, 0.5);
let end = na::Vector2::new(1.0, 1.0);
assert_eq!(Interpolate::lerp(0., start, end), start);
assert_eq!(Interpolate::lerp(1., start, end), end);
assert_eq!(Interpolate::lerp(0.5, start, end), mid);
}