bevy_math/curve/cores.rs
1//! Core data structures to be used internally in Curve implementations, encapsulating storage
2//! and access patterns for reuse.
3//!
4//! The `Core` types here expose their fields publicly so that it is easier to manipulate and
5//! extend them, but in doing so, you must maintain the invariants of those fields yourself. The
6//! provided methods all maintain the invariants, so this is only a concern if you manually mutate
7//! the fields.
8
9use super::interval::Interval;
10use core::fmt::Debug;
11use derive_more::derive::{Display, Error};
12use itertools::Itertools;
13
14#[cfg(feature = "bevy_reflect")]
15use bevy_reflect::Reflect;
16
17/// This type expresses the relationship of a value to a fixed collection of values. It is a kind
18/// of summary used intermediately by sampling operations.
19#[derive(Debug, Copy, Clone, PartialEq)]
20#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
21#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
22pub enum InterpolationDatum<T> {
23 /// This value lies exactly on a value in the family.
24 Exact(T),
25
26 /// This value is off the left tail of the family; the inner value is the family's leftmost.
27 LeftTail(T),
28
29 /// This value is off the right tail of the family; the inner value is the family's rightmost.
30 RightTail(T),
31
32 /// This value lies on the interior, in between two points, with a third parameter expressing
33 /// the interpolation factor between the two.
34 Between(T, T, f32),
35}
36
37impl<T> InterpolationDatum<T> {
38 /// Map all values using a given function `f`, leaving the interpolation parameters in any
39 /// [`Between`] variants unchanged.
40 ///
41 /// [`Between`]: `InterpolationDatum::Between`
42 #[must_use]
43 pub fn map<S>(self, f: impl Fn(T) -> S) -> InterpolationDatum<S> {
44 match self {
45 InterpolationDatum::Exact(v) => InterpolationDatum::Exact(f(v)),
46 InterpolationDatum::LeftTail(v) => InterpolationDatum::LeftTail(f(v)),
47 InterpolationDatum::RightTail(v) => InterpolationDatum::RightTail(f(v)),
48 InterpolationDatum::Between(u, v, s) => InterpolationDatum::Between(f(u), f(v), s),
49 }
50 }
51}
52
53/// The data core of a curve derived from evenly-spaced samples. The intention is to use this
54/// in addition to explicit or inferred interpolation information in user-space in order to
55/// implement curves using [`domain`] and [`sample_with`].
56///
57/// The internals are made transparent to give curve authors freedom, but [the provided constructor]
58/// enforces the required invariants, and the methods maintain those invariants.
59///
60/// [the provided constructor]: EvenCore::new
61/// [`domain`]: EvenCore::domain
62/// [`sample_with`]: EvenCore::sample_with
63///
64/// # Example
65/// ```rust
66/// # use bevy_math::curve::*;
67/// # use bevy_math::curve::cores::*;
68/// // Let's make a curve that interpolates evenly spaced samples using either linear interpolation
69/// // or step "interpolation" — i.e. just using the most recent sample as the source of truth.
70/// enum InterpolationMode {
71/// Linear,
72/// Step,
73/// }
74///
75/// // Linear interpolation mode is driven by a trait.
76/// trait LinearInterpolate {
77/// fn lerp(&self, other: &Self, t: f32) -> Self;
78/// }
79///
80/// // Step interpolation just uses an explicit function.
81/// fn step<T: Clone>(first: &T, second: &T, t: f32) -> T {
82/// if t >= 1.0 {
83/// second.clone()
84/// } else {
85/// first.clone()
86/// }
87/// }
88///
89/// // Omitted: Implementing `LinearInterpolate` on relevant types; e.g. `f32`, `Vec3`, and so on.
90///
91/// // The curve itself uses `EvenCore` to hold the evenly-spaced samples, and the `sample_with`
92/// // function will do all the work of interpolating once given a function to do it with.
93/// struct MyCurve<T> {
94/// core: EvenCore<T>,
95/// interpolation_mode: InterpolationMode,
96/// }
97///
98/// impl<T> Curve<T> for MyCurve<T>
99/// where
100/// T: LinearInterpolate + Clone,
101/// {
102/// fn domain(&self) -> Interval {
103/// self.core.domain()
104/// }
105///
106/// fn sample_unchecked(&self, t: f32) -> T {
107/// // To sample this curve, check the interpolation mode and dispatch accordingly.
108/// match self.interpolation_mode {
109/// InterpolationMode::Linear => self.core.sample_with(t, <T as LinearInterpolate>::lerp),
110/// InterpolationMode::Step => self.core.sample_with(t, step),
111/// }
112/// }
113/// }
114/// ```
115#[derive(Debug, Clone, PartialEq)]
116#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
117#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
118pub struct EvenCore<T> {
119 /// The domain over which the samples are taken, which corresponds to the domain of the curve
120 /// formed by interpolating them.
121 ///
122 /// # Invariants
123 /// This must always be a bounded interval; i.e. its endpoints must be finite.
124 pub domain: Interval,
125
126 /// The samples that are interpolated to extract values.
127 ///
128 /// # Invariants
129 /// This must always have a length of at least 2.
130 pub samples: Vec<T>,
131}
132
133/// An error indicating that an [`EvenCore`] could not be constructed.
134#[derive(Debug, Error, Display)]
135#[display("Could not construct an EvenCore")]
136pub enum EvenCoreError {
137 /// Not enough samples were provided.
138 #[display("Need at least two samples to create an EvenCore, but {samples} were provided")]
139 NotEnoughSamples {
140 /// The number of samples that were provided.
141 samples: usize,
142 },
143
144 /// Unbounded domains are not compatible with `EvenCore`.
145 #[display("Cannot create a EvenCore over an unbounded domain")]
146 UnboundedDomain,
147}
148
149impl<T> EvenCore<T> {
150 /// Create a new [`EvenCore`] from the specified `domain` and `samples`. The samples are
151 /// regarded to be evenly spaced within the given domain interval, so that the outermost
152 /// samples form the boundary of that interval. An error is returned if there are not at
153 /// least 2 samples or if the given domain is unbounded.
154 #[inline]
155 pub fn new(
156 domain: Interval,
157 samples: impl IntoIterator<Item = T>,
158 ) -> Result<Self, EvenCoreError> {
159 let samples: Vec<T> = samples.into_iter().collect();
160 if samples.len() < 2 {
161 return Err(EvenCoreError::NotEnoughSamples {
162 samples: samples.len(),
163 });
164 }
165 if !domain.is_bounded() {
166 return Err(EvenCoreError::UnboundedDomain);
167 }
168
169 Ok(EvenCore { domain, samples })
170 }
171
172 /// The domain of the curve derived from this core.
173 #[inline]
174 pub const fn domain(&self) -> Interval {
175 self.domain
176 }
177
178 /// Obtain a value from the held samples using the given `interpolation` to interpolate
179 /// between adjacent samples.
180 ///
181 /// The interpolation takes two values by reference together with a scalar parameter and
182 /// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
183 /// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
184 #[inline]
185 pub fn sample_with<I>(&self, t: f32, interpolation: I) -> T
186 where
187 T: Clone,
188 I: Fn(&T, &T, f32) -> T,
189 {
190 match even_interp(self.domain, self.samples.len(), t) {
191 InterpolationDatum::Exact(idx)
192 | InterpolationDatum::LeftTail(idx)
193 | InterpolationDatum::RightTail(idx) => self.samples[idx].clone(),
194 InterpolationDatum::Between(lower_idx, upper_idx, s) => {
195 interpolation(&self.samples[lower_idx], &self.samples[upper_idx], s)
196 }
197 }
198 }
199
200 /// Given a time `t`, obtain a [`InterpolationDatum`] which governs how interpolation might recover
201 /// a sample at time `t`. For example, when a [`Between`] value is returned, its contents can
202 /// be used to interpolate between the two contained values with the given parameter. The other
203 /// variants give additional context about where the value is relative to the family of samples.
204 ///
205 /// [`Between`]: `InterpolationDatum::Between`
206 pub fn sample_interp(&self, t: f32) -> InterpolationDatum<&T> {
207 even_interp(self.domain, self.samples.len(), t).map(|idx| &self.samples[idx])
208 }
209
210 /// Like [`sample_interp`], but the returned values include the sample times. This can be
211 /// useful when sample interpolation is not scale-invariant.
212 ///
213 /// [`sample_interp`]: EvenCore::sample_interp
214 pub fn sample_interp_timed(&self, t: f32) -> InterpolationDatum<(f32, &T)> {
215 let segment_len = self.domain.length() / (self.samples.len() - 1) as f32;
216 even_interp(self.domain, self.samples.len(), t).map(|idx| {
217 (
218 self.domain.start() + segment_len * idx as f32,
219 &self.samples[idx],
220 )
221 })
222 }
223}
224
225/// Given a domain and a number of samples taken over that interval, return an [`InterpolationDatum`]
226/// that governs how samples are extracted relative to the stored data.
227///
228/// `domain` must be a bounded interval (i.e. `domain.is_bounded() == true`).
229///
230/// `samples` must be at least 2.
231///
232/// This function will never panic, but it may return invalid indices if its assumptions are violated.
233pub fn even_interp(domain: Interval, samples: usize, t: f32) -> InterpolationDatum<usize> {
234 let subdivs = samples - 1;
235 let step = domain.length() / subdivs as f32;
236 let t_shifted = t - domain.start();
237 let steps_taken = t_shifted / step;
238
239 if steps_taken <= 0.0 {
240 // To the left side of all the samples.
241 InterpolationDatum::LeftTail(0)
242 } else if steps_taken >= subdivs as f32 {
243 // To the right side of all the samples
244 InterpolationDatum::RightTail(samples - 1)
245 } else {
246 let lower_index = steps_taken.floor() as usize;
247 // This upper index is always valid because `steps_taken` is a finite value
248 // strictly less than `samples - 1`, so its floor is at most `samples - 2`
249 let upper_index = lower_index + 1;
250 let s = steps_taken.fract();
251 InterpolationDatum::Between(lower_index, upper_index, s)
252 }
253}
254
255/// The data core of a curve defined by unevenly-spaced samples or keyframes. The intention is to
256/// use this in concert with implicitly or explicitly-defined interpolation in user-space in
257/// order to implement the curve interface using [`domain`] and [`sample_with`].
258///
259/// The internals are made transparent to give curve authors freedom, but [the provided constructor]
260/// enforces the required invariants, and the methods maintain those invariants.
261///
262/// # Example
263/// ```rust
264/// # use bevy_math::curve::*;
265/// # use bevy_math::curve::cores::*;
266/// // Let's make a curve formed by interpolating rotations.
267/// // We'll support two common modes of interpolation:
268/// // - Normalized linear: First do linear interpolation, then normalize to get a valid rotation.
269/// // - Spherical linear: Interpolate through valid rotations with constant angular velocity.
270/// enum InterpolationMode {
271/// NormalizedLinear,
272/// SphericalLinear,
273/// }
274///
275/// // Our interpolation modes will be driven by traits.
276/// trait NormalizedLinearInterpolate {
277/// fn nlerp(&self, other: &Self, t: f32) -> Self;
278/// }
279///
280/// trait SphericalLinearInterpolate {
281/// fn slerp(&self, other: &Self, t: f32) -> Self;
282/// }
283///
284/// // Omitted: These traits would be implemented for `Rot2`, `Quat`, and other rotation representations.
285///
286/// // The curve itself just needs to use the curve core for keyframes, `UnevenCore`, which handles
287/// // everything except for the explicit interpolation used.
288/// struct RotationCurve<T> {
289/// core: UnevenCore<T>,
290/// interpolation_mode: InterpolationMode,
291/// }
292///
293/// impl<T> Curve<T> for RotationCurve<T>
294/// where
295/// T: NormalizedLinearInterpolate + SphericalLinearInterpolate + Clone,
296/// {
297/// fn domain(&self) -> Interval {
298/// self.core.domain()
299/// }
300///
301/// fn sample_unchecked(&self, t: f32) -> T {
302/// // To sample the curve, we just look at the interpolation mode and
303/// // dispatch accordingly.
304/// match self.interpolation_mode {
305/// InterpolationMode::NormalizedLinear =>
306/// self.core.sample_with(t, <T as NormalizedLinearInterpolate>::nlerp),
307/// InterpolationMode::SphericalLinear =>
308/// self.core.sample_with(t, <T as SphericalLinearInterpolate>::slerp),
309/// }
310/// }
311/// }
312/// ```
313///
314/// [`domain`]: UnevenCore::domain
315/// [`sample_with`]: UnevenCore::sample_with
316/// [the provided constructor]: UnevenCore::new
317#[derive(Debug, Clone)]
318#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
319#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
320pub struct UnevenCore<T> {
321 /// The times for the samples of this curve.
322 ///
323 /// # Invariants
324 /// This must always have a length of at least 2, be sorted, and have no
325 /// duplicated or non-finite times.
326 pub times: Vec<f32>,
327
328 /// The samples corresponding to the times for this curve.
329 ///
330 /// # Invariants
331 /// This must always have the same length as `times`.
332 pub samples: Vec<T>,
333}
334
335/// An error indicating that an [`UnevenCore`] could not be constructed.
336#[derive(Debug, Error, Display)]
337#[display("Could not construct an UnevenCore")]
338pub enum UnevenCoreError {
339 /// Not enough samples were provided.
340 #[display(
341 "Need at least two unique samples to create an UnevenCore, but {samples} were provided"
342 )]
343 NotEnoughSamples {
344 /// The number of samples that were provided.
345 samples: usize,
346 },
347}
348
349impl<T> UnevenCore<T> {
350 /// Create a new [`UnevenCore`]. The given samples are filtered to finite times and
351 /// sorted internally; if there are not at least 2 valid timed samples, an error will be
352 /// returned.
353 pub fn new(timed_samples: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {
354 // Filter out non-finite sample times first so they don't interfere with sorting/deduplication.
355 let mut timed_samples = timed_samples
356 .into_iter()
357 .filter(|(t, _)| t.is_finite())
358 .collect_vec();
359 timed_samples
360 // Using `total_cmp` is fine because no NANs remain and because deduplication uses
361 // `PartialEq` anyway (so -0.0 and 0.0 will be considered equal later regardless).
362 .sort_by(|(t0, _), (t1, _)| t0.total_cmp(t1));
363 timed_samples.dedup_by_key(|(t, _)| *t);
364
365 if timed_samples.len() < 2 {
366 return Err(UnevenCoreError::NotEnoughSamples {
367 samples: timed_samples.len(),
368 });
369 }
370
371 let (times, samples): (Vec<f32>, Vec<T>) = timed_samples.into_iter().unzip();
372 Ok(UnevenCore { times, samples })
373 }
374
375 /// The domain of the curve derived from this core.
376 ///
377 /// # Panics
378 /// This method may panic if the type's invariants aren't satisfied.
379 #[inline]
380 pub fn domain(&self) -> Interval {
381 let start = self.times.first().unwrap();
382 let end = self.times.last().unwrap();
383 Interval::new(*start, *end).unwrap()
384 }
385
386 /// Obtain a value from the held samples using the given `interpolation` to interpolate
387 /// between adjacent samples.
388 ///
389 /// The interpolation takes two values by reference together with a scalar parameter and
390 /// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
391 /// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
392 #[inline]
393 pub fn sample_with<I>(&self, t: f32, interpolation: I) -> T
394 where
395 T: Clone,
396 I: Fn(&T, &T, f32) -> T,
397 {
398 match uneven_interp(&self.times, t) {
399 InterpolationDatum::Exact(idx)
400 | InterpolationDatum::LeftTail(idx)
401 | InterpolationDatum::RightTail(idx) => self.samples[idx].clone(),
402 InterpolationDatum::Between(lower_idx, upper_idx, s) => {
403 interpolation(&self.samples[lower_idx], &self.samples[upper_idx], s)
404 }
405 }
406 }
407
408 /// Given a time `t`, obtain a [`InterpolationDatum`] which governs how interpolation might recover
409 /// a sample at time `t`. For example, when a [`Between`] value is returned, its contents can
410 /// be used to interpolate between the two contained values with the given parameter. The other
411 /// variants give additional context about where the value is relative to the family of samples.
412 ///
413 /// [`Between`]: `InterpolationDatum::Between`
414 pub fn sample_interp(&self, t: f32) -> InterpolationDatum<&T> {
415 uneven_interp(&self.times, t).map(|idx| &self.samples[idx])
416 }
417
418 /// Like [`sample_interp`], but the returned values include the sample times. This can be
419 /// useful when sample interpolation is not scale-invariant.
420 ///
421 /// [`sample_interp`]: UnevenCore::sample_interp
422 pub fn sample_interp_timed(&self, t: f32) -> InterpolationDatum<(f32, &T)> {
423 uneven_interp(&self.times, t).map(|idx| (self.times[idx], &self.samples[idx]))
424 }
425
426 /// This core, but with the sample times moved by the map `f`.
427 /// In principle, when `f` is monotone, this is equivalent to [`Curve::reparametrize`],
428 /// but the function inputs to each are inverses of one another.
429 ///
430 /// The samples are re-sorted by time after mapping and deduplicated by output time, so
431 /// the function `f` should generally be injective over the set of sample times, otherwise
432 /// data will be deleted.
433 ///
434 /// [`Curve::reparametrize`]: crate::curve::Curve::reparametrize
435 #[must_use]
436 pub fn map_sample_times(mut self, f: impl Fn(f32) -> f32) -> UnevenCore<T> {
437 let mut timed_samples = self
438 .times
439 .into_iter()
440 .map(f)
441 .zip(self.samples)
442 .collect_vec();
443 timed_samples.sort_by(|(t1, _), (t2, _)| t1.total_cmp(t2));
444 timed_samples.dedup_by_key(|(t, _)| *t);
445 (self.times, self.samples) = timed_samples.into_iter().unzip();
446 self
447 }
448}
449
450/// The data core of a curve using uneven samples (i.e. keyframes), where each sample time
451/// yields some fixed number of values — the [sampling width]. This may serve as storage for
452/// curves that yield vectors or iterators, and in some cases, it may be useful for cache locality
453/// if the sample type can effectively be encoded as a fixed-length slice of values.
454///
455/// [sampling width]: ChunkedUnevenCore::width
456#[derive(Debug, Clone)]
457#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
458#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
459pub struct ChunkedUnevenCore<T> {
460 /// The times, one for each sample.
461 ///
462 /// # Invariants
463 /// This must always have a length of at least 2, be sorted, and have no duplicated or
464 /// non-finite times.
465 pub times: Vec<f32>,
466
467 /// The values that are used in sampling. Each width-worth of these correspond to a single sample.
468 ///
469 /// # Invariants
470 /// The length of this vector must always be some fixed integer multiple of that of `times`.
471 pub values: Vec<T>,
472}
473
474/// An error that indicates that a [`ChunkedUnevenCore`] could not be formed.
475#[derive(Debug, Error, Display)]
476#[display("Could not create a ChunkedUnevenCore")]
477pub enum ChunkedUnevenCoreError {
478 /// The width of a `ChunkedUnevenCore` cannot be zero.
479 #[display("Chunk width must be at least 1")]
480 ZeroWidth,
481
482 /// At least two sample times are necessary to interpolate in `ChunkedUnevenCore`.
483 #[display(
484 "Need at least two unique samples to create a ChunkedUnevenCore, but {samples} were provided"
485 )]
486 NotEnoughSamples {
487 /// The number of samples that were provided.
488 samples: usize,
489 },
490
491 /// The length of the value buffer is supposed to be the `width` times the number of samples.
492 #[display("Expected {expected} total values based on width, but {actual} were provided")]
493 MismatchedLengths {
494 /// The expected length of the value buffer.
495 expected: usize,
496 /// The actual length of the value buffer.
497 actual: usize,
498 },
499
500 /// Tried to infer the width, but the ratio of lengths wasn't an integer, so no such length exists.
501 #[display("The length of the list of values ({values_len}) was not divisible by that of the list of times ({times_len})")]
502 NonDivisibleLengths {
503 /// The length of the value buffer.
504 values_len: usize,
505 /// The length of the time buffer.
506 times_len: usize,
507 },
508}
509
510impl<T> ChunkedUnevenCore<T> {
511 /// Create a new [`ChunkedUnevenCore`]. The given `times` are sorted, filtered to finite times,
512 /// and deduplicated. See the [type-level documentation] for more information about this type.
513 ///
514 /// Produces an error in any of the following circumstances:
515 /// - `width` is zero.
516 /// - `times` has less than `2` unique valid entries.
517 /// - `values` has the incorrect length relative to `times`.
518 ///
519 /// [type-level documentation]: ChunkedUnevenCore
520 pub fn new(
521 times: impl IntoIterator<Item = f32>,
522 values: impl IntoIterator<Item = T>,
523 width: usize,
524 ) -> Result<Self, ChunkedUnevenCoreError> {
525 let times = times.into_iter().collect_vec();
526 let values = values.into_iter().collect_vec();
527
528 if width == 0 {
529 return Err(ChunkedUnevenCoreError::ZeroWidth);
530 }
531
532 let times = filter_sort_dedup_times(times);
533
534 if times.len() < 2 {
535 return Err(ChunkedUnevenCoreError::NotEnoughSamples {
536 samples: times.len(),
537 });
538 }
539
540 if values.len() != times.len() * width {
541 return Err(ChunkedUnevenCoreError::MismatchedLengths {
542 expected: times.len() * width,
543 actual: values.len(),
544 });
545 }
546
547 Ok(Self { times, values })
548 }
549
550 /// Create a new [`ChunkedUnevenCore`], inferring the width from the sizes of the inputs.
551 /// The given `times` are sorted, filtered to finite times, and deduplicated. See the
552 /// [type-level documentation] for more information about this type. Prefer using [`new`]
553 /// if possible, since that constructor has richer error checking.
554 ///
555 /// Produces an error in any of the following circumstances:
556 /// - `values` has length zero.
557 /// - `times` has less than `2` unique valid entries.
558 /// - The length of `values` is not divisible by that of `times` (once sorted, filtered,
559 /// and deduplicated).
560 ///
561 /// The [width] is implicitly taken to be the length of `values` divided by that of `times`
562 /// (once sorted, filtered, and deduplicated).
563 ///
564 /// [type-level documentation]: ChunkedUnevenCore
565 /// [`new`]: ChunkedUnevenCore::new
566 /// [width]: ChunkedUnevenCore::width
567 pub fn new_width_inferred(
568 times: impl IntoIterator<Item = f32>,
569 values: impl IntoIterator<Item = T>,
570 ) -> Result<Self, ChunkedUnevenCoreError> {
571 let times = times.into_iter().collect_vec();
572 let values = values.into_iter().collect_vec();
573
574 let times = filter_sort_dedup_times(times);
575
576 if times.len() < 2 {
577 return Err(ChunkedUnevenCoreError::NotEnoughSamples {
578 samples: times.len(),
579 });
580 }
581
582 if values.len() % times.len() != 0 {
583 return Err(ChunkedUnevenCoreError::NonDivisibleLengths {
584 values_len: values.len(),
585 times_len: times.len(),
586 });
587 }
588
589 if values.is_empty() {
590 return Err(ChunkedUnevenCoreError::ZeroWidth);
591 }
592
593 Ok(Self { times, values })
594 }
595
596 /// The domain of the curve derived from this core.
597 ///
598 /// # Panics
599 /// This may panic if this type's invariants aren't met.
600 #[inline]
601 pub fn domain(&self) -> Interval {
602 let start = self.times.first().unwrap();
603 let end = self.times.last().unwrap();
604 Interval::new(*start, *end).unwrap()
605 }
606
607 /// The sample width: the number of values that are contained in each sample.
608 #[inline]
609 pub fn width(&self) -> usize {
610 self.values.len() / self.times.len()
611 }
612
613 /// Given a time `t`, obtain a [`InterpolationDatum`] which governs how interpolation might recover
614 /// a sample at time `t`. For example, when a [`Between`] value is returned, its contents can
615 /// be used to interpolate between the two contained values with the given parameter. The other
616 /// variants give additional context about where the value is relative to the family of samples.
617 ///
618 /// [`Between`]: `InterpolationDatum::Between`
619 #[inline]
620 pub fn sample_interp(&self, t: f32) -> InterpolationDatum<&[T]> {
621 uneven_interp(&self.times, t).map(|idx| self.time_index_to_slice(idx))
622 }
623
624 /// Like [`sample_interp`], but the returned values include the sample times. This can be
625 /// useful when sample interpolation is not scale-invariant.
626 ///
627 /// [`sample_interp`]: ChunkedUnevenCore::sample_interp
628 pub fn sample_interp_timed(&self, t: f32) -> InterpolationDatum<(f32, &[T])> {
629 uneven_interp(&self.times, t).map(|idx| (self.times[idx], self.time_index_to_slice(idx)))
630 }
631
632 /// Given an index in [times], returns the slice of [values] that correspond to the sample at
633 /// that time.
634 ///
635 /// [times]: ChunkedUnevenCore::times
636 /// [values]: ChunkedUnevenCore::values
637 #[inline]
638 fn time_index_to_slice(&self, idx: usize) -> &[T] {
639 let width = self.width();
640 let lower_idx = width * idx;
641 let upper_idx = lower_idx + width;
642 &self.values[lower_idx..upper_idx]
643 }
644}
645
646/// Sort the given times, deduplicate them, and filter them to only finite times.
647fn filter_sort_dedup_times(times: impl IntoIterator<Item = f32>) -> Vec<f32> {
648 // Filter before sorting/deduplication so that NAN doesn't interfere with them.
649 let mut times = times.into_iter().filter(|t| t.is_finite()).collect_vec();
650 times.sort_by(f32::total_cmp);
651 times.dedup();
652 times
653}
654
655/// Given a list of `times` and a target value, get the interpolation relationship for the
656/// target value in terms of the indices of the starting list. In a sense, this encapsulates the
657/// heart of uneven/keyframe sampling.
658///
659/// `times` is assumed to be sorted, deduplicated, and consisting only of finite values. It is also
660/// assumed to contain at least two values.
661///
662/// # Panics
663/// This function will panic if `times` contains NAN.
664pub fn uneven_interp(times: &[f32], t: f32) -> InterpolationDatum<usize> {
665 match times.binary_search_by(|pt| pt.partial_cmp(&t).unwrap()) {
666 Ok(index) => InterpolationDatum::Exact(index),
667 Err(index) => {
668 if index == 0 {
669 // This is before the first keyframe.
670 InterpolationDatum::LeftTail(0)
671 } else if index >= times.len() {
672 // This is after the last keyframe.
673 InterpolationDatum::RightTail(times.len() - 1)
674 } else {
675 // This is actually in the middle somewhere.
676 let t_lower = times[index - 1];
677 let t_upper = times[index];
678 let s = (t - t_lower) / (t_upper - t_lower);
679 InterpolationDatum::Between(index - 1, index, s)
680 }
681 }
682 }
683}
684
685#[cfg(test)]
686mod tests {
687 use super::{ChunkedUnevenCore, EvenCore, UnevenCore};
688 use crate::curve::{cores::InterpolationDatum, interval};
689 // use approx::{assert_abs_diff_eq, AbsDiffEq};
690
691 // fn approx_between<T>(datum: InterpolationDatum<T>, start: T, end: T, p: f32) -> bool
692 // where
693 // T: PartialEq,
694 // {
695 // if let InterpolationDatum::Between(m_start, m_end, m_p) = datum {
696 // m_start == start && m_end == end && m_p.abs_diff_eq(&p, 1e-6)
697 // } else {
698 // false
699 // }
700 // }
701
702 fn is_left_tail<T>(datum: InterpolationDatum<T>) -> bool {
703 matches!(datum, InterpolationDatum::LeftTail(_))
704 }
705
706 fn is_right_tail<T>(datum: InterpolationDatum<T>) -> bool {
707 matches!(datum, InterpolationDatum::RightTail(_))
708 }
709
710 fn is_exact<T>(datum: InterpolationDatum<T>, target: T) -> bool
711 where
712 T: PartialEq,
713 {
714 if let InterpolationDatum::Exact(v) = datum {
715 v == target
716 } else {
717 false
718 }
719 }
720
721 #[test]
722 fn even_sample_interp() {
723 let even_core = EvenCore::<f32>::new(
724 interval(0.0, 1.0).unwrap(),
725 // 11 entries -> 10 segments
726 vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
727 )
728 .expect("Failed to construct test core");
729
730 let datum = even_core.sample_interp(-1.0);
731 assert!(is_left_tail(datum));
732 let datum = even_core.sample_interp(0.0);
733 assert!(is_left_tail(datum));
734 let datum = even_core.sample_interp(1.0);
735 assert!(is_right_tail(datum));
736 let datum = even_core.sample_interp(2.0);
737 assert!(is_right_tail(datum));
738
739 let datum = even_core.sample_interp(0.05);
740 let InterpolationDatum::Between(0.0, 1.0, p) = datum else {
741 panic!("Sample did not lie in the correct subinterval")
742 };
743 // assert_abs_diff_eq!(p, 0.5);
744
745 let datum = even_core.sample_interp(0.05);
746 // assert!(approx_between(datum, &0.0, &1.0, 0.5));
747 let datum = even_core.sample_interp(0.33);
748 // assert!(approx_between(datum, &3.0, &4.0, 0.3));
749 let datum = even_core.sample_interp(0.78);
750 // assert!(approx_between(datum, &7.0, &8.0, 0.8));
751
752 let datum = even_core.sample_interp(0.5);
753 // assert!(approx_between(datum, &4.0, &5.0, 1.0) || approx_between(datum, &5.0, &6.0, 0.0));
754 let datum = even_core.sample_interp(0.7);
755 // assert!(approx_between(datum, &6.0, &7.0, 1.0) || approx_between(datum, &7.0, &8.0, 0.0));
756 }
757
758 #[test]
759 fn uneven_sample_interp() {
760 let uneven_core = UnevenCore::<f32>::new(vec![
761 (0.0, 0.0),
762 (1.0, 3.0),
763 (2.0, 9.0),
764 (4.0, 10.0),
765 (8.0, -5.0),
766 ])
767 .expect("Failed to construct test core");
768
769 let datum = uneven_core.sample_interp(-1.0);
770 assert!(is_left_tail(datum));
771 let datum = uneven_core.sample_interp(0.0);
772 assert!(is_exact(datum, &0.0));
773 let datum = uneven_core.sample_interp(8.0);
774 assert!(is_exact(datum, &(-5.0)));
775 let datum = uneven_core.sample_interp(9.0);
776 assert!(is_right_tail(datum));
777
778 let datum = uneven_core.sample_interp(0.5);
779 // assert!(approx_between(datum, &0.0, &3.0, 0.5));
780 let datum = uneven_core.sample_interp(2.5);
781 // assert!(approx_between(datum, &9.0, &10.0, 0.25));
782 let datum = uneven_core.sample_interp(7.0);
783 // assert!(approx_between(datum, &10.0, &(-5.0), 0.75));
784
785 let datum = uneven_core.sample_interp(2.0);
786 assert!(is_exact(datum, &9.0));
787 let datum = uneven_core.sample_interp(4.0);
788 assert!(is_exact(datum, &10.0));
789 }
790
791 #[test]
792 fn chunked_uneven_sample_interp() {
793 let core =
794 ChunkedUnevenCore::new(vec![0.0, 2.0, 8.0], vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0], 2)
795 .expect("Failed to construct test core");
796
797 let datum = core.sample_interp(-1.0);
798 assert!(is_left_tail(datum));
799 let datum = core.sample_interp(0.0);
800 assert!(is_exact(datum, &[0.0, 1.0]));
801 let datum = core.sample_interp(8.0);
802 assert!(is_exact(datum, &[4.0, 5.0]));
803 let datum = core.sample_interp(10.0);
804 assert!(is_right_tail(datum));
805
806 let datum = core.sample_interp(1.0);
807 // assert!(approx_between(datum, &[0.0, 1.0], &[2.0, 3.0], 0.5));
808 let datum = core.sample_interp(3.0);
809 // assert!(approx_between(datum, &[2.0, 3.0], &[4.0, 5.0], 1.0 / 6.0));
810
811 let datum = core.sample_interp(2.0);
812 assert!(is_exact(datum, &[2.0, 3.0]));
813 }
814}