bevy_math/
cubic_splines.rs

1//! Provides types for building cubic splines for rendering curves and use with animation easing.
2
3use core::{fmt::Debug, iter::once};
4
5use crate::{ops::FloatPow, Vec2, VectorSpace};
6
7use derive_more::derive::{Display, Error};
8use itertools::Itertools;
9
10#[cfg(feature = "curve")]
11use crate::curve::{Curve, Interval};
12
13#[cfg(feature = "bevy_reflect")]
14use bevy_reflect::{std_traits::ReflectDefault, Reflect};
15
16/// A spline composed of a single cubic Bezier curve.
17///
18/// Useful for user-drawn curves with local control, or animation easing. See
19/// [`CubicSegment::new_bezier`] for use in easing.
20///
21/// ### Interpolation
22/// The curve only passes through the first and last control point in each set of four points. The curve
23/// is divided into "segments" by every fourth control point.
24///
25/// ### Tangency
26/// Tangents are manually defined by the two intermediate control points within each set of four points.
27/// You can think of the control points the curve passes through as "anchors", and as the intermediate
28/// control points as the anchors displaced along their tangent vectors
29///
30/// ### Continuity
31/// A Bezier curve is at minimum C0 continuous, meaning it has no holes or jumps. Each curve segment is
32/// C2, meaning the tangent vector changes smoothly between each set of four control points, but this
33/// doesn't hold at the control points between segments. Making the whole curve C1 or C2 requires moving
34/// the intermediate control points to align the tangent vectors between segments, and can result in a
35/// loss of local control.
36///
37/// ### Usage
38///
39/// ```
40/// # use bevy_math::{*, prelude::*};
41/// let points = [[
42///     vec2(-1.0, -20.0),
43///     vec2(3.0, 2.0),
44///     vec2(5.0, 3.0),
45///     vec2(9.0, 8.0),
46/// ]];
47/// let bezier = CubicBezier::new(points).to_curve().unwrap();
48/// let positions: Vec<_> = bezier.iter_positions(100).collect();
49/// ```
50#[derive(Clone, Debug)]
51#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
52pub struct CubicBezier<P: VectorSpace> {
53    /// The control points of the Bezier curve.
54    pub control_points: Vec<[P; 4]>,
55}
56
57impl<P: VectorSpace> CubicBezier<P> {
58    /// Create a new cubic Bezier curve from sets of control points.
59    pub fn new(control_points: impl Into<Vec<[P; 4]>>) -> Self {
60        Self {
61            control_points: control_points.into(),
62        }
63    }
64}
65impl<P: VectorSpace> CubicGenerator<P> for CubicBezier<P> {
66    type Error = CubicBezierError;
67
68    #[inline]
69    fn to_curve(&self) -> Result<CubicCurve<P>, Self::Error> {
70        // A derivation for this matrix can be found in "General Matrix Representations for B-splines" by Kaihuai Qin.
71        // <https://xiaoxingchen.github.io/2020/03/02/bspline_in_so3/general_matrix_representation_for_bsplines.pdf>
72        // See section 4.2 and equation 11.
73        let char_matrix = [
74            [1., 0., 0., 0.],
75            [-3., 3., 0., 0.],
76            [3., -6., 3., 0.],
77            [-1., 3., -3., 1.],
78        ];
79
80        let segments = self
81            .control_points
82            .iter()
83            .map(|p| CubicSegment::coefficients(*p, char_matrix))
84            .collect_vec();
85
86        if segments.is_empty() {
87            Err(CubicBezierError)
88        } else {
89            Ok(CubicCurve { segments })
90        }
91    }
92}
93
94/// An error returned during cubic curve generation for cubic Bezier curves indicating that a
95/// segment of control points was not present.
96#[derive(Clone, Debug, Error, Display)]
97#[display("Unable to generate cubic curve: at least one set of control points is required")]
98pub struct CubicBezierError;
99
100/// A spline interpolated continuously between the nearest two control points, with the position and
101/// velocity of the curve specified at both control points. This curve passes through all control
102/// points, with the specified velocity which includes direction and parametric speed.
103///
104/// Useful for smooth interpolation when you know the position and velocity at two points in time,
105/// such as network prediction.
106///
107/// ### Interpolation
108/// The curve passes through every control point.
109///
110/// ### Tangency
111/// Tangents are explicitly defined at each control point.
112///
113/// ### Continuity
114/// The curve is at minimum C1 continuous, meaning that it has no holes or jumps and the tangent vector also
115/// has no sudden jumps.
116///
117/// ### Parametrization
118/// The first segment of the curve connects the first two control points, the second connects the second and
119/// third, and so on. This remains true when a cyclic curve is formed with [`to_curve_cyclic`], in which case
120/// the final curve segment connects the last control point to the first.
121///
122/// ### Usage
123///
124/// ```
125/// # use bevy_math::{*, prelude::*};
126/// let points = [
127///     vec2(-1.0, -20.0),
128///     vec2(3.0, 2.0),
129///     vec2(5.0, 3.0),
130///     vec2(9.0, 8.0),
131/// ];
132/// let tangents = [
133///     vec2(0.0, 1.0),
134///     vec2(0.0, 1.0),
135///     vec2(0.0, 1.0),
136///     vec2(0.0, 1.0),
137/// ];
138/// let hermite = CubicHermite::new(points, tangents).to_curve().unwrap();
139/// let positions: Vec<_> = hermite.iter_positions(100).collect();
140/// ```
141///
142/// [`to_curve_cyclic`]: CyclicCubicGenerator::to_curve_cyclic
143#[derive(Clone, Debug)]
144#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
145pub struct CubicHermite<P: VectorSpace> {
146    /// The control points of the Hermite curve.
147    pub control_points: Vec<(P, P)>,
148}
149impl<P: VectorSpace> CubicHermite<P> {
150    /// Create a new Hermite curve from sets of control points.
151    pub fn new(
152        control_points: impl IntoIterator<Item = P>,
153        tangents: impl IntoIterator<Item = P>,
154    ) -> Self {
155        Self {
156            control_points: control_points.into_iter().zip(tangents).collect(),
157        }
158    }
159
160    /// The characteristic matrix for this spline construction.
161    ///
162    /// Each row of this matrix expresses the coefficients of a [`CubicSegment`] as a linear
163    /// combination of `p_i`, `v_i`, `p_{i+1}`, and `v_{i+1}`, where `(p_i, v_i)` and
164    /// `(p_{i+1}, v_{i+1})` are consecutive control points with tangents.
165    #[inline]
166    fn char_matrix(&self) -> [[f32; 4]; 4] {
167        [
168            [1., 0., 0., 0.],
169            [0., 1., 0., 0.],
170            [-3., -2., 3., -1.],
171            [2., 1., -2., 1.],
172        ]
173    }
174}
175impl<P: VectorSpace> CubicGenerator<P> for CubicHermite<P> {
176    type Error = InsufficientDataError;
177
178    #[inline]
179    fn to_curve(&self) -> Result<CubicCurve<P>, Self::Error> {
180        let segments = self
181            .control_points
182            .windows(2)
183            .map(|p| {
184                let (p0, v0, p1, v1) = (p[0].0, p[0].1, p[1].0, p[1].1);
185                CubicSegment::coefficients([p0, v0, p1, v1], self.char_matrix())
186            })
187            .collect_vec();
188
189        if segments.is_empty() {
190            Err(InsufficientDataError {
191                expected: 2,
192                given: self.control_points.len(),
193            })
194        } else {
195            Ok(CubicCurve { segments })
196        }
197    }
198}
199impl<P: VectorSpace> CyclicCubicGenerator<P> for CubicHermite<P> {
200    type Error = InsufficientDataError;
201
202    #[inline]
203    fn to_curve_cyclic(&self) -> Result<CubicCurve<P>, Self::Error> {
204        let segments = self
205            .control_points
206            .iter()
207            .circular_tuple_windows()
208            .map(|(&j0, &j1)| {
209                let (p0, v0, p1, v1) = (j0.0, j0.1, j1.0, j1.1);
210                CubicSegment::coefficients([p0, v0, p1, v1], self.char_matrix())
211            })
212            .collect_vec();
213
214        if segments.is_empty() {
215            Err(InsufficientDataError {
216                expected: 2,
217                given: self.control_points.len(),
218            })
219        } else {
220            Ok(CubicCurve { segments })
221        }
222    }
223}
224
225/// A spline interpolated continuously across the nearest four control points, with the position of
226/// the curve specified at every control point and the tangents computed automatically. The associated [`CubicCurve`]
227/// has one segment between each pair of adjacent control points.
228///
229/// **Note** the Catmull-Rom spline is a special case of Cardinal spline where the tension is 0.5.
230///
231/// ### Interpolation
232/// The curve passes through every control point.
233///
234/// ### Tangency
235/// Tangents are automatically computed based on the positions of control points.
236///
237/// ### Continuity
238/// The curve is at minimum C1, meaning that it is continuous (it has no holes or jumps), and its tangent
239/// vector is also well-defined everywhere, without sudden jumps.
240///
241/// ### Parametrization
242/// The first segment of the curve connects the first two control points, the second connects the second and
243/// third, and so on. This remains true when a cyclic curve is formed with [`to_curve_cyclic`], in which case
244/// the final curve segment connects the last control point to the first.
245///
246/// ### Usage
247///
248/// ```
249/// # use bevy_math::{*, prelude::*};
250/// let points = [
251///     vec2(-1.0, -20.0),
252///     vec2(3.0, 2.0),
253///     vec2(5.0, 3.0),
254///     vec2(9.0, 8.0),
255/// ];
256/// let cardinal = CubicCardinalSpline::new(0.3, points).to_curve().unwrap();
257/// let positions: Vec<_> = cardinal.iter_positions(100).collect();
258/// ```
259///
260/// [`to_curve_cyclic`]: CyclicCubicGenerator::to_curve_cyclic
261#[derive(Clone, Debug)]
262#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
263pub struct CubicCardinalSpline<P: VectorSpace> {
264    /// Tension
265    pub tension: f32,
266    /// The control points of the Cardinal spline
267    pub control_points: Vec<P>,
268}
269
270impl<P: VectorSpace> CubicCardinalSpline<P> {
271    /// Build a new Cardinal spline.
272    pub fn new(tension: f32, control_points: impl Into<Vec<P>>) -> Self {
273        Self {
274            tension,
275            control_points: control_points.into(),
276        }
277    }
278
279    /// Build a new Catmull-Rom spline, the special case of a Cardinal spline where tension = 1/2.
280    pub fn new_catmull_rom(control_points: impl Into<Vec<P>>) -> Self {
281        Self {
282            tension: 0.5,
283            control_points: control_points.into(),
284        }
285    }
286
287    /// The characteristic matrix for this spline construction.
288    ///
289    /// Each row of this matrix expresses the coefficients of a [`CubicSegment`] as a linear
290    /// combination of four consecutive control points.
291    #[inline]
292    fn char_matrix(&self) -> [[f32; 4]; 4] {
293        let s = self.tension;
294        [
295            [0., 1., 0., 0.],
296            [-s, 0., s, 0.],
297            [2. * s, s - 3., 3. - 2. * s, -s],
298            [-s, 2. - s, s - 2., s],
299        ]
300    }
301}
302impl<P: VectorSpace> CubicGenerator<P> for CubicCardinalSpline<P> {
303    type Error = InsufficientDataError;
304
305    #[inline]
306    fn to_curve(&self) -> Result<CubicCurve<P>, Self::Error> {
307        let length = self.control_points.len();
308
309        // Early return to avoid accessing an invalid index
310        if length < 2 {
311            return Err(InsufficientDataError {
312                expected: 2,
313                given: self.control_points.len(),
314            });
315        }
316
317        // Extend the list of control points by mirroring the last second-to-last control points on each end;
318        // this allows tangents for the endpoints to be provided, and the overall effect is that the tangent
319        // at an endpoint is proportional to twice the vector between it and its adjacent control point.
320        //
321        // The expression used here is P_{-1} := P_0 - (P_1 - P_0) = 2P_0 - P_1. (Analogously at the other end.)
322        let mirrored_first = self.control_points[0] * 2. - self.control_points[1];
323        let mirrored_last = self.control_points[length - 1] * 2. - self.control_points[length - 2];
324        let extended_control_points = once(&mirrored_first)
325            .chain(self.control_points.iter())
326            .chain(once(&mirrored_last));
327
328        let segments = extended_control_points
329            .tuple_windows()
330            .map(|(&p0, &p1, &p2, &p3)| {
331                CubicSegment::coefficients([p0, p1, p2, p3], self.char_matrix())
332            })
333            .collect_vec();
334
335        Ok(CubicCurve { segments })
336    }
337}
338impl<P: VectorSpace> CyclicCubicGenerator<P> for CubicCardinalSpline<P> {
339    type Error = InsufficientDataError;
340
341    #[inline]
342    fn to_curve_cyclic(&self) -> Result<CubicCurve<P>, Self::Error> {
343        let len = self.control_points.len();
344
345        if len < 2 {
346            return Err(InsufficientDataError {
347                expected: 2,
348                given: self.control_points.len(),
349            });
350        }
351
352        // This would ordinarily be the last segment, but we pick it out so that we can make it first
353        // in order to get a desirable parametrization where the first segment connects the first two
354        // control points instead of the second and third.
355        let first_segment = {
356            // We take the indices mod `len` in case `len` is very small.
357            let p0 = self.control_points[len - 1];
358            let p1 = self.control_points[0];
359            let p2 = self.control_points[1 % len];
360            let p3 = self.control_points[2 % len];
361            CubicSegment::coefficients([p0, p1, p2, p3], self.char_matrix())
362        };
363
364        let later_segments = self
365            .control_points
366            .iter()
367            .circular_tuple_windows()
368            .map(|(&p0, &p1, &p2, &p3)| {
369                CubicSegment::coefficients([p0, p1, p2, p3], self.char_matrix())
370            })
371            .take(len - 1);
372
373        let mut segments = Vec::with_capacity(len);
374        segments.push(first_segment);
375        segments.extend(later_segments);
376
377        Ok(CubicCurve { segments })
378    }
379}
380
381/// A spline interpolated continuously across the nearest four control points. The curve does not
382/// necessarily pass through any of the control points.
383///
384/// ### Interpolation
385/// The curve does not necessarily pass through its control points.
386///
387/// ### Tangency
388/// Tangents are automatically computed based on the positions of control points.
389///
390/// ### Continuity
391/// The curve is C2 continuous, meaning it has no holes or jumps, the tangent vector changes smoothly along
392/// the entire curve, and the acceleration also varies continuously. The acceleration continuity of this
393/// spline makes it useful for camera paths.
394///
395/// ### Parametrization
396/// Each curve segment is defined by a window of four control points taken in sequence. When [`to_curve_cyclic`]
397/// is used to form a cyclic curve, the three additional segments used to close the curve come last.
398///
399/// ### Usage
400///
401/// ```
402/// # use bevy_math::{*, prelude::*};
403/// let points = [
404///     vec2(-1.0, -20.0),
405///     vec2(3.0, 2.0),
406///     vec2(5.0, 3.0),
407///     vec2(9.0, 8.0),
408/// ];
409/// let b_spline = CubicBSpline::new(points).to_curve().unwrap();
410/// let positions: Vec<_> = b_spline.iter_positions(100).collect();
411/// ```
412///
413/// [`to_curve_cyclic`]: CyclicCubicGenerator::to_curve_cyclic
414#[derive(Clone, Debug)]
415#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
416pub struct CubicBSpline<P: VectorSpace> {
417    /// The control points of the spline
418    pub control_points: Vec<P>,
419}
420impl<P: VectorSpace> CubicBSpline<P> {
421    /// Build a new B-Spline.
422    pub fn new(control_points: impl Into<Vec<P>>) -> Self {
423        Self {
424            control_points: control_points.into(),
425        }
426    }
427
428    /// The characteristic matrix for this spline construction.
429    ///
430    /// Each row of this matrix expresses the coefficients of a [`CubicSegment`] as a linear
431    /// combination of four consecutive control points.
432    #[inline]
433    fn char_matrix(&self) -> [[f32; 4]; 4] {
434        // A derivation for this matrix can be found in "General Matrix Representations for B-splines" by Kaihuai Qin.
435        // <https://xiaoxingchen.github.io/2020/03/02/bspline_in_so3/general_matrix_representation_for_bsplines.pdf>
436        // See section 4.1 and equations 7 and 8.
437        let mut char_matrix = [
438            [1.0, 4.0, 1.0, 0.0],
439            [-3.0, 0.0, 3.0, 0.0],
440            [3.0, -6.0, 3.0, 0.0],
441            [-1.0, 3.0, -3.0, 1.0],
442        ];
443
444        char_matrix
445            .iter_mut()
446            .for_each(|r| r.iter_mut().for_each(|c| *c /= 6.0));
447
448        char_matrix
449    }
450}
451impl<P: VectorSpace> CubicGenerator<P> for CubicBSpline<P> {
452    type Error = InsufficientDataError;
453
454    #[inline]
455    fn to_curve(&self) -> Result<CubicCurve<P>, Self::Error> {
456        let segments = self
457            .control_points
458            .windows(4)
459            .map(|p| CubicSegment::coefficients([p[0], p[1], p[2], p[3]], self.char_matrix()))
460            .collect_vec();
461
462        if segments.is_empty() {
463            Err(InsufficientDataError {
464                expected: 4,
465                given: self.control_points.len(),
466            })
467        } else {
468            Ok(CubicCurve { segments })
469        }
470    }
471}
472
473impl<P: VectorSpace> CyclicCubicGenerator<P> for CubicBSpline<P> {
474    type Error = InsufficientDataError;
475
476    #[inline]
477    fn to_curve_cyclic(&self) -> Result<CubicCurve<P>, Self::Error> {
478        let segments = self
479            .control_points
480            .iter()
481            .circular_tuple_windows()
482            .map(|(&a, &b, &c, &d)| CubicSegment::coefficients([a, b, c, d], self.char_matrix()))
483            .collect_vec();
484
485        // Note that the parametrization is consistent with the one for `to_curve` but with
486        // the extra curve segments all tacked on at the end. This might be slightly counter-intuitive,
487        // since it means the first segment doesn't go "between" the first two control points, but
488        // between the second and third instead.
489
490        if segments.is_empty() {
491            Err(InsufficientDataError {
492                expected: 2,
493                given: self.control_points.len(),
494            })
495        } else {
496            Ok(CubicCurve { segments })
497        }
498    }
499}
500
501/// Error during construction of [`CubicNurbs`]
502#[derive(Clone, Debug, Error, Display)]
503pub enum CubicNurbsError {
504    /// Provided the wrong number of knots.
505    #[display("Wrong number of knots: expected {expected}, provided {provided}")]
506    KnotsNumberMismatch {
507        /// Expected number of knots
508        expected: usize,
509        /// Provided number of knots
510        provided: usize,
511    },
512    /// The provided knots had a descending knot pair. Subsequent knots must
513    /// either increase or stay the same.
514    #[display("Invalid knots: contains descending knot pair")]
515    DescendingKnots,
516    /// The provided knots were all equal. Knots must contain at least one increasing pair.
517    #[display("Invalid knots: all knots are equal")]
518    ConstantKnots,
519    /// Provided a different number of weights and control points.
520    #[display("Incorrect number of weights: expected {expected}, provided {provided}")]
521    WeightsNumberMismatch {
522        /// Expected number of weights
523        expected: usize,
524        /// Provided number of weights
525        provided: usize,
526    },
527    /// The number of control points provided is less than 4.
528    #[display("Not enough control points, at least 4 are required, {provided} were provided")]
529    NotEnoughControlPoints {
530        /// The number of control points provided
531        provided: usize,
532    },
533}
534
535/// Non-uniform Rational B-Splines (NURBS) are a powerful generalization of the [`CubicBSpline`] which can
536/// represent a much more diverse class of curves (like perfect circles and ellipses).
537///
538/// ### Non-uniformity
539/// The 'NU' part of NURBS stands for "Non-Uniform". This has to do with a parameter called 'knots'.
540/// The knots are a non-decreasing sequence of floating point numbers. The first and last three pairs of
541/// knots control the behavior of the curve as it approaches its endpoints. The intermediate pairs
542/// each control the length of one segment of the curve. Multiple repeated knot values are called
543/// "knot multiplicity". Knot multiplicity in the intermediate knots causes a "zero-length" segment,
544/// and can create sharp corners.
545///
546/// ### Rationality
547/// The 'R' part of NURBS stands for "Rational". This has to do with NURBS allowing each control point to
548/// be assigned a weighting, which controls how much it affects the curve compared to the other points.
549///
550/// ### Interpolation
551/// The curve will not pass through the control points except where a knot has multiplicity four.
552///
553/// ### Tangency
554/// Tangents are automatically computed based on the position of control points.
555///
556/// ### Continuity
557/// When there is no knot multiplicity, the curve is C2 continuous, meaning it has no holes or jumps and the
558/// tangent vector changes smoothly along the entire curve length. Like the [`CubicBSpline`], the acceleration
559/// continuity makes it useful for camera paths. Knot multiplicity of 2 in intermediate knots reduces the
560/// continuity to C1, and knot multiplicity of 3 reduces the continuity to C0. The curve is always at least
561/// C0, meaning it has no jumps or holes.
562///
563/// ### Usage
564///
565/// ```
566/// # use bevy_math::{*, prelude::*};
567/// let points = [
568///     vec2(-1.0, -20.0),
569///     vec2(3.0, 2.0),
570///     vec2(5.0, 3.0),
571///     vec2(9.0, 8.0),
572/// ];
573/// let weights = [1.0, 1.0, 2.0, 1.0];
574/// let knots = [0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0];
575/// let nurbs = CubicNurbs::new(points, Some(weights), Some(knots))
576///     .expect("NURBS construction failed!")
577///     .to_curve()
578///     .unwrap();
579/// let positions: Vec<_> = nurbs.iter_positions(100).collect();
580/// ```
581#[derive(Clone, Debug)]
582#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
583pub struct CubicNurbs<P: VectorSpace> {
584    /// The control points of the NURBS
585    pub control_points: Vec<P>,
586    /// Weights
587    pub weights: Vec<f32>,
588    /// Knots
589    pub knots: Vec<f32>,
590}
591impl<P: VectorSpace> CubicNurbs<P> {
592    /// Build a Non-Uniform Rational B-Spline.
593    ///
594    /// If provided, weights must be the same length as the control points. Defaults to equal weights.
595    ///
596    /// If provided, the number of knots must be n + 4 elements, where n is the amount of control
597    /// points. Defaults to open uniform knots: [`Self::open_uniform_knots`]. Knots cannot
598    /// all be equal.
599    ///
600    /// At least 4 points must be provided, otherwise an error will be returned.
601    pub fn new(
602        control_points: impl Into<Vec<P>>,
603        weights: Option<impl Into<Vec<f32>>>,
604        knots: Option<impl Into<Vec<f32>>>,
605    ) -> Result<Self, CubicNurbsError> {
606        let mut control_points: Vec<P> = control_points.into();
607        let control_points_len = control_points.len();
608
609        if control_points_len < 4 {
610            return Err(CubicNurbsError::NotEnoughControlPoints {
611                provided: control_points_len,
612            });
613        }
614
615        let weights = weights
616            .map(Into::into)
617            .unwrap_or_else(|| vec![1.0; control_points_len]);
618
619        let mut knots: Vec<f32> = knots.map(Into::into).unwrap_or_else(|| {
620            Self::open_uniform_knots(control_points_len)
621                .expect("The amount of control points was checked")
622        });
623
624        let expected_knots_len = Self::knots_len(control_points_len);
625
626        // Check the number of knots is correct
627        if knots.len() != expected_knots_len {
628            return Err(CubicNurbsError::KnotsNumberMismatch {
629                expected: expected_knots_len,
630                provided: knots.len(),
631            });
632        }
633
634        // Ensure the knots are non-descending (previous element is less than or equal
635        // to the next)
636        if knots.windows(2).any(|win| win[0] > win[1]) {
637            return Err(CubicNurbsError::DescendingKnots);
638        }
639
640        // Ensure the knots are non-constant
641        if knots.windows(2).all(|win| win[0] == win[1]) {
642            return Err(CubicNurbsError::ConstantKnots);
643        }
644
645        // Check that the number of weights equals the number of control points
646        if weights.len() != control_points_len {
647            return Err(CubicNurbsError::WeightsNumberMismatch {
648                expected: control_points_len,
649                provided: weights.len(),
650            });
651        }
652
653        // To align the evaluation behavior of nurbs with the other splines,
654        // make the intervals between knots form an exact cover of [0, N], where N is
655        // the number of segments of the final curve.
656        let curve_length = (control_points.len() - 3) as f32;
657        let min = *knots.first().unwrap();
658        let max = *knots.last().unwrap();
659        let knot_delta = max - min;
660        knots = knots
661            .into_iter()
662            .map(|k| k - min)
663            .map(|k| k * curve_length / knot_delta)
664            .collect();
665
666        control_points
667            .iter_mut()
668            .zip(weights.iter())
669            .for_each(|(p, w)| *p = *p * *w);
670
671        Ok(Self {
672            control_points,
673            weights,
674            knots,
675        })
676    }
677
678    /// Generates uniform knots that will generate the same curve as [`CubicBSpline`].
679    ///
680    /// "Uniform" means that the difference between two subsequent knots is the same.
681    ///
682    /// Will return `None` if there are less than 4 control points.
683    pub fn uniform_knots(control_points: usize) -> Option<Vec<f32>> {
684        if control_points < 4 {
685            return None;
686        }
687        Some(
688            (0..Self::knots_len(control_points))
689                .map(|v| v as f32)
690                .collect(),
691        )
692    }
693
694    /// Generates open uniform knots, which makes the ends of the curve pass through the
695    /// start and end points.
696    ///
697    /// The start and end knots have multiplicity 4, and intermediate knots have multiplicity 0 and
698    /// difference of 1.
699    ///
700    /// Will return `None` if there are less than 4 control points.
701    pub fn open_uniform_knots(control_points: usize) -> Option<Vec<f32>> {
702        if control_points < 4 {
703            return None;
704        }
705        let last_knots_value = control_points - 3;
706        Some(
707            core::iter::repeat(0.0)
708                .take(4)
709                .chain((1..last_knots_value).map(|v| v as f32))
710                .chain(core::iter::repeat(last_knots_value as f32).take(4))
711                .collect(),
712        )
713    }
714
715    #[inline(always)]
716    const fn knots_len(control_points_len: usize) -> usize {
717        control_points_len + 4
718    }
719
720    /// Generates a non-uniform B-spline characteristic matrix from a sequence of six knots. Each six
721    /// knots describe the relationship between four successive control points. For padding reasons,
722    /// this takes a vector of 8 knots, but only six are actually used.
723    fn generate_matrix(knots: &[f32; 8]) -> [[f32; 4]; 4] {
724        // A derivation for this matrix can be found in "General Matrix Representations for B-splines" by Kaihuai Qin.
725        // <https://xiaoxingchen.github.io/2020/03/02/bspline_in_so3/general_matrix_representation_for_bsplines.pdf>
726        // See section 3.1.
727
728        let t = knots;
729        // In the notation of the paper:
730        // t[1] := t_i-2
731        // t[2] := t_i-1
732        // t[3] := t_i   (the lower extent of the current knot span)
733        // t[4] := t_i+1 (the upper extent of the current knot span)
734        // t[5] := t_i+2
735        // t[6] := t_i+3
736
737        let m00 = (t[4] - t[3]).squared() / ((t[4] - t[2]) * (t[4] - t[1]));
738        let m02 = (t[3] - t[2]).squared() / ((t[5] - t[2]) * (t[4] - t[2]));
739        let m12 = (3.0 * (t[4] - t[3]) * (t[3] - t[2])) / ((t[5] - t[2]) * (t[4] - t[2]));
740        let m22 = 3.0 * (t[4] - t[3]).squared() / ((t[5] - t[2]) * (t[4] - t[2]));
741        let m33 = (t[4] - t[3]).squared() / ((t[6] - t[3]) * (t[5] - t[3]));
742        let m32 = -m22 / 3.0 - m33 - (t[4] - t[3]).squared() / ((t[5] - t[3]) * (t[5] - t[2]));
743        [
744            [m00, 1.0 - m00 - m02, m02, 0.0],
745            [-3.0 * m00, 3.0 * m00 - m12, m12, 0.0],
746            [3.0 * m00, -3.0 * m00 - m22, m22, 0.0],
747            [-m00, m00 - m32 - m33, m32, m33],
748        ]
749    }
750}
751impl<P: VectorSpace> RationalGenerator<P> for CubicNurbs<P> {
752    type Error = InsufficientDataError;
753
754    #[inline]
755    fn to_curve(&self) -> Result<RationalCurve<P>, Self::Error> {
756        let segments = self
757            .control_points
758            .windows(4)
759            .zip(self.weights.windows(4))
760            .zip(self.knots.windows(8))
761            .filter(|(_, knots)| knots[4] - knots[3] > 0.0)
762            .map(|((points, weights), knots)| {
763                // This is curve segment i. It uses control points P_i, P_i+2, P_i+2 and P_i+3,
764                // It is associated with knot span i+3 (which is the interval between knots i+3
765                // and i+4) and its characteristic matrix uses knots i+1 through i+6 (because
766                // those define the two knot spans on either side).
767                let span = knots[4] - knots[3];
768                let coefficient_knots = knots.try_into().expect("Knot windows are of length 6");
769                let matrix = Self::generate_matrix(coefficient_knots);
770                RationalSegment::coefficients(
771                    points.try_into().expect("Point windows are of length 4"),
772                    weights.try_into().expect("Weight windows are of length 4"),
773                    span,
774                    matrix,
775                )
776            })
777            .collect_vec();
778        if segments.is_empty() {
779            Err(InsufficientDataError {
780                expected: 4,
781                given: self.control_points.len(),
782            })
783        } else {
784            Ok(RationalCurve { segments })
785        }
786    }
787}
788
789/// A spline interpolated linearly between the nearest 2 points.
790///
791/// ### Interpolation
792/// The curve passes through every control point.
793///
794/// ### Tangency
795/// The curve is not generally differentiable at control points.
796///
797/// ### Continuity
798/// The curve is C0 continuous, meaning it has no holes or jumps.
799///
800/// ### Parametrization
801/// Each curve segment connects two adjacent control points in sequence. When a cyclic curve is
802/// formed with [`to_curve_cyclic`], the final segment connects the last control point with the first.
803///
804/// [`to_curve_cyclic`]: CyclicCubicGenerator::to_curve_cyclic
805#[derive(Clone, Debug)]
806#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
807pub struct LinearSpline<P: VectorSpace> {
808    /// The control points of the linear spline.
809    pub points: Vec<P>,
810}
811impl<P: VectorSpace> LinearSpline<P> {
812    /// Create a new linear spline from a list of points to be interpolated.
813    pub fn new(points: impl Into<Vec<P>>) -> Self {
814        Self {
815            points: points.into(),
816        }
817    }
818}
819impl<P: VectorSpace> CubicGenerator<P> for LinearSpline<P> {
820    type Error = InsufficientDataError;
821
822    #[inline]
823    fn to_curve(&self) -> Result<CubicCurve<P>, Self::Error> {
824        let segments = self
825            .points
826            .windows(2)
827            .map(|points| {
828                let a = points[0];
829                let b = points[1];
830                CubicSegment {
831                    coeff: [a, b - a, P::default(), P::default()],
832                }
833            })
834            .collect_vec();
835
836        if segments.is_empty() {
837            Err(InsufficientDataError {
838                expected: 2,
839                given: self.points.len(),
840            })
841        } else {
842            Ok(CubicCurve { segments })
843        }
844    }
845}
846impl<P: VectorSpace> CyclicCubicGenerator<P> for LinearSpline<P> {
847    type Error = InsufficientDataError;
848
849    #[inline]
850    fn to_curve_cyclic(&self) -> Result<CubicCurve<P>, Self::Error> {
851        let segments = self
852            .points
853            .iter()
854            .circular_tuple_windows()
855            .map(|(&a, &b)| CubicSegment {
856                coeff: [a, b - a, P::default(), P::default()],
857            })
858            .collect_vec();
859
860        if segments.is_empty() {
861            Err(InsufficientDataError {
862                expected: 2,
863                given: self.points.len(),
864            })
865        } else {
866            Ok(CubicCurve { segments })
867        }
868    }
869}
870
871/// An error indicating that a spline construction didn't have enough control points to generate a curve.
872#[derive(Clone, Debug, Error, Display)]
873#[display("Not enough data to build curve: needed at least {expected} control points but was only given {given}")]
874pub struct InsufficientDataError {
875    expected: usize,
876    given: usize,
877}
878
879/// Implement this on cubic splines that can generate a cubic curve from their spline parameters.
880pub trait CubicGenerator<P: VectorSpace> {
881    /// An error type indicating why construction might fail.
882    type Error;
883
884    /// Build a [`CubicCurve`] by computing the interpolation coefficients for each curve segment.
885    fn to_curve(&self) -> Result<CubicCurve<P>, Self::Error>;
886}
887
888/// Implement this on cubic splines that can generate a cyclic cubic curve from their spline parameters.
889///
890/// This makes sense only when the control data can be interpreted cyclically.
891pub trait CyclicCubicGenerator<P: VectorSpace> {
892    /// An error type indicating why construction might fail.
893    type Error;
894
895    /// Build a cyclic [`CubicCurve`] by computing the interpolation coefficients for each curve segment,
896    /// treating the control data as cyclic so that the result is a closed curve.
897    fn to_curve_cyclic(&self) -> Result<CubicCurve<P>, Self::Error>;
898}
899
900/// A segment of a cubic curve, used to hold precomputed coefficients for fast interpolation.
901/// It is a [`Curve`] with domain `[0, 1]`.
902///
903/// Segments can be chained together to form a longer [compound curve].
904///
905/// [compound curve]: CubicCurve
906#[derive(Copy, Clone, Debug, Default, PartialEq)]
907#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
908#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Default))]
909pub struct CubicSegment<P: VectorSpace> {
910    /// Polynomial coefficients for the segment.
911    pub coeff: [P; 4],
912}
913
914impl<P: VectorSpace> CubicSegment<P> {
915    /// Instantaneous position of a point at parametric value `t`.
916    #[inline]
917    pub fn position(&self, t: f32) -> P {
918        let [a, b, c, d] = self.coeff;
919        // Evaluate `a + bt + ct^2 + dt^3`, avoiding exponentiation
920        a + (b + (c + d * t) * t) * t
921    }
922
923    /// Instantaneous velocity of a point at parametric value `t`.
924    #[inline]
925    pub fn velocity(&self, t: f32) -> P {
926        let [_, b, c, d] = self.coeff;
927        // Evaluate the derivative, which is `b + 2ct + 3dt^2`, avoiding exponentiation
928        b + (c * 2.0 + d * 3.0 * t) * t
929    }
930
931    /// Instantaneous acceleration of a point at parametric value `t`.
932    #[inline]
933    pub fn acceleration(&self, t: f32) -> P {
934        let [_, _, c, d] = self.coeff;
935        // Evaluate the second derivative, which is `2c + 6dt`
936        c * 2.0 + d * 6.0 * t
937    }
938
939    /// Calculate polynomial coefficients for the cubic curve using a characteristic matrix.
940    #[inline]
941    fn coefficients(p: [P; 4], char_matrix: [[f32; 4]; 4]) -> Self {
942        let [c0, c1, c2, c3] = char_matrix;
943        // These are the polynomial coefficients, computed by multiplying the characteristic
944        // matrix by the point matrix.
945        let coeff = [
946            p[0] * c0[0] + p[1] * c0[1] + p[2] * c0[2] + p[3] * c0[3],
947            p[0] * c1[0] + p[1] * c1[1] + p[2] * c1[2] + p[3] * c1[3],
948            p[0] * c2[0] + p[1] * c2[1] + p[2] * c2[2] + p[3] * c2[3],
949            p[0] * c3[0] + p[1] * c3[1] + p[2] * c3[2] + p[3] * c3[3],
950        ];
951        Self { coeff }
952    }
953}
954
955/// The `CubicSegment<Vec2>` can be used as a 2-dimensional easing curve for animation.
956///
957/// The x-axis of the curve is time, and the y-axis is the output value. This struct provides
958/// methods for extremely fast solves for y given x.
959impl CubicSegment<Vec2> {
960    /// Construct a cubic Bezier curve for animation easing, with control points `p1` and `p2`. A
961    /// cubic Bezier easing curve has control point `p0` at (0, 0) and `p3` at (1, 1), leaving only
962    /// `p1` and `p2` as the remaining degrees of freedom. The first and last control points are
963    /// fixed to ensure the animation begins at 0, and ends at 1.
964    ///
965    /// This is a very common tool for UI animations that accelerate and decelerate smoothly. For
966    /// example, the ubiquitous "ease-in-out" is defined as `(0.25, 0.1), (0.25, 1.0)`.
967    pub fn new_bezier(p1: impl Into<Vec2>, p2: impl Into<Vec2>) -> Self {
968        let (p0, p3) = (Vec2::ZERO, Vec2::ONE);
969        let bezier = CubicBezier::new([[p0, p1.into(), p2.into(), p3]])
970            .to_curve()
971            .unwrap(); // Succeeds because resulting curve is guaranteed to have one segment
972        bezier.segments[0]
973    }
974
975    /// Maximum allowable error for iterative Bezier solve
976    const MAX_ERROR: f32 = 1e-5;
977
978    /// Maximum number of iterations during Bezier solve
979    const MAX_ITERS: u8 = 8;
980
981    /// Given a `time` within `0..=1`, returns an eased value that follows the cubic curve instead
982    /// of a straight line. This eased result may be outside the range `0..=1`, however it will
983    /// always start at 0 and end at 1: `ease(0) = 0` and `ease(1) = 1`.
984    ///
985    /// ```
986    /// # use bevy_math::prelude::*;
987    /// let cubic_bezier = CubicSegment::new_bezier((0.25, 0.1), (0.25, 1.0));
988    /// assert_eq!(cubic_bezier.ease(0.0), 0.0);
989    /// assert_eq!(cubic_bezier.ease(1.0), 1.0);
990    /// ```
991    ///
992    /// # How cubic easing works
993    ///
994    /// Easing is generally accomplished with the help of "shaping functions". These are curves that
995    /// start at (0,0) and end at (1,1). The x-axis of this plot is the current `time` of the
996    /// animation, from 0 to 1. The y-axis is how far along the animation is, also from 0 to 1. You
997    /// can imagine that if the shaping function is a straight line, there is a 1:1 mapping between
998    /// the `time` and how far along your animation is. If the `time` = 0.5, the animation is
999    /// halfway through. This is known as linear interpolation, and results in objects animating
1000    /// with a constant velocity, and no smooth acceleration or deceleration at the start or end.
1001    ///
1002    /// ```text
1003    /// y
1004    /// │         ●
1005    /// │       ⬈
1006    /// │     ⬈    
1007    /// │   ⬈
1008    /// │ ⬈
1009    /// ●─────────── x (time)
1010    /// ```
1011    ///
1012    /// Using cubic Beziers, we have a curve that starts at (0,0), ends at (1,1), and follows a path
1013    /// determined by the two remaining control points (handles). These handles allow us to define a
1014    /// smooth curve. As `time` (x-axis) progresses, we now follow the curve, and use the `y` value
1015    /// to determine how far along the animation is.
1016    ///
1017    /// ```text
1018    /// y
1019    ///          ⬈➔●
1020    /// │      ⬈   
1021    /// │     ↑      
1022    /// │     ↑
1023    /// │    ⬈
1024    /// ●➔⬈───────── x (time)
1025    /// ```
1026    ///
1027    /// To accomplish this, we need to be able to find the position `y` on a curve, given the `x`
1028    /// value. Cubic curves are implicit parametric functions like B(t) = (x,y). To find `y`, we
1029    /// first solve for `t` that corresponds to the given `x` (`time`). We use the Newton-Raphson
1030    /// root-finding method to quickly find a value of `t` that is very near the desired value of
1031    /// `x`. Once we have this we can easily plug that `t` into our curve's `position` function, to
1032    /// find the `y` component, which is how far along our animation should be. In other words:
1033    ///
1034    /// > Given `time` in `0..=1`
1035    ///
1036    /// > Use Newton's method to find a value of `t` that results in B(t) = (x,y) where `x == time`
1037    ///
1038    /// > Once a solution is found, use the resulting `y` value as the final result
1039    #[inline]
1040    pub fn ease(&self, time: f32) -> f32 {
1041        let x = time.clamp(0.0, 1.0);
1042        self.find_y_given_x(x)
1043    }
1044
1045    /// Find the `y` value of the curve at the given `x` value using the Newton-Raphson method.
1046    #[inline]
1047    fn find_y_given_x(&self, x: f32) -> f32 {
1048        let mut t_guess = x;
1049        let mut pos_guess = Vec2::ZERO;
1050        for _ in 0..Self::MAX_ITERS {
1051            pos_guess = self.position(t_guess);
1052            let error = pos_guess.x - x;
1053            if error.abs() <= Self::MAX_ERROR {
1054                break;
1055            }
1056            // Using Newton's method, use the tangent line to estimate a better guess value.
1057            let slope = self.velocity(t_guess).x; // dx/dt
1058            t_guess -= error / slope;
1059        }
1060        pos_guess.y
1061    }
1062}
1063
1064#[cfg(feature = "curve")]
1065impl<P: VectorSpace> Curve<P> for CubicSegment<P> {
1066    #[inline]
1067    fn domain(&self) -> Interval {
1068        Interval::UNIT
1069    }
1070
1071    #[inline]
1072    fn sample_unchecked(&self, t: f32) -> P {
1073        self.position(t)
1074    }
1075}
1076
1077/// A collection of [`CubicSegment`]s chained into a single parametric curve. It is a [`Curve`]
1078/// with domain `[0, N]`, where `N` is its number of segments.
1079///
1080/// Use any struct that implements the [`CubicGenerator`] trait to create a new curve, such as
1081/// [`CubicBezier`].
1082#[derive(Clone, Debug, PartialEq)]
1083#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1084#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
1085pub struct CubicCurve<P: VectorSpace> {
1086    /// The segments comprising the curve. This must always be nonempty.
1087    segments: Vec<CubicSegment<P>>,
1088}
1089
1090impl<P: VectorSpace> CubicCurve<P> {
1091    /// Create a new curve from a collection of segments. If the collection of segments is empty,
1092    /// a curve cannot be built and `None` will be returned instead.
1093    pub fn from_segments(segments: impl Into<Vec<CubicSegment<P>>>) -> Option<Self> {
1094        let segments: Vec<_> = segments.into();
1095        if segments.is_empty() {
1096            None
1097        } else {
1098            Some(Self { segments })
1099        }
1100    }
1101
1102    /// Compute the position of a point on the cubic curve at the parametric value `t`.
1103    ///
1104    /// Note that `t` varies from `0..=(n_points - 3)`.
1105    #[inline]
1106    pub fn position(&self, t: f32) -> P {
1107        let (segment, t) = self.segment(t);
1108        segment.position(t)
1109    }
1110
1111    /// Compute the first derivative with respect to t at `t`. This is the instantaneous velocity of
1112    /// a point on the cubic curve at `t`.
1113    ///
1114    /// Note that `t` varies from `0..=(n_points - 3)`.
1115    #[inline]
1116    pub fn velocity(&self, t: f32) -> P {
1117        let (segment, t) = self.segment(t);
1118        segment.velocity(t)
1119    }
1120
1121    /// Compute the second derivative with respect to t at `t`. This is the instantaneous
1122    /// acceleration of a point on the cubic curve at `t`.
1123    ///
1124    /// Note that `t` varies from `0..=(n_points - 3)`.
1125    #[inline]
1126    pub fn acceleration(&self, t: f32) -> P {
1127        let (segment, t) = self.segment(t);
1128        segment.acceleration(t)
1129    }
1130
1131    /// A flexible iterator used to sample curves with arbitrary functions.
1132    ///
1133    /// This splits the curve into `subdivisions` of evenly spaced `t` values across the
1134    /// length of the curve from start (t = 0) to end (t = n), where `n = self.segment_count()`,
1135    /// returning an iterator evaluating the curve with the supplied `sample_function` at each `t`.
1136    ///
1137    /// For `subdivisions = 2`, this will split the curve into two lines, or three points, and
1138    /// return an iterator with 3 items, the three points, one at the start, middle, and end.
1139    #[inline]
1140    pub fn iter_samples<'a, 'b: 'a>(
1141        &'b self,
1142        subdivisions: usize,
1143        mut sample_function: impl FnMut(&Self, f32) -> P + 'a,
1144    ) -> impl Iterator<Item = P> + 'a {
1145        self.iter_uniformly(subdivisions)
1146            .map(move |t| sample_function(self, t))
1147    }
1148
1149    /// An iterator that returns values of `t` uniformly spaced over `0..=subdivisions`.
1150    #[inline]
1151    fn iter_uniformly(&self, subdivisions: usize) -> impl Iterator<Item = f32> {
1152        let segments = self.segments.len() as f32;
1153        let step = segments / subdivisions as f32;
1154        (0..=subdivisions).map(move |i| i as f32 * step)
1155    }
1156
1157    /// The list of segments contained in this `CubicCurve`.
1158    ///
1159    /// This spline's global `t` value is equal to how many segments it has.
1160    ///
1161    /// All method accepting `t` on `CubicCurve` depends on the global `t`.
1162    /// When sampling over the entire curve, you should either use one of the
1163    /// `iter_*` methods or account for the segment count using `curve.segments().len()`.
1164    #[inline]
1165    pub fn segments(&self) -> &[CubicSegment<P>] {
1166        &self.segments
1167    }
1168
1169    /// Iterate over the curve split into `subdivisions`, sampling the position at each step.
1170    pub fn iter_positions(&self, subdivisions: usize) -> impl Iterator<Item = P> + '_ {
1171        self.iter_samples(subdivisions, Self::position)
1172    }
1173
1174    /// Iterate over the curve split into `subdivisions`, sampling the velocity at each step.
1175    pub fn iter_velocities(&self, subdivisions: usize) -> impl Iterator<Item = P> + '_ {
1176        self.iter_samples(subdivisions, Self::velocity)
1177    }
1178
1179    /// Iterate over the curve split into `subdivisions`, sampling the acceleration at each step.
1180    pub fn iter_accelerations(&self, subdivisions: usize) -> impl Iterator<Item = P> + '_ {
1181        self.iter_samples(subdivisions, Self::acceleration)
1182    }
1183
1184    #[inline]
1185    /// Adds a segment to the curve
1186    pub fn push_segment(&mut self, segment: CubicSegment<P>) {
1187        self.segments.push(segment);
1188    }
1189
1190    /// Returns the [`CubicSegment`] and local `t` value given a spline's global `t` value.
1191    #[inline]
1192    fn segment(&self, t: f32) -> (&CubicSegment<P>, f32) {
1193        if self.segments.len() == 1 {
1194            (&self.segments[0], t)
1195        } else {
1196            let i = (t.floor() as usize).clamp(0, self.segments.len() - 1);
1197            (&self.segments[i], t - i as f32)
1198        }
1199    }
1200}
1201
1202#[cfg(feature = "curve")]
1203impl<P: VectorSpace> Curve<P> for CubicCurve<P> {
1204    #[inline]
1205    fn domain(&self) -> Interval {
1206        // The non-emptiness invariant guarantees the success of this.
1207        Interval::new(0.0, self.segments.len() as f32)
1208            .expect("CubicCurve is invalid because it has no segments")
1209    }
1210
1211    #[inline]
1212    fn sample_unchecked(&self, t: f32) -> P {
1213        self.position(t)
1214    }
1215}
1216
1217impl<P: VectorSpace> Extend<CubicSegment<P>> for CubicCurve<P> {
1218    fn extend<T: IntoIterator<Item = CubicSegment<P>>>(&mut self, iter: T) {
1219        self.segments.extend(iter);
1220    }
1221}
1222
1223impl<P: VectorSpace> IntoIterator for CubicCurve<P> {
1224    type IntoIter = <Vec<CubicSegment<P>> as IntoIterator>::IntoIter;
1225
1226    type Item = CubicSegment<P>;
1227
1228    fn into_iter(self) -> Self::IntoIter {
1229        self.segments.into_iter()
1230    }
1231}
1232
1233/// Implement this on cubic splines that can generate a rational cubic curve from their spline parameters.
1234pub trait RationalGenerator<P: VectorSpace> {
1235    /// An error type indicating why construction might fail.
1236    type Error;
1237
1238    /// Build a [`RationalCurve`] by computing the interpolation coefficients for each curve segment.
1239    fn to_curve(&self) -> Result<RationalCurve<P>, Self::Error>;
1240}
1241
1242/// A segment of a rational cubic curve, used to hold precomputed coefficients for fast interpolation.
1243/// It is a [`Curve`] with domain `[0, 1]`.
1244///
1245/// Note that the `knot_span` is used only by [compound curves] constructed by chaining these
1246/// together.
1247///
1248/// [compound curves]: RationalCurve
1249#[derive(Copy, Clone, Debug, Default, PartialEq)]
1250#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1251#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Default))]
1252pub struct RationalSegment<P: VectorSpace> {
1253    /// The coefficients matrix of the cubic curve.
1254    pub coeff: [P; 4],
1255    /// The homogeneous weight coefficients.
1256    pub weight_coeff: [f32; 4],
1257    /// The width of the domain of this segment.
1258    pub knot_span: f32,
1259}
1260
1261impl<P: VectorSpace> RationalSegment<P> {
1262    /// Instantaneous position of a point at parametric value `t` in `[0, 1]`.
1263    #[inline]
1264    pub fn position(&self, t: f32) -> P {
1265        let [a, b, c, d] = self.coeff;
1266        let [x, y, z, w] = self.weight_coeff;
1267        // Compute a cubic polynomial for the control points
1268        let numerator = a + (b + (c + d * t) * t) * t;
1269        // Compute a cubic polynomial for the weights
1270        let denominator = x + (y + (z + w * t) * t) * t;
1271        numerator / denominator
1272    }
1273
1274    /// Instantaneous velocity of a point at parametric value `t` in `[0, 1]`.
1275    #[inline]
1276    pub fn velocity(&self, t: f32) -> P {
1277        // A derivation for the following equations can be found in "Matrix representation for NURBS
1278        // curves and surfaces" by Choi et al. See equation 19.
1279
1280        let [a, b, c, d] = self.coeff;
1281        let [x, y, z, w] = self.weight_coeff;
1282        // Compute a cubic polynomial for the control points
1283        let numerator = a + (b + (c + d * t) * t) * t;
1284        // Compute a cubic polynomial for the weights
1285        let denominator = x + (y + (z + w * t) * t) * t;
1286
1287        // Compute the derivative of the control point polynomial
1288        let numerator_derivative = b + (c * 2.0 + d * 3.0 * t) * t;
1289        // Compute the derivative of the weight polynomial
1290        let denominator_derivative = y + (z * 2.0 + w * 3.0 * t) * t;
1291
1292        // Velocity is the first derivative (wrt to the parameter `t`)
1293        // Position = N/D therefore
1294        // Velocity = (N/D)' = N'/D - N * D'/D^2 = (N' * D - N * D')/D^2
1295        numerator_derivative / denominator
1296            - numerator * (denominator_derivative / denominator.squared())
1297    }
1298
1299    /// Instantaneous acceleration of a point at parametric value `t` in `[0, 1]`.
1300    #[inline]
1301    pub fn acceleration(&self, t: f32) -> P {
1302        // A derivation for the following equations can be found in "Matrix representation for NURBS
1303        // curves and surfaces" by Choi et al. See equation 20. Note: In come copies of this paper, equation 20
1304        // is printed with the following two errors:
1305        // + The first term has incorrect sign.
1306        // + The second term uses R when it should use the first derivative.
1307
1308        let [a, b, c, d] = self.coeff;
1309        let [x, y, z, w] = self.weight_coeff;
1310        // Compute a cubic polynomial for the control points
1311        let numerator = a + (b + (c + d * t) * t) * t;
1312        // Compute a cubic polynomial for the weights
1313        let denominator = x + (y + (z + w * t) * t) * t;
1314
1315        // Compute the derivative of the control point polynomial
1316        let numerator_derivative = b + (c * 2.0 + d * 3.0 * t) * t;
1317        // Compute the derivative of the weight polynomial
1318        let denominator_derivative = y + (z * 2.0 + w * 3.0 * t) * t;
1319
1320        // Compute the second derivative of the control point polynomial
1321        let numerator_second_derivative = c * 2.0 + d * 6.0 * t;
1322        // Compute the second derivative of the weight polynomial
1323        let denominator_second_derivative = z * 2.0 + w * 6.0 * t;
1324
1325        // Velocity is the first derivative (wrt to the parameter `t`)
1326        // Position = N/D therefore
1327        // Velocity = (N/D)' = N'/D - N * D'/D^2 = (N' * D - N * D')/D^2
1328        // Acceleration = (N/D)'' = ((N' * D - N * D')/D^2)' = N''/D + N' * (-2D'/D^2) + N * (-D''/D^2 + 2D'^2/D^3)
1329        numerator_second_derivative / denominator
1330            + numerator_derivative * (-2.0 * denominator_derivative / denominator.squared())
1331            + numerator
1332                * (-denominator_second_derivative / denominator.squared()
1333                    + 2.0 * denominator_derivative.squared() / denominator.cubed())
1334    }
1335
1336    /// Calculate polynomial coefficients for the cubic polynomials using a characteristic matrix.
1337    #[inline]
1338    fn coefficients(
1339        control_points: [P; 4],
1340        weights: [f32; 4],
1341        knot_span: f32,
1342        char_matrix: [[f32; 4]; 4],
1343    ) -> Self {
1344        // An explanation of this use can be found in "Matrix representation for NURBS curves and surfaces"
1345        // by Choi et al. See section "Evaluation of NURB Curves and Surfaces", and equation 16.
1346
1347        let [c0, c1, c2, c3] = char_matrix;
1348        let p = control_points;
1349        let w = weights;
1350        // These are the control point polynomial coefficients, computed by multiplying the characteristic
1351        // matrix by the point matrix.
1352        let coeff = [
1353            p[0] * c0[0] + p[1] * c0[1] + p[2] * c0[2] + p[3] * c0[3],
1354            p[0] * c1[0] + p[1] * c1[1] + p[2] * c1[2] + p[3] * c1[3],
1355            p[0] * c2[0] + p[1] * c2[1] + p[2] * c2[2] + p[3] * c2[3],
1356            p[0] * c3[0] + p[1] * c3[1] + p[2] * c3[2] + p[3] * c3[3],
1357        ];
1358        // These are the weight polynomial coefficients, computed by multiplying the characteristic
1359        // matrix by the weight matrix.
1360        let weight_coeff = [
1361            w[0] * c0[0] + w[1] * c0[1] + w[2] * c0[2] + w[3] * c0[3],
1362            w[0] * c1[0] + w[1] * c1[1] + w[2] * c1[2] + w[3] * c1[3],
1363            w[0] * c2[0] + w[1] * c2[1] + w[2] * c2[2] + w[3] * c2[3],
1364            w[0] * c3[0] + w[1] * c3[1] + w[2] * c3[2] + w[3] * c3[3],
1365        ];
1366        Self {
1367            coeff,
1368            weight_coeff,
1369            knot_span,
1370        }
1371    }
1372}
1373
1374#[cfg(feature = "curve")]
1375impl<P: VectorSpace> Curve<P> for RationalSegment<P> {
1376    #[inline]
1377    fn domain(&self) -> Interval {
1378        Interval::UNIT
1379    }
1380
1381    #[inline]
1382    fn sample_unchecked(&self, t: f32) -> P {
1383        self.position(t)
1384    }
1385}
1386
1387/// A collection of [`RationalSegment`]s chained into a single parametric curve. It is a [`Curve`]
1388/// with domain `[0, N]`, where `N` is the number of segments.
1389///
1390/// Use any struct that implements the [`RationalGenerator`] trait to create a new curve, such as
1391/// [`CubicNurbs`], or convert [`CubicCurve`] using `into/from`.
1392#[derive(Clone, Debug, PartialEq)]
1393#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1394#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
1395pub struct RationalCurve<P: VectorSpace> {
1396    /// The segments comprising the curve. This must always be nonempty.
1397    segments: Vec<RationalSegment<P>>,
1398}
1399
1400impl<P: VectorSpace> RationalCurve<P> {
1401    /// Create a new curve from a collection of segments. If the collection of segments is empty,
1402    /// a curve cannot be built and `None` will be returned instead.
1403    pub fn from_segments(segments: impl Into<Vec<RationalSegment<P>>>) -> Option<Self> {
1404        let segments: Vec<_> = segments.into();
1405        if segments.is_empty() {
1406            None
1407        } else {
1408            Some(Self { segments })
1409        }
1410    }
1411
1412    /// Compute the position of a point on the curve at the parametric value `t`.
1413    ///
1414    /// Note that `t` varies from `0` to `self.length()`.
1415    #[inline]
1416    pub fn position(&self, t: f32) -> P {
1417        let (segment, t) = self.segment(t);
1418        segment.position(t)
1419    }
1420
1421    /// Compute the first derivative with respect to t at `t`. This is the instantaneous velocity of
1422    /// a point on the curve at `t`.
1423    ///
1424    /// Note that `t` varies from `0` to `self.length()`.
1425    #[inline]
1426    pub fn velocity(&self, t: f32) -> P {
1427        let (segment, t) = self.segment(t);
1428        segment.velocity(t)
1429    }
1430
1431    /// Compute the second derivative with respect to t at `t`. This is the instantaneous
1432    /// acceleration of a point on the curve at `t`.
1433    ///
1434    /// Note that `t` varies from `0` to `self.length()`.
1435    #[inline]
1436    pub fn acceleration(&self, t: f32) -> P {
1437        let (segment, t) = self.segment(t);
1438        segment.acceleration(t)
1439    }
1440
1441    /// A flexible iterator used to sample curves with arbitrary functions.
1442    ///
1443    /// This splits the curve into `subdivisions` of evenly spaced `t` values across the
1444    /// length of the curve from start (t = 0) to end (t = n), where `n = self.segment_count()`,
1445    /// returning an iterator evaluating the curve with the supplied `sample_function` at each `t`.
1446    ///
1447    /// For `subdivisions = 2`, this will split the curve into two lines, or three points, and
1448    /// return an iterator with 3 items, the three points, one at the start, middle, and end.
1449    #[inline]
1450    pub fn iter_samples<'a, 'b: 'a>(
1451        &'b self,
1452        subdivisions: usize,
1453        mut sample_function: impl FnMut(&Self, f32) -> P + 'a,
1454    ) -> impl Iterator<Item = P> + 'a {
1455        self.iter_uniformly(subdivisions)
1456            .map(move |t| sample_function(self, t))
1457    }
1458
1459    /// An iterator that returns values of `t` uniformly spaced over `0..=subdivisions`.
1460    #[inline]
1461    fn iter_uniformly(&self, subdivisions: usize) -> impl Iterator<Item = f32> {
1462        let length = self.length();
1463        let step = length / subdivisions as f32;
1464        (0..=subdivisions).map(move |i| i as f32 * step)
1465    }
1466
1467    /// The list of segments contained in this `RationalCurve`.
1468    ///
1469    /// This spline's global `t` value is equal to how many segments it has.
1470    ///
1471    /// All method accepting `t` on `RationalCurve` depends on the global `t`.
1472    /// When sampling over the entire curve, you should either use one of the
1473    /// `iter_*` methods or account for the segment count using `curve.segments().len()`.
1474    #[inline]
1475    pub fn segments(&self) -> &[RationalSegment<P>] {
1476        &self.segments
1477    }
1478
1479    /// Iterate over the curve split into `subdivisions`, sampling the position at each step.
1480    pub fn iter_positions(&self, subdivisions: usize) -> impl Iterator<Item = P> + '_ {
1481        self.iter_samples(subdivisions, Self::position)
1482    }
1483
1484    /// Iterate over the curve split into `subdivisions`, sampling the velocity at each step.
1485    pub fn iter_velocities(&self, subdivisions: usize) -> impl Iterator<Item = P> + '_ {
1486        self.iter_samples(subdivisions, Self::velocity)
1487    }
1488
1489    /// Iterate over the curve split into `subdivisions`, sampling the acceleration at each step.
1490    pub fn iter_accelerations(&self, subdivisions: usize) -> impl Iterator<Item = P> + '_ {
1491        self.iter_samples(subdivisions, Self::acceleration)
1492    }
1493
1494    /// Adds a segment to the curve.
1495    #[inline]
1496    pub fn push_segment(&mut self, segment: RationalSegment<P>) {
1497        self.segments.push(segment);
1498    }
1499
1500    /// Returns the [`RationalSegment`] and local `t` value given a spline's global `t` value.
1501    /// Input `t` will be clamped to the domain of the curve. Returned value will be in `[0, 1]`.
1502    #[inline]
1503    fn segment(&self, mut t: f32) -> (&RationalSegment<P>, f32) {
1504        if t <= 0.0 {
1505            (&self.segments[0], 0.0)
1506        } else if self.segments.len() == 1 {
1507            (&self.segments[0], t / self.segments[0].knot_span)
1508        } else {
1509            // Try to fit t into each segment domain
1510            for segment in self.segments.iter() {
1511                if t < segment.knot_span {
1512                    // The division here makes t a normalized parameter in [0, 1] that can be properly
1513                    // evaluated against a rational curve segment. See equations 6 & 16 from "Matrix representation
1514                    // of NURBS curves and surfaces" by Choi et al. or equation 3 from "General Matrix
1515                    // Representations for B-Splines" by Qin.
1516                    return (segment, t / segment.knot_span);
1517                }
1518                t -= segment.knot_span;
1519            }
1520            return (self.segments.last().unwrap(), 1.0);
1521        }
1522    }
1523
1524    /// Returns the length of the domain of the parametric curve.
1525    #[inline]
1526    pub fn length(&self) -> f32 {
1527        self.segments.iter().map(|segment| segment.knot_span).sum()
1528    }
1529}
1530
1531#[cfg(feature = "curve")]
1532impl<P: VectorSpace> Curve<P> for RationalCurve<P> {
1533    #[inline]
1534    fn domain(&self) -> Interval {
1535        // The non-emptiness invariant guarantees the success of this.
1536        Interval::new(0.0, self.length())
1537            .expect("RationalCurve is invalid because it has zero length")
1538    }
1539
1540    #[inline]
1541    fn sample_unchecked(&self, t: f32) -> P {
1542        self.position(t)
1543    }
1544}
1545
1546impl<P: VectorSpace> Extend<RationalSegment<P>> for RationalCurve<P> {
1547    fn extend<T: IntoIterator<Item = RationalSegment<P>>>(&mut self, iter: T) {
1548        self.segments.extend(iter);
1549    }
1550}
1551
1552impl<P: VectorSpace> IntoIterator for RationalCurve<P> {
1553    type IntoIter = <Vec<RationalSegment<P>> as IntoIterator>::IntoIter;
1554
1555    type Item = RationalSegment<P>;
1556
1557    fn into_iter(self) -> Self::IntoIter {
1558        self.segments.into_iter()
1559    }
1560}
1561
1562impl<P: VectorSpace> From<CubicSegment<P>> for RationalSegment<P> {
1563    fn from(value: CubicSegment<P>) -> Self {
1564        Self {
1565            coeff: value.coeff,
1566            weight_coeff: [1.0, 0.0, 0.0, 0.0],
1567            knot_span: 1.0, // Cubic curves are uniform, so every segment has domain [0, 1).
1568        }
1569    }
1570}
1571
1572impl<P: VectorSpace> From<CubicCurve<P>> for RationalCurve<P> {
1573    fn from(value: CubicCurve<P>) -> Self {
1574        Self {
1575            segments: value.segments.into_iter().map(Into::into).collect(),
1576        }
1577    }
1578}
1579
1580#[cfg(test)]
1581mod tests {
1582    use glam::{vec2, Vec2};
1583
1584    use crate::{
1585        cubic_splines::{
1586            CubicBSpline, CubicBezier, CubicGenerator, CubicNurbs, CubicSegment, RationalCurve,
1587            RationalGenerator,
1588        },
1589        ops::{self, FloatPow},
1590    };
1591
1592    /// How close two floats can be and still be considered equal
1593    const FLOAT_EQ: f32 = 1e-5;
1594
1595    /// Sweep along the full length of a 3D cubic Bezier, and manually check the position.
1596    #[test]
1597    fn cubic() {
1598        const N_SAMPLES: usize = 1000;
1599        let points = [[
1600            vec2(-1.0, -20.0),
1601            vec2(3.0, 2.0),
1602            vec2(5.0, 3.0),
1603            vec2(9.0, 8.0),
1604        ]];
1605        let bezier = CubicBezier::new(points).to_curve().unwrap();
1606        for i in 0..=N_SAMPLES {
1607            let t = i as f32 / N_SAMPLES as f32; // Check along entire length
1608            assert!(bezier.position(t).distance(cubic_manual(t, points[0])) <= FLOAT_EQ);
1609        }
1610    }
1611
1612    /// Manual, hardcoded function for computing the position along a cubic bezier.
1613    fn cubic_manual(t: f32, points: [Vec2; 4]) -> Vec2 {
1614        let p = points;
1615        p[0] * (1.0 - t).cubed()
1616            + 3.0 * p[1] * t * (1.0 - t).squared()
1617            + 3.0 * p[2] * t.squared() * (1.0 - t)
1618            + p[3] * t.cubed()
1619    }
1620
1621    /// Basic cubic Bezier easing test to verify the shape of the curve.
1622    #[test]
1623    fn easing_simple() {
1624        // A curve similar to ease-in-out, but symmetric
1625        let bezier = CubicSegment::new_bezier([1.0, 0.0], [0.0, 1.0]);
1626        assert_eq!(bezier.ease(0.0), 0.0);
1627        assert!(bezier.ease(0.2) < 0.2); // tests curve
1628        assert_eq!(bezier.ease(0.5), 0.5); // true due to symmetry
1629        assert!(bezier.ease(0.8) > 0.8); // tests curve
1630        assert_eq!(bezier.ease(1.0), 1.0);
1631    }
1632
1633    /// A curve that forms an upside-down "U", that should extend below 0.0. Useful for animations
1634    /// that go beyond the start and end positions, e.g. bouncing.
1635    #[test]
1636    fn easing_overshoot() {
1637        // A curve that forms an upside-down "U", that should extend above 1.0
1638        let bezier = CubicSegment::new_bezier([0.0, 2.0], [1.0, 2.0]);
1639        assert_eq!(bezier.ease(0.0), 0.0);
1640        assert!(bezier.ease(0.5) > 1.5);
1641        assert_eq!(bezier.ease(1.0), 1.0);
1642    }
1643
1644    /// A curve that forms a "U", that should extend below 0.0. Useful for animations that go beyond
1645    /// the start and end positions, e.g. bouncing.
1646    #[test]
1647    fn easing_undershoot() {
1648        let bezier = CubicSegment::new_bezier([0.0, -2.0], [1.0, -2.0]);
1649        assert_eq!(bezier.ease(0.0), 0.0);
1650        assert!(bezier.ease(0.5) < -0.5);
1651        assert_eq!(bezier.ease(1.0), 1.0);
1652    }
1653
1654    /// Test that a simple cardinal spline passes through all of its control points with
1655    /// the correct tangents.
1656    #[test]
1657    fn cardinal_control_pts() {
1658        use super::CubicCardinalSpline;
1659
1660        let tension = 0.2;
1661        let [p0, p1, p2, p3] = [vec2(-1., -2.), vec2(0., 1.), vec2(1., 2.), vec2(-2., 1.)];
1662        let curve = CubicCardinalSpline::new(tension, [p0, p1, p2, p3])
1663            .to_curve()
1664            .unwrap();
1665
1666        // Positions at segment endpoints
1667        assert!(curve.position(0.).abs_diff_eq(p0, FLOAT_EQ));
1668        assert!(curve.position(1.).abs_diff_eq(p1, FLOAT_EQ));
1669        assert!(curve.position(2.).abs_diff_eq(p2, FLOAT_EQ));
1670        assert!(curve.position(3.).abs_diff_eq(p3, FLOAT_EQ));
1671
1672        // Tangents at segment endpoints
1673        assert!(curve
1674            .velocity(0.)
1675            .abs_diff_eq((p1 - p0) * tension * 2., FLOAT_EQ));
1676        assert!(curve
1677            .velocity(1.)
1678            .abs_diff_eq((p2 - p0) * tension, FLOAT_EQ));
1679        assert!(curve
1680            .velocity(2.)
1681            .abs_diff_eq((p3 - p1) * tension, FLOAT_EQ));
1682        assert!(curve
1683            .velocity(3.)
1684            .abs_diff_eq((p3 - p2) * tension * 2., FLOAT_EQ));
1685    }
1686
1687    /// Test that [`RationalCurve`] properly generalizes [`CubicCurve`]. A Cubic upgraded to a rational
1688    /// should produce pretty much the same output.
1689    #[test]
1690    fn cubic_to_rational() {
1691        const EPSILON: f32 = 0.00001;
1692
1693        let points = [
1694            vec2(0.0, 0.0),
1695            vec2(1.0, 1.0),
1696            vec2(1.0, 1.0),
1697            vec2(2.0, -1.0),
1698            vec2(3.0, 1.0),
1699            vec2(0.0, 0.0),
1700        ];
1701
1702        let b_spline = CubicBSpline::new(points).to_curve().unwrap();
1703        let rational_b_spline = RationalCurve::from(b_spline.clone());
1704
1705        /// Tests if two vectors of points are approximately the same
1706        fn compare_vectors(cubic_curve: Vec<Vec2>, rational_curve: Vec<Vec2>, name: &str) {
1707            assert_eq!(
1708                cubic_curve.len(),
1709                rational_curve.len(),
1710                "{name} vector lengths mismatch"
1711            );
1712            for (i, (a, b)) in cubic_curve.iter().zip(rational_curve.iter()).enumerate() {
1713                assert!(
1714                    a.distance(*b) < EPSILON,
1715                    "Mismatch at {name} value {i}. CubicCurve: {} Converted RationalCurve: {}",
1716                    a,
1717                    b
1718                );
1719            }
1720        }
1721
1722        // Both curves should yield the same values
1723        let cubic_positions: Vec<_> = b_spline.iter_positions(10).collect();
1724        let rational_positions: Vec<_> = rational_b_spline.iter_positions(10).collect();
1725        compare_vectors(cubic_positions, rational_positions, "position");
1726
1727        let cubic_velocities: Vec<_> = b_spline.iter_velocities(10).collect();
1728        let rational_velocities: Vec<_> = rational_b_spline.iter_velocities(10).collect();
1729        compare_vectors(cubic_velocities, rational_velocities, "velocity");
1730
1731        let cubic_accelerations: Vec<_> = b_spline.iter_accelerations(10).collect();
1732        let rational_accelerations: Vec<_> = rational_b_spline.iter_accelerations(10).collect();
1733        compare_vectors(cubic_accelerations, rational_accelerations, "acceleration");
1734    }
1735
1736    /// Test that a nurbs curve can approximate a portion of a circle.
1737    #[test]
1738    fn nurbs_circular_arc() {
1739        use core::f32::consts::FRAC_PI_2;
1740        const EPSILON: f32 = 0.0000001;
1741
1742        // The following NURBS parameters were determined by constraining the first two
1743        // points to the line y=1, the second two points to the line x=1, and the distance
1744        // between each pair of points to be equal. One can solve the weights by assuming the
1745        // first and last weights to be one, the intermediate weights to be equal, and
1746        // subjecting ones self to a lot of tedious matrix algebra.
1747
1748        let alpha = FRAC_PI_2;
1749        let leg = 2.0 * ops::sin(alpha / 2.0) / (1.0 + 2.0 * ops::cos(alpha / 2.0));
1750        let weight = (1.0 + 2.0 * ops::cos(alpha / 2.0)) / 3.0;
1751        let points = [
1752            vec2(1.0, 0.0),
1753            vec2(1.0, leg),
1754            vec2(leg, 1.0),
1755            vec2(0.0, 1.0),
1756        ];
1757        let weights = [1.0, weight, weight, 1.0];
1758        let knots = [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0];
1759        let spline = CubicNurbs::new(points, Some(weights), Some(knots)).unwrap();
1760        let curve = spline.to_curve().unwrap();
1761        for (i, point) in curve.iter_positions(10).enumerate() {
1762            assert!(
1763                f32::abs(point.length() - 1.0) < EPSILON,
1764                "Point {i} is not on the unit circle: {point:?} has length {}",
1765                point.length()
1766            );
1767        }
1768    }
1769}