use std::ops::{Add, Div, Mul, RangeInclusive, Sub};
mod pos2;
mod rect;
pub mod smart_aim;
mod vec2;
pub use {pos2::*, rect::*, vec2::*};
pub trait One {
fn one() -> Self;
}
impl One for f32 {
fn one() -> Self {
1.0
}
}
impl One for f64 {
fn one() -> Self {
1.0
}
}
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 {}
impl Real for f64 {}
pub fn lerp<R, T>(range: RangeInclusive<R>, t: T) -> R
where
T: Real + Mul<R, Output = R>,
R: Copy + Add<R, Output = R>,
{
(T::one() - t) * *range.start() + t * *range.end()
}
pub fn remap<T>(x: T, from: RangeInclusive<T>, to: RangeInclusive<T>) -> T
where
T: Real,
{
#![allow(clippy::float_cmp)]
debug_assert!(from.start() != from.end());
let t = (x - *from.start()) / (*from.end() - *from.start());
lerp(to, t)
}
pub fn remap_clamp<T>(x: T, from: RangeInclusive<T>, to: RangeInclusive<T>) -> T
where
T: Real,
{
#![allow(clippy::float_cmp)]
if from.end() < from.start() {
return remap_clamp(x, *from.end()..=*from.start(), *to.end()..=*to.start());
}
if x <= *from.start() {
*to.start()
} else if *from.end() <= x {
*to.end()
} else {
debug_assert!(from.start() != from.end());
let t = (x - *from.start()) / (*from.end() - *from.start());
if T::one() <= t {
*to.end()
} else {
lerp(to, t)
}
}
}
pub fn clamp<T>(x: T, range: RangeInclusive<T>) -> T
where
T: Copy + PartialOrd,
{
debug_assert!(range.start() <= range.end());
if x <= *range.start() {
*range.start()
} else if *range.end() <= x {
*range.end()
} else {
x
}
}
pub fn ease_in_ease_out(t: f32) -> f32 {
3.0 * t * t - 2.0 * t * t * t
}
pub const TAU: f32 = 2.0 * std::f32::consts::PI;
pub fn round_to_precision(value: f64, decimal_places: usize) -> f64 {
format!("{:.*}", decimal_places, value)
.parse()
.unwrap_or_else(|_| value)
}
pub fn format_with_minimum_precision(value: f32, precision: usize) -> String {
debug_assert!(precision < 100);
let precision = precision.min(16);
let text = format!("{:.*}", precision, value);
let epsilon = 16.0 * f32::EPSILON; if almost_equal(text.parse::<f32>().unwrap(), value, epsilon) {
text
} else {
value.to_string()
}
}
pub fn almost_equal(a: f32, b: f32, epsilon: f32) -> bool {
#![allow(clippy::float_cmp)]
if a == b {
true } else {
let abs_max = a.abs().max(b.abs());
abs_max <= epsilon || ((a - b).abs() / abs_max) <= epsilon
}
}
#[allow(clippy::approx_constant)]
#[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"
);
}
#[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::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)
);
}
}
}
}
#[allow(clippy::float_cmp)]
#[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);
}
pub trait NumExt {
fn at_least(self, lower_limit: Self) -> Self;
fn at_most(self, upper_limit: Self) -> Self;
}
impl NumExt for f32 {
fn at_least(self, lower_limit: Self) -> Self {
self.max(lower_limit)
}
fn at_most(self, upper_limit: Self) -> Self {
self.min(upper_limit)
}
}
impl NumExt for f64 {
fn at_least(self, lower_limit: Self) -> Self {
self.max(lower_limit)
}
fn at_most(self, upper_limit: Self) -> Self {
self.min(upper_limit)
}
}
impl NumExt for usize {
fn at_least(self, lower_limit: Self) -> Self {
self.max(lower_limit)
}
fn at_most(self, upper_limit: Self) -> Self {
self.min(upper_limit)
}
}
impl NumExt for Vec2 {
fn at_least(self, lower_limit: Self) -> Self {
self.max(lower_limit)
}
fn at_most(self, upper_limit: Self) -> Self {
self.min(upper_limit)
}
}
impl NumExt for Pos2 {
fn at_least(self, lower_limit: Self) -> Self {
self.max(lower_limit)
}
fn at_most(self, upper_limit: Self) -> Self {
self.min(upper_limit)
}
}