19 Commits
2.0 ... 3.1

Author SHA1 Message Date
d80de42d2f Prepare 3.1.0. 2020-01-26 21:19:25 +01:00
2e6a5a0dfb Merge pull request #38 from alexbool/update-nalgebra
update nalgebra
2020-01-26 21:18:30 +01:00
62147d5348 update nalgebra 2020-01-26 22:42:05 +03:00
2dfc11c908 Fix CHANGELOG entry date. 2019-10-22 21:43:50 +02:00
0c23df7bf0 Merge pull request #35 from phaazon/fix/bézier
Fix Bézier interpolation.
2019-10-22 21:09:15 +02:00
3b6ddc5ea6 Update integration tests for stroke Bézier. 2019-10-22 20:59:46 +02:00
824afef513 Fix Bézier interpolation. 2019-10-22 20:23:36 +02:00
f2b356b78d Working on tests. 2019-10-22 18:13:51 +02:00
955050ecee Fix examples. 2019-10-22 13:34:11 +02:00
22e75c6901 Fix Bézier interpolation. 2019-10-22 13:34:10 +02:00
425433cd5b Merge pull request #33 from phaazon/feature/stroke-bezier
Add Interpolation::StrokeBezier.
2019-10-17 17:26:16 +02:00
cc0a9580ab Add Interpolation::StrokeBezier. 2019-10-17 17:23:46 +02:00
05e131baad 2.1.1 2019-10-17 01:49:34 +02:00
0a15fb48a3 Add missing LICENSE file. 2019-10-17 01:45:53 +02:00
ebc6e16aef Merge pull request #32 from phaazon/feature/sample-key
Add Spline::sample_with_key and Spline::clamped_sample_with_key.
2019-09-30 12:59:11 +02:00
cae599e0d7 Add Spline::sample_with_key and Spline::clamped_sample_with_key. 2019-09-30 12:49:36 +02:00
336c1c7e80 Merge pull request #31 from phaazon/fix/bezier-interpolation
Fix/bezier interpolation
2019-09-24 21:36:04 +02:00
ea29e08836 Fix cubic Bézier interpolation. 2019-09-24 21:31:18 +02:00
3ab98420c8 Remove unneeded comments. 2019-09-24 17:40:01 +02:00
13 changed files with 187 additions and 59 deletions

View File

@ -26,14 +26,18 @@ jobs:
cargo test --verbose --all-features
build-macosx:
runs-on: macosx-latest
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- name: Rust requirements
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal
- name: Build
run: |
. ~/.cargo/env
cargo build --verbose --all-features
- name: Test
run: |
. ~/.cargo/env
cargo test --verbose --all-features
check-readme:

View File

@ -1,6 +1,53 @@
# 3.1.0
> San Jan 26th 2020
- Add support for `nalgebra-0.19`.
# 3.0.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.0
> 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.0
> Mon Sep 24th 2019
> Mon Sep 23rd 2019
## Major changes

View File

@ -1,6 +1,6 @@
[package]
name = "splines"
version = "2.0.0"
version = "3.1.0"
license = "BSD-3-Clause"
authors = ["Dimitri Sabadie <dimitri.sabadie@gmail.com>"]
description = "Spline interpolation made easy"
@ -29,10 +29,21 @@ std = []
[dependencies]
alga = { version = "0.9", optional = true }
cgmath = { version = "0.17", optional = true }
nalgebra = { version = ">=0.14, <0.19", optional = true }
nalgebra = { version = ">=0.14, <0.20", optional = true }
num-traits = { version = "0.2", optional = true }
serde = { version = "1", optional = true }
serde_derive = { version = "1", optional = true }
[dev-dependencies]
float-cmp = "0.5"
serde_json = "1"
[package.metadata.docs.rs]
all-features = true
[[example]]
name = "hello-world"
[[example]]
name = "serialization"
required-features = ["serialization"]

30
LICENSE Normal file
View File

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

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

View File

@ -1,8 +0,0 @@
[package]
name = "serialization"
version = "0.2.0"
authors = ["Dimitri Sabadie <dimitri.sabadie@gmail.com>"]
[dependencies]
serde_json = "1"
splines = { version = "1.0.0-rc.2", features = ["serialization"] }

View File

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

View File

