bevy_math/curve/
interval.rs

1//! The [`Interval`] type for nonempty intervals used by the [`Curve`](super::Curve) trait.
2
3use core::{
4    cmp::{max_by, min_by},
5    ops::RangeInclusive,
6};
7use derive_more::derive::{Display, Error};
8use itertools::Either;
9
10#[cfg(feature = "bevy_reflect")]
11use bevy_reflect::Reflect;
12#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
13use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
14
15/// A nonempty closed interval, possibly unbounded in either direction.
16///
17/// In other words, the interval may stretch all the way to positive or negative infinity, but it
18/// will always have some nonempty interior.
19#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
20#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
21#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
22#[cfg_attr(
23    all(feature = "serialize", feature = "bevy_reflect"),
24    reflect(Serialize, Deserialize)
25)]
26pub struct Interval {
27    start: f32,
28    end: f32,
29}
30
31/// An error that indicates that an operation would have returned an invalid [`Interval`].
32#[derive(Debug, Error, Display)]
33#[display("The resulting interval would be invalid (empty or with a NaN endpoint)")]
34pub struct InvalidIntervalError;
35
36/// An error indicating that spaced points could not be extracted from an unbounded interval.
37#[derive(Debug, Error, Display)]
38#[display("Cannot extract spaced points from an unbounded interval")]
39pub struct SpacedPointsError;
40
41/// An error indicating that a linear map between intervals could not be constructed because of
42/// unboundedness.
43#[derive(Debug, Error, Display)]
44#[display("Could not construct linear function to map between intervals")]
45pub(super) enum LinearMapError {
46    /// The source interval being mapped out of was unbounded.
47    #[display("The source interval is unbounded")]
48    SourceUnbounded,
49
50    /// The target interval being mapped into was unbounded.
51    #[display("The target interval is unbounded")]
52    TargetUnbounded,
53}
54
55impl Interval {
56    /// Create a new [`Interval`] with the specified `start` and `end`. The interval can be unbounded
57    /// but cannot be empty (so `start` must be less than `end`) and neither endpoint can be NaN; invalid
58    /// parameters will result in an error.
59    #[inline]
60    pub fn new(start: f32, end: f32) -> Result<Self, InvalidIntervalError> {
61        if start >= end || start.is_nan() || end.is_nan() {
62            Err(InvalidIntervalError)
63        } else {
64            Ok(Self { start, end })
65        }
66    }
67
68    /// An interval of length 1.0, starting at 0.0 and ending at 1.0.
69    pub const UNIT: Self = Self {
70        start: 0.0,
71        end: 1.0,
72    };
73
74    /// An interval which stretches across the entire real line from negative infinity to infinity.
75    pub const EVERYWHERE: Self = Self {
76        start: f32::NEG_INFINITY,
77        end: f32::INFINITY,
78    };
79
80    /// Get the start of this interval.
81    #[inline]
82    pub const fn start(self) -> f32 {
83        self.start
84    }
85
86    /// Get the end of this interval.
87    #[inline]
88    pub const fn end(self) -> f32 {
89        self.end
90    }
91
92    /// Create an [`Interval`] by intersecting this interval with another. Returns an error if the
93    /// intersection would be empty (hence an invalid interval).
94    pub fn intersect(self, other: Interval) -> Result<Interval, InvalidIntervalError> {
95        let lower = max_by(self.start, other.start, f32::total_cmp);
96        let upper = min_by(self.end, other.end, f32::total_cmp);
97        Self::new(lower, upper)
98    }
99
100    /// Get the length of this interval. Note that the result may be infinite (`f32::INFINITY`).
101    #[inline]
102    pub fn length(self) -> f32 {
103        self.end - self.start
104    }
105
106    /// Returns `true` if this interval is bounded — that is, if both its start and end are finite.
107    ///
108    /// Equivalently, an interval is bounded if its length is finite.
109    #[inline]
110    pub fn is_bounded(self) -> bool {
111        self.length().is_finite()
112    }
113
114    /// Returns `true` if this interval has a finite start.
115    #[inline]
116    pub fn has_finite_start(self) -> bool {
117        self.start.is_finite()
118    }
119
120    /// Returns `true` if this interval has a finite end.
121    #[inline]
122    pub fn has_finite_end(self) -> bool {
123        self.end.is_finite()
124    }
125
126    /// Returns `true` if `item` is contained in this interval.
127    #[inline]
128    pub fn contains(self, item: f32) -> bool {
129        (self.start..=self.end).contains(&item)
130    }
131
132    /// Returns `true` if the other interval is contained in this interval.
133    ///
134    /// This is non-strict: each interval will contain itself.
135    #[inline]
136    pub fn contains_interval(self, other: Self) -> bool {
137        self.start <= other.start && self.end >= other.end
138    }
139
140    /// Clamp the given `value` to lie within this interval.
141    #[inline]
142    pub fn clamp(self, value: f32) -> f32 {
143        value.clamp(self.start, self.end)
144    }
145
146    /// Get an iterator over equally-spaced points from this interval in increasing order.
147    /// If `points` is 1, the start of this interval is returned. If `points` is 0, an empty
148    /// iterator is returned. An error is returned if the interval is unbounded.
149    #[inline]
150    pub fn spaced_points(
151        self,
152        points: usize,
153    ) -> Result<impl Iterator<Item = f32>, SpacedPointsError> {
154        if !self.is_bounded() {
155            return Err(SpacedPointsError);
156        }
157        if points < 2 {
158            // If `points` is 1, this is `Some(self.start)` as an iterator, and if `points` is 0,
159            // then this is `None` as an iterator. This is written this way to avoid having to
160            // introduce a ternary disjunction of iterators.
161            let iter = (points == 1).then_some(self.start).into_iter();
162            return Ok(Either::Left(iter));
163        }
164        let step = self.length() / (points - 1) as f32;
165        let iter = (0..points).map(move |x| self.start + x as f32 * step);
166        Ok(Either::Right(iter))
167    }
168
169    /// Get the linear function which maps this interval onto the `other` one. Returns an error if either
170    /// interval is unbounded.
171    #[inline]
172    pub(super) fn linear_map_to(self, other: Self) -> Result<impl Fn(f32) -> f32, LinearMapError> {
173        if !self.is_bounded() {
174            return Err(LinearMapError::SourceUnbounded);
175        }
176
177        if !other.is_bounded() {
178            return Err(LinearMapError::TargetUnbounded);
179        }
180
181        let scale = other.length() / self.length();
182        Ok(move |x| (x - self.start) * scale + other.start)
183    }
184}
185
186impl TryFrom<RangeInclusive<f32>> for Interval {
187    type Error = InvalidIntervalError;
188    fn try_from(range: RangeInclusive<f32>) -> Result<Self, Self::Error> {
189        Interval::new(*range.start(), *range.end())
190    }
191}
192
193/// Create an [`Interval`] with a given `start` and `end`. Alias of [`Interval::new`].
194#[inline]
195pub fn interval(start: f32, end: f32) -> Result<Interval, InvalidIntervalError> {
196    Interval::new(start, end)
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    // use approx::{assert_abs_diff_eq, AbsDiffEq};
203
204    #[test]
205    fn make_intervals() {
206        let ivl = Interval::new(2.0, -1.0);
207        assert!(ivl.is_err());
208
209        let ivl = Interval::new(-0.0, 0.0);
210        assert!(ivl.is_err());
211
212        let ivl = Interval::new(f32::NEG_INFINITY, 15.5);
213        assert!(ivl.is_ok());
214
215        let ivl = Interval::new(-2.0, f32::INFINITY);
216        assert!(ivl.is_ok());
217
218        let ivl = Interval::new(f32::NEG_INFINITY, f32::INFINITY);
219        assert!(ivl.is_ok());
220
221        let ivl = Interval::new(f32::INFINITY, f32::NEG_INFINITY);
222        assert!(ivl.is_err());
223
224        let ivl = Interval::new(-1.0, f32::NAN);
225        assert!(ivl.is_err());
226
227        let ivl = Interval::new(f32::NAN, -42.0);
228        assert!(ivl.is_err());
229
230        let ivl = Interval::new(f32::NAN, f32::NAN);
231        assert!(ivl.is_err());
232
233        let ivl = Interval::new(0.0, 1.0);
234        assert!(ivl.is_ok());
235    }
236
237    #[test]
238    fn lengths() {
239        let ivl = interval(-5.0, 10.0).unwrap();
240        assert!((ivl.length() - 15.0).abs() <= f32::EPSILON);
241
242        let ivl = interval(5.0, 100.0).unwrap();
243        assert!((ivl.length() - 95.0).abs() <= f32::EPSILON);
244
245        let ivl = interval(0.0, f32::INFINITY).unwrap();
246        assert_eq!(ivl.length(), f32::INFINITY);
247
248        let ivl = interval(f32::NEG_INFINITY, 0.0).unwrap();
249        assert_eq!(ivl.length(), f32::INFINITY);
250
251        let ivl = Interval::EVERYWHERE;
252        assert_eq!(ivl.length(), f32::INFINITY);
253    }
254
255    #[test]
256    fn intersections() {
257        let ivl1 = interval(-1.0, 1.0).unwrap();
258        let ivl2 = interval(0.0, 2.0).unwrap();
259        let ivl3 = interval(-3.0, 0.0).unwrap();
260        let ivl4 = interval(0.0, f32::INFINITY).unwrap();
261        let ivl5 = interval(f32::NEG_INFINITY, 0.0).unwrap();
262        let ivl6 = Interval::EVERYWHERE;
263
264        assert!(ivl1.intersect(ivl2).is_ok_and(|ivl| ivl == Interval::UNIT));
265        assert!(ivl1
266            .intersect(ivl3)
267            .is_ok_and(|ivl| ivl == interval(-1.0, 0.0).unwrap()));
268        assert!(ivl2.intersect(ivl3).is_err());
269        assert!(ivl1.intersect(ivl4).is_ok_and(|ivl| ivl == Interval::UNIT));
270        assert!(ivl1
271            .intersect(ivl5)
272            .is_ok_and(|ivl| ivl == interval(-1.0, 0.0).unwrap()));
273        assert!(ivl4.intersect(ivl5).is_err());
274        assert_eq!(ivl1.intersect(ivl6).unwrap(), ivl1);
275        assert_eq!(ivl4.intersect(ivl6).unwrap(), ivl4);
276        assert_eq!(ivl5.intersect(ivl6).unwrap(), ivl5);
277    }
278
279    #[test]
280    fn containment() {
281        let ivl = Interval::UNIT;
282        assert!(ivl.contains(0.0));
283        assert!(ivl.contains(1.0));
284        assert!(ivl.contains(0.5));
285        assert!(!ivl.contains(-0.1));
286        assert!(!ivl.contains(1.1));
287        assert!(!ivl.contains(f32::NAN));
288
289        let ivl = interval(3.0, f32::INFINITY).unwrap();
290        assert!(ivl.contains(3.0));
291        assert!(ivl.contains(2.0e5));
292        assert!(ivl.contains(3.5e6));
293        assert!(!ivl.contains(2.5));
294        assert!(!ivl.contains(-1e5));
295        assert!(!ivl.contains(f32::NAN));
296    }
297
298    #[test]
299    fn interval_containment() {
300        let ivl = Interval::UNIT;
301        assert!(ivl.contains_interval(interval(-0.0, 0.5).unwrap()));
302        assert!(ivl.contains_interval(interval(0.5, 1.0).unwrap()));
303        assert!(ivl.contains_interval(interval(0.25, 0.75).unwrap()));
304        assert!(!ivl.contains_interval(interval(-0.25, 0.5).unwrap()));
305        assert!(!ivl.contains_interval(interval(0.5, 1.25).unwrap()));
306        assert!(!ivl.contains_interval(interval(0.25, f32::INFINITY).unwrap()));
307        assert!(!ivl.contains_interval(interval(f32::NEG_INFINITY, 0.75).unwrap()));
308
309        let big_ivl = interval(0.0, f32::INFINITY).unwrap();
310        assert!(big_ivl.contains_interval(interval(0.0, 5.0).unwrap()));
311        assert!(big_ivl.contains_interval(interval(0.0, f32::INFINITY).unwrap()));
312        assert!(big_ivl.contains_interval(interval(1.0, 5.0).unwrap()));
313        assert!(!big_ivl.contains_interval(interval(-1.0, f32::INFINITY).unwrap()));
314        assert!(!big_ivl.contains_interval(interval(-2.0, 5.0).unwrap()));
315    }
316
317    #[test]
318    fn boundedness() {
319        assert!(!Interval::EVERYWHERE.is_bounded());
320        assert!(interval(0.0, 3.5e5).unwrap().is_bounded());
321        assert!(!interval(-2.0, f32::INFINITY).unwrap().is_bounded());
322        assert!(!interval(f32::NEG_INFINITY, 5.0).unwrap().is_bounded());
323    }
324
325    #[test]
326    fn linear_maps() {
327        let ivl1 = interval(-3.0, 5.0).unwrap();
328        let ivl2 = Interval::UNIT;
329        let map = ivl1.linear_map_to(ivl2);
330        // assert!(map.is_ok_and(|f| f(-3.0).abs_diff_eq(&0.0, f32::EPSILON)
331        //     && f(5.0).abs_diff_eq(&1.0, f32::EPSILON)
332        //     && f(1.0).abs_diff_eq(&0.5, f32::EPSILON)));
333
334        let ivl1 = Interval::UNIT;
335        let ivl2 = Interval::EVERYWHERE;
336        assert!(ivl1.linear_map_to(ivl2).is_err());
337
338        let ivl1 = interval(f32::NEG_INFINITY, -4.0).unwrap();
339        let ivl2 = Interval::UNIT;
340        assert!(ivl1.linear_map_to(ivl2).is_err());
341    }
342
343    #[test]
344    fn spaced_points() {
345        let ivl = interval(0.0, 50.0).unwrap();
346        let points_iter: Vec<f32> = ivl.spaced_points(1).unwrap().collect();
347        // assert_abs_diff_eq!(points_iter[0], 0.0);
348        assert_eq!(points_iter.len(), 1);
349        let points_iter: Vec<f32> = ivl.spaced_points(2).unwrap().collect();
350        // assert_abs_diff_eq!(points_iter[0], 0.0);
351        // assert_abs_diff_eq!(points_iter[1], 50.0);
352        let points_iter = ivl.spaced_points(21).unwrap();
353        let step = ivl.length() / 20.0;
354        for (index, point) in points_iter.enumerate() {
355            let expected = ivl.start() + step * index as f32;
356            // assert_abs_diff_eq!(point, expected);
357        }
358
359        let ivl = interval(-21.0, 79.0).unwrap();
360        let points_iter = ivl.spaced_points(10000).unwrap();
361        let step = ivl.length() / 9999.0;
362        for (index, point) in points_iter.enumerate() {
363            let expected = ivl.start() + step * index as f32;
364            // assert_abs_diff_eq!(point, expected);
365        }
366
367        let ivl = interval(-1.0, f32::INFINITY).unwrap();
368        let points_iter = ivl.spaced_points(25);
369        assert!(points_iter.is_err());
370
371        let ivl = interval(f32::NEG_INFINITY, -25.0).unwrap();
372        let points_iter = ivl.spaced_points(9);
373        assert!(points_iter.is_err());
374    }
375}