pub mod adaptors;
pub mod cores;
pub mod derivatives;
pub mod easing;
pub mod interval;
pub mod iterable;
#[cfg(feature = "alloc")]
pub mod sample_curves;
pub use adaptors::*;
pub use easing::*;
pub use interval::{interval, Interval};
#[cfg(feature = "alloc")]
pub use {
cores::{EvenCore, UnevenCore},
sample_curves::*,
};
use crate::VectorSpace;
use core::{marker::PhantomData, ops::Deref};
use interval::InvalidIntervalError;
use thiserror::Error;
#[cfg(feature = "alloc")]
use {crate::StableInterpolate, itertools::Itertools};
pub trait Curve<T> {
fn domain(&self) -> Interval;
fn sample_unchecked(&self, t: f32) -> T;
fn sample(&self, t: f32) -> Option<T> {
match self.domain().contains(t) {
true => Some(self.sample_unchecked(t)),
false => None,
}
}
fn sample_clamped(&self, t: f32) -> T {
let t = self.domain().clamp(t);
self.sample_unchecked(t)
}
}
impl<T, C, D> Curve<T> for D
where
C: Curve<T> + ?Sized,
D: Deref<Target = C>,
{
fn domain(&self) -> Interval {
<C as Curve<T>>::domain(self)
}
fn sample_unchecked(&self, t: f32) -> T {
<C as Curve<T>>::sample_unchecked(self, t)
}
}
pub trait CurveExt<T>: Curve<T> + Sized {
fn sample_iter(&self, iter: impl IntoIterator<Item = f32>) -> impl Iterator<Item = Option<T>> {
iter.into_iter().map(|t| self.sample(t))
}
fn sample_iter_unchecked(
&self,
iter: impl IntoIterator<Item = f32>,
) -> impl Iterator<Item = T> {
iter.into_iter().map(|t| self.sample_unchecked(t))
}
fn sample_iter_clamped(&self, iter: impl IntoIterator<Item = f32>) -> impl Iterator<Item = T> {
iter.into_iter().map(|t| self.sample_clamped(t))
}
#[must_use]
fn map<S, F>(self, f: F) -> MapCurve<T, S, Self, F>
where
F: Fn(T) -> S,
{
MapCurve {
preimage: self,
f,
_phantom: PhantomData,
}
}
#[must_use]
fn reparametrize<F>(self, domain: Interval, f: F) -> ReparamCurve<T, Self, F>
where
F: Fn(f32) -> f32,
{
ReparamCurve {
domain,
base: self,
f,
_phantom: PhantomData,
}
}
fn reparametrize_linear(
self,
domain: Interval,
) -> Result<LinearReparamCurve<T, Self>, LinearReparamError> {
if !self.domain().is_bounded() {
return Err(LinearReparamError::SourceCurveUnbounded);
}
if !domain.is_bounded() {
return Err(LinearReparamError::TargetIntervalUnbounded);
}
Ok(LinearReparamCurve {
base: self,
new_domain: domain,
_phantom: PhantomData,
})
}
#[must_use]
fn reparametrize_by_curve<C>(self, other: C) -> CurveReparamCurve<T, Self, C>
where
C: Curve<f32>,
{
CurveReparamCurve {
base: self,
reparam_curve: other,
_phantom: PhantomData,
}
}
#[must_use]
fn graph(self) -> GraphCurve<T, Self> {
GraphCurve {
base: self,
_phantom: PhantomData,
}
}
fn zip<S, C>(self, other: C) -> Result<ZipCurve<T, S, Self, C>, InvalidIntervalError>
where
C: Curve<S> + Sized,
{
let domain = self.domain().intersect(other.domain())?;
Ok(ZipCurve {
domain,
first: self,
second: other,
_phantom: PhantomData,
})
}
fn chain<C>(self, other: C) -> Result<ChainCurve<T, Self, C>, ChainError>
where
C: Curve<T>,
{
if !self.domain().has_finite_end() {
return Err(ChainError::FirstEndInfinite);
}
if !other.domain().has_finite_start() {
return Err(ChainError::SecondStartInfinite);
}
Ok(ChainCurve {
first: self,
second: other,
_phantom: PhantomData,
})
}
fn reverse(self) -> Result<ReverseCurve<T, Self>, ReverseError> {
self.domain()
.is_bounded()
.then(|| ReverseCurve {
curve: self,
_phantom: PhantomData,
})
.ok_or(ReverseError::SourceDomainEndInfinite)
}
fn repeat(self, count: usize) -> Result<RepeatCurve<T, Self>, RepeatError> {
self.domain()
.is_bounded()
.then(|| {
let domain = Interval::new(
self.domain().start(),
self.domain().end() + self.domain().length() * count as f32,
)
.unwrap();
RepeatCurve {
domain,
curve: self,
_phantom: PhantomData,
}
})
.ok_or(RepeatError::SourceDomainUnbounded)
}
fn forever(self) -> Result<ForeverCurve<T, Self>, RepeatError> {
self.domain()
.is_bounded()
.then(|| ForeverCurve {
curve: self,
_phantom: PhantomData,
})
.ok_or(RepeatError::SourceDomainUnbounded)
}
fn ping_pong(self) -> Result<PingPongCurve<T, Self>, PingPongError> {
self.domain()
.has_finite_end()
.then(|| PingPongCurve {
curve: self,
_phantom: PhantomData,
})
.ok_or(PingPongError::SourceDomainEndInfinite)
}
fn chain_continue<C>(self, other: C) -> Result<ContinuationCurve<T, Self, C>, ChainError>
where
T: VectorSpace,
C: Curve<T>,
{
if !self.domain().has_finite_end() {
return Err(ChainError::FirstEndInfinite);
}
if !other.domain().has_finite_start() {
return Err(ChainError::SecondStartInfinite);
}
let offset = self.sample_unchecked(self.domain().end())
- other.sample_unchecked(self.domain().start());
Ok(ContinuationCurve {
first: self,
second: other,
offset,
_phantom: PhantomData,
})
}
fn samples(&self, samples: usize) -> Result<impl Iterator<Item = T>, ResamplingError> {
if samples < 2 {
return Err(ResamplingError::NotEnoughSamples(samples));
}
if !self.domain().is_bounded() {
return Err(ResamplingError::UnboundedDomain);
}
Ok(self
.domain()
.spaced_points(samples)
.unwrap()
.map(|t| self.sample_unchecked(t)))
}
fn by_ref(&self) -> &Self {
self
}
#[must_use]
fn flip<U, V>(self) -> impl Curve<(V, U)>
where
Self: CurveExt<(U, V)>,
{
self.map(|(u, v)| (v, u))
}
}
impl<C, T> CurveExt<T> for C where C: Curve<T> {}
#[cfg(feature = "alloc")]
pub trait CurveResampleExt<T>: Curve<T> {
fn resample<I>(
&self,
segments: usize,
interpolation: I,
) -> Result<SampleCurve<T, I>, ResamplingError>
where
I: Fn(&T, &T, f32) -> T,
{
let samples = self.samples(segments + 1)?.collect_vec();
Ok(SampleCurve {
core: EvenCore {
domain: self.domain(),
samples,
},
interpolation,
})
}
fn resample_auto(&self, segments: usize) -> Result<SampleAutoCurve<T>, ResamplingError>
where
T: StableInterpolate,
{
let samples = self.samples(segments + 1)?.collect_vec();
Ok(SampleAutoCurve {
core: EvenCore {
domain: self.domain(),
samples,
},
})
}
fn resample_uneven<I>(
&self,
sample_times: impl IntoIterator<Item = f32>,
interpolation: I,
) -> Result<UnevenSampleCurve<T, I>, ResamplingError>
where
I: Fn(&T, &T, f32) -> T,
{
let domain = self.domain();
let mut times = sample_times
.into_iter()
.filter(|t| t.is_finite() && domain.contains(*t))
.collect_vec();
times.sort_by(f32::total_cmp);
times.dedup();
if times.len() < 2 {
return Err(ResamplingError::NotEnoughSamples(times.len()));
}
let samples = times.iter().map(|t| self.sample_unchecked(*t)).collect();
Ok(UnevenSampleCurve {
core: UnevenCore { times, samples },
interpolation,
})
}
fn resample_uneven_auto(
&self,
sample_times: impl IntoIterator<Item = f32>,
) -> Result<UnevenSampleAutoCurve<T>, ResamplingError>
where
T: StableInterpolate,
{
let domain = self.domain();
let mut times = sample_times
.into_iter()
.filter(|t| t.is_finite() && domain.contains(*t))
.collect_vec();
times.sort_by(f32::total_cmp);
times.dedup();
if times.len() < 2 {
return Err(ResamplingError::NotEnoughSamples(times.len()));
}
let samples = times.iter().map(|t| self.sample_unchecked(*t)).collect();
Ok(UnevenSampleAutoCurve {
core: UnevenCore { times, samples },
})
}
}
#[cfg(feature = "alloc")]
impl<C, T> CurveResampleExt<T> for C where C: Curve<T> + ?Sized {}
#[derive(Debug, Error)]
#[error("Could not build a linear function to reparametrize this curve")]
pub enum LinearReparamError {
#[error("This curve has unbounded domain")]
SourceCurveUnbounded,
#[error("The target interval for reparameterization is unbounded")]
TargetIntervalUnbounded,
}
#[derive(Debug, Error)]
#[error("Could not reverse this curve")]
pub enum ReverseError {
#[error("This curve has an unbounded domain end")]
SourceDomainEndInfinite,
}
#[derive(Debug, Error)]
#[error("Could not repeat this curve")]
pub enum RepeatError {
#[error("This curve has an unbounded domain")]
SourceDomainUnbounded,
}
#[derive(Debug, Error)]
#[error("Could not ping pong this curve")]
pub enum PingPongError {
#[error("This curve has an unbounded domain end")]
SourceDomainEndInfinite,
}
#[derive(Debug, Error)]
#[error("Could not compose these curves together")]
pub enum ChainError {
#[error("The first curve's domain has an infinite end")]
FirstEndInfinite,
#[error("The second curve's domain has an infinite start")]
SecondStartInfinite,
}
#[derive(Debug, Error)]
#[error("Could not resample from this curve because of bad inputs")]
pub enum ResamplingError {
#[error("Not enough unique samples to construct resampled curve")]
NotEnoughSamples(usize),
#[error("Could not resample because this curve has unbounded domain")]
UnboundedDomain,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ops, Quat};
use alloc::vec::Vec;
use approx::{assert_abs_diff_eq, AbsDiffEq};
use core::f32::consts::TAU;
use glam::*;
#[test]
fn curve_can_be_made_into_an_object() {
let curve = ConstantCurve::new(Interval::UNIT, 42.0);
let curve: &dyn Curve<f64> = &curve;
assert_eq!(curve.sample(1.0), Some(42.0));
assert_eq!(curve.sample(2.0), None);
}
#[test]
fn constant_curves() {
let curve = ConstantCurve::new(Interval::EVERYWHERE, 5.0);
assert!(curve.sample_unchecked(-35.0) == 5.0);
let curve = ConstantCurve::new(Interval::UNIT, true);
assert!(curve.sample_unchecked(2.0));
assert!(curve.sample(2.0).is_none());
}
#[test]
fn function_curves() {
let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * t);
assert!(curve.sample_unchecked(2.0).abs_diff_eq(&4.0, f32::EPSILON));
assert!(curve.sample_unchecked(-3.0).abs_diff_eq(&9.0, f32::EPSILON));
let curve = FunctionCurve::new(interval(0.0, f32::INFINITY).unwrap(), ops::log2);
assert_eq!(curve.sample_unchecked(3.5), ops::log2(3.5));
assert!(curve.sample_unchecked(-1.0).is_nan());
assert!(curve.sample(-1.0).is_none());
}
#[test]
fn linear_curve() {
let start = Vec2::ZERO;
let end = Vec2::new(1.0, 2.0);
let curve = EasingCurve::new(start, end, EaseFunction::Linear);
let mid = (start + end) / 2.0;
[(0.0, start), (0.5, mid), (1.0, end)]
.into_iter()
.for_each(|(t, x)| {
assert!(curve.sample_unchecked(t).abs_diff_eq(x, f32::EPSILON));
});
}
#[test]
fn easing_curves_step() {
let start = Vec2::ZERO;
let end = Vec2::new(1.0, 2.0);
let curve = EasingCurve::new(start, end, EaseFunction::Steps(4, JumpAt::End));
[
(0.0, start),
(0.249, start),
(0.250, Vec2::new(0.25, 0.5)),
(0.499, Vec2::new(0.25, 0.5)),
(0.500, Vec2::new(0.5, 1.0)),
(0.749, Vec2::new(0.5, 1.0)),
(0.750, Vec2::new(0.75, 1.5)),
(1.0, end),
]
.into_iter()
.for_each(|(t, x)| {
assert!(curve.sample_unchecked(t).abs_diff_eq(x, f32::EPSILON));
});
}
#[test]
fn easing_curves_quadratic() {
let start = Vec2::ZERO;
let end = Vec2::new(1.0, 2.0);
let curve = EasingCurve::new(start, end, EaseFunction::QuadraticIn);
[
(0.0, start),
(0.25, Vec2::new(0.0625, 0.125)),
(0.5, Vec2::new(0.25, 0.5)),
(1.0, end),
]
.into_iter()
.for_each(|(t, x)| {
assert!(curve.sample_unchecked(t).abs_diff_eq(x, f32::EPSILON),);
});
}
#[expect(
clippy::neg_multiply,
reason = "Clippy doesn't like this, but it's correct"
)]
#[test]
fn mapping() {
let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * 3.0 + 1.0);
let mapped_curve = curve.map(|x| x / 7.0);
assert_eq!(mapped_curve.sample_unchecked(3.5), (3.5 * 3.0 + 1.0) / 7.0);
assert_eq!(
mapped_curve.sample_unchecked(-1.0),
(-1.0 * 3.0 + 1.0) / 7.0
);
assert_eq!(mapped_curve.domain(), Interval::EVERYWHERE);
let curve = FunctionCurve::new(Interval::UNIT, |t| t * TAU);
let mapped_curve = curve.map(Quat::from_rotation_z);
assert_eq!(mapped_curve.sample_unchecked(0.0), Quat::IDENTITY);
assert!(mapped_curve.sample_unchecked(1.0).is_near_identity());
assert_eq!(mapped_curve.domain(), Interval::UNIT);
}
#[test]
fn reverse() {
let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let rev_curve = curve.reverse().unwrap();
assert_eq!(rev_curve.sample(-0.1), None);
assert_eq!(rev_curve.sample(0.0), Some(1.0 * 3.0 + 1.0));
assert_eq!(rev_curve.sample(0.5), Some(0.5 * 3.0 + 1.0));
assert_eq!(rev_curve.sample(1.0), Some(0.0 * 3.0 + 1.0));
assert_eq!(rev_curve.sample(1.1), None);
let curve = FunctionCurve::new(Interval::new(-2.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let rev_curve = curve.reverse().unwrap();
assert_eq!(rev_curve.sample(-2.1), None);
assert_eq!(rev_curve.sample(-2.0), Some(1.0 * 3.0 + 1.0));
assert_eq!(rev_curve.sample(-0.5), Some(-0.5 * 3.0 + 1.0));
assert_eq!(rev_curve.sample(1.0), Some(-2.0 * 3.0 + 1.0));
assert_eq!(rev_curve.sample(1.1), None);
}
#[test]
fn repeat() {
let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let repeat_curve = curve.by_ref().repeat(1).unwrap();
assert_eq!(repeat_curve.sample(-0.1), None);
assert_eq!(repeat_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(0.5), Some(0.5 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(0.99), Some(0.99 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(1.0), Some(1.0 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(1.01), Some(0.01 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(1.5), Some(0.5 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(1.99), Some(0.99 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(2.0), Some(1.0 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(2.01), None);
let repeat_curve = curve.by_ref().repeat(3).unwrap();
assert_eq!(repeat_curve.sample(2.0), Some(1.0 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(3.0), Some(1.0 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(4.0), Some(1.0 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(5.0), None);
let repeat_curve = curve.by_ref().forever().unwrap();
assert_eq!(repeat_curve.sample(-1.0), Some(1.0 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(2.0), Some(1.0 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(3.0), Some(1.0 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(4.0), Some(1.0 * 3.0 + 1.0));
assert_eq!(repeat_curve.sample(5.0), Some(1.0 * 3.0 + 1.0));
}
#[test]
fn ping_pong() {
let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let ping_pong_curve = curve.ping_pong().unwrap();
assert_eq!(ping_pong_curve.sample(-0.1), None);
assert_eq!(ping_pong_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));
assert_eq!(ping_pong_curve.sample(0.5), Some(0.5 * 3.0 + 1.0));
assert_eq!(ping_pong_curve.sample(1.0), Some(1.0 * 3.0 + 1.0));
assert_eq!(ping_pong_curve.sample(1.5), Some(0.5 * 3.0 + 1.0));
assert_eq!(ping_pong_curve.sample(2.0), Some(0.0 * 3.0 + 1.0));
assert_eq!(ping_pong_curve.sample(2.1), None);
let curve = FunctionCurve::new(Interval::new(-2.0, 2.0).unwrap(), |t| t * 3.0 + 1.0);
let ping_pong_curve = curve.ping_pong().unwrap();
assert_eq!(ping_pong_curve.sample(-2.1), None);
assert_eq!(ping_pong_curve.sample(-2.0), Some(-2.0 * 3.0 + 1.0));
assert_eq!(ping_pong_curve.sample(-0.5), Some(-0.5 * 3.0 + 1.0));
assert_eq!(ping_pong_curve.sample(2.0), Some(2.0 * 3.0 + 1.0));
assert_eq!(ping_pong_curve.sample(4.5), Some(-0.5 * 3.0 + 1.0));
assert_eq!(ping_pong_curve.sample(6.0), Some(-2.0 * 3.0 + 1.0));
assert_eq!(ping_pong_curve.sample(6.1), None);
}
#[test]
fn continue_chain() {
let first = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let second = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * t);
let c0_chain_curve = first.chain_continue(second).unwrap();
assert_eq!(c0_chain_curve.sample(-0.1), None);
assert_eq!(c0_chain_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));
assert_eq!(c0_chain_curve.sample(0.5), Some(0.5 * 3.0 + 1.0));
assert_eq!(c0_chain_curve.sample(1.0), Some(1.0 * 3.0 + 1.0));
assert_eq!(c0_chain_curve.sample(1.5), Some(1.0 * 3.0 + 1.0 + 0.25));
assert_eq!(c0_chain_curve.sample(2.0), Some(1.0 * 3.0 + 1.0 + 1.0));
assert_eq!(c0_chain_curve.sample(2.1), None);
}
#[test]
fn reparameterization() {
let curve = FunctionCurve::new(interval(1.0, f32::INFINITY).unwrap(), ops::log2);
let reparametrized_curve = curve
.by_ref()
.reparametrize(interval(0.0, f32::INFINITY).unwrap(), ops::exp2);
assert_abs_diff_eq!(reparametrized_curve.sample_unchecked(3.5), 3.5);
assert_abs_diff_eq!(reparametrized_curve.sample_unchecked(100.0), 100.0);
assert_eq!(
reparametrized_curve.domain(),
interval(0.0, f32::INFINITY).unwrap()
);
let reparametrized_curve = curve.by_ref().reparametrize(Interval::UNIT, |t| t + 1.0);
assert_abs_diff_eq!(reparametrized_curve.sample_unchecked(0.0), 0.0);
assert_abs_diff_eq!(reparametrized_curve.sample_unchecked(1.0), 1.0);
assert_eq!(reparametrized_curve.domain(), Interval::UNIT);
}
#[test]
fn multiple_maps() {
let curve = FunctionCurve::new(Interval::UNIT, ops::exp2);
let first_mapped = curve.map(ops::log2);
let second_mapped = first_mapped.map(|x| x * -2.0);
assert_abs_diff_eq!(second_mapped.sample_unchecked(0.0), 0.0);
assert_abs_diff_eq!(second_mapped.sample_unchecked(0.5), -1.0);
assert_abs_diff_eq!(second_mapped.sample_unchecked(1.0), -2.0);
}
#[test]
fn multiple_reparams() {
let curve = FunctionCurve::new(Interval::UNIT, ops::exp2);
let first_reparam = curve.reparametrize(interval(1.0, 2.0).unwrap(), ops::log2);
let second_reparam = first_reparam.reparametrize(Interval::UNIT, |t| t + 1.0);
assert_abs_diff_eq!(second_reparam.sample_unchecked(0.0), 1.0);
assert_abs_diff_eq!(second_reparam.sample_unchecked(0.5), 1.5);
assert_abs_diff_eq!(second_reparam.sample_unchecked(1.0), 2.0);
}
#[test]
fn resampling() {
let curve = FunctionCurve::new(interval(1.0, 4.0).unwrap(), ops::log2);
let nice_try = curve.by_ref().resample_auto(0);
assert!(nice_try.is_err());
let resampled_curve = curve.by_ref().resample_auto(100).unwrap();
for test_pt in curve.domain().spaced_points(101).unwrap() {
let expected = curve.sample_unchecked(test_pt);
assert_abs_diff_eq!(
resampled_curve.sample_unchecked(test_pt),
expected,
epsilon = 1e-6
);
}
let curve = FunctionCurve::new(interval(0.0, TAU).unwrap(), ops::cos);
let resampled_curve = curve.by_ref().resample_auto(1000).unwrap();
for test_pt in curve.domain().spaced_points(1001).unwrap() {
let expected = curve.sample_unchecked(test_pt);
assert_abs_diff_eq!(
resampled_curve.sample_unchecked(test_pt),
expected,
epsilon = 1e-6
);
}
}
#[test]
fn uneven_resampling() {
let curve = FunctionCurve::new(interval(0.0, f32::INFINITY).unwrap(), ops::exp);
let nice_try = curve.by_ref().resample_uneven_auto([1.0; 1]);
assert!(nice_try.is_err());
let sample_points = (0..100).map(|idx| idx as f32 * 0.1);
let resampled_curve = curve.by_ref().resample_uneven_auto(sample_points).unwrap();
for idx in 0..100 {
let test_pt = idx as f32 * 0.1;
let expected = curve.sample_unchecked(test_pt);
assert_eq!(resampled_curve.sample_unchecked(test_pt), expected);
}
assert_abs_diff_eq!(resampled_curve.domain().start(), 0.0);
assert_abs_diff_eq!(resampled_curve.domain().end(), 9.9, epsilon = 1e-6);
let curve = FunctionCurve::new(interval(1.0, f32::INFINITY).unwrap(), ops::log2);
let sample_points = (0..10).map(|idx| ops::exp2(idx as f32));
let resampled_curve = curve.by_ref().resample_uneven_auto(sample_points).unwrap();
for idx in 0..10 {
let test_pt = ops::exp2(idx as f32);
let expected = curve.sample_unchecked(test_pt);
assert_eq!(resampled_curve.sample_unchecked(test_pt), expected);
}
assert_abs_diff_eq!(resampled_curve.domain().start(), 1.0);
assert_abs_diff_eq!(resampled_curve.domain().end(), 512.0);
}
#[test]
fn sample_iterators() {
let times = [-0.5, 0.0, 0.5, 1.0, 1.5];
let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * 3.0 + 1.0);
let samples = curve.sample_iter_unchecked(times).collect::<Vec<_>>();
let [y0, y1, y2, y3, y4] = samples.try_into().unwrap();
assert_eq!(y0, -0.5 * 3.0 + 1.0);
assert_eq!(y1, 0.0 * 3.0 + 1.0);
assert_eq!(y2, 0.5 * 3.0 + 1.0);
assert_eq!(y3, 1.0 * 3.0 + 1.0);
assert_eq!(y4, 1.5 * 3.0 + 1.0);
let finite_curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let samples = finite_curve.sample_iter(times).collect::<Vec<_>>();
let [y0, y1, y2, y3, y4] = samples.try_into().unwrap();
assert_eq!(y0, None);
assert_eq!(y1, Some(0.0 * 3.0 + 1.0));
assert_eq!(y2, Some(0.5 * 3.0 + 1.0));
assert_eq!(y3, Some(1.0 * 3.0 + 1.0));
assert_eq!(y4, None);
let samples = finite_curve.sample_iter_clamped(times).collect::<Vec<_>>();
let [y0, y1, y2, y3, y4] = samples.try_into().unwrap();
assert_eq!(y0, 0.0 * 3.0 + 1.0);
assert_eq!(y1, 0.0 * 3.0 + 1.0);
assert_eq!(y2, 0.5 * 3.0 + 1.0);
assert_eq!(y3, 1.0 * 3.0 + 1.0);
assert_eq!(y4, 1.0 * 3.0 + 1.0);
}
}