1use core::{
4 cmp::{max_by, min_by},
5 ops::RangeInclusive,
6};
7use itertools::Either;
8use thiserror::Error;
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#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
20#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
21#[cfg_attr(
22 feature = "bevy_reflect",
23 derive(Reflect),
24 reflect(Debug, PartialEq, Clone)
25)]
26#[cfg_attr(
27 all(feature = "serialize", feature = "bevy_reflect"),
28 reflect(Serialize, Deserialize)
29)]
30pub struct Interval {
31 start: f32,
32 end: f32,
33}
34
35#[derive(Debug, Error)]
37#[error("The resulting interval would be invalid (empty or with a NaN endpoint)")]
38pub struct InvalidIntervalError;
39
40#[derive(Debug, Error)]
42#[error("Cannot extract spaced points from an unbounded interval")]
43pub struct SpacedPointsError;
44
45#[derive(Debug, Error)]
48#[error("Could not construct linear function to map between intervals")]
49pub(super) enum LinearMapError {
50 #[error("The source interval is unbounded")]
52 SourceUnbounded,
53
54 #[error("The target interval is unbounded")]
56 TargetUnbounded,
57}
58
59impl Interval {
60 #[inline]
64 pub const fn new(start: f32, end: f32) -> Result<Self, InvalidIntervalError> {
65 if start >= end || start.is_nan() || end.is_nan() {
66 Err(InvalidIntervalError)
67 } else {
68 Ok(Self { start, end })
69 }
70 }
71
72 pub const UNIT: Self = Self {
74 start: 0.0,
75 end: 1.0,
76 };
77
78 pub const EVERYWHERE: Self = Self {
80 start: f32::NEG_INFINITY,
81 end: f32::INFINITY,
82 };
83
84 #[inline]
86 pub const fn start(self) -> f32 {
87 self.start
88 }
89
90 #[inline]
92 pub const fn end(self) -> f32 {
93 self.end
94 }
95
96 pub fn intersect(self, other: Interval) -> Result<Interval, InvalidIntervalError> {
99 let lower = max_by(self.start, other.start, f32::total_cmp);
100 let upper = min_by(self.end, other.end, f32::total_cmp);
101 Self::new(lower, upper)
102 }
103
104 #[inline]
106 pub const fn length(self) -> f32 {
107 self.end - self.start
108 }
109
110 #[inline]
114 pub const fn is_bounded(self) -> bool {
115 self.length().is_finite()
116 }
117
118 #[inline]
120 pub const fn has_finite_start(self) -> bool {
121 self.start.is_finite()
122 }
123
124 #[inline]
126 pub const fn has_finite_end(self) -> bool {
127 self.end.is_finite()
128 }
129
130 #[inline]
132 pub fn contains(self, item: f32) -> bool {
133 (self.start..=self.end).contains(&item)
134 }
135
136 #[inline]
140 pub const fn contains_interval(self, other: Self) -> bool {
141 self.start <= other.start && self.end >= other.end
142 }
143
144 #[inline]
146 pub const fn clamp(self, value: f32) -> f32 {
147 value.clamp(self.start, self.end)
148 }
149
150 #[inline]
154 pub fn spaced_points(
155 self,
156 points: usize,
157 ) -> Result<impl Iterator<Item = f32>, SpacedPointsError> {
158 if !self.is_bounded() {
159 return Err(SpacedPointsError);
160 }
161 if points < 2 {
162 let iter = (points == 1).then_some(self.start).into_iter();
166 return Ok(Either::Left(iter));
167 }
168 let step = self.length() / (points - 1) as f32;
169 let iter = (0..points).map(move |x| self.start + x as f32 * step);
170 Ok(Either::Right(iter))
171 }
172
173 #[inline]
176 pub(super) const fn linear_map_to(
177 self,
178 other: Self,
179 ) -> Result<impl Fn(f32) -> f32, LinearMapError> {
180 if !self.is_bounded() {
181 return Err(LinearMapError::SourceUnbounded);
182 }
183
184 if !other.is_bounded() {
185 return Err(LinearMapError::TargetUnbounded);
186 }
187
188 let scale = other.length() / self.length();
189 Ok(move |x| (x - self.start) * scale + other.start)
190 }
191}
192
193impl TryFrom<RangeInclusive<f32>> for Interval {
194 type Error = InvalidIntervalError;
195 fn try_from(range: RangeInclusive<f32>) -> Result<Self, Self::Error> {
196 Interval::new(*range.start(), *range.end())
197 }
198}
199
200#[inline]
202pub const fn interval(start: f32, end: f32) -> Result<Interval, InvalidIntervalError> {
203 Interval::new(start, end)
204}
205
206#[cfg(test)]
207mod tests {
208 use bevy_math::ops;
209
210 use super::*;
211 use alloc::vec::Vec;
212 use approx::{assert_abs_diff_eq, AbsDiffEq};
213
214 #[test]
215 fn make_intervals() {
216 let ivl = Interval::new(2.0, -1.0);
217 assert!(ivl.is_err());
218
219 let ivl = Interval::new(-0.0, 0.0);
220 assert!(ivl.is_err());
221
222 let ivl = Interval::new(f32::NEG_INFINITY, 15.5);
223 assert!(ivl.is_ok());
224
225 let ivl = Interval::new(-2.0, f32::INFINITY);
226 assert!(ivl.is_ok());
227
228 let ivl = Interval::new(f32::NEG_INFINITY, f32::INFINITY);
229 assert!(ivl.is_ok());
230
231 let ivl = Interval::new(f32::INFINITY, f32::NEG_INFINITY);
232 assert!(ivl.is_err());
233
234 let ivl = Interval::new(-1.0, f32::NAN);
235 assert!(ivl.is_err());
236
237 let ivl = Interval::new(f32::NAN, -42.0);
238 assert!(ivl.is_err());
239
240 let ivl = Interval::new(f32::NAN, f32::NAN);
241 assert!(ivl.is_err());
242
243 let ivl = Interval::new(0.0, 1.0);
244 assert!(ivl.is_ok());
245 }
246
247 #[test]
248 fn lengths() {
249 let ivl = interval(-5.0, 10.0).unwrap();
250 assert!(ops::abs(ivl.length() - 15.0) <= f32::EPSILON);
251
252 let ivl = interval(5.0, 100.0).unwrap();
253 assert!(ops::abs(ivl.length() - 95.0) <= f32::EPSILON);
254
255 let ivl = interval(0.0, f32::INFINITY).unwrap();
256 assert_eq!(ivl.length(), f32::INFINITY);
257
258 let ivl = interval(f32::NEG_INFINITY, 0.0).unwrap();
259 assert_eq!(ivl.length(), f32::INFINITY);
260
261 let ivl = Interval::EVERYWHERE;
262 assert_eq!(ivl.length(), f32::INFINITY);
263 }
264
265 #[test]
266 fn intersections() {
267 let ivl1 = interval(-1.0, 1.0).unwrap();
268 let ivl2 = interval(0.0, 2.0).unwrap();
269 let ivl3 = interval(-3.0, 0.0).unwrap();
270 let ivl4 = interval(0.0, f32::INFINITY).unwrap();
271 let ivl5 = interval(f32::NEG_INFINITY, 0.0).unwrap();
272 let ivl6 = Interval::EVERYWHERE;
273
274 assert!(ivl1.intersect(ivl2).is_ok_and(|ivl| ivl == Interval::UNIT));
275 assert!(ivl1
276 .intersect(ivl3)
277 .is_ok_and(|ivl| ivl == interval(-1.0, 0.0).unwrap()));
278 assert!(ivl2.intersect(ivl3).is_err());
279 assert!(ivl1.intersect(ivl4).is_ok_and(|ivl| ivl == Interval::UNIT));
280 assert!(ivl1
281 .intersect(ivl5)
282 .is_ok_and(|ivl| ivl == interval(-1.0, 0.0).unwrap()));
283 assert!(ivl4.intersect(ivl5).is_err());
284 assert_eq!(ivl1.intersect(ivl6).unwrap(), ivl1);
285 assert_eq!(ivl4.intersect(ivl6).unwrap(), ivl4);
286 assert_eq!(ivl5.intersect(ivl6).unwrap(), ivl5);
287 }
288
289 #[test]
290 fn containment() {
291 let ivl = Interval::UNIT;
292 assert!(ivl.contains(0.0));
293 assert!(ivl.contains(1.0));
294 assert!(ivl.contains(0.5));
295 assert!(!ivl.contains(-0.1));
296 assert!(!ivl.contains(1.1));
297 assert!(!ivl.contains(f32::NAN));
298
299 let ivl = interval(3.0, f32::INFINITY).unwrap();
300 assert!(ivl.contains(3.0));
301 assert!(ivl.contains(2.0e5));
302 assert!(ivl.contains(3.5e6));
303 assert!(!ivl.contains(2.5));
304 assert!(!ivl.contains(-1e5));
305 assert!(!ivl.contains(f32::NAN));
306 }
307
308 #[test]
309 fn interval_containment() {
310 let ivl = Interval::UNIT;
311 assert!(ivl.contains_interval(interval(-0.0, 0.5).unwrap()));
312 assert!(ivl.contains_interval(interval(0.5, 1.0).unwrap()));
313 assert!(ivl.contains_interval(interval(0.25, 0.75).unwrap()));
314 assert!(!ivl.contains_interval(interval(-0.25, 0.5).unwrap()));
315 assert!(!ivl.contains_interval(interval(0.5, 1.25).unwrap()));
316 assert!(!ivl.contains_interval(interval(0.25, f32::INFINITY).unwrap()));
317 assert!(!ivl.contains_interval(interval(f32::NEG_INFINITY, 0.75).unwrap()));
318
319 let big_ivl = interval(0.0, f32::INFINITY).unwrap();
320 assert!(big_ivl.contains_interval(interval(0.0, 5.0).unwrap()));
321 assert!(big_ivl.contains_interval(interval(0.0, f32::INFINITY).unwrap()));
322 assert!(big_ivl.contains_interval(interval(1.0, 5.0).unwrap()));
323 assert!(!big_ivl.contains_interval(interval(-1.0, f32::INFINITY).unwrap()));
324 assert!(!big_ivl.contains_interval(interval(-2.0, 5.0).unwrap()));
325 }
326
327 #[test]
328 fn boundedness() {
329 assert!(!Interval::EVERYWHERE.is_bounded());
330 assert!(interval(0.0, 3.5e5).unwrap().is_bounded());
331 assert!(!interval(-2.0, f32::INFINITY).unwrap().is_bounded());
332 assert!(!interval(f32::NEG_INFINITY, 5.0).unwrap().is_bounded());
333 }
334
335 #[test]
336 fn linear_maps() {
337 let ivl1 = interval(-3.0, 5.0).unwrap();
338 let ivl2 = Interval::UNIT;
339 let map = ivl1.linear_map_to(ivl2);
340 assert!(map.is_ok_and(|f| f(-3.0).abs_diff_eq(&0.0, f32::EPSILON)
341 && f(5.0).abs_diff_eq(&1.0, f32::EPSILON)
342 && f(1.0).abs_diff_eq(&0.5, f32::EPSILON)));
343
344 let ivl1 = Interval::UNIT;
345 let ivl2 = Interval::EVERYWHERE;
346 assert!(ivl1.linear_map_to(ivl2).is_err());
347
348 let ivl1 = interval(f32::NEG_INFINITY, -4.0).unwrap();
349 let ivl2 = Interval::UNIT;
350 assert!(ivl1.linear_map_to(ivl2).is_err());
351 }
352
353 #[test]
354 fn spaced_points() {
355 let ivl = interval(0.0, 50.0).unwrap();
356 let points_iter: Vec<f32> = ivl.spaced_points(1).unwrap().collect();
357 assert_abs_diff_eq!(points_iter[0], 0.0);
358 assert_eq!(points_iter.len(), 1);
359 let points_iter: Vec<f32> = ivl.spaced_points(2).unwrap().collect();
360 assert_abs_diff_eq!(points_iter[0], 0.0);
361 assert_abs_diff_eq!(points_iter[1], 50.0);
362 let points_iter = ivl.spaced_points(21).unwrap();
363 let step = ivl.length() / 20.0;
364 for (index, point) in points_iter.enumerate() {
365 let expected = ivl.start() + step * index as f32;
366 assert_abs_diff_eq!(point, expected);
367 }
368
369 let ivl = interval(-21.0, 79.0).unwrap();
370 let points_iter = ivl.spaced_points(10000).unwrap();
371 let step = ivl.length() / 9999.0;
372 for (index, point) in points_iter.enumerate() {
373 let expected = ivl.start() + step * index as f32;
374 assert_abs_diff_eq!(point, expected);
375 }
376
377 let ivl = interval(-1.0, f32::INFINITY).unwrap();
378 let points_iter = ivl.spaced_points(25);
379 assert!(points_iter.is_err());
380
381 let ivl = interval(f32::NEG_INFINITY, -25.0).unwrap();
382 let points_iter = ivl.spaced_points(9);
383 assert!(points_iter.is_err());
384 }
385}