2020-08-09 15:24:32 +00:00
|
|
|
//! Vectors, positions, rectangles etc.
|
|
|
|
|
2020-08-27 16:07:33 +00:00
|
|
|
use std::ops::{Add, Mul, RangeInclusive};
|
2020-04-18 23:05:49 +00:00
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2020-08-27 16:07:33 +00:00
|
|
|
mod movement_tracker;
|
|
|
|
mod pos2;
|
|
|
|
mod rect;
|
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
|
|
|
|
2020-08-27 16:07:33 +00:00
|
|
|
pub use {movement_tracker::*, pos2::*, rect::*, vec2::*};
|
2020-04-21 05:39:23 +00:00
|
|
|
|
2019-01-19 16:09:00 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2020-08-09 15:24:32 +00:00
|
|
|
/// Linear interpolation.
|
2020-04-25 09:14:32 +00:00
|
|
|
pub fn lerp<T>(range: RangeInclusive<T>, t: f32) -> T
|
2019-04-25 16:07:36 +00:00
|
|
|
where
|
2020-04-21 18:48:31 +00:00
|
|
|
f32: Mul<T, Output = T>,
|
2020-04-25 09:14:32 +00:00
|
|
|
T: Add<T, Output = T> + Copy,
|
2019-04-25 16:07:36 +00:00
|
|
|
{
|
2020-04-25 09:14:32 +00:00
|
|
|
(1.0 - 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-04-25 09:11:44 +00:00
|
|
|
pub fn remap(x: f32, from: RangeInclusive<f32>, to: RangeInclusive<f32>) -> f32 {
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-08-09 15:24:32 +00:00
|
|
|
/// Like `remap`, but also clamps the value so that the returned value is always in the `to` range.
|
2020-04-25 09:11:44 +00:00
|
|
|
pub fn remap_clamp(x: f32, from: RangeInclusive<f32>, to: RangeInclusive<f32>) -> f32 {
|
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 {
|
2020-05-08 19:31:27 +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-05-08 19:31:27 +00:00
|
|
|
if 1.0 <= t {
|
|
|
|
*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
|
|
|
/// Returns `range.start()` if `x <= range.start()`,
|
|
|
|
/// returns `range.end()` if `x >= range.end()`
|
|
|
|
/// and returns `x` elsewhen.
|
2020-05-16 17:38:46 +00:00
|
|
|
pub fn clamp<T>(x: T, range: RangeInclusive<T>) -> T
|
|
|
|
where
|
|
|
|
T: Copy + PartialOrd,
|
|
|
|
{
|
2020-04-25 09:14:32 +00:00
|
|
|
if x <= *range.start() {
|
|
|
|
*range.start()
|
2020-05-08 19:31:27 +00:00
|
|
|
} else if *range.end() <= x {
|
2020-04-25 09:14:32 +00:00
|
|
|
*range.end()
|
2019-01-17 23:34:01 +00:00
|
|
|
} else {
|
|
|
|
x
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-25 16:07:36 +00:00
|
|
|
/// For t=[0,1], returns [0,1] with a derivate of zero at both ends
|
|
|
|
pub fn ease_in_ease_out(t: f32) -> f32 {
|
2020-04-24 16:47:14 +00:00
|
|
|
3.0 * t * t - 2.0 * t * t * t
|
2019-04-25 16:07:36 +00:00
|
|
|
}
|
|
|
|
|
2020-07-23 12:35:12 +00:00
|
|
|
/// The circumference of a circle divided by its radius.
|
|
|
|
///
|
|
|
|
/// Represents one turn in radian angles. Equal to `2 * pi`.
|
|
|
|
///
|
2020-07-30 10:30:20 +00:00
|
|
|
/// See <https://tauday.com/>
|
2019-01-05 14:28:07 +00:00
|
|
|
pub const TAU: f32 = 2.0 * std::f32::consts::PI;
|
2020-06-10 14:22:31 +00:00
|
|
|
|
2020-08-09 15:24:32 +00:00
|
|
|
/// Round a value to the given number of decimal places.
|
2020-09-02 04:04:59 +00:00
|
|
|
pub fn round_to_precision_f32(value: f32, decimal_places: usize) -> f32 {
|
|
|
|
// This is a stupid way of doing this, but stupid works.
|
|
|
|
format!("{:.*}", decimal_places, value)
|
|
|
|
.parse()
|
|
|
|
.unwrap_or_else(|_| value)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Round a value to the given number of decimal places.
|
|
|
|
pub fn round_to_precision(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()
|
|
|
|
.unwrap_or_else(|_| value)
|
|
|
|
}
|
2020-08-27 22:22:08 +00:00
|
|
|
|
|
|
|
pub fn format_with_minimum_precision(value: f32, precision: usize) -> String {
|
|
|
|
let text = format!("{:.*}", precision, value);
|
2020-08-29 12:41:32 +00:00
|
|
|
let epsilon = 16.0 * f32::EPSILON; // margin large enough to handle most peoples round-tripping needs
|
|
|
|
if almost_equal(text.parse::<f32>().unwrap(), value, epsilon) {
|
2020-08-27 22:22:08 +00:00
|
|
|
// Enough precision to show the value accurately - good!
|
|
|
|
text
|
|
|
|
} else {
|
|
|
|
// 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
|
|
|
|
value.to_string()
|
|
|
|
}
|
|
|
|
}
|
2020-08-29 12:41:32 +00:00
|
|
|
|
|
|
|
/// Should return true when arguments are the same within some rounding error.
|
|
|
|
/// 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 {
|
|
|
|
#![allow(clippy::float_cmp)]
|
|
|
|
|
|
|
|
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-09-02 04:04:59 +00:00
|
|
|
#[test]
|
|
|
|
fn test_format() {
|
|
|
|
assert_eq!(format_with_minimum_precision(1_234_567.0, 0), "1234567");
|
|
|
|
assert_eq!(format_with_minimum_precision(1_234_567.0, 1), "1234567.0");
|
|
|
|
assert_eq!(format_with_minimum_precision(3.14, 2), "3.14");
|
|
|
|
assert_eq!(
|
|
|
|
format_with_minimum_precision(std::f32::consts::PI, 2),
|
|
|
|
"3.1415927"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|