2021-01-17 09:52:01 +00:00
|
|
|
//! Opinionated 2D math library for building GUIs.
|
|
|
|
//!
|
|
|
|
//! Includes vectors, positions, rectangles etc.
|
2020-12-27 11:57:15 +00:00
|
|
|
//!
|
|
|
|
//! Conventions (unless otherwise specified):
|
2021-01-10 10:37:47 +00:00
|
|
|
//!
|
2020-12-27 11:57:15 +00:00
|
|
|
//! * All angles are in radians
|
2021-01-10 10:37:47 +00:00
|
|
|
//! * X+ is right and Y+ is down.
|
|
|
|
//! * (0,0) is left top.
|
|
|
|
//! * Dimension order is always `x y`
|
2022-01-22 07:56:36 +00:00
|
|
|
//!
|
|
|
|
//! ## Integrating with other math libraries.
|
|
|
|
//! `emath` does not strive to become a general purpose or all-powerful math library.
|
|
|
|
//!
|
|
|
|
//! For that, use something else ([`glam`](https://docs.rs/glam), [`nalgebra`](https://docs.rs/nalgebra), …)
|
|
|
|
//! and enable the `mint` feature flag in `emath` to enable implicit conversion to/from `emath`.
|
2022-06-09 13:27:22 +00:00
|
|
|
//!
|
|
|
|
//! ## Feature flags
|
2022-06-09 15:41:37 +00:00
|
|
|
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
|
2022-06-09 13:27:22 +00:00
|
|
|
//!
|
2021-01-10 10:37:47 +00:00
|
|
|
|
2021-05-09 12:00:53 +00:00
|
|
|
#![allow(clippy::float_cmp)]
|
2021-01-10 10:37:47 +00:00
|
|
|
#![allow(clippy::manual_range_contains)]
|
2020-08-09 15:24:32 +00:00
|
|
|
|
2020-10-06 18:51:59 +00:00
|
|
|
use std::ops::{Add, Div, Mul, RangeInclusive, Sub};
|
2020-04-18 23:05:49 +00:00
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2021-01-10 09:54:34 +00:00
|
|
|
pub mod align;
|
2022-11-21 13:14:33 +00:00
|
|
|
mod history;
|
2021-03-27 15:03:11 +00:00
|
|
|
mod numeric;
|
2020-08-27 16:07:33 +00:00
|
|
|
mod pos2;
|
|
|
|
mod rect;
|
2021-02-14 09:33:44 +00:00
|
|
|
mod rect_transform;
|
2020-12-25 11:22:10 +00:00
|
|
|
mod rot2;
|
2020-08-27 18:58:41 +00:00
|
|
|
pub mod smart_aim;
|
2020-08-27 16:07:33 +00:00
|
|
|
mod vec2;
|
2020-04-21 05:39:23 +00:00
|
|
|
|
2021-01-10 09:54:34 +00:00
|
|
|
pub use {
|
|
|
|
align::{Align, Align2},
|
2022-11-21 13:14:33 +00:00
|
|
|
history::History,
|
2021-03-27 15:03:11 +00:00
|
|
|
numeric::*,
|
2021-01-10 09:54:34 +00:00
|
|
|
pos2::*,
|
|
|
|
rect::*,
|
2021-02-14 09:33:44 +00:00
|
|
|
rect_transform::*,
|
2021-01-10 09:54:34 +00:00
|
|
|
rot2::*,
|
|
|
|
vec2::*,
|
|
|
|
};
|
2020-04-21 05:39:23 +00:00
|
|
|
|
2019-01-19 16:09:00 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2020-12-27 11:57:15 +00:00
|
|
|
/// Helper trait to implement [`lerp`] and [`remap`].
|
2020-10-06 18:51:59 +00:00
|
|
|
pub trait One {
|
|
|
|
fn one() -> Self;
|
|
|
|
}
|
2022-08-02 15:26:33 +00:00
|
|
|
|
2020-10-06 18:51:59 +00:00
|
|
|
impl One for f32 {
|
2021-03-28 21:16:19 +00:00
|
|
|
#[inline(always)]
|
2020-10-06 18:51:59 +00:00
|
|
|
fn one() -> Self {
|
|
|
|
1.0
|
|
|
|
}
|
|
|
|
}
|
2022-08-02 15:26:33 +00:00
|
|
|
|
2020-10-06 18:51:59 +00:00
|
|
|
impl One for f64 {
|
2021-03-28 21:16:19 +00:00
|
|
|
#[inline(always)]
|
2020-10-06 18:51:59 +00:00
|
|
|
fn one() -> Self {
|
|
|
|
1.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-27 11:57:15 +00:00
|
|
|
/// Helper trait to implement [`lerp`] and [`remap`].
|
2020-10-06 18:51:59 +00:00
|
|
|
pub trait Real:
|
|
|
|
Copy
|
|
|
|
+ PartialEq
|
|
|
|
+ PartialOrd
|
|
|
|
+ One
|
|
|
|
+ Add<Self, Output = Self>
|
|
|
|
+ Sub<Self, Output = Self>
|
|
|
|
+ Mul<Self, Output = Self>
|
|
|
|
+ Div<Self, Output = Self>
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Real for f32 {}
|
2022-08-02 15:26:33 +00:00
|
|
|
|
2020-10-06 18:51:59 +00:00
|
|
|
impl Real for f64 {}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2020-08-09 15:24:32 +00:00
|
|
|
/// Linear interpolation.
|
2021-03-28 21:16:19 +00:00
|
|
|
#[inline(always)]
|
2020-10-06 18:51:59 +00:00
|
|
|
pub fn lerp<R, T>(range: RangeInclusive<R>, t: T) -> R
|
2019-04-25 16:07:36 +00:00
|
|
|
where
|
2020-10-06 18:51:59 +00:00
|
|
|
T: Real + Mul<R, Output = R>,
|
|
|
|
R: Copy + Add<R, Output = R>,
|
2019-04-25 16:07:36 +00:00
|
|
|
{
|
2020-10-06 18:51:59 +00:00
|
|
|
(T::one() - t) * *range.start() + t * *range.end()
|
2018-12-26 16:01:46 +00:00
|
|
|
}
|
|
|
|
|
2020-08-09 15:24:32 +00:00
|
|
|
/// Linearly remap a value from one range to another,
|
|
|
|
/// so that when `x == from.start()` returns `to.start()`
|
|
|
|
/// and when `x == from.end()` returns `to.end()`.
|
2020-10-06 18:51:59 +00:00
|
|
|
pub fn remap<T>(x: T, from: RangeInclusive<T>, to: RangeInclusive<T>) -> T
|
|
|
|
where
|
|
|
|
T: Real,
|
|
|
|
{
|
2021-05-17 20:34:29 +00:00
|
|
|
crate::emath_assert!(from.start() != from.end());
|
2020-10-06 18:51:59 +00:00
|
|
|
let t = (x - *from.start()) / (*from.end() - *from.start());
|
2020-04-25 09:14:32 +00:00
|
|
|
lerp(to, t)
|
2019-01-05 14:28:07 +00:00
|
|
|
}
|
|
|
|
|
2021-01-17 09:52:01 +00:00
|
|
|
/// Like [`remap`], but also clamps the value so that the returned value is always in the `to` range.
|
2020-10-06 18:51:59 +00:00
|
|
|
pub fn remap_clamp<T>(x: T, from: RangeInclusive<T>, to: RangeInclusive<T>) -> T
|
|
|
|
where
|
|
|
|
T: Real,
|
|
|
|
{
|
2020-09-06 05:04:47 +00:00
|
|
|
if from.end() < from.start() {
|
|
|
|
return remap_clamp(x, *from.end()..=*from.start(), *to.end()..=*to.start());
|
|
|
|
}
|
2020-05-08 19:31:27 +00:00
|
|
|
if x <= *from.start() {
|
|
|
|
*to.start()
|
|
|
|
} else if *from.end() <= x {
|
|
|
|
*to.end()
|
2018-12-26 16:01:46 +00:00
|
|
|
} else {
|
2021-05-17 20:34:29 +00:00
|
|
|
crate::emath_assert!(from.start() != from.end());
|
2020-10-06 18:51:59 +00:00
|
|
|
let t = (x - *from.start()) / (*from.end() - *from.start());
|
2020-08-27 16:07:33 +00:00
|
|
|
// Ensure no numerical inaccuracies sneak in:
|
2020-10-06 18:51:59 +00:00
|
|
|
if T::one() <= t {
|
2020-05-08 19:31:27 +00:00
|
|
|
*to.end()
|
|
|
|
} else {
|
|
|
|
lerp(to, t)
|
|
|
|
}
|
|
|
|
}
|
2018-12-26 16:01:46 +00:00
|
|
|
}
|
2019-01-05 14:28:07 +00:00
|
|
|
|
2020-08-09 15:24:32 +00:00
|
|
|
/// Round a value to the given number of decimal places.
|
2020-12-18 09:21:00 +00:00
|
|
|
pub fn round_to_decimals(value: f64, decimal_places: usize) -> f64 {
|
2020-06-10 14:22:31 +00:00
|
|
|
// This is a stupid way of doing this, but stupid works.
|
2020-08-09 15:24:32 +00:00
|
|
|
format!("{:.*}", decimal_places, value)
|
2020-06-10 14:22:31 +00:00
|
|
|
.parse()
|
2020-11-20 11:28:24 +00:00
|
|
|
.unwrap_or(value)
|
2020-06-10 14:22:31 +00:00
|
|
|
}
|
2020-08-27 22:22:08 +00:00
|
|
|
|
2021-01-10 10:37:47 +00:00
|
|
|
pub fn format_with_minimum_decimals(value: f64, decimals: usize) -> String {
|
2020-12-18 09:21:00 +00:00
|
|
|
format_with_decimals_in_range(value, decimals..=6)
|
|
|
|
}
|
|
|
|
|
2021-01-10 10:37:47 +00:00
|
|
|
pub fn format_with_decimals_in_range(value: f64, decimal_range: RangeInclusive<usize>) -> String {
|
2020-12-18 09:21:00 +00:00
|
|
|
let min_decimals = *decimal_range.start();
|
|
|
|
let max_decimals = *decimal_range.end();
|
2021-05-17 20:34:29 +00:00
|
|
|
crate::emath_assert!(min_decimals <= max_decimals);
|
|
|
|
crate::emath_assert!(max_decimals < 100);
|
2020-12-18 09:21:00 +00:00
|
|
|
let max_decimals = max_decimals.min(16);
|
|
|
|
let min_decimals = min_decimals.min(max_decimals);
|
|
|
|
|
2021-06-22 21:25:54 +00:00
|
|
|
if min_decimals != max_decimals {
|
2022-05-21 14:53:25 +00:00
|
|
|
// Ugly/slow way of doing this. TODO(emilk): clean up precision.
|
2020-12-18 09:21:00 +00:00
|
|
|
for decimals in min_decimals..max_decimals {
|
|
|
|
let text = format!("{:.*}", decimals, value);
|
|
|
|
let epsilon = 16.0 * f32::EPSILON; // margin large enough to handle most peoples round-tripping needs
|
|
|
|
if almost_equal(text.parse::<f32>().unwrap(), value as f32, epsilon) {
|
|
|
|
// Enough precision to show the value accurately - good!
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
}
|
2020-08-27 22:22:08 +00:00
|
|
|
// The value has more precision than we expected.
|
|
|
|
// Probably the value was set not by the slider, but from outside.
|
|
|
|
// In any case: show the full value
|
|
|
|
}
|
2021-06-22 21:25:54 +00:00
|
|
|
format!("{:.*}", max_decimals, value)
|
2020-08-27 22:22:08 +00:00
|
|
|
}
|
2020-08-29 12:41:32 +00:00
|
|
|
|
2020-12-27 11:57:15 +00:00
|
|
|
/// Return true when arguments are the same within some rounding error.
|
|
|
|
///
|
2020-08-29 12:41:32 +00:00
|
|
|
/// For instance `almost_equal(x, x.to_degrees().to_radians(), f32::EPSILON)` should hold true for all x.
|
|
|
|
/// The `epsilon` can be `f32::EPSILON` to handle simple transforms (like degrees -> radians)
|
|
|
|
/// but should be higher to handle more complex transformations.
|
|
|
|
pub fn almost_equal(a: f32, b: f32, epsilon: f32) -> bool {
|
|
|
|
if a == b {
|
|
|
|
true // handle infinites
|
|
|
|
} else {
|
|
|
|
let abs_max = a.abs().max(b.abs());
|
|
|
|
abs_max <= epsilon || ((a - b).abs() / abs_max) <= epsilon
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-17 09:00:58 +00:00
|
|
|
#[allow(clippy::approx_constant)]
|
2020-09-02 04:04:59 +00:00
|
|
|
#[test]
|
|
|
|
fn test_format() {
|
2020-12-18 09:21:00 +00:00
|
|
|
assert_eq!(format_with_minimum_decimals(1_234_567.0, 0), "1234567");
|
|
|
|
assert_eq!(format_with_minimum_decimals(1_234_567.0, 1), "1234567.0");
|
|
|
|
assert_eq!(format_with_minimum_decimals(3.14, 2), "3.14");
|
|
|
|
assert_eq!(format_with_minimum_decimals(3.14, 3), "3.140");
|
2020-09-02 04:04:59 +00:00
|
|
|
assert_eq!(
|
2020-12-18 09:21:00 +00:00
|
|
|
format_with_minimum_decimals(std::f64::consts::PI, 2),
|
|
|
|
"3.14159"
|
2020-09-02 04:04:59 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-08-29 12:41:32 +00:00
|
|
|
#[test]
|
|
|
|
fn test_almost_equal() {
|
|
|
|
for &x in &[
|
|
|
|
0.0_f32,
|
|
|
|
f32::MIN_POSITIVE,
|
|
|
|
1e-20,
|
|
|
|
1e-10,
|
|
|
|
f32::EPSILON,
|
|
|
|
0.1,
|
|
|
|
0.99,
|
|
|
|
1.0,
|
|
|
|
1.001,
|
|
|
|
1e10,
|
|
|
|
f32::MAX / 100.0,
|
|
|
|
// f32::MAX, // overflows in rad<->deg test
|
|
|
|
f32::INFINITY,
|
|
|
|
] {
|
|
|
|
for &x in &[-x, x] {
|
|
|
|
for roundtrip in &[
|
|
|
|
|x: f32| x.to_degrees().to_radians(),
|
|
|
|
|x: f32| x.to_radians().to_degrees(),
|
|
|
|
] {
|
|
|
|
let epsilon = f32::EPSILON;
|
|
|
|
assert!(
|
|
|
|
almost_equal(x, roundtrip(x), epsilon),
|
|
|
|
"{} vs {}",
|
|
|
|
x,
|
|
|
|
roundtrip(x)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-06 05:04:47 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_remap() {
|
|
|
|
assert_eq!(remap_clamp(1.0, 0.0..=1.0, 0.0..=16.0), 16.0);
|
|
|
|
assert_eq!(remap_clamp(1.0, 1.0..=0.0, 16.0..=0.0), 16.0);
|
|
|
|
assert_eq!(remap_clamp(0.5, 1.0..=0.0, 16.0..=0.0), 8.0);
|
|
|
|
}
|
2020-09-16 19:39:45 +00:00
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2022-04-03 16:18:35 +00:00
|
|
|
/// Extends `f32`, [`Vec2`] etc with `at_least` and `at_most` as aliases for `max` and `min`.
|
2020-09-16 19:39:45 +00:00
|
|
|
pub trait NumExt {
|
|
|
|
/// More readable version of `self.max(lower_limit)`
|
|
|
|
fn at_least(self, lower_limit: Self) -> Self;
|
|
|
|
|
|
|
|
/// More readable version of `self.min(upper_limit)`
|
|
|
|
fn at_most(self, upper_limit: Self) -> Self;
|
|
|
|
}
|
|
|
|
|
2020-12-27 11:57:15 +00:00
|
|
|
macro_rules! impl_num_ext {
|
|
|
|
($t: ty) => {
|
|
|
|
impl NumExt for $t {
|
2021-03-28 21:16:19 +00:00
|
|
|
#[inline(always)]
|
2020-12-27 11:57:15 +00:00
|
|
|
fn at_least(self, lower_limit: Self) -> Self {
|
|
|
|
self.max(lower_limit)
|
|
|
|
}
|
2022-08-02 15:26:33 +00:00
|
|
|
|
2021-03-28 21:16:19 +00:00
|
|
|
#[inline(always)]
|
2020-12-27 11:57:15 +00:00
|
|
|
fn at_most(self, upper_limit: Self) -> Self {
|
|
|
|
self.min(upper_limit)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2020-09-16 19:39:45 +00:00
|
|
|
}
|
|
|
|
|
2022-05-09 10:44:28 +00:00
|
|
|
impl_num_ext!(u8);
|
|
|
|
impl_num_ext!(u16);
|
|
|
|
impl_num_ext!(u32);
|
|
|
|
impl_num_ext!(u64);
|
|
|
|
impl_num_ext!(u128);
|
|
|
|
impl_num_ext!(usize);
|
|
|
|
impl_num_ext!(i8);
|
|
|
|
impl_num_ext!(i16);
|
|
|
|
impl_num_ext!(i32);
|
|
|
|
impl_num_ext!(i64);
|
|
|
|
impl_num_ext!(i128);
|
|
|
|
impl_num_ext!(isize);
|
2020-12-27 11:57:15 +00:00
|
|
|
impl_num_ext!(f32);
|
|
|
|
impl_num_ext!(f64);
|
|
|
|
impl_num_ext!(Vec2);
|
|
|
|
impl_num_ext!(Pos2);
|
2021-05-08 21:42:17 +00:00
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
/// Wrap angle to `[-PI, PI]` range.
|
|
|
|
pub fn normalized_angle(mut angle: f32) -> f32 {
|
|
|
|
use std::f32::consts::{PI, TAU};
|
|
|
|
angle %= TAU;
|
|
|
|
if angle > PI {
|
|
|
|
angle -= TAU;
|
|
|
|
} else if angle < -PI {
|
|
|
|
angle += TAU;
|
|
|
|
}
|
|
|
|
angle
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_normalized_angle() {
|
|
|
|
macro_rules! almost_eq {
|
2021-10-02 19:08:00 +00:00
|
|
|
($left: expr, $right: expr) => {
|
2021-05-08 21:42:17 +00:00
|
|
|
let left = $left;
|
|
|
|
let right = $right;
|
|
|
|
assert!((left - right).abs() < 1e-6, "{} != {}", left, right);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
use std::f32::consts::TAU;
|
|
|
|
almost_eq!(normalized_angle(-3.0 * TAU), 0.0);
|
|
|
|
almost_eq!(normalized_angle(-2.3 * TAU), -0.3 * TAU);
|
|
|
|
almost_eq!(normalized_angle(-TAU), 0.0);
|
|
|
|
almost_eq!(normalized_angle(0.0), 0.0);
|
|
|
|
almost_eq!(normalized_angle(TAU), 0.0);
|
|
|
|
almost_eq!(normalized_angle(2.7 * TAU), -0.3 * TAU);
|
|
|
|
}
|
2021-05-17 20:34:29 +00:00
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2022-07-29 14:07:35 +00:00
|
|
|
/// Calculate a lerp-factor for exponential smoothing using a time step.
|
|
|
|
///
|
|
|
|
/// * `exponential_smooth_factor(0.90, 1.0, dt)`: reach 90% in 1.0 seconds
|
|
|
|
/// * `exponential_smooth_factor(0.50, 0.2, dt)`: reach 50% in 0.2 seconds
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
/// ```
|
|
|
|
/// # use emath::{lerp, exponential_smooth_factor};
|
|
|
|
/// # let (mut smoothed_value, target_value, dt) = (0.0_f32, 1.0_f32, 0.01_f32);
|
|
|
|
/// let t = exponential_smooth_factor(0.90, 0.2, dt); // reach 90% in 0.2 seconds
|
|
|
|
/// smoothed_value = lerp(smoothed_value..=target_value, t);
|
|
|
|
/// ```
|
|
|
|
pub fn exponential_smooth_factor(
|
|
|
|
reach_this_fraction: f32,
|
|
|
|
in_this_many_seconds: f32,
|
|
|
|
dt: f32,
|
|
|
|
) -> f32 {
|
|
|
|
1.0 - (1.0 - reach_this_fraction).powf(dt / in_this_many_seconds)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2021-10-10 13:35:13 +00:00
|
|
|
/// An assert that is only active when `emath` is compiled with the `extra_asserts` feature
|
|
|
|
/// or with the `extra_debug_asserts` feature in debug builds.
|
2021-05-17 20:34:29 +00:00
|
|
|
#[macro_export]
|
|
|
|
macro_rules! emath_assert {
|
2021-10-02 19:08:00 +00:00
|
|
|
($($arg: tt)*) => {
|
2021-05-17 20:34:29 +00:00
|
|
|
if cfg!(any(
|
|
|
|
feature = "extra_asserts",
|
|
|
|
all(feature = "extra_debug_asserts", debug_assertions),
|
|
|
|
)) {
|
|
|
|
assert!($($arg)*);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|