1use 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#[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#[derive(Debug, Error, Display)]
33#[display("The resulting interval would be invalid (empty or with a NaN endpoint)")]
34pub struct InvalidIntervalError;
35
36#[derive(Debug, Error, Display)]
38#[display("Cannot extract spaced points from an unbounded interval")]
39pub struct SpacedPointsError;
40
41#[derive(Debug, Error, Display)]
44#[display("Could not construct linear function to map between intervals")]
45pub(super) enum LinearMapError {
46 #[display("The source interval is unbounded")]
48 SourceUnbounded,
49
50 #[display("The target interval is unbounded")]
52 TargetUnbounded,
53}
54
55impl Interval {
56 #[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 pub const UNIT: Self = Self {
70 start: 0.0,
71 end: 1.0,
72 };
73
74 pub const EVERYWHERE: Self = Self {
76 start: f32::NEG_INFINITY,
77 end: f32::INFINITY,
78 };
79
80 #[inline]
82 pub const fn start(self) -> f32 {
83 self.start
84 }
85
86 #[inline]
88 pub const fn end(self) -> f32 {
89 self.end
90 }
91
92 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 #[inline]
102 pub fn length(self) -> f32 {
103 self.end - self.start
104 }
105
106 #[inline]
110 pub fn is_bounded(self) -> bool {
111 self.length().is_finite()
112 }
113
114 #[inline]
116 pub fn has_finite_start(self) -> bool {
117 self.start.is_finite()
118 }
119
120 #[inline]
122 pub fn has_finite_end(self) -> bool {
123 self.end.is_finite()
124 }
125
126 #[inline]
128 pub fn contains(self, item: f32) -> bool {
129 (self.start..=self.end).contains(&item)
130 }
131
132 #[inline]
136 pub fn contains_interval(self, other: Self) -> bool {
137 self.start <= other.start && self.end >= other.end
138 }
139
140 #[inline]
142 pub fn clamp(self, value: f32) -> f32 {
143 value.clamp(self.start, self.end)
144 }
145
146 #[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 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 #[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#[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 #[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 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_eq!(points_iter.len(), 1);
349 let points_iter: Vec<f32> = ivl.spaced_points(2).unwrap().collect();
350 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 }
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 }
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}