Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_math/src/curve/sample_curves.rs
6596 views
1
//! Sample-interpolated curves constructed using the [`Curve`] API.
2
3
use super::cores::{EvenCore, EvenCoreError, UnevenCore, UnevenCoreError};
4
use super::{Curve, Interval};
5
6
use crate::StableInterpolate;
7
#[cfg(feature = "bevy_reflect")]
8
use alloc::format;
9
use core::any::type_name;
10
use core::fmt::{self, Debug};
11
12
#[cfg(feature = "bevy_reflect")]
13
use bevy_reflect::{utility::GenericTypePathCell, Reflect, TypePath};
14
15
#[cfg(feature = "bevy_reflect")]
16
mod paths {
17
pub(super) const THIS_MODULE: &str = "bevy_math::curve::sample_curves";
18
pub(super) const THIS_CRATE: &str = "bevy_math";
19
}
20
21
/// A curve that is defined by explicit neighbor interpolation over a set of evenly-spaced samples.
22
#[derive(Clone)]
23
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
24
#[cfg_attr(
25
feature = "bevy_reflect",
26
derive(Reflect),
27
reflect(where T: TypePath),
28
reflect(from_reflect = false, type_path = false),
29
)]
30
pub struct SampleCurve<T, I> {
31
pub(crate) core: EvenCore<T>,
32
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
33
pub(crate) interpolation: I,
34
}
35
36
impl<T, I> Debug for SampleCurve<T, I>
37
where
38
EvenCore<T>: Debug,
39
{
40
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41
f.debug_struct("SampleCurve")
42
.field("core", &self.core)
43
.field("interpolation", &type_name::<I>())
44
.finish()
45
}
46
}
47
48
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
49
/// for function members.
50
#[cfg(feature = "bevy_reflect")]
51
impl<T, I> TypePath for SampleCurve<T, I>
52
where
53
T: TypePath,
54
I: 'static,
55
{
56
fn type_path() -> &'static str {
57
static CELL: GenericTypePathCell = GenericTypePathCell::new();
58
CELL.get_or_insert::<Self, _>(|| {
59
format!(
60
"{}::SampleCurve<{},{}>",
61
paths::THIS_MODULE,
62
T::type_path(),
63
type_name::<I>()
64
)
65
})
66
}
67
68
fn short_type_path() -> &'static str {
69
static CELL: GenericTypePathCell = GenericTypePathCell::new();
70
CELL.get_or_insert::<Self, _>(|| {
71
format!("SampleCurve<{},{}>", T::type_path(), type_name::<I>())
72
})
73
}
74
75
fn type_ident() -> Option<&'static str> {
76
Some("SampleCurve")
77
}
78
79
fn crate_name() -> Option<&'static str> {
80
Some(paths::THIS_CRATE)
81
}
82
83
fn module_path() -> Option<&'static str> {
84
Some(paths::THIS_MODULE)
85
}
86
}
87
88
impl<T, I> Curve<T> for SampleCurve<T, I>
89
where
90
T: Clone,
91
I: Fn(&T, &T, f32) -> T,
92
{
93
#[inline]
94
fn domain(&self) -> Interval {
95
self.core.domain()
96
}
97
98
#[inline]
99
fn sample_clamped(&self, t: f32) -> T {
100
// `EvenCore::sample_with` is implicitly clamped.
101
self.core.sample_with(t, &self.interpolation)
102
}
103
104
#[inline]
105
fn sample_unchecked(&self, t: f32) -> T {
106
self.sample_clamped(t)
107
}
108
}
109
110
impl<T, I> SampleCurve<T, I> {
111
/// Create a new [`SampleCurve`] using the specified `interpolation` to interpolate between
112
/// the given `samples`. An error is returned if there are not at least 2 samples or if the
113
/// given `domain` is unbounded.
114
///
115
/// The interpolation takes two values by reference together with a scalar parameter and
116
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
117
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
118
pub fn new(
119
domain: Interval,
120
samples: impl IntoIterator<Item = T>,
121
interpolation: I,
122
) -> Result<Self, EvenCoreError>
123
where
124
I: Fn(&T, &T, f32) -> T,
125
{
126
Ok(Self {
127
core: EvenCore::new(domain, samples)?,
128
interpolation,
129
})
130
}
131
}
132
133
/// A curve that is defined by neighbor interpolation over a set of evenly-spaced samples,
134
/// interpolated automatically using [a particularly well-behaved interpolation].
135
///
136
/// [a particularly well-behaved interpolation]: StableInterpolate
137
#[derive(Clone, Debug)]
138
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
139
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
140
pub struct SampleAutoCurve<T> {
141
pub(crate) core: EvenCore<T>,
142
}
143
144
impl<T> Curve<T> for SampleAutoCurve<T>
145
where
146
T: StableInterpolate,
147
{
148
#[inline]
149
fn domain(&self) -> Interval {
150
self.core.domain()
151
}
152
153
#[inline]
154
fn sample_clamped(&self, t: f32) -> T {
155
// `EvenCore::sample_with` is implicitly clamped.
156
self.core
157
.sample_with(t, <T as StableInterpolate>::interpolate_stable)
158
}
159
160
#[inline]
161
fn sample_unchecked(&self, t: f32) -> T {
162
self.sample_clamped(t)
163
}
164
}
165
166
impl<T> SampleAutoCurve<T> {
167
/// Create a new [`SampleCurve`] using type-inferred interpolation to interpolate between
168
/// the given `samples`. An error is returned if there are not at least 2 samples or if the
169
/// given `domain` is unbounded.
170
pub fn new(
171
domain: Interval,
172
samples: impl IntoIterator<Item = T>,
173
) -> Result<Self, EvenCoreError> {
174
Ok(Self {
175
core: EvenCore::new(domain, samples)?,
176
})
177
}
178
}
179
180
/// A curve that is defined by interpolation over unevenly spaced samples with explicit
181
/// interpolation.
182
#[derive(Clone)]
183
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
184
#[cfg_attr(
185
feature = "bevy_reflect",
186
derive(Reflect),
187
reflect(where T: TypePath),
188
reflect(from_reflect = false, type_path = false),
189
)]
190
pub struct UnevenSampleCurve<T, I> {
191
pub(crate) core: UnevenCore<T>,
192
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
193
pub(crate) interpolation: I,
194
}
195
196
impl<T, I> Debug for UnevenSampleCurve<T, I>
197
where
198
UnevenCore<T>: Debug,
199
{
200
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201
f.debug_struct("SampleCurve")
202
.field("core", &self.core)
203
.field("interpolation", &type_name::<I>())
204
.finish()
205
}
206
}
207
208
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
209
/// for function members.
210
#[cfg(feature = "bevy_reflect")]
211
impl<T, I> TypePath for UnevenSampleCurve<T, I>
212
where
213
T: TypePath,
214
I: 'static,
215
{
216
fn type_path() -> &'static str {
217
static CELL: GenericTypePathCell = GenericTypePathCell::new();
218
CELL.get_or_insert::<Self, _>(|| {
219
format!(
220
"{}::UnevenSampleCurve<{},{}>",
221
paths::THIS_MODULE,
222
T::type_path(),
223
type_name::<I>()
224
)
225
})
226
}
227
228
fn short_type_path() -> &'static str {
229
static CELL: GenericTypePathCell = GenericTypePathCell::new();
230
CELL.get_or_insert::<Self, _>(|| {
231
format!("UnevenSampleCurve<{},{}>", T::type_path(), type_name::<I>())
232
})
233
}
234
235
fn type_ident() -> Option<&'static str> {
236
Some("UnevenSampleCurve")
237
}
238
239
fn crate_name() -> Option<&'static str> {
240
Some(paths::THIS_CRATE)
241
}
242
243
fn module_path() -> Option<&'static str> {
244
Some(paths::THIS_MODULE)
245
}
246
}
247
248
impl<T, I> Curve<T> for UnevenSampleCurve<T, I>
249
where
250
T: Clone,
251
I: Fn(&T, &T, f32) -> T,
252
{
253
#[inline]
254
fn domain(&self) -> Interval {
255
self.core.domain()
256
}
257
258
#[inline]
259
fn sample_clamped(&self, t: f32) -> T {
260
// `UnevenCore::sample_with` is implicitly clamped.
261
self.core.sample_with(t, &self.interpolation)
262
}
263
264
#[inline]
265
fn sample_unchecked(&self, t: f32) -> T {
266
self.sample_clamped(t)
267
}
268
}
269
270
impl<T, I> UnevenSampleCurve<T, I> {
271
/// Create a new [`UnevenSampleCurve`] using the provided `interpolation` to interpolate
272
/// between adjacent `timed_samples`. The given samples are filtered to finite times and
273
/// sorted internally; if there are not at least 2 valid timed samples, an error will be
274
/// returned.
275
///
276
/// The interpolation takes two values by reference together with a scalar parameter and
277
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
278
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
279
pub fn new(
280
timed_samples: impl IntoIterator<Item = (f32, T)>,
281
interpolation: I,
282
) -> Result<Self, UnevenCoreError>
283
where
284
I: Fn(&T, &T, f32) -> T,
285
{
286
Ok(Self {
287
core: UnevenCore::new(timed_samples)?,
288
interpolation,
289
})
290
}
291
292
/// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.
293
/// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],
294
/// but the function inputs to each are inverses of one another.
295
///
296
/// The samples are re-sorted by time after mapping and deduplicated by output time, so
297
/// the function `f` should generally be injective over the sample times of the curve.
298
///
299
/// [`CurveExt::reparametrize`]: super::CurveExt::reparametrize
300
pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleCurve<T, I> {
301
Self {
302
core: self.core.map_sample_times(f),
303
interpolation: self.interpolation,
304
}
305
}
306
}
307
308
/// A curve that is defined by interpolation over unevenly spaced samples,
309
/// interpolated automatically using [a particularly well-behaved interpolation].
310
///
311
/// [a particularly well-behaved interpolation]: StableInterpolate
312
#[derive(Clone, Debug)]
313
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
314
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
315
pub struct UnevenSampleAutoCurve<T> {
316
pub(crate) core: UnevenCore<T>,
317
}
318
319
impl<T> Curve<T> for UnevenSampleAutoCurve<T>
320
where
321
T: StableInterpolate,
322
{
323
#[inline]
324
fn domain(&self) -> Interval {
325
self.core.domain()
326
}
327
328
#[inline]
329
fn sample_clamped(&self, t: f32) -> T {
330
// `UnevenCore::sample_with` is implicitly clamped.
331
self.core
332
.sample_with(t, <T as StableInterpolate>::interpolate_stable)
333
}
334
335
#[inline]
336
fn sample_unchecked(&self, t: f32) -> T {
337
self.sample_clamped(t)
338
}
339
}
340
341
impl<T> UnevenSampleAutoCurve<T> {
342
/// Create a new [`UnevenSampleAutoCurve`] from a given set of timed samples.
343
///
344
/// The samples are filtered to finite times and sorted internally; if there are not
345
/// at least 2 valid timed samples, an error will be returned.
346
pub fn new(timed_samples: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {
347
Ok(Self {
348
core: UnevenCore::new(timed_samples)?,
349
})
350
}
351
352
/// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.
353
/// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],
354
/// but the function inputs to each are inverses of one another.
355
///
356
/// The samples are re-sorted by time after mapping and deduplicated by output time, so
357
/// the function `f` should generally be injective over the sample times of the curve.
358
///
359
/// [`CurveExt::reparametrize`]: super::CurveExt::reparametrize
360
pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleAutoCurve<T> {
361
Self {
362
core: self.core.map_sample_times(f),
363
}
364
}
365
}
366
367
#[cfg(test)]
368
#[cfg(feature = "bevy_reflect")]
369
mod tests {
370
//! These tests should guarantee (by even compiling) that `SampleCurve` and `UnevenSampleCurve`
371
//! can be `Reflect` under reasonable circumstances where their interpolation is defined by:
372
//! - function items
373
//! - 'static closures
374
//! - function pointers
375
use super::{SampleCurve, UnevenSampleCurve};
376
use crate::{curve::Interval, VectorSpace};
377
use alloc::boxed::Box;
378
use bevy_reflect::Reflect;
379
380
#[test]
381
fn reflect_sample_curve() {
382
fn foo(x: &f32, y: &f32, t: f32) -> f32 {
383
x.lerp(*y, t)
384
}
385
let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);
386
let baz: fn(&f32, &f32, f32) -> f32 = bar;
387
388
let samples = [0.0, 1.0, 2.0];
389
390
let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, foo).unwrap());
391
let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, bar).unwrap());
392
let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, baz).unwrap());
393
}
394
395
#[test]
396
fn reflect_uneven_sample_curve() {
397
fn foo(x: &f32, y: &f32, t: f32) -> f32 {
398
x.lerp(*y, t)
399
}
400
let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);
401
let baz: fn(&f32, &f32, f32) -> f32 = bar;
402
403
let keyframes = [(0.0, 1.0), (1.0, 0.0), (2.0, -1.0)];
404
405
let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, foo).unwrap());
406
let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, bar).unwrap());
407
let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, baz).unwrap());
408
}
409
#[test]
410
fn test_infer_interp_arguments() {
411
// it should be possible to infer the x and y arguments of the interpolation function
412
// from the input samples. If that becomes impossible, this will fail to compile.
413
SampleCurve::new(Interval::UNIT, [0.0, 1.0], |x, y, t| x.lerp(*y, t)).ok();
414
UnevenSampleCurve::new([(0.1, 1.0), (1.0, 3.0)], |x, y, t| x.lerp(*y, t)).ok();
415
}
416
}
417
418