278 lines
9.1 KiB
Rust
278 lines
9.1 KiB
Rust
use std::ops::{Add, Div, Mul, Sub};
|
|
|
|
pub use self::circle::*;
|
|
pub use self::matrix3x3::*;
|
|
pub use self::rect::*;
|
|
pub use self::vector2::*;
|
|
|
|
pub mod circle;
|
|
pub mod matrix3x3;
|
|
pub mod rect;
|
|
pub mod vector2;
|
|
|
|
pub const PI: f32 = std::f32::consts::PI; // 180 degrees
|
|
pub const HALF_PI: f32 = PI / 2.0; // 90 degrees
|
|
pub const QUARTER_PI: f32 = PI / 4.0; // 45 degrees
|
|
pub const TWO_PI: f32 = PI * 2.0; // 360 degrees
|
|
|
|
pub const RADIANS_0: f32 = 0.0;
|
|
pub const RADIANS_45: f32 = PI / 4.0;
|
|
pub const RADIANS_90: f32 = PI / 2.0;
|
|
pub const RADIANS_135: f32 = (3.0 * PI) / 4.0;
|
|
pub const RADIANS_180: f32 = PI;
|
|
pub const RADIANS_225: f32 = (5.0 * PI) / 4.0;
|
|
pub const RADIANS_270: f32 = (3.0 * PI) / 2.0;
|
|
pub const RADIANS_315: f32 = (7.0 * PI) / 4.0;
|
|
pub const RADIANS_360: f32 = PI * 2.0;
|
|
|
|
pub const PI_OVER_180: f32 = PI / 180.0;
|
|
pub const INVERSE_PI_OVER_180: f32 = 180.0 / PI;
|
|
|
|
// 2d directions. intended to be usable in a 2d screen-space where the origin 0,0 is at the
|
|
// top-left of the screen, where moving up is accomplished by decrementing Y.
|
|
// TODO: this is not a perfect solution and does pose some problems in various math calculations ...
|
|
pub const UP: f32 = -RADIANS_90;
|
|
pub const DOWN: f32 = RADIANS_90;
|
|
pub const LEFT: f32 = RADIANS_180;
|
|
pub const RIGHT: f32 = RADIANS_0;
|
|
|
|
/// Returns true if the two f32 values are "close enough" to be considered equal using the
|
|
/// precision of the provided epsilon value.
|
|
#[inline]
|
|
pub fn nearly_equal(a: f32, b: f32, epsilon: f32) -> bool {
|
|
// HACK/TODO: this is a shitty way to do this. but it's "good enough" for me ...
|
|
(a - b).abs() <= epsilon
|
|
}
|
|
|
|
/// Linearly interpolates between two values.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `a`: first value (low end of range)
|
|
/// * `b`: second value (high end of range)
|
|
/// * `t`: the amount to interpolate between the two values, specified as a fraction
|
|
#[inline]
|
|
pub fn lerp<N>(a: N, b: N, t: f32) -> N
|
|
where
|
|
N: Copy + Add<Output = N> + Sub<Output = N> + Mul<f32, Output = N>,
|
|
{
|
|
a + (b - a) * t
|
|
}
|
|
|
|
/// Given a linearly interpolated value and the original range (high and low) of the linear
|
|
/// interpolation, this will return the original interpolation factor (as a fraction)
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `a`: first value (low end of range)
|
|
/// * `b`: second value (high end of range)
|
|
/// * `lerp_result`: the interpolated value between the range given
|
|
#[inline]
|
|
pub fn inverse_lerp<N>(a: N, b: N, lerp_result: N) -> f32
|
|
where
|
|
N: Copy + Sub<Output = N> + Div<N, Output = f32>,
|
|
{
|
|
(lerp_result - a) / (b - a)
|
|
}
|
|
|
|
/// Interpolates between two values using a cubic equation.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `a`: first value (low end of range)
|
|
/// * `b`: second value (high end of range)
|
|
/// * `t`: the amount to interpolate between the two values, specified as a fraction
|
|
#[inline]
|
|
pub fn smooth_lerp<N>(a: N, b: N, t: f32) -> N
|
|
where
|
|
N: Copy + Add<Output = N> + Sub<Output = N> + Mul<f32, Output = N>,
|
|
{
|
|
let t = t.clamp(0.0, 1.0);
|
|
lerp(a, b, (t * t) * (3.0 - (2.0 * t)))
|
|
}
|
|
|
|
/// Re-scales a given value from an old min/max range to a new and different min/max range such
|
|
/// that the returned value is approximately at the same relative position within the new min/max
|
|
/// range.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `value`: the value to be re-scaled which is currently between `old_min` and `old_max`
|
|
/// * `old_min`: original min value (low end of range)
|
|
/// * `old_max`: original max value (high end of range)
|
|
/// * `new_min`: new min value (low end of range)
|
|
/// * `new_max`: new max value (high end of range)
|
|
#[inline]
|
|
pub fn scale_range<N>(value: N, old_min: N, old_max: N, new_min: N, new_max: N) -> N
|
|
where
|
|
N: Copy + Add<Output = N> + Sub<Output = N> + Mul<Output = N> + Div<Output = N>,
|
|
{
|
|
(new_max - new_min) * (value - old_min) / (old_max - old_min) + new_min
|
|
}
|
|
|
|
/// Calculates the angle (in radians) between the two points.
|
|
#[inline]
|
|
pub fn angle_between(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
|
|
let delta_x = x2 - x1;
|
|
let delta_y = y2 - y1;
|
|
delta_y.atan2(delta_x)
|
|
}
|
|
|
|
/// Returns the X and Y point of a normalized 2D vector that points in the same direction as
|
|
/// the given angle.
|
|
#[inline]
|
|
pub fn angle_to_direction(radians: f32) -> (f32, f32) {
|
|
let x = radians.cos();
|
|
let y = radians.sin();
|
|
(x, y)
|
|
}
|
|
|
|
/// Calculates the squared distance between two 2D points.
|
|
#[inline]
|
|
pub fn distance_squared_between(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
|
|
(x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)
|
|
}
|
|
|
|
/// Calculates the distance between two 2D points.
|
|
#[inline]
|
|
pub fn distance_between(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
|
|
distance_squared_between(x1, y1, x2, y2).sqrt()
|
|
}
|
|
|
|
pub trait NearlyEqual {
|
|
type Output;
|
|
|
|
/// Returns true if the two f32 values are "close enough" to be considered equal using the
|
|
/// precision of the provided epsilon value.
|
|
fn nearly_equal(self, other: Self::Output, epsilon: f32) -> bool;
|
|
}
|
|
|
|
impl NearlyEqual for f32 {
|
|
type Output = f32;
|
|
|
|
#[inline(always)]
|
|
fn nearly_equal(self, other: Self::Output, epsilon: f32) -> bool {
|
|
nearly_equal(self, other, epsilon)
|
|
}
|
|
}
|
|
|
|
pub trait WrappingRadians {
|
|
type Type;
|
|
|
|
/// Adds two angle values in radians together returning the result. The addition will wrap
|
|
/// around so that the returned result always lies within 0 -> 2π radians (0 -> 360 degrees).
|
|
fn wrapping_radians_add(self, other: Self::Type) -> Self::Type;
|
|
|
|
/// Subtracts two angle values in radians returning the result. The subtraction will wrap
|
|
/// around so that the returned result always lies within 0 -> 2π radians (0 -> 360 degrees).
|
|
fn wrapping_radians_sub(self, other: Self::Type) -> Self::Type;
|
|
}
|
|
|
|
impl WrappingRadians for f32 {
|
|
type Type = f32;
|
|
|
|
fn wrapping_radians_add(self, other: Self::Type) -> Self::Type {
|
|
let result = self + other;
|
|
if result < RADIANS_0 {
|
|
result + RADIANS_360
|
|
} else if result >= RADIANS_360 {
|
|
result - RADIANS_360
|
|
} else {
|
|
result
|
|
}
|
|
}
|
|
|
|
fn wrapping_radians_sub(self, other: Self::Type) -> Self::Type {
|
|
let result = self - other;
|
|
if result < RADIANS_0 {
|
|
result + RADIANS_360
|
|
} else if result >= RADIANS_360 {
|
|
result - RADIANS_360
|
|
} else {
|
|
result
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
pub fn test_nearly_equal() {
|
|
assert!(nearly_equal(4.0, 4.0, 0.1));
|
|
assert!(4.0f32.nearly_equal(4.0, 0.1));
|
|
assert!(nearly_equal(0.1 + 0.2, 0.3, 0.01));
|
|
assert!(!nearly_equal(1.0001, 1.0005, 0.0001));
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_lerp() {
|
|
assert!(nearly_equal(15.0, lerp(10.0, 20.0, 0.5), 0.0001));
|
|
assert!(nearly_equal(10.0, lerp(10.0, 20.0, 0.0), 0.0001));
|
|
assert!(nearly_equal(20.0, lerp(10.0, 20.0, 1.0), 0.0001));
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_inverse_lerp() {
|
|
assert_eq!(0.5, inverse_lerp(10.0, 20.0, 15.0f32))
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_angle_between() {
|
|
let angle = angle_between(20.0, 20.0, 10.0, 10.0);
|
|
assert!(nearly_equal(-RADIANS_135, angle, 0.0001));
|
|
let angle = angle_between(0.0, 0.0, 10.0, 10.0);
|
|
assert!(nearly_equal(RADIANS_45, angle, 0.0001));
|
|
let angle = angle_between(5.0, 5.0, 5.0, 5.0);
|
|
assert!(nearly_equal(0.0, angle, 0.0001));
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_angle_to_direction() {
|
|
let (x, y) = angle_to_direction(RADIANS_0);
|
|
assert!(nearly_equal(x, 1.0, 0.000001));
|
|
assert!(nearly_equal(y, 0.0, 0.000001));
|
|
let (x, y) = angle_to_direction(RADIANS_45);
|
|
assert!(nearly_equal(x, 0.707106, 0.000001));
|
|
assert!(nearly_equal(y, 0.707106, 0.000001));
|
|
let (x, y) = angle_to_direction(RADIANS_225);
|
|
assert!(nearly_equal(x, -0.707106, 0.000001));
|
|
assert!(nearly_equal(y, -0.707106, 0.000001));
|
|
|
|
let (x, y) = angle_to_direction(UP);
|
|
assert!(nearly_equal(x, 0.0, 0.000001));
|
|
assert!(nearly_equal(y, -1.0, 0.000001));
|
|
let (x, y) = angle_to_direction(DOWN);
|
|
assert!(nearly_equal(x, 0.0, 0.000001));
|
|
assert!(nearly_equal(y, 1.0, 0.000001));
|
|
let (x, y) = angle_to_direction(LEFT);
|
|
assert!(nearly_equal(x, -1.0, 0.000001));
|
|
assert!(nearly_equal(y, 0.0, 0.000001));
|
|
let (x, y) = angle_to_direction(RIGHT);
|
|
assert!(nearly_equal(x, 1.0, 0.000001));
|
|
assert!(nearly_equal(y, 0.0, 0.000001));
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_distance_between() {
|
|
let x1 = -2.0;
|
|
let y1 = 1.0;
|
|
let x2 = 4.0;
|
|
let y2 = 3.0;
|
|
let distance_squared = distance_squared_between(x1, y1, x2, y2);
|
|
let distance = distance_between(x1, y1, x2, y2);
|
|
assert!(nearly_equal(distance_squared, 40.0000, 0.0001));
|
|
assert!(nearly_equal(distance, 6.3245, 0.0001));
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_wrapping_radians() {
|
|
assert!(nearly_equal(RADIANS_90, RADIANS_45.wrapping_radians_add(RADIANS_45), 0.0001));
|
|
assert!(nearly_equal(RADIANS_90, RADIANS_180.wrapping_radians_sub(RADIANS_90), 0.0001));
|
|
|
|
assert!(nearly_equal(RADIANS_45, RADIANS_315.wrapping_radians_add(RADIANS_90), 0.0001));
|
|
assert!(nearly_equal(RADIANS_315, RADIANS_90.wrapping_radians_sub(RADIANS_135), 0.0001));
|
|
}
|
|
}
|