emath/
lib.rs

1//! Opinionated 2D math library for building GUIs.
2//!
3//! Includes vectors, positions, rectangles etc.
4//!
5//! Conventions (unless otherwise specified):
6//!
7//! * All angles are in radians
8//! * X+ is right and Y+ is down.
9//! * (0,0) is left top.
10//! * Dimension order is always `x y`
11//!
12//! ## Integrating with other math libraries.
13//! `emath` does not strive to become a general purpose or all-powerful math library.
14//!
15//! For that, use something else ([`glam`](https://docs.rs/glam), [`nalgebra`](https://docs.rs/nalgebra), …)
16//! and enable the `mint` feature flag in `emath` to enable implicit conversion to/from `emath`.
17//!
18//! ## Feature flags
19#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
20//!
21
22#![allow(clippy::float_cmp)]
23
24use std::ops::{Add, Div, Mul, RangeInclusive, Sub};
25
26// ----------------------------------------------------------------------------
27
28pub mod align;
29pub mod easing;
30mod gui_rounding;
31mod history;
32mod numeric;
33mod ordered_float;
34mod pos2;
35mod range;
36mod rect;
37mod rect_transform;
38mod rot2;
39pub mod smart_aim;
40mod ts_transform;
41mod vec2;
42mod vec2b;
43
44pub use self::{
45    align::{Align, Align2},
46    gui_rounding::{GuiRounding, GUI_ROUNDING},
47    history::History,
48    numeric::*,
49    ordered_float::*,
50    pos2::*,
51    range::Rangef,
52    rect::*,
53    rect_transform::*,
54    rot2::*,
55    ts_transform::*,
56    vec2::*,
57    vec2b::*,
58};
59
60// ----------------------------------------------------------------------------
61
62/// Helper trait to implement [`lerp`] and [`remap`].
63pub trait One {
64    const ONE: Self;
65}
66
67impl One for f32 {
68    const ONE: Self = 1.0;
69}
70
71impl One for f64 {
72    const ONE: Self = 1.0;
73}
74
75/// Helper trait to implement [`lerp`] and [`remap`].
76pub trait Real:
77    Copy
78    + PartialEq
79    + PartialOrd
80    + One
81    + Add<Self, Output = Self>
82    + Sub<Self, Output = Self>
83    + Mul<Self, Output = Self>
84    + Div<Self, Output = Self>
85{
86}
87
88impl Real for f32 {}
89
90impl Real for f64 {}
91
92// ----------------------------------------------------------------------------
93
94/// Linear interpolation.
95///
96/// ```
97/// # use emath::lerp;
98/// assert_eq!(lerp(1.0..=5.0, 0.0), 1.0);
99/// assert_eq!(lerp(1.0..=5.0, 0.5), 3.0);
100/// assert_eq!(lerp(1.0..=5.0, 1.0), 5.0);
101/// assert_eq!(lerp(1.0..=5.0, 2.0), 9.0);
102/// ```
103#[inline(always)]
104pub fn lerp<R, T>(range: impl Into<RangeInclusive<R>>, t: T) -> R
105where
106    T: Real + Mul<R, Output = R>,
107    R: Copy + Add<R, Output = R>,
108{
109    let range = range.into();
110    (T::ONE - t) * *range.start() + t * *range.end()
111}
112
113/// Where in the range is this value? Returns 0-1 if within the range.
114///
115/// Returns <0 if before and >1 if after.
116///
117/// Returns `None` if the input range is zero-width.
118///
119/// ```
120/// # use emath::inverse_lerp;
121/// assert_eq!(inverse_lerp(1.0..=5.0, 1.0), Some(0.0));
122/// assert_eq!(inverse_lerp(1.0..=5.0, 3.0), Some(0.5));
123/// assert_eq!(inverse_lerp(1.0..=5.0, 5.0), Some(1.0));
124/// assert_eq!(inverse_lerp(1.0..=5.0, 9.0), Some(2.0));
125/// assert_eq!(inverse_lerp(1.0..=1.0, 3.0), None);
126/// ```
127#[inline]
128pub fn inverse_lerp<R>(range: RangeInclusive<R>, value: R) -> Option<R>
129where
130    R: Copy + PartialEq + Sub<R, Output = R> + Div<R, Output = R>,
131{
132    let min = *range.start();
133    let max = *range.end();
134    if min == max {
135        None
136    } else {
137        Some((value - min) / (max - min))
138    }
139}
140
141/// Linearly remap a value from one range to another,
142/// so that when `x == from.start()` returns `to.start()`
143/// and when `x == from.end()` returns `to.end()`.
144pub fn remap<T>(x: T, from: impl Into<RangeInclusive<T>>, to: impl Into<RangeInclusive<T>>) -> T
145where
146    T: Real,
147{
148    let from = from.into();
149    let to = to.into();
150    debug_assert!(from.start() != from.end());
151    let t = (x - *from.start()) / (*from.end() - *from.start());
152    lerp(to, t)
153}
154
155/// Like [`remap`], but also clamps the value so that the returned value is always in the `to` range.
156pub fn remap_clamp<T>(
157    x: T,
158    from: impl Into<RangeInclusive<T>>,
159    to: impl Into<RangeInclusive<T>>,
160) -> T
161where
162    T: Real,
163{
164    let from = from.into();
165    let to = to.into();
166    if from.end() < from.start() {
167        return remap_clamp(x, *from.end()..=*from.start(), *to.end()..=*to.start());
168    }
169    if x <= *from.start() {
170        *to.start()
171    } else if *from.end() <= x {
172        *to.end()
173    } else {
174        debug_assert!(from.start() != from.end());
175        let t = (x - *from.start()) / (*from.end() - *from.start());
176        // Ensure no numerical inaccuracies sneak in:
177        if T::ONE <= t {
178            *to.end()
179        } else {
180            lerp(to, t)
181        }
182    }
183}
184
185/// Round a value to the given number of decimal places.
186pub fn round_to_decimals(value: f64, decimal_places: usize) -> f64 {
187    // This is a stupid way of doing this, but stupid works.
188    format!("{value:.decimal_places$}").parse().unwrap_or(value)
189}
190
191pub fn format_with_minimum_decimals(value: f64, decimals: usize) -> String {
192    format_with_decimals_in_range(value, decimals..=6)
193}
194
195/// Use as few decimals as possible to show the value accurately, but within the given range.
196///
197/// Decimals are counted after the decimal point.
198pub fn format_with_decimals_in_range(value: f64, decimal_range: RangeInclusive<usize>) -> String {
199    let min_decimals = *decimal_range.start();
200    let max_decimals = *decimal_range.end();
201    debug_assert!(min_decimals <= max_decimals);
202    debug_assert!(max_decimals < 100);
203    let max_decimals = max_decimals.min(16);
204    let min_decimals = min_decimals.min(max_decimals);
205
206    if min_decimals < max_decimals {
207        // Ugly/slow way of doing this. TODO(emilk): clean up precision.
208        for decimals in min_decimals..max_decimals {
209            let text = format!("{value:.decimals$}");
210            let epsilon = 16.0 * f32::EPSILON; // margin large enough to handle most peoples round-tripping needs
211            if almost_equal(text.parse::<f32>().unwrap(), value as f32, epsilon) {
212                // Enough precision to show the value accurately - good!
213                return text;
214            }
215        }
216        // The value has more precision than we expected.
217        // Probably the value was set not by the slider, but from outside.
218        // In any case: show the full value
219    }
220    format!("{value:.max_decimals$}")
221}
222
223/// Return true when arguments are the same within some rounding error.
224///
225/// For instance `almost_equal(x, x.to_degrees().to_radians(), f32::EPSILON)` should hold true for all x.
226/// The `epsilon`  can be `f32::EPSILON` to handle simple transforms (like degrees -> radians)
227/// but should be higher to handle more complex transformations.
228pub fn almost_equal(a: f32, b: f32, epsilon: f32) -> bool {
229    if a == b {
230        true // handle infinites
231    } else {
232        let abs_max = a.abs().max(b.abs());
233        abs_max <= epsilon || ((a - b).abs() / abs_max) <= epsilon
234    }
235}
236
237#[allow(clippy::approx_constant)]
238#[test]
239fn test_format() {
240    assert_eq!(format_with_minimum_decimals(1_234_567.0, 0), "1234567");
241    assert_eq!(format_with_minimum_decimals(1_234_567.0, 1), "1234567.0");
242    assert_eq!(format_with_minimum_decimals(3.14, 2), "3.14");
243    assert_eq!(format_with_minimum_decimals(3.14, 3), "3.140");
244    assert_eq!(
245        format_with_minimum_decimals(std::f64::consts::PI, 2),
246        "3.14159"
247    );
248}
249
250#[test]
251fn test_almost_equal() {
252    for &x in &[
253        0.0_f32,
254        f32::MIN_POSITIVE,
255        1e-20,
256        1e-10,
257        f32::EPSILON,
258        0.1,
259        0.99,
260        1.0,
261        1.001,
262        1e10,
263        f32::MAX / 100.0,
264        // f32::MAX, // overflows in rad<->deg test
265        f32::INFINITY,
266    ] {
267        for &x in &[-x, x] {
268            for roundtrip in &[
269                |x: f32| x.to_degrees().to_radians(),
270                |x: f32| x.to_radians().to_degrees(),
271            ] {
272                let epsilon = f32::EPSILON;
273                assert!(
274                    almost_equal(x, roundtrip(x), epsilon),
275                    "{} vs {}",
276                    x,
277                    roundtrip(x)
278                );
279            }
280        }
281    }
282}
283
284#[test]
285fn test_remap() {
286    assert_eq!(remap_clamp(1.0, 0.0..=1.0, 0.0..=16.0), 16.0);
287    assert_eq!(remap_clamp(1.0, 1.0..=0.0, 16.0..=0.0), 16.0);
288    assert_eq!(remap_clamp(0.5, 1.0..=0.0, 16.0..=0.0), 8.0);
289}
290
291// ----------------------------------------------------------------------------
292
293/// Extends `f32`, [`Vec2`] etc with `at_least` and `at_most` as aliases for `max` and `min`.
294pub trait NumExt {
295    /// More readable version of `self.max(lower_limit)`
296    #[must_use]
297    fn at_least(self, lower_limit: Self) -> Self;
298
299    /// More readable version of `self.min(upper_limit)`
300    #[must_use]
301    fn at_most(self, upper_limit: Self) -> Self;
302}
303
304macro_rules! impl_num_ext {
305    ($t: ty) => {
306        impl NumExt for $t {
307            #[inline(always)]
308            fn at_least(self, lower_limit: Self) -> Self {
309                self.max(lower_limit)
310            }
311
312            #[inline(always)]
313            fn at_most(self, upper_limit: Self) -> Self {
314                self.min(upper_limit)
315            }
316        }
317    };
318}
319
320impl_num_ext!(u8);
321impl_num_ext!(u16);
322impl_num_ext!(u32);
323impl_num_ext!(u64);
324impl_num_ext!(u128);
325impl_num_ext!(usize);
326impl_num_ext!(i8);
327impl_num_ext!(i16);
328impl_num_ext!(i32);
329impl_num_ext!(i64);
330impl_num_ext!(i128);
331impl_num_ext!(isize);
332impl_num_ext!(f32);
333impl_num_ext!(f64);
334impl_num_ext!(Vec2);
335impl_num_ext!(Pos2);
336
337// ----------------------------------------------------------------------------
338
339/// Wrap angle to `[-PI, PI]` range.
340pub fn normalized_angle(mut angle: f32) -> f32 {
341    use std::f32::consts::{PI, TAU};
342    angle %= TAU;
343    if angle > PI {
344        angle -= TAU;
345    } else if angle < -PI {
346        angle += TAU;
347    }
348    angle
349}
350
351#[test]
352fn test_normalized_angle() {
353    macro_rules! almost_eq {
354        ($left: expr, $right: expr) => {
355            let left = $left;
356            let right = $right;
357            assert!((left - right).abs() < 1e-6, "{} != {}", left, right);
358        };
359    }
360
361    use std::f32::consts::TAU;
362    almost_eq!(normalized_angle(-3.0 * TAU), 0.0);
363    almost_eq!(normalized_angle(-2.3 * TAU), -0.3 * TAU);
364    almost_eq!(normalized_angle(-TAU), 0.0);
365    almost_eq!(normalized_angle(0.0), 0.0);
366    almost_eq!(normalized_angle(TAU), 0.0);
367    almost_eq!(normalized_angle(2.7 * TAU), -0.3 * TAU);
368}
369
370// ----------------------------------------------------------------------------
371
372/// Calculate a lerp-factor for exponential smoothing using a time step.
373///
374/// * `exponential_smooth_factor(0.90, 1.0, dt)`: reach 90% in 1.0 seconds
375/// * `exponential_smooth_factor(0.50, 0.2, dt)`: reach 50% in 0.2 seconds
376///
377/// Example:
378/// ```
379/// # use emath::{lerp, exponential_smooth_factor};
380/// # let (mut smoothed_value, target_value, dt) = (0.0_f32, 1.0_f32, 0.01_f32);
381/// let t = exponential_smooth_factor(0.90, 0.2, dt); // reach 90% in 0.2 seconds
382/// smoothed_value = lerp(smoothed_value..=target_value, t);
383/// ```
384pub fn exponential_smooth_factor(
385    reach_this_fraction: f32,
386    in_this_many_seconds: f32,
387    dt: f32,
388) -> f32 {
389    1.0 - (1.0 - reach_this_fraction).powf(dt / in_this_many_seconds)
390}
391
392/// If you have a value animating over time,
393/// how much towards its target do you need to move it this frame?
394///
395/// You only need to store the start time and target value in order to animate using this function.
396///
397/// ``` rs
398/// struct Animation {
399///     current_value: f32,
400///
401///     animation_time_span: (f64, f64),
402///     target_value: f32,
403/// }
404///
405/// impl Animation {
406///     fn update(&mut self, now: f64, dt: f32) {
407///         let t = interpolation_factor(self.animation_time_span, now, dt, ease_in_ease_out);
408///         self.current_value = emath::lerp(self.current_value..=self.target_value, t);
409///     }
410/// }
411/// ```
412pub fn interpolation_factor(
413    (start_time, end_time): (f64, f64),
414    current_time: f64,
415    dt: f32,
416    easing: impl Fn(f32) -> f32,
417) -> f32 {
418    let animation_duration = (end_time - start_time) as f32;
419    let prev_time = current_time - dt as f64;
420    let prev_t = easing((prev_time - start_time) as f32 / animation_duration);
421    let end_t = easing((current_time - start_time) as f32 / animation_duration);
422    if end_t < 1.0 {
423        (end_t - prev_t) / (1.0 - prev_t)
424    } else {
425        1.0
426    }
427}
428
429/// Ease in, ease out.
430///
431/// `f(0) = 0, f'(0) = 0, f(1) = 1, f'(1) = 0`.
432#[inline]
433pub fn ease_in_ease_out(t: f32) -> f32 {
434    let t = t.clamp(0.0, 1.0);
435    (3.0 * t * t - 2.0 * t * t * t).clamp(0.0, 1.0)
436}