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