From 65a713c51b12e7745647a848857287ba5f6863c3 Mon Sep 17 00:00:00 2001 From: Dimitri Sabadie Date: Sun, 21 Apr 2019 17:54:24 +0200 Subject: [PATCH] Implement impl-nalgebra feature. --- Cargo.toml | 9 +-- src/interpolate.rs | 159 +++++++++++++++++++++++++++++++++++++++++---- src/lib.rs | 2 + src/nalgebra.rs | 75 ++++++++++++++------- src/spline.rs | 18 ++--- 5 files changed, 214 insertions(+), 49 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c5e6533..9b6f6aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,14 +21,15 @@ maintenance = { status = "actively-developed" } [features] default = ["std"] -serialization = ["serde", "serde_derive"] -std = ["num-traits/std"] impl-cgmath = ["cgmath"] -impl-nalgebra = ["nalgebra"] +impl-nalgebra = ["alga", "nalgebra", "num-traits"] +serialization = ["serde", "serde_derive"] +std = [] [dependencies] +alga = { version = "0.9", optional = true } cgmath = { version = "0.17", optional = true } nalgebra = { version = ">=0.14, <0.19", optional = true } -num-traits = { version = "0.2", default-features = false } +num-traits = { version = "0.2", optional = true } serde = { version = "1", optional = true } serde_derive = { version = "1", optional = true } diff --git a/src/interpolate.rs b/src/interpolate.rs index 7a1d603..314f37c 100644 --- a/src/interpolate.rs +++ b/src/interpolate.rs @@ -1,7 +1,11 @@ -#[cfg(feature = "std")] use std::ops::{Div, Mul}; -#[cfg(not(feature = "std"))] use core::ops::{Div, Mul}; - -use num_traits::Float; +#[cfg(feature = "std")] use std::f32; +#[cfg(not(feature = "std"))] use core::f32; +#[cfg(not(feature = "std"))] use core::intrinsics::cosf32; +#[cfg(feature = "std")] use std::f64; +#[cfg(not(feature = "std"))] use core::f64; +#[cfg(not(feature = "std"))] use core::intrinsics::cosf64; +#[cfg(feature = "std")] use std::ops::{Add, Mul, Sub}; +#[cfg(not(feature = "std"))] use core::ops::{Add, Mul, Sub}; /// Keys that can be interpolated in between. Implementing this trait is required to perform /// sampling on splines. @@ -20,15 +24,147 @@ pub trait Interpolate: Sized + Copy { } } +/// A trait for anything that supports additions, subtraction, multiplication and division. +pub trait Additive: + Copy + + PartialEq + + PartialOrd + + Add + + Sub { +} + +impl Additive for T +where T: Copy + + PartialEq + + PartialOrd + + Add + + Sub { +} + +/// Linear combination. +pub trait Linear { + /// 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 + } + + /// Apply an outer division law. + fn outer_div(self, t: $t) -> Self { + self / t + } + } + } +} + +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 + } + + /// 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(x: (V, T), a: (V, T), b: (V, T), y: (V, T), t: T) -> V -where V: Float + Mul + Div, - T: Float { +where V: Additive + Linear, + T: Additive + Mul + One { // some stupid generic constants, because Rust doesn’t have polymorphic literals… - let two_t = T::one() + T::one(); // lolololol - let three_t = two_t + T::one(); // megalol + 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; @@ -37,10 +173,10 @@ where V: Float + Mul + Div, let three_t2 = t2 * three_t; // tangents - let m0 = (b.0 - x.0) / (b.1 - x.1); - let m1 = (y.0 - a.0) / (y.1 - a.1); + 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 * (two_t3 - three_t2 + T::one()) + m0 * (t3 - t2 * two_t + t) + b.0 * (three_t2 - two_t3) + m1 * (t3 - t2) + a.0.outer_mul(two_t3 - three_t2 + one_t) + m0.outer_mul(t3 - t2 * two_t + t) + b.0.outer_mul(three_t2 - two_t3) + m1.outer_mul(t3 - t2) } macro_rules! impl_interpolate_simple { @@ -76,4 +212,3 @@ macro_rules! impl_interpolate_via { impl_interpolate_via!(f32, f64); impl_interpolate_via!(f64, f32); - diff --git a/src/lib.rs b/src/lib.rs index 15031c2..2ca01c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,6 +97,8 @@ #![cfg_attr(not(feature = "std"), feature(alloc))] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] +#[cfg(not(feature = "std"))] extern crate alloc; + #[cfg(feature = "impl-cgmath")] mod cgmath; pub mod interpolate; pub mod interpolation; diff --git a/src/nalgebra.rs b/src/nalgebra.rs index cccd27d..35dcea0 100644 --- a/src/nalgebra.rs +++ b/src/nalgebra.rs @@ -1,36 +1,63 @@ -use crate::Interpolate; +use alga::general::{ClosedAdd, ClosedDiv, ClosedMul, ClosedSub}; +use nalgebra::{DefaultAllocator, DimName, Point, Scalar, Vector, Vector1, Vector2, Vector3, Vector4, Vector5, Vector6}; +use nalgebra::allocator::Allocator; +use num_traits as nt; +use std::ops::Mul; - use nalgebra as na; +use crate::interpolate::{Interpolate, Linear, Additive, One, cubic_hermite_def}; -use num_traits::Float; - -macro_rules! impl_interpolate_na_vector { +macro_rules! impl_interpolate_vector { ($($t:tt)*) => { - impl Interpolate for $($t)* where T: Float, V: na::Scalar + Interpolate { + // implement Linear + impl Linear for $($t)* where T: Scalar + ClosedMul + ClosedDiv { + fn outer_mul(self, t: T) -> Self { + self * t + } + + fn outer_div(self, t: T) -> Self { + self / t + } + } + + impl Interpolate for $($t)* + where Self: Linear, + T: Additive + One + Mul, + V: nt::One + + nt::Zero + + Additive + + Scalar + + ClosedAdd + + ClosedMul + + ClosedSub + + Interpolate { fn lerp(a: Self, b: Self, t: T) -> Self { - na::Vector::zip_map(&a, &b, |c1, c2| Interpolate::lerp(c1, c2, t)) + Vector::zip_map(&a, &b, |c1, c2| Interpolate::lerp(c1, c2, t)) + } + + 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_na_vector!(na::Vector1); -impl_interpolate_na_vector!(na::Vector2); -impl_interpolate_na_vector!(na::Vector3); -impl_interpolate_na_vector!(na::Vector4); -impl_interpolate_na_vector!(na::Vector5); -impl_interpolate_na_vector!(na::Vector6); +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 Interpolate for na::Point -where D: na::DimName, - na::DefaultAllocator: na::allocator::Allocator, - >::Buffer: Copy, - N: na::Scalar + Interpolate, - T: Float { - fn lerp(a: Self, b: Self, t: T) -> Self { - // The 'coords' of a point is just a vector, so we can interpolate component-wise - // over these vectors. - let coords = na::Vector::zip_map(&a.coords, &b.coords, |c1, c2| Interpolate::lerp(c1, c2, t)); - na::Point::from(coords) +impl Linear for Point +where D: DimName, + DefaultAllocator: Allocator, + >::Buffer: Copy, + T: Scalar + ClosedDiv + ClosedMul { + fn outer_mul(self, t: T) -> Self { + self * t + } + + fn outer_div(self, t: T) -> Self { + self / t } } diff --git a/src/spline.rs b/src/spline.rs index e0a6478..f4e7f16 100644 --- a/src/spline.rs +++ b/src/spline.rs @@ -1,14 +1,14 @@ #[cfg(feature = "serialization")] use serde_derive::{Deserialize, Serialize}; -#[cfg(feature = "std")] use std::cmp::Ordering; -#[cfg(not(feature = "std"))] use core::cmp::Ordering; #[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; -use crate::interpolate::Interpolate; +use crate::interpolate::{Interpolate, Additive, One, Trigo}; use crate::interpolation::Interpolation; use crate::key::Key; -use num_traits::{Float, FloatConst}; - /// Spline curve used to provide interpolation between control points (keys). #[derive(Debug, Clone)] #[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))] @@ -53,7 +53,7 @@ impl Spline { /// sampling impossible. For instance, `Interpolate::CatmullRom` requires *four* keys. If you’re /// 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 where T: Float + FloatConst, V: Interpolate { + pub fn sample(&self, t: T) -> Option where T: Additive + One + Trigo + Mul + Div, V: Interpolate { let keys = &self.0; let i = search_lower_cp(keys, t)?; let cp0 = &keys[i]; @@ -76,7 +76,7 @@ impl Spline { let two_t = T::one() + T::one(); let cp1 = &keys[i+1]; let nt = normalize_time(t, cp0, cp1); - let cos_nt = (T::one() - (nt * T::PI()).cos()) / two_t; + let cos_nt = (T::one() - (nt * T::pi()).cos()) / two_t; Some(Interpolate::lerp(cp0.value, cp1.value, cos_nt)) } @@ -108,7 +108,7 @@ impl Spline { /// # Error /// /// This function returns `None` if you have no key. - pub fn clamped_sample(&self, t: T) -> Option where T: Float + FloatConst, V: Interpolate { + pub fn clamped_sample(&self, t: T) -> Option where T: Additive + One + Trigo + Mul + Div, V: Interpolate { if self.0.is_empty() { return None; } @@ -136,7 +136,7 @@ pub(crate) fn normalize_time( t: T, cp: &Key, cp1: &Key -) -> T where T: Float { +) -> T where T: Additive + Div { assert!(cp1.t != cp.t, "overlapping keys"); (t - cp.t) / (cp1.t - cp.t) }