bevy/crates/bevy_math/src/curve/adaptors.rs
Zachary Harrold 0403948aa2
Remove Implicit std Prelude from no_std Crates (#17086)
# Background

In `no_std` compatible crates, there is often an `std` feature which
will allow access to the standard library. Currently, with the `std`
feature _enabled_, the
[`std::prelude`](https://doc.rust-lang.org/std/prelude/index.html) is
implicitly imported in all modules. With the feature _disabled_, instead
the [`core::prelude`](https://doc.rust-lang.org/core/prelude/index.html)
is implicitly imported. This creates a subtle and pervasive issue where
`alloc` items _may_ be implicitly included (if `std` is enabled), or
must be explicitly included (if `std` is not enabled).

# Objective

- Make the implicit imports for `no_std` crates consistent regardless of
what features are/not enabled.

## Solution

- Replace the `cfg_attr` "double negative" `no_std` attribute with
conditional compilation to _include_ `std` as an external crate.
```rust
// Before
#![cfg_attr(not(feature = "std"), no_std)]

// After
#![no_std]

#[cfg(feature = "std")]
extern crate std;
```
- Fix imports that are currently broken but are only now visible with
the above fix.

## Testing

- CI

## Notes

I had previously used the "double negative" version of `no_std` based on
general consensus that it was "cleaner" within the Rust embedded
community. However, this implicit prelude issue likely was considered
when forming this consensus. I believe the reason why is the items most
affected by this issue are provided by the `alloc` crate, which is
rarely used within embedded but extensively used within Bevy.
2025-01-03 01:58:43 +00:00

816 lines
23 KiB
Rust

//! Adaptors used by the Curve API for transforming and combining curves together.
use super::interval::*;
use super::Curve;
use crate::ops;
use crate::VectorSpace;
use core::any::type_name;
use core::fmt::{self, Debug};
use core::marker::PhantomData;
#[cfg(feature = "bevy_reflect")]
use {
alloc::format,
bevy_reflect::{utility::GenericTypePathCell, FromReflect, Reflect, TypePath},
};
#[cfg(feature = "bevy_reflect")]
mod paths {
pub(super) const THIS_MODULE: &str = "bevy_math::curve::adaptors";
pub(super) const THIS_CRATE: &str = "bevy_math";
}
#[expect(unused, reason = "imported just for doc links")]
use super::CurveExt;
// NOTE ON REFLECTION:
//
// Function members of structs pose an obstacle for reflection, because they don't implement
// reflection traits themselves. Some of these are more problematic than others; for example,
// `FromReflect` is basically hopeless for function members regardless, so function-containing
// adaptors will just never be `FromReflect` (at least until function item types implement
// Default, if that ever happens). Similarly, they do not implement `TypePath`, and as a result,
// those adaptors also need custom `TypePath` adaptors which use `type_name` instead.
//
// The sum total weirdness of the `Reflect` implementations amounts to this; those adaptors:
// - are currently never `FromReflect`;
// - have custom `TypePath` implementations which are not fully stable;
// - have custom `Debug` implementations which display the function only by type name.
/// A curve with a constant value over its domain.
///
/// This is a curve that holds an inner value and always produces a clone of that value when sampled.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct ConstantCurve<T> {
pub(crate) domain: Interval,
pub(crate) value: T,
}
impl<T> ConstantCurve<T>
where
T: Clone,
{
/// Create a constant curve, which has the given `domain` and always produces the given `value`
/// when sampled.
pub fn new(domain: Interval, value: T) -> Self {
Self { domain, value }
}
}
impl<T> Curve<T> for ConstantCurve<T>
where
T: Clone,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, _t: f32) -> T {
self.value.clone()
}
}
/// A curve defined by a function together with a fixed domain.
///
/// This is a curve that holds an inner function `f` which takes numbers (`f32`) as input and produces
/// output of type `T`. The value of this curve when sampled at time `t` is just `f(t)`.
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct FunctionCurve<T, F> {
pub(crate) domain: Interval,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) f: F,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, F> Debug for FunctionCurve<T, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FunctionCurve")
.field("domain", &self.domain)
.field("f", &type_name::<F>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, F> TypePath for FunctionCurve<T, F>
where
T: TypePath,
F: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::FunctionCurve<{},{}>",
paths::THIS_MODULE,
T::type_path(),
type_name::<F>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"FunctionCurve<{},{}>",
T::short_type_path(),
type_name::<F>()
)
})
}
fn type_ident() -> Option<&'static str> {
Some("FunctionCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, F> FunctionCurve<T, F>
where
F: Fn(f32) -> T,
{
/// Create a new curve with the given `domain` from the given `function`. When sampled, the
/// `function` is evaluated at the sample time to compute the output.
pub fn new(domain: Interval, function: F) -> Self {
FunctionCurve {
domain,
f: function,
_phantom: PhantomData,
}
}
}
impl<T, F> Curve<T> for FunctionCurve<T, F>
where
F: Fn(f32) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
(self.f)(t)
}
}
/// A curve whose samples are defined by mapping samples from another curve through a
/// given function. Curves of this type are produced by [`CurveExt::map`].
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where S: TypePath, T: TypePath, C: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct MapCurve<S, T, C, F> {
pub(crate) preimage: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) f: F,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) _phantom: PhantomData<(fn() -> S, fn(S) -> T)>,
}
impl<S, T, C, F> Debug for MapCurve<S, T, C, F>
where
C: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MapCurve")
.field("preimage", &self.preimage)
.field("f", &type_name::<F>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<S, T, C, F> TypePath for MapCurve<S, T, C, F>
where
S: TypePath,
T: TypePath,
C: TypePath,
F: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::MapCurve<{},{},{},{}>",
paths::THIS_MODULE,
S::type_path(),
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"MapCurve<{},{},{},{}>",
S::type_path(),
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn type_ident() -> Option<&'static str> {
Some("MapCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<S, T, C, F> Curve<T> for MapCurve<S, T, C, F>
where
C: Curve<S>,
F: Fn(S) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.preimage.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
(self.f)(self.preimage.sample_unchecked(t))
}
}
/// A curve whose sample space is mapped onto that of some base curve's before sampling.
/// Curves of this type are produced by [`CurveExt::reparametrize`].
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath, C: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct ReparamCurve<T, C, F> {
pub(crate) domain: Interval,
pub(crate) base: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) f: F,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, F> Debug for ReparamCurve<T, C, F>
where
C: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ReparamCurve")
.field("domain", &self.domain)
.field("base", &self.base)
.field("f", &type_name::<F>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, C, F> TypePath for ReparamCurve<T, C, F>
where
T: TypePath,
C: TypePath,
F: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::ReparamCurve<{},{},{}>",
paths::THIS_MODULE,
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"ReparamCurve<{},{},{}>",
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn type_ident() -> Option<&'static str> {
Some("ReparamCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, C, F> Curve<T> for ReparamCurve<T, C, F>
where
C: Curve<T>,
F: Fn(f32) -> f32,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.base.sample_unchecked((self.f)(t))
}
}
/// A curve that has had its domain changed by a linear reparameterization (stretching and scaling).
/// Curves of this type are produced by [`CurveExt::reparametrize_linear`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct LinearReparamCurve<T, C> {
/// Invariants: The domain of this curve must always be bounded.
pub(crate) base: C,
/// Invariants: This interval must always be bounded.
pub(crate) new_domain: Interval,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for LinearReparamCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.new_domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
// The invariants imply this unwrap always succeeds.
let f = self.new_domain.linear_map_to(self.base.domain()).unwrap();
self.base.sample_unchecked(f(t))
}
}
/// A curve that has been reparametrized by another curve, using that curve to transform the
/// sample times before sampling. Curves of this type are produced by [`CurveExt::reparametrize_by_curve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct CurveReparamCurve<T, C, D> {
pub(crate) base: C,
pub(crate) reparam_curve: D,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, D> Curve<T> for CurveReparamCurve<T, C, D>
where
C: Curve<T>,
D: Curve<f32>,
{
#[inline]
fn domain(&self) -> Interval {
self.reparam_curve.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
let sample_time = self.reparam_curve.sample_unchecked(t);
self.base.sample_unchecked(sample_time)
}
}
/// A curve that is the graph of another curve over its parameter space. Curves of this type are
/// produced by [`CurveExt::graph`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct GraphCurve<T, C> {
pub(crate) base: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<(f32, T)> for GraphCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.base.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> (f32, T) {
(t, self.base.sample_unchecked(t))
}
}
/// A curve that combines the output data from two constituent curves into a tuple output. Curves
/// of this type are produced by [`CurveExt::zip`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ZipCurve<S, T, C, D> {
pub(crate) domain: Interval,
pub(crate) first: C,
pub(crate) second: D,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) _phantom: PhantomData<fn() -> (S, T)>,
}
impl<S, T, C, D> Curve<(S, T)> for ZipCurve<S, T, C, D>
where
C: Curve<S>,
D: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> (S, T) {
(
self.first.sample_unchecked(t),
self.second.sample_unchecked(t),
)
}
}
/// The curve that results from chaining one curve with another. The second curve is
/// effectively reparametrized so that its start is at the end of the first.
///
/// For this to be well-formed, the first curve's domain must be right-finite and the second's
/// must be left-finite.
///
/// Curves of this type are produced by [`CurveExt::chain`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ChainCurve<T, C, D> {
pub(crate) first: C,
pub(crate) second: D,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, D> Curve<T> for ChainCurve<T, C, D>
where
C: Curve<T>,
D: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
// This unwrap always succeeds because `first` has a valid Interval as its domain and the
// length of `second` cannot be NAN. It's still fine if it's infinity.
Interval::new(
self.first.domain().start(),
self.first.domain().end() + self.second.domain().length(),
)
.unwrap()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
if t > self.first.domain().end() {
self.second.sample_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
)
} else {
self.first.sample_unchecked(t)
}
}
}
/// The curve that results from reversing another.
///
/// Curves of this type are produced by [`CurveExt::reverse`].
///
/// # Domain
///
/// The original curve's domain must be bounded to get a valid [`ReverseCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ReverseCurve<T, C> {
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for ReverseCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.curve.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.curve
.sample_unchecked(self.domain().end() - (t - self.domain().start()))
}
}
/// The curve that results from repeating a curve `N` times.
///
/// # Notes
///
/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
/// value at `domain.end()` in the original curve
///
/// Curves of this type are produced by [`CurveExt::repeat`].
///
/// # Domain
///
/// The original curve's domain must be bounded to get a valid [`RepeatCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct RepeatCurve<T, C> {
pub(crate) domain: Interval,
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for RepeatCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
let t = self.base_curve_sample_time(t);
self.curve.sample_unchecked(t)
}
}
impl<T, C> RepeatCurve<T, C>
where
C: Curve<T>,
{
#[inline]
pub(crate) fn base_curve_sample_time(&self, t: f32) -> f32 {
// the domain is bounded by construction
let d = self.curve.domain();
let cyclic_t = ops::rem_euclid(t - d.start(), d.length());
if t != d.start() && cyclic_t == 0.0 {
d.end()
} else {
d.start() + cyclic_t
}
}
}
/// The curve that results from repeating a curve forever.
///
/// # Notes
///
/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
/// value at `domain.end()` in the original curve
///
/// Curves of this type are produced by [`CurveExt::forever`].
///
/// # Domain
///
/// The original curve's domain must be bounded to get a valid [`ForeverCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ForeverCurve<T, C> {
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for ForeverCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
Interval::EVERYWHERE
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
let t = self.base_curve_sample_time(t);
self.curve.sample_unchecked(t)
}
}
impl<T, C> ForeverCurve<T, C>
where
C: Curve<T>,
{
#[inline]
pub(crate) fn base_curve_sample_time(&self, t: f32) -> f32 {
// the domain is bounded by construction
let d = self.curve.domain();
let cyclic_t = ops::rem_euclid(t - d.start(), d.length());
if t != d.start() && cyclic_t == 0.0 {
d.end()
} else {
d.start() + cyclic_t
}
}
}
/// The curve that results from chaining a curve with its reversed version. The transition point
/// is guaranteed to make no jump.
///
/// Curves of this type are produced by [`CurveExt::ping_pong`].
///
/// # Domain
///
/// The original curve's domain must be right-finite to get a valid [`PingPongCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct PingPongCurve<T, C> {
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for PingPongCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
// This unwrap always succeeds because `curve` has a valid Interval as its domain and the
// length of `curve` cannot be NAN. It's still fine if it's infinity.
Interval::new(
self.curve.domain().start(),
self.curve.domain().end() + self.curve.domain().length(),
)
.unwrap()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
// the domain is bounded by construction
let final_t = if t > self.curve.domain().end() {
self.curve.domain().end() * 2.0 - t
} else {
t
};
self.curve.sample_unchecked(final_t)
}
}
/// The curve that results from chaining two curves.
///
/// Additionally the transition of the samples is guaranteed to not make sudden jumps. This is
/// useful if you really just know about the shapes of your curves and don't want to deal with
/// stitching them together properly when it would just introduce useless complexity. It is
/// realized by translating the second curve so that its start sample point coincides with the
/// first curves' end sample point.
///
/// Curves of this type are produced by [`CurveExt::chain_continue`].
///
/// # Domain
///
/// The first curve's domain must be right-finite and the second's must be left-finite to get a
/// valid [`ContinuationCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ContinuationCurve<T, C, D> {
pub(crate) first: C,
pub(crate) second: D,
// cache the offset in the curve directly to prevent triple sampling for every sample we make
pub(crate) offset: T,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, D> Curve<T> for ContinuationCurve<T, C, D>
where
T: VectorSpace,
C: Curve<T>,
D: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
// This unwrap always succeeds because `curve` has a valid Interval as its domain and the
// length of `curve` cannot be NAN. It's still fine if it's infinity.
Interval::new(
self.first.domain().start(),
self.first.domain().end() + self.second.domain().length(),
)
.unwrap()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
if t > self.first.domain().end() {
self.second.sample_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
) + self.offset
} else {
self.first.sample_unchecked(t)
}
}
}