10 Commits
2.1.0 ... 3.0.0

Author SHA1 Message Date
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
13 changed files with 124 additions and 46 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,3 +1,28 @@
# 3.0.0
> Sun Oct 20th 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

View File

@ -1,6 +1,6 @@
[package]
name = "splines"
version = "2.1.0"
version = "3.0.0"
license = "BSD-3-Clause"
authors = ["Dimitri Sabadie <dimitri.sabadie@gmail.com>"]
description = "Spline interpolation made easy"
@ -34,5 +34,16 @@ 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;
@ -240,10 +240,7 @@ where V: Linear<T>,
let one_t_3 = one_t_2 * one_t;
let three = T::one() + T::one() + T::one();
// mirror the “output” tangent based on the next key “input” tangent
let v_ = b + b - v;
a.outer_mul(one_t_3) + u.outer_mul(three * one_t_2 * t) + v_.outer_mul(three * one_t * t * t) + b.outer_mul(t * t * t)
a.outer_mul(one_t_3) + u.outer_mul(three * one_t_2 * t) + v.outer_mul(three * one_t * t * t) + b.outer_mul(t * t * t)
}
macro_rules! impl_interpolate_simple {

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;
@ -84,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_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];
@ -135,16 +134,22 @@ impl<T, V> Spline<T, V> {
}
}
Interpolation::Bezier(u) => {
Interpolation::Bezier(u) | Interpolation::StrokeBezier(_, u) => {
// We need to check the next control point to see whether we want quadratic or cubic Bezier.
let cp1 = &keys[i + 1];
let nt = normalize_time(t, cp0, cp1);
let value =
if let Interpolation::Bezier(v) = cp1.interpolation {
Interpolate::cubic_bezier(cp0.value, u, v, cp1.value, nt)
} else {
Interpolate::quadratic_bezier(cp0.value, u, cp1.value, nt)
match cp1.interpolation {
Interpolation::Bezier(v) => {
Interpolate::cubic_bezier(cp0.value, u, cp1.value + cp1.value - v, cp1.value, nt)
}
Interpolation::StrokeBezier(v, _) => {
Interpolate::cubic_bezier(cp0.value, u, v, cp1.value, nt)
}
_ => Interpolate::quadratic_bezier(cp0.value, u, cp1.value, nt)
};
Some((value, cp0, Some(cp1)))
@ -158,7 +163,7 @@ impl<T, V> Spline<T, V> {
///
pub fn sample(&self, t: T) -> Option<V>
where T: Additive + One + Trigo + Mul<T, Output = T> + Div<T, Output = T> + PartialOrd,
V: Interpolate<T> {
V: Additive + Interpolate<T> {
self.sample_with_key(t).map(|(v, _, _)| v)
}
@ -175,7 +180,7 @@ impl<T, V> Spline<T, V> {
/// This function returns [`None`] if you have no key.
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;
}
@ -200,7 +205,7 @@ 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: Interpolate<T> {
V: Additive + Interpolate<T> {
self.clamped_sample_with_key(t).map(|(v, _, _)| v)
}

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() {
@ -149,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;
@ -163,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;