├── .gitignore ├── README.md ├── rustfmt.toml ├── examples ├── hello-world.rs └── serialization.rs ├── tests ├── nalgebra.rs ├── cgmath.rs └── integ.rs ├── .github ├── dependabot.yml └── workflows │ └── ci.yaml ├── src ├── glam.rs ├── cgmath.rs ├── nalgebra.rs ├── iter.rs ├── key.rs ├── interpolation.rs ├── lib.rs ├── interpolate.rs └── spline.rs ├── Cargo.toml ├── LICENSE └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **The project moved to [sourcehut](https://sr.ht/~hadronized/splines/).** 2 | 3 | Feel free to visit [issues](https://todo.sr.ht/~hadronized/splines) and the [mailing list](https://lists.sr.ht/~hadronized/splines). 4 | 5 | Thank you. 6 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | 3 | fn_params_layout = "Tall" 4 | force_explicit_abi = true 5 | hard_tabs = false 6 | max_width = 100 7 | merge_derives = true 8 | newline_style = "Unix" 9 | remove_nested_parens = true 10 | reorder_imports = true 11 | reorder_modules = true 12 | tab_spaces = 2 13 | use_field_init_shorthand = true 14 | use_small_heuristics = "Default" 15 | use_try_shorthand = true 16 | -------------------------------------------------------------------------------- /examples/hello-world.rs: -------------------------------------------------------------------------------- 1 | extern crate splines; 2 | 3 | use splines::{Interpolation, Key, Spline}; 4 | 5 | fn main() { 6 | let keys = vec![ 7 | Key::new(0., 0., Interpolation::default()), 8 | Key::new(5., 1., Interpolation::default()), 9 | ]; 10 | let spline = Spline::from_vec(keys); 11 | 12 | println!("value at 0: {:?}", spline.clamped_sample(0.)); 13 | println!("value at 3: {:?}", spline.clamped_sample(3.)); 14 | } 15 | -------------------------------------------------------------------------------- /tests/nalgebra.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "nalgebra")] 2 | 3 | use nalgebra as na; 4 | 5 | #[test] 6 | fn nalgebra_vector_interpolation() { 7 | use splines::Interpolate; 8 | 9 | let start = na::Vector2::new(0.0, 0.0); 10 | let mid = na::Vector2::new(0.5, 0.5); 11 | let end = na::Vector2::new(1.0, 1.0); 12 | 13 | assert_eq!(Interpolate::lerp(0., start, end), start); 14 | assert_eq!(Interpolate::lerp(1., start, end), end); 15 | assert_eq!(Interpolate::lerp(0.5, start, end), mid); 16 | } 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/." 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | target-branch: master 10 | reviewers: 11 | - hadronized 12 | assignees: 13 | - hadronized 14 | labels: 15 | - dependency-update 16 | ignore: 17 | - dependency-name: glam 18 | versions: 19 | - 0.13.0 20 | - dependency-name: nalgebra 21 | versions: 22 | - 0.25.0 23 | - dependency-name: cgmath 24 | versions: 25 | - 0.18.0 26 | -------------------------------------------------------------------------------- /src/glam.rs: -------------------------------------------------------------------------------- 1 | use crate::impl_Interpolate; 2 | use glam::{DQuat, DVec2, DVec3, DVec4, Quat, Vec2, Vec3, Vec3A, Vec4}; 3 | 4 | impl_Interpolate!(f32, Vec2, std::f32::consts::PI); 5 | impl_Interpolate!(f32, Vec3, std::f32::consts::PI); 6 | impl_Interpolate!(f32, Vec3A, std::f32::consts::PI); 7 | impl_Interpolate!(f32, Vec4, std::f32::consts::PI); 8 | impl_Interpolate!(f32, Quat, std::f32::consts::PI); 9 | 10 | impl_Interpolate!(f64, DVec2, std::f64::consts::PI); 11 | impl_Interpolate!(f64, DVec3, std::f64::consts::PI); 12 | impl_Interpolate!(f64, DVec4, std::f64::consts::PI); 13 | impl_Interpolate!(f64, DQuat, std::f64::consts::PI); 14 | -------------------------------------------------------------------------------- /examples/serialization.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_json; 3 | extern crate splines; 4 | 5 | use serde_json::from_value; 6 | use splines::Spline; 7 | 8 | fn main() { 9 | let value = json! { 10 | [ 11 | { 12 | "t": 0, 13 | "interpolation": "linear", 14 | "value": 0 15 | }, 16 | { 17 | "t": 1, 18 | "interpolation": { "step": 0.5 }, 19 | "value": 1 20 | }, 21 | { 22 | "t": 5, 23 | "interpolation": "cosine", 24 | "value": 10 25 | }, 26 | ] 27 | }; 28 | 29 | let spline = from_value::>(value); 30 | println!("{:?}", spline); 31 | } 32 | -------------------------------------------------------------------------------- /src/cgmath.rs: -------------------------------------------------------------------------------- 1 | use crate::impl_Interpolate; 2 | 3 | use cgmath::{Quaternion, Vector1, Vector2, Vector3, Vector4}; 4 | 5 | impl_Interpolate!(f32, Vector1, std::f32::consts::PI); 6 | impl_Interpolate!(f32, Vector2, std::f32::consts::PI); 7 | impl_Interpolate!(f32, Vector3, std::f32::consts::PI); 8 | impl_Interpolate!(f32, Vector4, std::f32::consts::PI); 9 | impl_Interpolate!(f32, Quaternion, std::f32::consts::PI); 10 | 11 | impl_Interpolate!(f64, Vector1, std::f64::consts::PI); 12 | impl_Interpolate!(f64, Vector2, std::f64::consts::PI); 13 | impl_Interpolate!(f64, Vector3, std::f64::consts::PI); 14 | impl_Interpolate!(f64, Vector4, std::f64::consts::PI); 15 | impl_Interpolate!(f64, Quaternion, std::f64::consts::PI); 16 | -------------------------------------------------------------------------------- /src/nalgebra.rs: -------------------------------------------------------------------------------- 1 | use crate::impl_Interpolate; 2 | use nalgebra::{Quaternion, Vector1, Vector2, Vector3, Vector4, Vector5, Vector6}; 3 | 4 | impl_Interpolate!(f32, Vector1, std::f32::consts::PI); 5 | impl_Interpolate!(f32, Vector2, std::f32::consts::PI); 6 | impl_Interpolate!(f32, Vector3, std::f32::consts::PI); 7 | impl_Interpolate!(f32, Vector4, std::f32::consts::PI); 8 | impl_Interpolate!(f32, Vector5, std::f32::consts::PI); 9 | impl_Interpolate!(f32, Vector6, std::f32::consts::PI); 10 | impl_Interpolate!(f32, Quaternion, std::f32::consts::PI); 11 | 12 | impl_Interpolate!(f64, Vector1, std::f64::consts::PI); 13 | impl_Interpolate!(f64, Vector2, std::f64::consts::PI); 14 | impl_Interpolate!(f64, Vector3, std::f64::consts::PI); 15 | impl_Interpolate!(f64, Vector4, std::f64::consts::PI); 16 | impl_Interpolate!(f64, Vector5, std::f64::consts::PI); 17 | impl_Interpolate!(f64, Vector6, std::f64::consts::PI); 18 | impl_Interpolate!(f64, Quaternion, std::f64::consts::PI); 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build-linux: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v1 9 | - name: Test 10 | run: cargo test --verbose --all-features 11 | 12 | build-windows: 13 | runs-on: windows-latest 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Test 17 | run: cargo test --verbose --all-features 18 | 19 | build-macosx: 20 | runs-on: macOS-latest 21 | steps: 22 | - uses: actions/checkout@v1 23 | - name: Test 24 | run: cargo test --verbose --all-features 25 | 26 | quality: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v1 30 | - name: Install dependencies 31 | run: | 32 | cargo install --force cargo-sync-readme 33 | rustup component add rustfmt 34 | - name: cargo sync-readme 35 | run: | 36 | cargo sync-readme -c 37 | - name: rustfmt 38 | run: cargo fmt -- --check 39 | -------------------------------------------------------------------------------- /src/iter.rs: -------------------------------------------------------------------------------- 1 | //! Spline [`Iterator`], in a nutshell. 2 | //! 3 | //! You can iterate over a [`Spline`]’s keys with the [`IntoIterator`] trait on 4 | //! `&Spline`. This gives you iterated [`Key`] keys. 5 | //! 6 | //! [`Spline`]: crate::spline::Spline 7 | //! [`Key`]: crate::key::Key 8 | 9 | use crate::{Key, Spline}; 10 | 11 | /// Iterator over spline keys. 12 | /// 13 | /// This iterator type is guaranteed to iterate over sorted keys. 14 | pub struct Iter<'a, T, V> 15 | where 16 | T: 'a, 17 | V: 'a, 18 | { 19 | spline: &'a Spline, 20 | i: usize, 21 | } 22 | 23 | impl<'a, T, V> Iterator for Iter<'a, T, V> { 24 | type Item = &'a Key; 25 | 26 | fn next(&mut self) -> Option { 27 | let r = self.spline.0.get(self.i); 28 | 29 | if let Some(_) = r { 30 | self.i += 1; 31 | } 32 | 33 | r 34 | } 35 | } 36 | 37 | impl<'a, T, V> IntoIterator for &'a Spline { 38 | type Item = &'a Key; 39 | type IntoIter = Iter<'a, T, V>; 40 | 41 | fn into_iter(self) -> Self::IntoIter { 42 | Iter { spline: self, i: 0 } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "splines" 3 | version = "4.4.0" 4 | license = "BSD-3-Clause" 5 | authors = ["Dimitri Sabadie "] 6 | description = "Spline interpolation made easy" 7 | keywords = ["spline", "interpolation"] 8 | categories = ["science"] 9 | homepage = "https://github.com/hadronized/splines" 10 | repository = "https://github.com/hadronized/splines" 11 | documentation = "https://docs.rs/splines" 12 | readme = "README.md" 13 | 14 | edition = "2021" 15 | 16 | [features] 17 | default = ["std"] 18 | impl-cgmath = ["cgmath"] 19 | impl-glam = ["glam"] 20 | impl-nalgebra = ["nalgebra"] 21 | serialization = ["serde"] 22 | std = [] 23 | 24 | [dependencies] 25 | cgmath = { version = ">=0.17, <0.19", optional = true } 26 | glam = { version = ">=0.10, <0.30", optional = true } 27 | nalgebra = { version = ">=0.21, <0.34", optional = true } 28 | serde = { version = "1", features = ["derive"], optional = true } 29 | 30 | [dev-dependencies] 31 | float-cmp = ">=0.6, < 0.11" 32 | serde_json = "1" 33 | 34 | [package.metadata.docs.rs] 35 | features = ["std", "cgmath", "glam", "nalgebra", "serde"] 36 | 37 | [[example]] 38 | name = "hello-world" 39 | 40 | [[example]] 41 | name = "serialization" 42 | required-features = ["serde"] 43 | -------------------------------------------------------------------------------- /tests/cgmath.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "cgmath")] 2 | 3 | use cgmath as cg; 4 | use splines::{Interpolation, Key, Spline}; 5 | 6 | #[test] 7 | fn cgmath_vector_interpolation() { 8 | use splines::Interpolate; 9 | 10 | let start = cg::Vector2::new(0.0, 0.0); 11 | let mid = cg::Vector2::new(0.5, 0.5); 12 | let end = cg::Vector2::new(1.0, 1.0); 13 | 14 | assert_eq!(Interpolate::lerp(0., start, end), start); 15 | assert_eq!(Interpolate::lerp(1., start, end), end); 16 | assert_eq!(Interpolate::lerp(0.5, start, end), mid); 17 | } 18 | 19 | #[test] 20 | fn stroke_bezier_straight() { 21 | use float_cmp::approx_eq; 22 | 23 | let keys = vec![ 24 | Key::new( 25 | 0.0, 26 | cg::Vector2::new(0., 1.), 27 | Interpolation::StrokeBezier(cg::Vector2::new(0., 1.), cg::Vector2::new(0., 1.)), 28 | ), 29 | Key::new( 30 | 5.0, 31 | cg::Vector2::new(5., 1.), 32 | Interpolation::StrokeBezier(cg::Vector2::new(5., 1.), cg::Vector2::new(5., 1.)), 33 | ), 34 | ]; 35 | let spline = Spline::from_vec(keys); 36 | 37 | assert!(approx_eq!(f32, spline.clamped_sample(0.0).unwrap().y, 1.)); 38 | assert!(approx_eq!(f32, spline.clamped_sample(1.0).unwrap().y, 1.)); 39 | assert!(approx_eq!(f32, spline.clamped_sample(2.0).unwrap().y, 1.)); 40 | assert!(approx_eq!(f32, spline.clamped_sample(3.0).unwrap().y, 1.)); 41 | assert!(approx_eq!(f32, spline.clamped_sample(4.0).unwrap().y, 1.)); 42 | assert!(approx_eq!(f32, spline.clamped_sample(5.0).unwrap().y, 1.)); 43 | } 44 | -------------------------------------------------------------------------------- /src/key.rs: -------------------------------------------------------------------------------- 1 | //! Spline control points. 2 | //! 3 | //! A control point associates to a “sampling value” (a.k.a. time) a carried value that can be 4 | //! interpolated along the curve made by the control points. 5 | //! 6 | //! Splines constructed with this crate have the property that it’s possible to change the 7 | //! interpolation mode on a key-based way, allowing you to implement and encode complex curves. 8 | 9 | use crate::interpolation::Interpolation; 10 | #[cfg(any(feature = "serialization", feature = "serde"))] 11 | use serde::{Deserialize, Serialize}; 12 | 13 | /// A spline control point. 14 | /// 15 | /// This type associates a value at a given interpolation parameter value. It also contains an 16 | /// interpolation mode used to determine how to interpolate values on the segment defined by this 17 | /// key and the next one – if existing. Have a look at [`Interpolation`] for further details. 18 | /// 19 | /// [`Interpolation`]: crate::interpolation::Interpolation 20 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 21 | #[cfg_attr( 22 | any(feature = "serialization", feature = "serde"), 23 | derive(Deserialize, Serialize), 24 | serde(rename_all = "snake_case") 25 | )] 26 | pub struct Key { 27 | /// Interpolation parameter at which the [`Key`] should be reached. 28 | pub t: T, 29 | /// Carried value. 30 | pub value: V, 31 | /// Interpolation mode. 32 | pub interpolation: Interpolation, 33 | } 34 | 35 | impl Key { 36 | /// Create a new key. 37 | pub fn new(t: T, value: V, interpolation: Interpolation) -> Self { 38 | Key { 39 | t, 40 | value, 41 | interpolation, 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Dimitri Sabadie 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Dimitri Sabadie nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /src/interpolation.rs: -------------------------------------------------------------------------------- 1 | //! Available interpolation modes. 2 | 3 | #[cfg(any(feature = "serialization", feature = "serde"))] 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// Available kind of interpolations. 7 | /// 8 | /// Feel free to visit each variant for more documentation. 9 | #[non_exhaustive] 10 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 11 | #[cfg_attr( 12 | any(feature = "serialization", feature = "serde"), 13 | derive(Deserialize, Serialize), 14 | serde(rename_all = "snake_case") 15 | )] 16 | pub enum Interpolation { 17 | /// Hold a [`Key`] until the sampling value passes the normalized step threshold, in which 18 | /// case the next key is used. 19 | /// 20 | /// > Note: if you set the threshold to `0.5`, the first key will be used until half the time 21 | /// > between the two keys; the second key will be in used afterwards. If you set it to `1.0`, the 22 | /// > first key will be kept until the next key. Set it to `0.` and the first key will never be 23 | /// > used. 24 | /// 25 | /// [`Key`]: crate::key::Key 26 | Step(T), 27 | 28 | /// Linear interpolation between a key and the next one. 29 | Linear, 30 | 31 | /// Cosine interpolation between a key and the next one. 32 | Cosine, 33 | 34 | /// Catmull-Rom interpolation, performing a cubic Hermite interpolation using four keys. 35 | CatmullRom, 36 | 37 | /// Bézier interpolation. 38 | /// 39 | /// A control point that uses such an interpolation is associated with an extra point. The segmant 40 | /// connecting both is called the _tangent_ of this point. The part of the spline defined between 41 | /// this control point and the next one will be interpolated across with Bézier interpolation. Two 42 | /// cases are possible: 43 | /// 44 | /// - The next control point also has a Bézier interpolation mode. In this case, its tangent is 45 | /// used for the interpolation process. This is called _cubic Bézier interpolation_ and it 46 | /// kicks ass. 47 | /// - The next control point doesn’t have a Bézier interpolation mode set. In this case, the 48 | /// tangent used for the next control point is defined as the segment connecting that control 49 | /// point and the current control point’s associated point. This is called _quadratic Bézer 50 | /// interpolation_ and it kicks ass too, but a bit less than cubic. 51 | Bezier(V), 52 | 53 | /// A special Bézier interpolation using an _input tangent_ and an _output tangent_. 54 | /// 55 | /// With this kind of interpolation, a control point has an input tangent, which has the same role 56 | /// as the one defined by [`Interpolation::Bezier`], and an output tangent, which has the same 57 | /// role defined by the next key’s [`Interpolation::Bezier`] if present, normally. 58 | /// 59 | /// What it means is that instead of setting the output tangent as the next key’s Bézier tangent, 60 | /// this interpolation mode allows you to manually set the output tangent. That will yield more 61 | /// control on the tangents but might generate discontinuities. Use with care. 62 | /// 63 | /// Stroke Bézier interpolation is always a cubic Bézier interpolation by default. 64 | StrokeBezier(V, V), 65 | } 66 | 67 | impl Default for Interpolation { 68 | /// [`Interpolation::Linear`] is the default. 69 | fn default() -> Self { 70 | Interpolation::Linear 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Spline interpolation made easy. 2 | //! 3 | //! This crate exposes splines for which each sections can be interpolated independently of each 4 | //! other – i.e. it’s possible to interpolate with a linear interpolator on one section and then 5 | //! switch to a cubic Hermite interpolator for the next section. 6 | //! 7 | //! Most of the crate consists of three types: 8 | //! 9 | //! - [`Key`], which represents the control points by which the spline must pass. 10 | //! - [`Interpolation`], the type of possible interpolation for each segment. 11 | //! - [`Spline`], a spline from which you can *sample* points by interpolation. 12 | //! 13 | //! When adding control points, you add new sections. Two control points define a section – i.e. 14 | //! it’s not possible to define a spline without at least two control points. Every time you add a 15 | //! new control point, a new section is created. Each section is assigned an interpolation mode that 16 | //! is picked from its lower control point. 17 | //! 18 | //! # Quickly create splines 19 | //! 20 | //! ``` 21 | //! use splines::{Interpolation, Key, Spline}; 22 | //! 23 | //! let start = Key::new(0., 0., Interpolation::Linear); 24 | //! let end = Key::new(1., 10., Interpolation::default()); 25 | //! let spline = Spline::from_vec(vec![start, end]); 26 | //! ``` 27 | //! 28 | //! You will notice that we used `Interpolation::Linear` for the first key. The first key `start`’s 29 | //! interpolation will be used for the whole segment defined by those two keys. The `end`’s 30 | //! interpolation won’t be used. You can in theory use any [`Interpolation`] you want for the last 31 | //! key. We use the default one because we don’t care. 32 | //! 33 | //! # Interpolate values 34 | //! 35 | //! The whole purpose of splines is to interpolate discrete values to yield continuous ones. This is 36 | //! usually done with the [`Spline::sample`] method. This method expects the sampling parameter 37 | //! (often, this will be the time of your simulation) as argument and will yield an interpolated 38 | //! value. 39 | //! 40 | //! If you try to sample in out-of-bounds sampling parameter, you’ll get no value. 41 | //! 42 | //! ``` 43 | //! # use splines::{Interpolation, Key, Spline}; 44 | //! # let start = Key::new(0., 0., Interpolation::Linear); 45 | //! # let end = Key::new(1., 10., Interpolation::Linear); 46 | //! # let spline = Spline::from_vec(vec![start, end]); 47 | //! assert_eq!(spline.sample(0.), Some(0.)); 48 | //! assert_eq!(spline.clamped_sample(1.), Some(10.)); 49 | //! assert_eq!(spline.sample(1.1), None); 50 | //! ``` 51 | //! 52 | //! It’s possible that you want to get a value even if you’re out-of-bounds. This is especially 53 | //! important for simulations / animations. Feel free to use the `Spline::clamped_interpolation` for 54 | //! that purpose. 55 | //! 56 | //! ``` 57 | //! # use splines::{Interpolation, Key, Spline}; 58 | //! # let start = Key::new(0., 0., Interpolation::Linear); 59 | //! # let end = Key::new(1., 10., Interpolation::Linear); 60 | //! # let spline = Spline::from_vec(vec![start, end]); 61 | //! assert_eq!(spline.clamped_sample(-0.9), Some(0.)); // clamped to the first key 62 | //! assert_eq!(spline.clamped_sample(1.1), Some(10.)); // clamped to the last key 63 | //! ``` 64 | //! 65 | //! # Polymorphic sampling types 66 | //! 67 | //! [`Spline`] curves are parametered both by the carried value (being interpolated) but also the 68 | //! sampling type. It’s very typical to use `f32` or `f64` but really, you can in theory use any 69 | //! kind of type; that type must, however, implement a contract defined by a set of traits to 70 | //! implement. See [the documentation of this module](crate::interpolate) for further details. 71 | //! 72 | //! # Features and customization 73 | //! 74 | //! This crate was written with features baked in and hidden behind feature-gates. The idea is that 75 | //! the default configuration (i.e. you just add `"splines = …"` to your `Cargo.toml`) will always 76 | //! give you the minimal, core and raw concepts of what splines, keys / knots and interpolation 77 | //! modes are. However, you might want more. Instead of letting other people do the extra work to 78 | //! add implementations for very famous and useful traits – and do it in less efficient way, because 79 | //! they wouldn’t have access to the internals of this crate, it’s possible to enable features in an 80 | //! ad hoc way. 81 | //! 82 | //! This mechanism is not final and this is currently an experiment to see how people like it or 83 | //! not. It’s especially important to see how it copes with the documentation. 84 | //! 85 | //! So here’s a list of currently supported features and how to enable them: 86 | //! 87 | //! - **Serde.** 88 | //! - This feature implements both the `Serialize` and `Deserialize` traits from `serde` for all 89 | //! types exported by this crate. 90 | //! - Enable with the `"serde"` feature. 91 | //! - **[cgmath](https://crates.io/crates/cgmath) implementors.** 92 | //! - Adds some useful implementations of `Interpolate` for some cgmath types. 93 | //! - Enable with the `"cgmath"` feature. 94 | //! - **[glam](https://crates.io/crates/glam) implementors.** 95 | //! - Adds some useful implementations of `Interpolate` for some glam types. 96 | //! - Enable with the `"glam"` feature. 97 | //! - **[nalgebra](https://crates.io/crates/nalgebra) implementors.** 98 | //! - Adds some useful implementations of `Interpolate` for some nalgebra types. 99 | //! - Enable with the `"nalgebra"` feature. 100 | //! - **Standard library / no standard library.** 101 | //! - It’s possible to compile against the standard library or go on your own without it. 102 | //! - Compiling with the standard library is enabled by default. 103 | //! - Use `default-features = []` in your `Cargo.toml` to disable. 104 | //! - Enable explicitly with the `"std"` feature. 105 | //! 106 | //! [`Interpolation`]: crate::interpolation::Interpolation 107 | 108 | #![cfg_attr(not(feature = "std"), no_std)] 109 | #![cfg_attr(not(feature = "std"), feature(alloc))] 110 | #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] 111 | #![cfg_attr( 112 | any( 113 | feature = "impl-cgmath", 114 | feature = "impl-glam", 115 | feature = "impl-nalgebra" 116 | ), 117 | deprecated( 118 | since = "4.2.0", 119 | note = "you are using an impl-* feature gate; please switch to * (e.g. impl-cgmath becomes cgmath)" 120 | ) 121 | )] 122 | 123 | #[cfg(not(feature = "std"))] 124 | extern crate alloc; 125 | 126 | #[cfg(any(feature = "impl-cgmath", feature = "cgmath"))] 127 | mod cgmath; 128 | #[cfg(any(feature = "impl-glam", feature = "glam"))] 129 | mod glam; 130 | pub mod interpolate; 131 | pub mod interpolation; 132 | pub mod iter; 133 | pub mod key; 134 | #[cfg(any(feature = "impl-nalgebra", feature = "nalgebra"))] 135 | mod nalgebra; 136 | pub mod spline; 137 | 138 | pub use crate::interpolate::Interpolate; 139 | pub use crate::interpolation::Interpolation; 140 | pub use crate::key::Key; 141 | pub use crate::spline::Spline; 142 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 4.4.0 2 | 3 | > Oct 29, 2024 4 | 5 | - fix: f64 glam interpolation support [d9a5b30](https://github.com/hadronized/splines/commit/d9a5b30) 6 | - Dependencies versions bumps. 7 | 8 | # ~4.3.2~ yanked 9 | 10 | > Feb 29, 2024 11 | 12 | > **Yank reason**: this version was yanked because of a bug introduced during the `VecDeque` adoption; indeed, mutating 13 | > the keys with `remove` or `add` can make the underlying slices unsorted again, causing contiguous assumptions wrong. 14 | > The next patch version should fix this. Sorry for the issue. 15 | 16 | - Switch from holding keys in `Vec` into `VecDeque`. [1b4fe44](https://github.com/hadronized/splines/commit/1b4fe44) 17 | 18 | # 4.3.1 19 | 20 | > Nov 22, 2023 21 | 22 | - Add `Default` implementation for `Spline`. [c6ba847](https://github.com/hadronized/splines/commit/c6ba847) 23 | 24 | # 4.3 25 | 26 | > Sep 23, 2023 27 | 28 | - Add support for `glam-0.23` and `glam-0.24`. [cdc48a4](https://github.com/hadronized/splines/commit/cdc48a4) 29 | - Add `Spline::clear` to clear a spline keys without deallocating its internal storage. [eca09f1](https://github.com/hadronized/splines/commit/eca09f1) 30 | 31 | # 4.2 32 | 33 | > Feb 1, 2023 34 | 35 | - Add support for `glam-0.22`. 36 | - Add support for `nalgebra-0.32`. 37 | - Add deprecation lints for `impl-*` feature gates. Those shouldn’t be used anymore and the `*` variant should be 38 | preferred. For instance, if you used `impl-cgmath`, you should just use the `cgmath` feature gate now. 39 | 40 | # 4.1.1 41 | 42 | > Jul 27, 2022 43 | 44 | - Internal enhancement of sampling splines by looking for control points. That brings the lookup from _O(N)_ to 45 | _O(log(N))_. That is super embarassing because it should have been the default from the very first commit. Sorry 46 | about that. 47 | - Fix hermite cubic interpolation. 48 | - Add support for `glam-0.21`. 49 | - Add support for `nalgebra-0.31`. 50 | 51 | # 4.1 52 | 53 | > Mar 28, 2022 54 | 55 | - Support for edition 2021. 56 | - Bump `float-cmp` dependency. 57 | - Bump `glam` dependency. 58 | - Bump `nalgebra` dependency. 59 | - Simplify the CI. 60 | 61 | # 4.0.3 62 | 63 | > Jul 11, 2021 64 | 65 | - Add more implementors for `Interpolate`. 66 | 67 | # 4.0.2 68 | 69 | > Jul 11, 2021 70 | 71 | - **Yanked.** 72 | 73 | # 4.0.1 74 | 75 | > Jul 11, 2021 76 | 77 | - Add support up to `glam-0.17`. 78 | - Add support up to `nalgebra-0.27`. 79 | - Replace the name of some feature gates: 80 | - `serialization` becomes `serde`. 81 | - `impl-*` becomes `*`. 82 | - The previous feature gates are kept around to prevent a breaking change but will eventually be removed in the next 83 | major update. 84 | 85 | # 4.0 86 | 87 | > Mar 05, 2021 88 | 89 | ## Major changes 90 | 91 | - Switch the `Interpolation` enum to `#[non_exhaustive]` to allow adding more interpolation modes (if any) in the 92 | future. 93 | - Introduce `SampledWithKey`, which is a more elegant / typed way to access a sample along with its associated key 94 | index. 95 | - Refactor the `Interpolate` trait and add the `Interpolator` trait. 96 | 97 | ## Patch changes 98 | 99 | - Highly simplify the various implementors (`cgmath`, `nalgebra` and `glam`) so that maintenance is easy. 100 | - Expose the `impl_Interpolate` macro, allowing to implement the API all at once if a type implements the various 101 | `std::ops:*` traits. Since most of the crates do, this macro makes it really easy to add support for a crate. 102 | - Drop `simba` as a direct dependency. 103 | - Drop `num-traits` as a direct dependency. 104 | 105 | # 3.5.4 106 | 107 | > Feb 27, 2021 108 | 109 | - Support of `cgmath-0.18`. 110 | 111 | # 3.5.3 112 | 113 | > Jan 16, 2021 114 | 115 | - Resynchronize and fix links in the README (fix in `cargo sync-readme`). 116 | 117 | # 3.5.2 118 | 119 | > Fri Jan 01, 2021 120 | 121 | - Support of `nalgebra-0.24`. 122 | 123 | # 3.5.1 124 | 125 | > Dec 5th, 2020 126 | 127 | - Support of `glam-0.11`. 128 | 129 | # 3.5 130 | 131 | > Nov 23rd, 2020 132 | 133 | - Add support for [glam](https://crates.io/crates/glam) via the `"impl-glam"` feature gate. 134 | - Support of `nalgebra-0.23`. 135 | 136 | # 3.4.2 137 | 138 | > Oct 24th, 2020 139 | 140 | - Support of `simba-0.3`. 141 | 142 | # 3.4.1 143 | 144 | > Sep 5th, 2020 145 | 146 | - Support of `simba-0.2`. 147 | - Support of `nalgebra-0.22`. 148 | 149 | # 3.4 150 | 151 | > Thu May 21st 2020 152 | 153 | - Add support for `float-cmp-0.7` and `float-cmp-0.8`. Because this uses a SemVer range, if you 154 | already have a `Cargo.lock`, don’t forget to update `splines` with `cargo update --aggressive`. 155 | 156 | # 3.3 157 | 158 | > Thu Apr 10th 2020 159 | 160 | - Add support for `nalgebra-0.21`. 161 | 162 | # 3.2 163 | 164 | > Thu Mar 19th 2020 165 | 166 | - Add support for `nalgebra-0.20`. 167 | - Add support for `float-cmp-0.6`. 168 | 169 | # 3.1 170 | 171 | > Sat Jan 26th 2020 172 | 173 | - Add support for `nalgebra-0.19`. 174 | 175 | # 3.0 176 | 177 | > Tue Oct 22th 2019 178 | 179 | ## Major changes 180 | 181 | - Sampling now requires the value of the key to be `Linear` for `Interpolate`. That is needed 182 | to ease some interpolation mode (especially Bézier). 183 | 184 | ## Patch changes 185 | 186 | - Fix Bézier interpolation when the next key is Bézier too. 187 | 188 | # 2.2 189 | 190 | > Mon Oct 17th 2019 191 | 192 | - Add `Interpolation::StrokeBezier`. 193 | 194 | # 2.1.1 195 | 196 | > Mon Oct 17th 2019 197 | 198 | - Licensing support in the crate. 199 | 200 | # 2.1 201 | 202 | > Mon Sep 30th 2019 203 | 204 | - Add `Spline::sample_with_key` and `Spline::clamped_sample_with_key`. Those methods allow one to 205 | perform the regular `Spline::sample` and `Spline::clamped_sample` but also retreive the base 206 | key that was used to perform the interpolation. The key can be inspected to get the base time, 207 | interpolation, etc. The next key is also returned, if present. 208 | 209 | # 2.0.1 210 | 211 | > Tue Sep 24th 2019 212 | 213 | - Fix the cubic Bézier curve interpolation. The “output” tangent is now taken by mirroring the 214 | next key’s tangent around its control point. 215 | 216 | # 2.0 217 | 218 | > Mon Sep 23rd 2019 219 | 220 | ## Major changes 221 | 222 | - Add support for [Bézier curves](https://en.wikipedia.org/wiki/B%C3%A9zier_curve). 223 | - Because of Bézier curves, the `Interpolation` type now has one more type variable to know how we 224 | should interpolate with Bézier. 225 | 226 | ## Minor changes 227 | 228 | - Add `Spline::get`, `Spline::get_mut` and `Spline::replace`. 229 | 230 | # 1.0 231 | 232 | > Sun Sep 22nd 2019 233 | 234 | ## Major changes 235 | 236 | - Make `Spline::clamped_sample` failible via `Option` instead of panicking. 237 | - Add support for polymorphic sampling type. 238 | 239 | ## Minor changes 240 | 241 | - Add the `std` feature (and hence support for `no_std`). 242 | - Add `impl-nalgebra` feature. 243 | - Add `impl-cgmath` feature. 244 | - Add support for adding keys to splines. 245 | - Add support for removing keys from splines. 246 | 247 | ## Patch changes 248 | 249 | - Migrate to Rust 2018. 250 | - Documentation typo fixes. 251 | 252 | # 0.2.3 253 | 254 | > Sat 13th October 2018 255 | 256 | - Add the `"impl-nalgebra"` feature gate. It gives access to some implementors for the `nalgebra` 257 | crate. 258 | - Enhance the documentation. 259 | 260 | # 0.2.2 261 | 262 | > Sun 30th September 2018 263 | 264 | - Bump version numbers (`splines-0.2`) in examples. 265 | - Fix several typos in the documentation. 266 | 267 | # 0.2.1 268 | 269 | > Thu 20th September 2018 270 | 271 | - Enhance the features documentation. 272 | 273 | # 0.2 274 | 275 | > Thu 6th September 2018 276 | 277 | - Add the `"std"` feature gate, that can be used to compile with the standard library. 278 | - Add the `"impl-cgmath"` feature gate in order to make optional, if wanted, the `cgmath` 279 | dependency. 280 | - Enhance the documentation. 281 | 282 | # 0.1.1 283 | 284 | > Wed 8th August 2018 285 | 286 | - Add a feature gate, `"serialization"`, that can be used to automatically derive `Serialize` and 287 | `Deserialize` from the [serde](https://crates.io/crates/serde) crate. 288 | - Enhance the documentation. 289 | 290 | # 0.1 291 | 292 | > Sunday 5th August 2018 293 | 294 | - Initial revision. 295 | -------------------------------------------------------------------------------- /tests/integ.rs: -------------------------------------------------------------------------------- 1 | use splines::{spline::SampledWithKey, Interpolation, Key, Spline}; 2 | 3 | #[test] 4 | fn step_interpolation_f32() { 5 | let start = Key::new(0., 0., Interpolation::Step(0.)); 6 | let end = Key::new(1., 10., Interpolation::default()); 7 | let spline = Spline::::from_vec(vec![start, end]); 8 | 9 | assert_eq!(spline.sample(0.), Some(10.)); 10 | assert_eq!(spline.sample(0.1), Some(10.)); 11 | assert_eq!(spline.sample(0.2), Some(10.)); 12 | assert_eq!(spline.sample(0.5), Some(10.)); 13 | assert_eq!(spline.sample(0.9), Some(10.)); 14 | assert_eq!(spline.sample(1.), None); 15 | assert_eq!(spline.clamped_sample(1.), Some(10.)); 16 | assert_eq!( 17 | spline.sample_with_key(0.2), 18 | Some(SampledWithKey { value: 10., key: 0 }) 19 | ); 20 | assert_eq!( 21 | spline.clamped_sample_with_key(1.), 22 | Some(SampledWithKey { value: 10., key: 1 }) 23 | ); 24 | } 25 | 26 | #[test] 27 | fn step_interpolation_f64() { 28 | let start = Key::new(0., 0., Interpolation::Step(0.)); 29 | let end = Key::new(1., 10., Interpolation::default()); 30 | let spline = Spline::::from_vec(vec![start, end]); 31 | 32 | assert_eq!(spline.sample(0.), Some(10.)); 33 | assert_eq!(spline.sample(0.1), Some(10.)); 34 | assert_eq!(spline.sample(0.2), Some(10.)); 35 | assert_eq!(spline.sample(0.5), Some(10.)); 36 | assert_eq!(spline.sample(0.9), Some(10.)); 37 | assert_eq!(spline.sample(1.), None); 38 | assert_eq!(spline.clamped_sample(1.), Some(10.)); 39 | assert_eq!( 40 | spline.sample_with_key(0.2), 41 | Some(SampledWithKey { value: 10., key: 0 }) 42 | ); 43 | assert_eq!( 44 | spline.clamped_sample_with_key(1.), 45 | Some(SampledWithKey { value: 10., key: 1 }) 46 | ); 47 | } 48 | 49 | #[test] 50 | fn step_interpolation_0_5() { 51 | let start = Key::new(0., 0., Interpolation::Step(0.5)); 52 | let end = Key::new(1., 10., Interpolation::default()); 53 | let spline = Spline::from_vec(vec![start, end]); 54 | 55 | assert_eq!(spline.sample(0.), Some(0.)); 56 | assert_eq!(spline.sample(0.1), Some(0.)); 57 | assert_eq!(spline.sample(0.2), Some(0.)); 58 | assert_eq!(spline.sample(0.5), Some(10.)); 59 | assert_eq!(spline.sample(0.9), Some(10.)); 60 | assert_eq!(spline.sample(1.), None); 61 | assert_eq!(spline.clamped_sample(1.), Some(10.)); 62 | } 63 | 64 | #[test] 65 | fn step_interpolation_0_75() { 66 | let start = Key::new(0., 0., Interpolation::Step(0.75)); 67 | let end = Key::new(1., 10., Interpolation::default()); 68 | let spline = Spline::from_vec(vec![start, end]); 69 | 70 | assert_eq!(spline.sample(0.), Some(0.)); 71 | assert_eq!(spline.sample(0.1), Some(0.)); 72 | assert_eq!(spline.sample(0.2), Some(0.)); 73 | assert_eq!(spline.sample(0.5), Some(0.)); 74 | assert_eq!(spline.sample(0.9), Some(10.)); 75 | assert_eq!(spline.sample(1.), None); 76 | assert_eq!(spline.clamped_sample(1.), Some(10.)); 77 | } 78 | 79 | #[test] 80 | fn step_interpolation_1() { 81 | let start = Key::new(0., 0., Interpolation::Step(1.)); 82 | let end = Key::new(1., 10., Interpolation::default()); 83 | let spline = Spline::from_vec(vec![start, end]); 84 | 85 | assert_eq!(spline.sample(0.), Some(0.)); 86 | assert_eq!(spline.sample(0.1), Some(0.)); 87 | assert_eq!(spline.sample(0.2), Some(0.)); 88 | assert_eq!(spline.sample(0.5), Some(0.)); 89 | assert_eq!(spline.sample(0.9), Some(0.)); 90 | assert_eq!(spline.sample(1.), None); 91 | assert_eq!(spline.clamped_sample(1.), Some(10.)); 92 | } 93 | 94 | #[test] 95 | fn linear_interpolation() { 96 | let start = Key::new(0., 0., Interpolation::Linear); 97 | let end = Key::new(1., 10., Interpolation::default()); 98 | let spline = Spline::from_vec(vec![start, end]); 99 | 100 | assert_eq!(spline.sample(0.), Some(0.)); 101 | assert_eq!(spline.sample(0.1), Some(1.)); 102 | assert_eq!(spline.sample(0.2), Some(2.)); 103 | assert_eq!(spline.sample(0.5), Some(5.)); 104 | assert_eq!(spline.sample(0.9), Some(9.)); 105 | assert_eq!(spline.sample(1.), None); 106 | assert_eq!(spline.clamped_sample(1.), Some(10.)); 107 | } 108 | 109 | #[test] 110 | fn linear_interpolation_several_keys() { 111 | let start = Key::new(0., 0., Interpolation::Linear); 112 | let k1 = Key::new(1., 5., Interpolation::Linear); 113 | let k2 = Key::new(2., 0., Interpolation::Linear); 114 | let k3 = Key::new(3., 1., Interpolation::Linear); 115 | let k4 = Key::new(10., 2., Interpolation::Linear); 116 | let end = Key::new(11., 4., Interpolation::default()); 117 | let spline = Spline::from_vec(vec![start, k1, k2, k3, k4, end]); 118 | 119 | assert_eq!(spline.sample(0.), Some(0.)); 120 | assert_eq!(spline.sample(0.1), Some(0.5)); 121 | assert_eq!(spline.sample(0.2), Some(1.)); 122 | assert_eq!(spline.sample(0.5), Some(2.5)); 123 | assert_eq!(spline.sample(0.9), Some(4.5)); 124 | assert_eq!(spline.sample(1.), Some(5.)); 125 | assert_eq!(spline.sample(1.5), Some(2.5)); 126 | assert_eq!(spline.sample(2.), Some(0.)); 127 | assert_eq!(spline.sample(2.75), Some(0.75)); 128 | assert_eq!(spline.sample(3.), Some(1.)); 129 | assert_eq!(spline.sample(6.5), Some(1.5)); 130 | assert_eq!(spline.sample(10.), Some(2.)); 131 | assert_eq!(spline.clamped_sample(11.), Some(4.)); 132 | } 133 | 134 | #[test] 135 | fn several_interpolations_several_keys() { 136 | let start = Key::new(0., 0., Interpolation::Step(0.5)); 137 | let k1 = Key::new(1., 5., Interpolation::Linear); 138 | let k2 = Key::new(2., 0., Interpolation::Step(0.1)); 139 | let k3 = Key::new(3., 1., Interpolation::Linear); 140 | let k4 = Key::new(10., 2., Interpolation::Linear); 141 | let end = Key::new(11., 4., Interpolation::default()); 142 | let spline = Spline::from_vec(vec![start, k1, k2, k3, k4, end]); 143 | 144 | assert_eq!(spline.sample(0.), Some(0.)); 145 | assert_eq!(spline.sample(0.1), Some(0.)); 146 | assert_eq!(spline.sample(0.2), Some(0.)); 147 | assert_eq!(spline.sample(0.5), Some(5.)); 148 | assert_eq!(spline.sample(0.9), Some(5.)); 149 | assert_eq!(spline.sample(1.), Some(5.)); 150 | assert_eq!(spline.sample(1.5), Some(2.5)); 151 | assert_eq!(spline.sample(2.), Some(0.)); 152 | assert_eq!(spline.sample(2.05), Some(0.)); 153 | assert_eq!(spline.sample(2.099), Some(0.)); 154 | assert_eq!(spline.sample(2.75), Some(1.)); 155 | assert_eq!(spline.sample(3.), Some(1.)); 156 | assert_eq!(spline.sample(6.5), Some(1.5)); 157 | assert_eq!(spline.sample(10.), Some(2.)); 158 | assert_eq!(spline.clamped_sample(11.), Some(4.)); 159 | } 160 | 161 | #[test] 162 | fn add_key_empty() { 163 | let mut spline: Spline = Spline::from_vec(vec![]); 164 | spline.add(Key::new(0., 0., Interpolation::Linear)); 165 | 166 | assert_eq!(spline.keys(), &[Key::new(0., 0., Interpolation::Linear)]); 167 | } 168 | 169 | #[test] 170 | fn add_key() { 171 | let start = Key::new(0., 0., Interpolation::Step(0.5)); 172 | let k1 = Key::new(1., 5., Interpolation::Linear); 173 | let k2 = Key::new(2., 0., Interpolation::Step(0.1)); 174 | let k3 = Key::new(3., 1., Interpolation::Linear); 175 | let k4 = Key::new(10., 2., Interpolation::Linear); 176 | let end = Key::new(11., 4., Interpolation::default()); 177 | let new = Key::new(2.4, 40., Interpolation::Linear); 178 | let mut spline = Spline::from_vec(vec![start, k1, k2.clone(), k3, k4, end]); 179 | 180 | assert_eq!(spline.keys(), &[start, k1, k2, k3, k4, end]); 181 | spline.add(new); 182 | assert_eq!(spline.keys(), &[start, k1, k2, new, k3, k4, end]); 183 | } 184 | 185 | #[test] 186 | fn remove_element_empty() { 187 | let mut spline: Spline = Spline::from_vec(vec![]); 188 | let removed = spline.remove(0); 189 | 190 | assert_eq!(removed, None); 191 | assert!(spline.is_empty()); 192 | } 193 | 194 | #[test] 195 | fn remove_element() { 196 | let start = Key::new(0., 0., Interpolation::Step(0.5)); 197 | let k1 = Key::new(1., 5., Interpolation::Linear); 198 | let k2 = Key::new(2., 0., Interpolation::Step(0.1)); 199 | let k3 = Key::new(3., 1., Interpolation::Linear); 200 | let k4 = Key::new(10., 2., Interpolation::Linear); 201 | let end = Key::new(11., 4., Interpolation::default()); 202 | let mut spline = Spline::from_vec(vec![start, k1, k2.clone(), k3, k4, end]); 203 | let removed = spline.remove(2); 204 | 205 | assert_eq!(removed, Some(k2)); 206 | assert_eq!(spline.len(), 5); 207 | } 208 | -------------------------------------------------------------------------------- /src/interpolate.rs: -------------------------------------------------------------------------------- 1 | //! The [`Interpolate`] trait and associated symbols. 2 | //! 3 | //! The [`Interpolate`] trait is the central concept of the crate. It enables a spline to be 4 | //! sampled at by interpolating in between control points. 5 | //! 6 | //! In order for a type to be used in [`Spline`], some properties must be met about the `K` 7 | //! type must implementing several traits: 8 | //! 9 | //! - [`One`], giving a neutral element for the multiplication monoid. 10 | //! - [`Additive`], making the type additive (i.e. one can add or subtract with it). 11 | //! - [`Linear`], unlocking linear combinations, required for interpolating. 12 | //! - [`Trigo`], a trait giving *π* and *cosine*, required for e.g. cosine interpolation. 13 | //! 14 | //! Feel free to have a look at current implementors for further help. 15 | //! 16 | //! > *Why doesn’t this crate use [num-traits] instead of 17 | //! > defining its own traits?* 18 | //! 19 | //! The reason for this is quite simple: this crate provides a `no_std` support, which is not 20 | //! currently available easily with [num-traits]. Also, if something changes in [num-traits] with 21 | //! those traits, it would make this whole crate unstable. 22 | //! 23 | //! [`Interpolate`]: crate::interpolate::Interpolate 24 | //! [`Spline`]: crate::spline::Spline 25 | //! [`One`]: crate::interpolate::One 26 | //! [`Additive`]: crate::interpolate::Additive 27 | //! [`Linear`]: crate::interpolate::Linear 28 | //! [`Trigo`]: crate::interpolate::Trigo 29 | //! [num-traits]: https://crates.io/crates/num-traits 30 | 31 | #[cfg(not(feature = "std"))] 32 | use core::f32; 33 | #[cfg(not(feature = "std"))] 34 | use core::f64; 35 | #[cfg(not(feature = "std"))] 36 | use core::intrinsics::cosf32; 37 | #[cfg(not(feature = "std"))] 38 | use core::intrinsics::cosf64; 39 | #[cfg(not(feature = "std"))] 40 | use core::ops::{Add, Mul, Sub}; 41 | #[cfg(feature = "std")] 42 | use std::f32; 43 | #[cfg(feature = "std")] 44 | use std::f64; 45 | 46 | /// Types that can be used as interpolator in splines. 47 | /// 48 | /// An interpolator value is like the fabric on which control keys (and sampled values) live on. 49 | pub trait Interpolator: Sized + Copy + PartialOrd { 50 | /// Normalize the interpolator. 51 | fn normalize(self, start: Self, end: Self) -> Self; 52 | } 53 | 54 | macro_rules! impl_Interpolator { 55 | ($t:ty) => { 56 | impl Interpolator for $t { 57 | fn normalize(self, start: Self, end: Self) -> Self { 58 | (self - start) / (end - start) 59 | } 60 | } 61 | }; 62 | } 63 | 64 | impl_Interpolator!(f32); 65 | impl_Interpolator!(f64); 66 | 67 | /// Values that can be interpolated. Implementing this trait is required to perform sampling on splines. 68 | /// 69 | /// `T` is the interpolator used to sample with. Typical implementations use [`f32`] or [`f64`], but 70 | /// you’re free to use the ones you like. 71 | pub trait Interpolate: Sized + Copy { 72 | /// Step interpolation. 73 | fn step(t: T, threshold: T, a: Self, b: Self) -> Self; 74 | 75 | /// Linear interpolation. 76 | fn lerp(t: T, a: Self, b: Self) -> Self; 77 | 78 | /// Cosine interpolation. 79 | fn cosine(t: T, a: Self, b: Self) -> Self; 80 | 81 | /// Cubic hermite interpolation. 82 | fn cubic_hermite(t: T, x: (T, Self), a: (T, Self), b: (T, Self), y: (T, Self)) -> Self; 83 | 84 | /// Quadratic Bézier interpolation. 85 | /// 86 | /// `a` is the first point; `b` is the second point and `u` is the tangent of `a` to the curve. 87 | fn quadratic_bezier(t: T, a: Self, u: Self, b: Self) -> Self; 88 | 89 | /// Cubic Bézier interpolation. 90 | /// 91 | /// `a` is the first point; `b` is the second point; `u` is the output tangent of `a` to the curve and `v` is the 92 | /// input tangent of `b` to the curve. 93 | fn cubic_bezier(t: T, a: Self, u: Self, v: Self, b: Self) -> Self; 94 | 95 | /// Cubic Bézier interpolation – special case for non-explicit second tangent. 96 | /// 97 | /// This version does the same computation as [`Interpolate::cubic_bezier`] but computes the second tangent by 98 | /// inversing it (typical when the next point uses a Bézier interpolation, where input and output tangents are 99 | /// mirrored for the same key). 100 | fn cubic_bezier_mirrored(t: T, a: Self, u: Self, v: Self, b: Self) -> Self; 101 | } 102 | 103 | #[macro_export] 104 | macro_rules! impl_Interpolate { 105 | ($t:ty, $v:ty, $pi:expr) => { 106 | impl $crate::interpolate::Interpolate<$t> for $v { 107 | fn step(t: $t, threshold: $t, a: Self, b: Self) -> Self { 108 | if t < threshold { 109 | a 110 | } else { 111 | b 112 | } 113 | } 114 | 115 | fn cosine(t: $t, a: Self, b: Self) -> Self { 116 | let cos_nt = (1. - (t * $pi).cos()) * 0.5; 117 | >::lerp(cos_nt, a, b) 118 | } 119 | 120 | fn lerp(t: $t, a: Self, b: Self) -> Self { 121 | a * (1. - t) + b * t 122 | } 123 | 124 | fn cubic_hermite(t: $t, x: ($t, Self), a: ($t, Self), b: ($t, Self), y: ($t, Self)) -> Self { 125 | // sampler stuff 126 | let two_t = t * 2.; 127 | let three_t = t * 3.; 128 | let t2 = t * t; 129 | let t3 = t2 * t; 130 | let two_t3 = t2 * two_t; 131 | let two_t2 = t * two_t; 132 | let three_t2 = t * three_t; 133 | 134 | // tangents 135 | let m0 = (b.1 - x.1) / (b.0 - x.0) * (b.0 - a.0); 136 | let m1 = (y.1 - a.1) / (y.0 - a.0) * (b.0 - a.0); 137 | 138 | a.1 * (two_t3 - three_t2 + 1.) 139 | + m0 * (t3 - two_t2 + t) 140 | + b.1 * (three_t2 - two_t3) 141 | + m1 * (t3 - t2) 142 | } 143 | 144 | fn quadratic_bezier(t: $t, a: Self, u: Self, b: Self) -> Self { 145 | let one_t = 1. - t; 146 | let one_t2 = one_t * one_t; 147 | 148 | u + (a - u) * one_t2 + (b - u) * t * t 149 | } 150 | 151 | fn cubic_bezier(t: $t, a: Self, u: Self, v: Self, b: Self) -> Self { 152 | let one_t = 1. - t; 153 | let one_t2 = one_t * one_t; 154 | let one_t3 = one_t2 * one_t; 155 | let t2 = t * t; 156 | 157 | a * one_t3 + (u * one_t2 * t + v * one_t * t2) * 3. + b * t2 * t 158 | } 159 | 160 | fn cubic_bezier_mirrored(t: $t, a: Self, u: Self, v: Self, b: Self) -> Self { 161 | >::cubic_bezier(t, a, u, b + b - v, b) 162 | } 163 | } 164 | }; 165 | } 166 | 167 | #[macro_export] 168 | macro_rules! impl_InterpolateT { 169 | ($t:ty, $v:ty, $pi:expr) => { 170 | impl $crate::interpolate::Interpolate<$t> for $v { 171 | fn step(t: $t, threshold: $t, a: Self, b: Self) -> Self { 172 | if t < threshold { 173 | a 174 | } else { 175 | b 176 | } 177 | } 178 | 179 | fn cosine(t: $t, a: Self, b: Self) -> Self { 180 | let cos_nt = (1. - (t * $pi).cos()) * 0.5; 181 | >::lerp(cos_nt, a, b) 182 | } 183 | 184 | fn lerp(t: $t, a: Self, b: Self) -> Self { 185 | let t = Self::from(t); 186 | a * (1. - t) + b * t 187 | } 188 | 189 | fn cubic_hermite(t: $t, x: ($t, Self), a: ($t, Self), b: ($t, Self), y: ($t, Self)) -> Self { 190 | // sampler stuff 191 | let t = Self::from(t); 192 | let two_t = t * 2.; 193 | let three_t = t * 3.; 194 | let t2 = t * t; 195 | let t3 = t2 * t; 196 | let two_t3 = t2 * two_t; 197 | let two_t2 = t * two_t; 198 | let three_t2 = t * three_t; 199 | 200 | // tangents 201 | let m0 = (b.1 - x.1) / (Self::from(b.0 - x.0)) * (Self::from(b.0 - a.0)); 202 | let m1 = (y.1 - a.1) / (Self::from(y.0 - a.0)) * (Self::from(b.0 - a.0)); 203 | 204 | a.1 * (two_t3 - three_t2 + 1.) 205 | + m0 * (t3 - two_t2 + t) 206 | + b.1 * (three_t2 - two_t3) 207 | + m1 * (t3 - t2) 208 | } 209 | 210 | fn quadratic_bezier(t: $t, a: Self, u: Self, b: Self) -> Self { 211 | let t = Self::from(t); 212 | let one_t = 1. - t; 213 | let one_t2 = one_t * one_t; 214 | 215 | u + (a - u) * one_t2 + (b - u) * t * t 216 | } 217 | 218 | fn cubic_bezier(t: $t, a: Self, u: Self, v: Self, b: Self) -> Self { 219 | let t = Self::from(t); 220 | let one_t = 1. - t; 221 | let one_t2 = one_t * one_t; 222 | let one_t3 = one_t2 * one_t; 223 | let t2 = t * t; 224 | 225 | a * one_t3 + (u * one_t2 * t + v * one_t * t2) * 3. + b * t2 * t 226 | } 227 | 228 | fn cubic_bezier_mirrored(t: $t, a: Self, u: Self, v: Self, b: Self) -> Self { 229 | >::cubic_bezier(t, a, u, b + b - v, b) 230 | } 231 | } 232 | }; 233 | } 234 | 235 | impl_Interpolate!(f32, f32, std::f32::consts::PI); 236 | impl_Interpolate!(f64, f64, std::f64::consts::PI); 237 | impl_InterpolateT!(f32, f64, std::f32::consts::PI); 238 | -------------------------------------------------------------------------------- /src/spline.rs: -------------------------------------------------------------------------------- 1 | //! Spline curves and operations. 2 | 3 | #[cfg(feature = "std")] 4 | use crate::interpolate::{Interpolate, Interpolator}; 5 | use crate::interpolation::Interpolation; 6 | use crate::key::Key; 7 | #[cfg(not(feature = "std"))] 8 | use alloc::vec::Vec; 9 | #[cfg(not(feature = "std"))] 10 | use core::cmp::Ordering; 11 | #[cfg(not(feature = "std"))] 12 | use core::ops::{Div, Mul}; 13 | #[cfg(any(feature = "serialization", feature = "serde"))] 14 | use serde::{Deserialize, Serialize}; 15 | #[cfg(feature = "std")] 16 | use std::cmp::Ordering; 17 | 18 | /// Spline curve used to provide interpolation between control points (keys). 19 | /// 20 | /// Splines are made out of control points ([`Key`]). When creating a [`Spline`] with 21 | /// [`Spline::from_vec`] or [`Spline::from_iter`], the keys don’t have to be sorted (they are sorted 22 | /// automatically by the sampling value). 23 | /// 24 | /// You can sample from a spline with several functions: 25 | /// 26 | /// - [`Spline::sample`]: allows you to sample from a spline. If not enough keys are available 27 | /// for the required interpolation mode, you get `None`. 28 | /// - [`Spline::clamped_sample`]: behaves like [`Spline::sample`] but will return either the first 29 | /// or last key if out of bound; it will return `None` if not enough key. 30 | #[derive(Debug, Clone, Default)] 31 | #[cfg_attr( 32 | any(feature = "serialization", feature = "serde"), 33 | derive(Deserialize, Serialize) 34 | )] 35 | pub struct Spline(pub(crate) Vec>); 36 | 37 | impl Spline { 38 | /// Internal sort to ensure invariant of sorting keys is valid. 39 | fn internal_sort(&mut self) 40 | where 41 | T: PartialOrd, 42 | { 43 | self 44 | .0 45 | .sort_by(|k0, k1| k0.t.partial_cmp(&k1.t).unwrap_or(Ordering::Less)); 46 | } 47 | 48 | /// Create a new spline out of keys. The keys don’t have to be sorted even though it’s recommended 49 | /// to provide ascending sorted ones (for performance purposes). 50 | pub fn from_vec(keys: Vec>) -> Self 51 | where 52 | T: PartialOrd, 53 | { 54 | let mut spline = Spline(keys); 55 | spline.internal_sort(); 56 | spline 57 | } 58 | 59 | /// Clear the spline by removing all keys. Keeps the underlying allocated storage, so adding 60 | /// new keys should be faster than creating a new [`Spline`] 61 | #[inline] 62 | pub fn clear(&mut self) { 63 | self.0.clear() 64 | } 65 | 66 | /// Create a new spline by consuming an `Iterater>`. They keys don’t have to be 67 | /// sorted. 68 | /// 69 | /// # Note on iterators 70 | /// 71 | /// It’s valid to use any iterator that implements `Iterator>`. However, you should 72 | /// use [`Spline::from_vec`] if you are passing a [`Vec`]. 73 | pub fn from_iter(iter: I) -> Self 74 | where 75 | I: Iterator>, 76 | T: PartialOrd, 77 | { 78 | Self::from_vec(iter.collect()) 79 | } 80 | 81 | /// Retrieve the keys of a spline. 82 | pub fn keys(&self) -> &[Key] { 83 | &self.0 84 | } 85 | 86 | /// Number of keys. 87 | #[inline(always)] 88 | pub fn len(&self) -> usize { 89 | self.0.len() 90 | } 91 | 92 | /// Check whether the spline has no key. 93 | #[inline(always)] 94 | pub fn is_empty(&self) -> bool { 95 | self.0.is_empty() 96 | } 97 | 98 | /// Sample a spline at a given time, returning the interpolated value along with its associated 99 | /// key. 100 | /// 101 | /// The current implementation, based on immutability, cannot perform in constant time. This means 102 | /// that sampling’s processing complexity is currently *O(log n)*. It’s possible to achieve *O(1)* 103 | /// performance by using a slightly different spline type. If you are interested by this feature, 104 | /// an implementation for a dedicated type is foreseen yet not started yet. 105 | /// 106 | /// # Return 107 | /// 108 | /// `None` if you try to sample a value at a time that has no key associated with. That can also 109 | /// happen if you try to sample between two keys with a specific interpolation mode that makes the 110 | /// sampling impossible. For instance, [`Interpolation::CatmullRom`] requires *four* keys. If 111 | /// you’re near the beginning of the spline or its end, ensure you have enough keys around to make 112 | /// the sampling. 113 | pub fn sample_with_key(&self, t: T) -> Option> 114 | where 115 | T: Interpolator, 116 | V: Interpolate, 117 | { 118 | let keys = &self.0; 119 | let i = search_lower_cp(keys, t)?; 120 | let cp0 = &keys[i]; 121 | 122 | let value = match cp0.interpolation { 123 | Interpolation::Step(threshold) => { 124 | let cp1 = &keys[i + 1]; 125 | let nt = t.normalize(cp0.t, cp1.t); 126 | let value = V::step(nt, threshold, cp0.value, cp1.value); 127 | 128 | Some(value) 129 | } 130 | 131 | Interpolation::Linear => { 132 | let cp1 = &keys[i + 1]; 133 | let nt = t.normalize(cp0.t, cp1.t); 134 | let value = V::lerp(nt, cp0.value, cp1.value); 135 | 136 | Some(value) 137 | } 138 | 139 | Interpolation::Cosine => { 140 | let cp1 = &keys[i + 1]; 141 | let nt = t.normalize(cp0.t, cp1.t); 142 | let value = V::cosine(nt, cp0.value, cp1.value); 143 | 144 | Some(value) 145 | } 146 | 147 | Interpolation::CatmullRom => { 148 | // We need at least four points for Catmull Rom; ensure we have them, otherwise, return 149 | // None. 150 | if i == 0 || i >= keys.len() - 2 { 151 | None 152 | } else { 153 | let cp1 = &keys[i + 1]; 154 | let cpm0 = &keys[i - 1]; 155 | let cpm1 = &keys[i + 2]; 156 | let nt = t.normalize(cp0.t, cp1.t); 157 | let value = V::cubic_hermite( 158 | nt, 159 | (cpm0.t, cpm0.value), 160 | (cp0.t, cp0.value), 161 | (cp1.t, cp1.value), 162 | (cpm1.t, cpm1.value), 163 | ); 164 | 165 | Some(value) 166 | } 167 | } 168 | 169 | Interpolation::Bezier(u) | Interpolation::StrokeBezier(_, u) => { 170 | // We need to check the next control point to see whether we want quadratic or cubic Bezier. 171 | let cp1 = &keys[i + 1]; 172 | let nt = t.normalize(cp0.t, cp1.t); 173 | 174 | let value = match cp1.interpolation { 175 | Interpolation::Bezier(v) => V::cubic_bezier_mirrored(nt, cp0.value, u, v, cp1.value), 176 | 177 | Interpolation::StrokeBezier(v, _) => V::cubic_bezier(nt, cp0.value, u, v, cp1.value), 178 | 179 | _ => V::quadratic_bezier(nt, cp0.value, u, cp1.value), 180 | }; 181 | 182 | Some(value) 183 | } 184 | }; 185 | 186 | value.map(|value| SampledWithKey { value, key: i }) 187 | } 188 | 189 | /// Sample a spline at a given time. 190 | /// 191 | pub fn sample(&self, t: T) -> Option 192 | where 193 | T: Interpolator, 194 | V: Interpolate, 195 | { 196 | self.sample_with_key(t).map(|sampled| sampled.value) 197 | } 198 | 199 | /// Sample a spline at a given time with clamping, returning the interpolated value along with its 200 | /// associated key. 201 | /// 202 | /// # Return 203 | /// 204 | /// If you sample before the first key or after the last one, return the first key or the last 205 | /// one, respectively. Otherwise, behave the same way as [`Spline::sample`]. 206 | /// 207 | /// # Error 208 | /// 209 | /// This function returns [`None`] if you have no key. 210 | pub fn clamped_sample_with_key(&self, t: T) -> Option> 211 | where 212 | T: Interpolator, 213 | V: Interpolate, 214 | { 215 | if self.0.is_empty() { 216 | return None; 217 | } 218 | 219 | self.sample_with_key(t).or_else(move || { 220 | let first = self.0.first().unwrap(); 221 | 222 | if t <= first.t { 223 | let sampled = SampledWithKey { 224 | value: first.value, 225 | key: 0, 226 | }; 227 | Some(sampled) 228 | } else { 229 | let last = self.0.last().unwrap(); 230 | 231 | if t >= last.t { 232 | let sampled = SampledWithKey { 233 | value: last.value, 234 | key: self.0.len() - 1, 235 | }; 236 | Some(sampled) 237 | } else { 238 | None 239 | } 240 | } 241 | }) 242 | } 243 | 244 | /// Sample a spline at a given time with clamping. 245 | pub fn clamped_sample(&self, t: T) -> Option 246 | where 247 | T: Interpolator, 248 | V: Interpolate, 249 | { 250 | self.clamped_sample_with_key(t).map(|sampled| sampled.value) 251 | } 252 | 253 | /// Add a key into the spline. 254 | pub fn add(&mut self, key: Key) 255 | where 256 | T: PartialOrd, 257 | { 258 | self.0.push(key); 259 | self.internal_sort(); 260 | } 261 | 262 | /// Remove a key from the spline. 263 | pub fn remove(&mut self, index: usize) -> Option> { 264 | if index >= self.0.len() { 265 | None 266 | } else { 267 | Some(self.0.remove(index)) 268 | } 269 | } 270 | 271 | /// Update a key and return the key already present. 272 | /// 273 | /// The key is updated — if present — with the provided function. 274 | /// 275 | /// # Notes 276 | /// 277 | /// That function makes sense only if you want to change the interpolator (i.e. [`Key::t`]) of 278 | /// your key. If you just want to change the interpolation mode or the carried value, consider 279 | /// using the [`Spline::get_mut`] method instead as it will be way faster. 280 | pub fn replace(&mut self, index: usize, f: F) -> Option> 281 | where 282 | F: FnOnce(&Key) -> Key, 283 | T: PartialOrd, 284 | { 285 | let key = self.remove(index)?; 286 | self.add(f(&key)); 287 | Some(key) 288 | } 289 | 290 | /// Get a key at a given index. 291 | pub fn get(&self, index: usize) -> Option<&Key> { 292 | self.0.get(index) 293 | } 294 | 295 | /// Mutably get a key at a given index. 296 | pub fn get_mut(&mut self, index: usize) -> Option> { 297 | self.0.get_mut(index).map(|key| KeyMut { 298 | value: &mut key.value, 299 | interpolation: &mut key.interpolation, 300 | }) 301 | } 302 | } 303 | 304 | /// A sampled value along with its key index. 305 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 306 | pub struct SampledWithKey { 307 | /// Sampled value. 308 | pub value: V, 309 | 310 | /// Key index. 311 | pub key: usize, 312 | } 313 | 314 | /// A mutable [`Key`]. 315 | /// 316 | /// Mutable keys allow to edit the carried values and the interpolation mode but not the actual 317 | /// interpolator value as it would invalidate the internal structure of the [`Spline`]. If you 318 | /// want to achieve this, you’re advised to use [`Spline::replace`]. 319 | #[derive(Debug)] 320 | pub struct KeyMut<'a, T, V> { 321 | /// Carried value. 322 | pub value: &'a mut V, 323 | /// Interpolation mode to use for that key. 324 | pub interpolation: &'a mut Interpolation, 325 | } 326 | 327 | // Find the lower control point corresponding to a given time. 328 | // It has the property to have a timestamp smaller or equal to t 329 | fn search_lower_cp(cps: &[Key], t: T) -> Option 330 | where 331 | T: PartialOrd, 332 | { 333 | let len = cps.len(); 334 | if len < 2 { 335 | return None; 336 | } 337 | match cps.binary_search_by(|key| key.t.partial_cmp(&t).unwrap()) { 338 | Err(i) if i >= len => None, 339 | Err(i) if i == 0 => None, 340 | Err(i) => Some(i - 1), 341 | Ok(i) if i == len - 1 => None, 342 | Ok(i) => Some(i), 343 | } 344 | } 345 | --------------------------------------------------------------------------------