@ -45,7 +45,7 @@
/// instance to know which trait your type must implement to be usable.
///
/// [`Spline::sample`]: crate::spline::Spline::sample
pub trait Interpolate<T>: Sized + Copy {
pub trait Interpolate<T>: Sized + Copy + Linear<T> {
/// Linear interpolation.
fn lerp(a: Self, b: Self, t: T) -> Self;

View File

@ -40,6 +40,18 @@ pub enum Interpolation<T, V> {
/// 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),
#[doc(hidden)]
__NonExhaustive
}

View File

@ -7,7 +7,7 @@
#[cfg(not(feature = "std"))] use core::ops::{Div, Mul};
#[cfg(not(feature = "std"))] use core::cmp::Ordering;
use crate::interpolate::{Interpolate, Additive, One, Trigo};
use crate::interpolate::{Additive, Interpolate, One, Trigo};
use crate::interpolation::Interpolation;
use crate::key::Key;
@ -69,7 +69,8 @@ impl<T, V> Spline<T, V> {
self.0.is_empty()
}
/// Sample a spline at a given time.
/// 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
/// that samplings processing complexity is currently *O(log n)*. Its possible to achieve *O(1)*
@ -83,10 +84,9 @@ impl<T, V> Spline<T, V> {
/// sampling impossible. For instance, [`Interpolation::CatmullRom`] requires *four* keys. If
/// youre near the beginning of the spline or its end, ensure you have enough keys around to make
/// the sampling.
///
pub fn sample(&self, t: T) -> Option<V>
pub fn sample_with_key(&self, t: T) -> Option<(V, &Key<T, V>, Option<&Key<T, V>>)>
where T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
V: Interpolate<T> {
V: Additive + Interpolate<T> {
let keys = &self.0;
let i = search_lower_cp(keys, t)?;
let cp0 = &keys[i];
@ -95,14 +95,17 @@ impl<T, V> Spline<T, V> {
Interpolation::Step(threshold) => {
let cp1 = &keys[i + 1];
let nt = normalize_time(t, cp0, cp1);
Some(if nt < threshold { cp0.value } else { cp1.value })
let value = if nt < threshold { cp0.value } else { cp1.value };
Some((value, cp0, Some(cp1)))
}
Interpolation::Linear => {
let cp1 = &keys[i + 1];
let nt = normalize_time(t, cp0, cp1);
let value = Interpolate::lerp(cp0.value, cp1.value, nt);
Some(Interpolate::lerp(cp0.value, cp1.value, nt))
Some((value, cp0, Some(cp1)))
}
Interpolation::Cosine => {
@ -110,8 +113,9 @@ impl<T, V> Spline<T, V> {
let cp1 = &keys[i + 1];
let nt = normalize_time(t, cp0, cp1);
let cos_nt = (T::one() - (nt * T::pi()).cos()) / two_t;
let value = Interpolate::lerp(cp0.value, cp1.value, cos_nt);
Some(Interpolate::lerp(cp0.value, cp1.value, cos_nt))
Some((value, cp0, Some(cp1)))
}
Interpolation::CatmullRom => {
@ -124,33 +128,47 @@ impl<T, V> Spline<T, V> {
let cpm0 = &keys[i - 1];
let cpm1 = &keys[i + 2];
let nt = normalize_time(t, cp0, cp1);
let value = Interpolate::cubic_hermite((cpm0.value, cpm0.t), (cp0.value, cp0.t), (cp1.value, cp1.t), (cpm1.value, cpm1.t), nt);
Some(Interpolate::cubic_hermite((cpm0.value, cpm0.t), (cp0.value, cp0.t), (cp1.value, cp1.t), (cpm1.value, cpm1.t), nt))
Some((value, cp0, Some(cp1)))
}
}
Interpolation::Bezier(u) => {
Interpolation::Bezier(u) | Interpolation::StrokeBezier(_, u) => {
// We need to check the next control point to see whether we want quadratic or cubic Bezier.
let cp1 = &keys[i + 1];
let nt = normalize_time(t, cp0, cp1);
if let Interpolation::Bezier(v) = cp1.interpolation {
Some(Interpolate::cubic_bezier(cp0.value, u, v, cp1.value, nt))
//let one_nt = T::one() - nt;
//let one_nt_2 = one_nt * one_nt;
//let one_nt_3 = one_nt_2 * one_nt;
//let three_one_nt_2 = one_nt_2 + one_nt_2 + one_nt_2; // one_nt_2 * 3
//let r = cp0.value * one_nt_3;
} else {
Some(Interpolate::quadratic_bezier(cp0.value, u, cp1.value, nt))
}
let value =
match cp1.interpolation {
Interpolation::Bezier(v) => {
Interpolate::cubic_bezier(cp0.value, u, cp1.value + cp1.value - v, cp1.value, nt)
}
Interpolation::StrokeBezier(v, _) => {
Interpolate::cubic_bezier(cp0.value, u, v, cp1.value, nt)
}
_ => Interpolate::quadratic_bezier(cp0.value, u, cp1.value, nt)
};
Some((value, cp0, Some(cp1)))
}
Interpolation::__NonExhaustive => unreachable!(),
}
}
/// Sample a spline at a given time with clamping.
/// Sample a spline at a given time.
///
pub fn sample(&self, t: T) -> Option<V>
where T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
V: Additive + Interpolate<T> {
self.sample_with_key(t).map(|(v, _, _)| v)
}
/// Sample a spline at a given time with clamping, returning the interpolated value along with its
/// associated key.
///
/// # Return
///
@ -160,22 +178,23 @@ impl<T, V> Spline<T, V> {
/// # Error
///
/// This function returns [`None`] if you have no key.
pub fn clamped_sample(&self, t: T) -> Option<V>
pub fn clamped_sample_with_key(&self, t: T) -> Option<(V, &Key<T, V>, Option<&Key<T, V>>)>
where T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
V: Interpolate<T> {
V: Additive + Interpolate<T> {
if self.0.is_empty() {
return None;
}
self.sample(t).or_else(move || {
self.sample_with_key(t).or_else(move || {
let first = self.0.first().unwrap();
if t <= first.t {
Some(first.value)
let second = if self.0.len() >= 2 { Some(&self.0[1]) } else { None };
Some((first.value, &first, second))
} else {
let last = self.0.last().unwrap();
if t >= last.t {
Some(last.value)
Some((last.value, &last, None))
} else {
None
}
@ -183,6 +202,13 @@ impl<T, V> Spline<T, V> {
})
}
/// Sample a spline at a given time with clamping.
pub fn clamped_sample(&self, t: T) -> Option<V>
where T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
V: Additive + Interpolate<T> {
self.clamped_sample_with_key(t).map(|(v, _, _)| v)
}
/// Add a key into the spline.
pub fn add(&mut self, key: Key<T, V>) where T: PartialOrd {
self.0.push(key);

View File

@ -1,7 +1,8 @@
use float_cmp::approx_eq;
use splines::{Interpolation, Key, Spline};
#[cfg(feature = "impl-cgmath")] use cgmath as cg;
#[cfg(feature = "impl-nalgebra")] use nalgebra as na;
#[cfg(feature = "cgmath")] use cgmath as cg;
#[cfg(feature = "nalgebra")] use nalgebra as na;
#[test]
fn step_interpolation_f32() {
@ -16,6 +17,8 @@ fn step_interpolation_f32() {
assert_eq!(spline.sample(0.9), Some(10.));
assert_eq!(spline.sample(1.), None);
assert_eq!(spline.clamped_sample(1.), Some(10.));
assert_eq!(spline.sample_with_key(0.2), Some((10., &start, Some(&end))));
assert_eq!(spline.clamped_sample_with_key(1.), Some((10., &end, None)));
}
#[test]
@ -31,6 +34,8 @@ fn step_interpolation_f64() {
assert_eq!(spline.sample(0.9), Some(10.));
assert_eq!(spline.sample(1.), None);
assert_eq!(spline.clamped_sample(1.), Some(10.));
assert_eq!(spline.sample_with_key(0.2), Some((10., &start, Some(&end))));
assert_eq!(spline.clamped_sample_with_key(1.), Some((10., &end, None)));
}
#[test]
@ -145,7 +150,24 @@ fn several_interpolations_several_keys() {
assert_eq!(spline.clamped_sample(11.), Some(4.));
}
#[cfg(feature = "impl-cgmath")]
#[cfg(feature = "cgmath")]
#[test]
fn stroke_bezier_straight() {
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.));
}
#[cfg(feature = "cgmath")]
#[test]
fn cgmath_vector_interpolation() {
use splines::Interpolate;
@ -159,7 +181,7 @@ fn cgmath_vector_interpolation() {
assert_eq!(Interpolate::lerp(start, end, 0.5), mid);
}
#[cfg(feature = "impl-nalgebra")]
#[cfg(feature = "nalgebra")]
#[test]
fn nalgebra_vector_interpolation() {
use splines::Interpolate;