Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_math/src/curve/adaptors.rs
6596 views
1
//! Adaptors used by the Curve API for transforming and combining curves together.
2
3
use super::interval::*;
4
use super::Curve;
5
6
use crate::ops;
7
use crate::VectorSpace;
8
use core::any::type_name;
9
use core::fmt::{self, Debug};
10
use core::marker::PhantomData;
11
12
#[cfg(feature = "bevy_reflect")]
13
use {
14
alloc::format,
15
bevy_reflect::{utility::GenericTypePathCell, FromReflect, Reflect, TypePath},
16
};
17
18
#[cfg(feature = "bevy_reflect")]
19
mod paths {
20
pub(super) const THIS_MODULE: &str = "bevy_math::curve::adaptors";
21
pub(super) const THIS_CRATE: &str = "bevy_math";
22
}
23
24
#[expect(unused, reason = "imported just for doc links")]
25
use super::CurveExt;
26
27
// NOTE ON REFLECTION:
28
//
29
// Function members of structs pose an obstacle for reflection, because they don't implement
30
// reflection traits themselves. Some of these are more problematic than others; for example,
31
// `FromReflect` is basically hopeless for function members regardless, so function-containing
32
// adaptors will just never be `FromReflect` (at least until function item types implement
33
// Default, if that ever happens). Similarly, they do not implement `TypePath`, and as a result,
34
// those adaptors also need custom `TypePath` adaptors which use `type_name` instead.
35
//
36
// The sum total weirdness of the `Reflect` implementations amounts to this; those adaptors:
37
// - are currently never `FromReflect`;
38
// - have custom `TypePath` implementations which are not fully stable;
39
// - have custom `Debug` implementations which display the function only by type name.
40
41
/// A curve with a constant value over its domain.
42
///
43
/// This is a curve that holds an inner value and always produces a clone of that value when sampled.
44
#[derive(Clone, Copy, Debug)]
45
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
46
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
47
pub struct ConstantCurve<T> {
48
pub(crate) domain: Interval,
49
pub(crate) value: T,
50
}
51
52
impl<T> ConstantCurve<T>
53
where
54
T: Clone,
55
{
56
/// Create a constant curve, which has the given `domain` and always produces the given `value`
57
/// when sampled.
58
pub fn new(domain: Interval, value: T) -> Self {
59
Self { domain, value }
60
}
61
}
62
63
impl<T> Curve<T> for ConstantCurve<T>
64
where
65
T: Clone,
66
{
67
#[inline]
68
fn domain(&self) -> Interval {
69
self.domain
70
}
71
72
#[inline]
73
fn sample_unchecked(&self, _t: f32) -> T {
74
self.value.clone()
75
}
76
}
77
78
/// A curve defined by a function together with a fixed domain.
79
///
80
/// This is a curve that holds an inner function `f` which takes numbers (`f32`) as input and produces
81
/// output of type `T`. The value of this curve when sampled at time `t` is just `f(t)`.
82
#[derive(Clone)]
83
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
84
#[cfg_attr(
85
feature = "bevy_reflect",
86
derive(Reflect),
87
reflect(where T: TypePath),
88
reflect(from_reflect = false, type_path = false),
89
)]
90
pub struct FunctionCurve<T, F> {
91
pub(crate) domain: Interval,
92
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
93
pub(crate) f: F,
94
#[cfg_attr(feature = "serialize", serde(skip))]
95
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
96
pub(crate) _phantom: PhantomData<fn() -> T>,
97
}
98
99
impl<T, F> Debug for FunctionCurve<T, F> {
100
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101
f.debug_struct("FunctionCurve")
102
.field("domain", &self.domain)
103
.field("f", &type_name::<F>())
104
.finish()
105
}
106
}
107
108
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
109
/// for function members.
110
#[cfg(feature = "bevy_reflect")]
111
impl<T, F> TypePath for FunctionCurve<T, F>
112
where
113
T: TypePath,
114
F: 'static,
115
{
116
fn type_path() -> &'static str {
117
static CELL: GenericTypePathCell = GenericTypePathCell::new();
118
CELL.get_or_insert::<Self, _>(|| {
119
format!(
120
"{}::FunctionCurve<{},{}>",
121
paths::THIS_MODULE,
122
T::type_path(),
123
type_name::<F>()
124
)
125
})
126
}
127
128
fn short_type_path() -> &'static str {
129
static CELL: GenericTypePathCell = GenericTypePathCell::new();
130
CELL.get_or_insert::<Self, _>(|| {
131
format!(
132
"FunctionCurve<{},{}>",
133
T::short_type_path(),
134
type_name::<F>()
135
)
136
})
137
}
138
139
fn type_ident() -> Option<&'static str> {
140
Some("FunctionCurve")
141
}
142
143
fn crate_name() -> Option<&'static str> {
144
Some(paths::THIS_CRATE)
145
}
146
147
fn module_path() -> Option<&'static str> {
148
Some(paths::THIS_MODULE)
149
}
150
}
151
152
impl<T, F> FunctionCurve<T, F>
153
where
154
F: Fn(f32) -> T,
155
{
156
/// Create a new curve with the given `domain` from the given `function`. When sampled, the
157
/// `function` is evaluated at the sample time to compute the output.
158
pub fn new(domain: Interval, function: F) -> Self {
159
FunctionCurve {
160
domain,
161
f: function,
162
_phantom: PhantomData,
163
}
164
}
165
}
166
167
impl<T, F> Curve<T> for FunctionCurve<T, F>
168
where
169
F: Fn(f32) -> T,
170
{
171
#[inline]
172
fn domain(&self) -> Interval {
173
self.domain
174
}
175
176
#[inline]
177
fn sample_unchecked(&self, t: f32) -> T {
178
(self.f)(t)
179
}
180
}
181
182
/// A curve whose samples are defined by mapping samples from another curve through a
183
/// given function. Curves of this type are produced by [`CurveExt::map`].
184
#[derive(Clone)]
185
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
186
#[cfg_attr(
187
feature = "bevy_reflect",
188
derive(Reflect),
189
reflect(where S: TypePath, T: TypePath, C: TypePath),
190
reflect(from_reflect = false, type_path = false),
191
)]
192
pub struct MapCurve<S, T, C, F> {
193
pub(crate) preimage: C,
194
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
195
pub(crate) f: F,
196
#[cfg_attr(feature = "serialize", serde(skip))]
197
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
198
pub(crate) _phantom: PhantomData<(fn() -> S, fn(S) -> T)>,
199
}
200
201
impl<S, T, C, F> Debug for MapCurve<S, T, C, F>
202
where
203
C: Debug,
204
{
205
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206
f.debug_struct("MapCurve")
207
.field("preimage", &self.preimage)
208
.field("f", &type_name::<F>())
209
.finish()
210
}
211
}
212
213
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
214
/// for function members.
215
#[cfg(feature = "bevy_reflect")]
216
impl<S, T, C, F> TypePath for MapCurve<S, T, C, F>
217
where
218
S: TypePath,
219
T: TypePath,
220
C: TypePath,
221
F: 'static,
222
{
223
fn type_path() -> &'static str {
224
static CELL: GenericTypePathCell = GenericTypePathCell::new();
225
CELL.get_or_insert::<Self, _>(|| {
226
format!(
227
"{}::MapCurve<{},{},{},{}>",
228
paths::THIS_MODULE,
229
S::type_path(),
230
T::type_path(),
231
C::type_path(),
232
type_name::<F>()
233
)
234
})
235
}
236
237
fn short_type_path() -> &'static str {
238
static CELL: GenericTypePathCell = GenericTypePathCell::new();
239
CELL.get_or_insert::<Self, _>(|| {
240
format!(
241
"MapCurve<{},{},{},{}>",
242
S::type_path(),
243
T::type_path(),
244
C::type_path(),
245
type_name::<F>()
246
)
247
})
248
}
249
250
fn type_ident() -> Option<&'static str> {
251
Some("MapCurve")
252
}
253
254
fn crate_name() -> Option<&'static str> {
255
Some(paths::THIS_CRATE)
256
}
257
258
fn module_path() -> Option<&'static str> {
259
Some(paths::THIS_MODULE)
260
}
261
}
262
263
impl<S, T, C, F> Curve<T> for MapCurve<S, T, C, F>
264
where
265
C: Curve<S>,
266
F: Fn(S) -> T,
267
{
268
#[inline]
269
fn domain(&self) -> Interval {
270
self.preimage.domain()
271
}
272
273
#[inline]
274
fn sample_unchecked(&self, t: f32) -> T {
275
(self.f)(self.preimage.sample_unchecked(t))
276
}
277
}
278
279
/// A curve whose sample space is mapped onto that of some base curve's before sampling.
280
/// Curves of this type are produced by [`CurveExt::reparametrize`].
281
#[derive(Clone)]
282
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
283
#[cfg_attr(
284
feature = "bevy_reflect",
285
derive(Reflect),
286
reflect(where T: TypePath, C: TypePath),
287
reflect(from_reflect = false, type_path = false),
288
)]
289
pub struct ReparamCurve<T, C, F> {
290
pub(crate) domain: Interval,
291
pub(crate) base: C,
292
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
293
pub(crate) f: F,
294
#[cfg_attr(feature = "serialize", serde(skip))]
295
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
296
pub(crate) _phantom: PhantomData<fn() -> T>,
297
}
298
299
impl<T, C, F> Debug for ReparamCurve<T, C, F>
300
where
301
C: Debug,
302
{
303
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304
f.debug_struct("ReparamCurve")
305
.field("domain", &self.domain)
306
.field("base", &self.base)
307
.field("f", &type_name::<F>())
308
.finish()
309
}
310
}
311
312
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
313
/// for function members.
314
#[cfg(feature = "bevy_reflect")]
315
impl<T, C, F> TypePath for ReparamCurve<T, C, F>
316
where
317
T: TypePath,
318
C: TypePath,
319
F: 'static,
320
{
321
fn type_path() -> &'static str {
322
static CELL: GenericTypePathCell = GenericTypePathCell::new();
323
CELL.get_or_insert::<Self, _>(|| {
324
format!(
325
"{}::ReparamCurve<{},{},{}>",
326
paths::THIS_MODULE,
327
T::type_path(),
328
C::type_path(),
329
type_name::<F>()
330
)
331
})
332
}
333
334
fn short_type_path() -> &'static str {
335
static CELL: GenericTypePathCell = GenericTypePathCell::new();
336
CELL.get_or_insert::<Self, _>(|| {
337
format!(
338
"ReparamCurve<{},{},{}>",
339
T::type_path(),
340
C::type_path(),
341
type_name::<F>()
342
)
343
})
344
}
345
346
fn type_ident() -> Option<&'static str> {
347
Some("ReparamCurve")
348
}
349
350
fn crate_name() -> Option<&'static str> {
351
Some(paths::THIS_CRATE)
352
}
353
354
fn module_path() -> Option<&'static str> {
355
Some(paths::THIS_MODULE)
356
}
357
}
358
359
impl<T, C, F> Curve<T> for ReparamCurve<T, C, F>
360
where
361
C: Curve<T>,
362
F: Fn(f32) -> f32,
363
{
364
#[inline]
365
fn domain(&self) -> Interval {
366
self.domain
367
}
368
369
#[inline]
370
fn sample_unchecked(&self, t: f32) -> T {
371
self.base.sample_unchecked((self.f)(t))
372
}
373
}
374
375
/// A curve that has had its domain changed by a linear reparameterization (stretching and scaling).
376
/// Curves of this type are produced by [`CurveExt::reparametrize_linear`].
377
#[derive(Clone, Debug)]
378
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
379
#[cfg_attr(
380
feature = "bevy_reflect",
381
derive(Reflect, FromReflect),
382
reflect(from_reflect = false)
383
)]
384
pub struct LinearReparamCurve<T, C> {
385
/// Invariants: The domain of this curve must always be bounded.
386
pub(crate) base: C,
387
/// Invariants: This interval must always be bounded.
388
pub(crate) new_domain: Interval,
389
#[cfg_attr(feature = "serialize", serde(skip))]
390
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
391
pub(crate) _phantom: PhantomData<fn() -> T>,
392
}
393
394
impl<T, C> Curve<T> for LinearReparamCurve<T, C>
395
where
396
C: Curve<T>,
397
{
398
#[inline]
399
fn domain(&self) -> Interval {
400
self.new_domain
401
}
402
403
#[inline]
404
fn sample_unchecked(&self, t: f32) -> T {
405
// The invariants imply this unwrap always succeeds.
406
let f = self.new_domain.linear_map_to(self.base.domain()).unwrap();
407
self.base.sample_unchecked(f(t))
408
}
409
}
410
411
/// A curve that has been reparametrized by another curve, using that curve to transform the
412
/// sample times before sampling. Curves of this type are produced by [`CurveExt::reparametrize_by_curve`].
413
#[derive(Clone, Debug)]
414
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
415
#[cfg_attr(
416
feature = "bevy_reflect",
417
derive(Reflect, FromReflect),
418
reflect(from_reflect = false)
419
)]
420
pub struct CurveReparamCurve<T, C, D> {
421
pub(crate) base: C,
422
pub(crate) reparam_curve: D,
423
#[cfg_attr(feature = "serialize", serde(skip))]
424
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
425
pub(crate) _phantom: PhantomData<fn() -> T>,
426
}
427
428
impl<T, C, D> Curve<T> for CurveReparamCurve<T, C, D>
429
where
430
C: Curve<T>,
431
D: Curve<f32>,
432
{
433
#[inline]
434
fn domain(&self) -> Interval {
435
self.reparam_curve.domain()
436
}
437
438
#[inline]
439
fn sample_unchecked(&self, t: f32) -> T {
440
let sample_time = self.reparam_curve.sample_unchecked(t);
441
self.base.sample_unchecked(sample_time)
442
}
443
}
444
445
/// A curve that is the graph of another curve over its parameter space. Curves of this type are
446
/// produced by [`CurveExt::graph`].
447
#[derive(Clone, Debug)]
448
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
449
#[cfg_attr(
450
feature = "bevy_reflect",
451
derive(Reflect, FromReflect),
452
reflect(from_reflect = false)
453
)]
454
pub struct GraphCurve<T, C> {
455
pub(crate) base: C,
456
#[cfg_attr(feature = "serialize", serde(skip))]
457
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
458
pub(crate) _phantom: PhantomData<fn() -> T>,
459
}
460
461
impl<T, C> Curve<(f32, T)> for GraphCurve<T, C>
462
where
463
C: Curve<T>,
464
{
465
#[inline]
466
fn domain(&self) -> Interval {
467
self.base.domain()
468
}
469
470
#[inline]
471
fn sample_unchecked(&self, t: f32) -> (f32, T) {
472
(t, self.base.sample_unchecked(t))
473
}
474
}
475
476
/// A curve that combines the output data from two constituent curves into a tuple output. Curves
477
/// of this type are produced by [`CurveExt::zip`].
478
#[derive(Clone, Debug)]
479
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
480
#[cfg_attr(
481
feature = "bevy_reflect",
482
derive(Reflect, FromReflect),
483
reflect(from_reflect = false)
484
)]
485
pub struct ZipCurve<S, T, C, D> {
486
pub(crate) domain: Interval,
487
pub(crate) first: C,
488
pub(crate) second: D,
489
#[cfg_attr(feature = "serialize", serde(skip))]
490
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
491
pub(crate) _phantom: PhantomData<fn() -> (S, T)>,
492
}
493
494
impl<S, T, C, D> Curve<(S, T)> for ZipCurve<S, T, C, D>
495
where
496
C: Curve<S>,
497
D: Curve<T>,
498
{
499
#[inline]
500
fn domain(&self) -> Interval {
501
self.domain
502
}
503
504
#[inline]
505
fn sample_unchecked(&self, t: f32) -> (S, T) {
506
(
507
self.first.sample_unchecked(t),
508
self.second.sample_unchecked(t),
509
)
510
}
511
}
512
513
/// The curve that results from chaining one curve with another. The second curve is
514
/// effectively reparametrized so that its start is at the end of the first.
515
///
516
/// For this to be well-formed, the first curve's domain must be right-finite and the second's
517
/// must be left-finite.
518
///
519
/// Curves of this type are produced by [`CurveExt::chain`].
520
#[derive(Clone, Debug)]
521
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
522
#[cfg_attr(
523
feature = "bevy_reflect",
524
derive(Reflect, FromReflect),
525
reflect(from_reflect = false)
526
)]
527
pub struct ChainCurve<T, C, D> {
528
pub(crate) first: C,
529
pub(crate) second: D,
530
#[cfg_attr(feature = "serialize", serde(skip))]
531
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
532
pub(crate) _phantom: PhantomData<fn() -> T>,
533
}
534
535
impl<T, C, D> Curve<T> for ChainCurve<T, C, D>
536
where
537
C: Curve<T>,
538
D: Curve<T>,
539
{
540
#[inline]
541
fn domain(&self) -> Interval {
542
// This unwrap always succeeds because `first` has a valid Interval as its domain and the
543
// length of `second` cannot be NAN. It's still fine if it's infinity.
544
Interval::new(
545
self.first.domain().start(),
546
self.first.domain().end() + self.second.domain().length(),
547
)
548
.unwrap()
549
}
550
551
#[inline]
552
fn sample_unchecked(&self, t: f32) -> T {
553
if t > self.first.domain().end() {
554
self.second.sample_unchecked(
555
// `t - first.domain.end` computes the offset into the domain of the second.
556
t - self.first.domain().end() + self.second.domain().start(),
557
)
558
} else {
559
self.first.sample_unchecked(t)
560
}
561
}
562
}
563
564
/// The curve that results from reversing another.
565
///
566
/// Curves of this type are produced by [`CurveExt::reverse`].
567
///
568
/// # Domain
569
///
570
/// The original curve's domain must be bounded to get a valid [`ReverseCurve`].
571
#[derive(Clone, Debug)]
572
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
573
#[cfg_attr(
574
feature = "bevy_reflect",
575
derive(Reflect, FromReflect),
576
reflect(from_reflect = false)
577
)]
578
pub struct ReverseCurve<T, C> {
579
pub(crate) curve: C,
580
#[cfg_attr(feature = "serialize", serde(skip))]
581
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
582
pub(crate) _phantom: PhantomData<fn() -> T>,
583
}
584
585
impl<T, C> Curve<T> for ReverseCurve<T, C>
586
where
587
C: Curve<T>,
588
{
589
#[inline]
590
fn domain(&self) -> Interval {
591
self.curve.domain()
592
}
593
594
#[inline]
595
fn sample_unchecked(&self, t: f32) -> T {
596
self.curve
597
.sample_unchecked(self.domain().end() - (t - self.domain().start()))
598
}
599
}
600
601
/// The curve that results from repeating a curve `N` times.
602
///
603
/// # Notes
604
///
605
/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
606
/// value at `domain.end()` in the original curve
607
///
608
/// Curves of this type are produced by [`CurveExt::repeat`].
609
///
610
/// # Domain
611
///
612
/// The original curve's domain must be bounded to get a valid [`RepeatCurve`].
613
#[derive(Clone, Debug)]
614
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
615
#[cfg_attr(
616
feature = "bevy_reflect",
617
derive(Reflect, FromReflect),
618
reflect(from_reflect = false)
619
)]
620
pub struct RepeatCurve<T, C> {
621
pub(crate) domain: Interval,
622
pub(crate) curve: C,
623
#[cfg_attr(feature = "serialize", serde(skip))]
624
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
625
pub(crate) _phantom: PhantomData<fn() -> T>,
626
}
627
628
impl<T, C> Curve<T> for RepeatCurve<T, C>
629
where
630
C: Curve<T>,
631
{
632
#[inline]
633
fn domain(&self) -> Interval {
634
self.domain
635
}
636
637
#[inline]
638
fn sample_unchecked(&self, t: f32) -> T {
639
let t = self.base_curve_sample_time(t);
640
self.curve.sample_unchecked(t)
641
}
642
}
643
644
impl<T, C> RepeatCurve<T, C>
645
where
646
C: Curve<T>,
647
{
648
#[inline]
649
pub(crate) fn base_curve_sample_time(&self, t: f32) -> f32 {
650
// the domain is bounded by construction
651
let d = self.curve.domain();
652
let cyclic_t = ops::rem_euclid(t - d.start(), d.length());
653
if t != d.start() && cyclic_t == 0.0 {
654
d.end()
655
} else {
656
d.start() + cyclic_t
657
}
658
}
659
}
660
661
/// The curve that results from repeating a curve forever.
662
///
663
/// # Notes
664
///
665
/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
666
/// value at `domain.end()` in the original curve
667
///
668
/// Curves of this type are produced by [`CurveExt::forever`].
669
///
670
/// # Domain
671
///
672
/// The original curve's domain must be bounded to get a valid [`ForeverCurve`].
673
#[derive(Clone, Debug)]
674
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
675
#[cfg_attr(
676
feature = "bevy_reflect",
677
derive(Reflect, FromReflect),
678
reflect(from_reflect = false)
679
)]
680
pub struct ForeverCurve<T, C> {
681
pub(crate) curve: C,
682
#[cfg_attr(feature = "serialize", serde(skip))]
683
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
684
pub(crate) _phantom: PhantomData<fn() -> T>,
685
}
686
687
impl<T, C> Curve<T> for ForeverCurve<T, C>
688
where
689
C: Curve<T>,
690
{
691
#[inline]
692
fn domain(&self) -> Interval {
693
Interval::EVERYWHERE
694
}
695
696
#[inline]
697
fn sample_unchecked(&self, t: f32) -> T {
698
let t = self.base_curve_sample_time(t);
699
self.curve.sample_unchecked(t)
700
}
701
}
702
703
impl<T, C> ForeverCurve<T, C>
704
where
705
C: Curve<T>,
706
{
707
#[inline]
708
pub(crate) fn base_curve_sample_time(&self, t: f32) -> f32 {
709
// the domain is bounded by construction
710
let d = self.curve.domain();
711
let cyclic_t = ops::rem_euclid(t - d.start(), d.length());
712
if t != d.start() && cyclic_t == 0.0 {
713
d.end()
714
} else {
715
d.start() + cyclic_t
716
}
717
}
718
}
719
720
/// The curve that results from chaining a curve with its reversed version. The transition point
721
/// is guaranteed to make no jump.
722
///
723
/// Curves of this type are produced by [`CurveExt::ping_pong`].
724
///
725
/// # Domain
726
///
727
/// The original curve's domain must be right-finite to get a valid [`PingPongCurve`].
728
#[derive(Clone, Debug)]
729
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
730
#[cfg_attr(
731
feature = "bevy_reflect",
732
derive(Reflect, FromReflect),
733
reflect(from_reflect = false)
734
)]
735
pub struct PingPongCurve<T, C> {
736
pub(crate) curve: C,
737
#[cfg_attr(feature = "serialize", serde(skip))]
738
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
739
pub(crate) _phantom: PhantomData<fn() -> T>,
740
}
741
742
impl<T, C> Curve<T> for PingPongCurve<T, C>
743
where
744
C: Curve<T>,
745
{
746
#[inline]
747
fn domain(&self) -> Interval {
748
// This unwrap always succeeds because `curve` has a valid Interval as its domain and the
749
// length of `curve` cannot be NAN. It's still fine if it's infinity.
750
Interval::new(
751
self.curve.domain().start(),
752
self.curve.domain().end() + self.curve.domain().length(),
753
)
754
.unwrap()
755
}
756
757
#[inline]
758
fn sample_unchecked(&self, t: f32) -> T {
759
// the domain is bounded by construction
760
let final_t = if t > self.curve.domain().end() {
761
self.curve.domain().end() * 2.0 - t
762
} else {
763
t
764
};
765
self.curve.sample_unchecked(final_t)
766
}
767
}
768
769
/// The curve that results from chaining two curves.
770
///
771
/// Additionally the transition of the samples is guaranteed to not make sudden jumps. This is
772
/// useful if you really just know about the shapes of your curves and don't want to deal with
773
/// stitching them together properly when it would just introduce useless complexity. It is
774
/// realized by translating the second curve so that its start sample point coincides with the
775
/// first curves' end sample point.
776
///
777
/// Curves of this type are produced by [`CurveExt::chain_continue`].
778
///
779
/// # Domain
780
///
781
/// The first curve's domain must be right-finite and the second's must be left-finite to get a
782
/// valid [`ContinuationCurve`].
783
#[derive(Clone, Debug)]
784
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
785
#[cfg_attr(
786
feature = "bevy_reflect",
787
derive(Reflect, FromReflect),
788
reflect(from_reflect = false)
789
)]
790
pub struct ContinuationCurve<T, C, D> {
791
pub(crate) first: C,
792
pub(crate) second: D,
793
// cache the offset in the curve directly to prevent triple sampling for every sample we make
794
pub(crate) offset: T,
795
#[cfg_attr(feature = "serialize", serde(skip))]
796
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
797
pub(crate) _phantom: PhantomData<fn() -> T>,
798
}
799
800
impl<T, C, D> Curve<T> for ContinuationCurve<T, C, D>
801
where
802
T: VectorSpace,
803
C: Curve<T>,
804
D: Curve<T>,
805
{
806
#[inline]
807
fn domain(&self) -> Interval {
808
// This unwrap always succeeds because `curve` has a valid Interval as its domain and the
809
// length of `curve` cannot be NAN. It's still fine if it's infinity.
810
Interval::new(
811
self.first.domain().start(),
812
self.first.domain().end() + self.second.domain().length(),
813
)
814
.unwrap()
815
}
816
817
#[inline]
818
fn sample_unchecked(&self, t: f32) -> T {
819
if t > self.first.domain().end() {
820
self.second.sample_unchecked(
821
// `t - first.domain.end` computes the offset into the domain of the second.
822
t - self.first.domain().end() + self.second.domain().start(),
823
) + self.offset
824
} else {
825
self.first.sample_unchecked(t)
826
}
827
}
828
}
829
830