//! Provides a [`Vector2`]. //! ``` //! # use vecto::Vec2; //! let mut v = Vec2::new(5.0, 7.0); //! v *= 2.0; //! assert_eq!(v, Vec2::new(10.0, 14.0)); //! ```` #![allow(mixed_script_confusables)] #![warn(clippy::pedantic, clippy::dbg_macro, missing_docs)] mod from; mod ops; #[doc(hidden)] pub trait Kinda where Self: Sized, { fn kinda_eq(self, other: Self, tolerance: f32) -> bool; fn approx_eq(self, other: Self) -> bool; } impl Kinda for f32 { fn kinda_eq(self, other: Self, tolerance: f32) -> bool { if self == other { true } else { (self - other).abs() < tolerance } } fn approx_eq(self, other: Self) -> bool { self.kinda_eq(other, 0.00001) } } impl Kinda for Vec2 { fn kinda_eq(self, other: Self, tolerance: f32) -> bool { self.x.kinda_eq(other.x, tolerance) && self.y.kinda_eq(other.y, tolerance) } fn approx_eq(self, other: Self) -> bool { self.kinda_eq(other, 0.00001) } } use umath::generic_float::{FloatAlone, Rounding}; /// Alias for [`Vector2`]<[`f32`]> pub type Vec2 = Vector2; /// Vector2. #[derive(Copy, Clone, PartialEq, PartialOrd, Default, Hash, Eq, Ord)] #[repr(C)] pub struct Vector2 { /// The vector's X component. pub x: T, /// The vector's Y component. pub y: T, } impl std::fmt::Debug for Vector2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "({:?}, {:?})", self.x, self.y) } } impl Vector2 { /// Construct a new [`Vector2`]. pub const fn new(x: T, y: T) -> Self { Self { x, y } } } impl Vector2 { /// Construct a new [`Vector2`] with x and y set to the given value. pub const fn splat(x: T) -> Self { Self { x, y: x } } } impl Vec2 { /// Zero unit vector. `(0, 0)` pub const ZERO: Vec2 = Vec2::new(0.0, 0.0); /// Right unit vector. `(1, 0)` pub const RIGHT: Vec2 = Vec2::new(1.0, 0.0); /// Left unit vector. `(-1, 0)` pub const LEFT: Vec2 = Vec2::new(-1.0, 0.0); /// Up unit vector. Y-Down, so points -Y. `(0, -1)` pub const UP: Vec2 = Vec2::new(0.0, -1.0); /// Down unit vector. Y-Down, so points +Y. `(0, 1)` pub const DOWN: Vec2 = Vec2::new(0.0, 1.0); } impl Vector2 { /// Zero unit vector. `(0, 0)` pub const ZERO: Vec2 = Vec2::new(0.0, 0.0); /// Right unit vector. `(1, 0)` pub const RIGHT: Vec2 = Vec2::new(1.0, 0.0); /// Left unit vector. `(-1, 0)` pub const LEFT: Vec2 = Vec2::new(-1.0, 0.0); /// Up unit vector. Y-Down, so points -Y. `(0, -1)` pub const UP: Vec2 = Vec2::new(0.0, -1.0); /// Down unit vector. Y-Down, so points +Y. `(0, 1)` pub const DOWN: Vec2 = Vec2::new(0.0, 1.0); } impl> Vector2 { /// Returns a perpendicular vector, rotated 90 degrees counter-clockwise, with the same length. #[must_use = "Does not modify in place."] pub fn orthogonal(self) -> Self { Self::new(self.y, -self.x) } } impl Vector2 { /// Creates a unit [`Vector2`] rotated to the given angle (radians). /// This is equivalent to `Vec2::new(angle.cos(), angle.sin())`. /// ``` /// # use vecto::{Vec2, Kinda}; /// # use std::f32::consts::PI; /// assert_eq!(Vec2::from_angle(0.0), Vec2::RIGHT); /// assert_eq!(Vec2::RIGHT.angle(), 0.0); /// assert!(Vec2::from_angle(PI / 2.0).approx_eq(Vec2::new(0.0, 1.0))); /// ``` pub fn from_angle(angle: T) -> Self { Self::new(angle.cos(), angle.sin()) } /// Returns a new vector with all components in absolute values (i.e. positive). #[must_use = "Does not modify in place."] pub fn abs(self) -> Self { Self::new(self.x.abs(), self.y.abs()) } /// Returns this vector's angle with respect to the positive X axis, or the [`Vec2::RIGHT`] vector, in radians. /// ``` /// # use vecto::Vec2; /// # use std::f32::consts::PI; /// assert_eq!(Vec2::RIGHT.angle(), 0.0); /// assert_eq!(Vec2::DOWN.angle(), PI / 2.0); // 90 degrees /// assert_eq!(Vec2::new(1.0, -1.0).angle(), -PI / 4.0); // -45 degrees /// ``` pub fn angle(&self) -> T { self.y.atan2(self.x) } /// Returns the cross product of `self` and `with`. pub fn cross(&self, with: &Self) -> T { self.x * with.y - self.y * with.x } /// Returns the distance from `self` to `to`. pub fn distance_to(&self, to: &Self) -> T { ((self.x - to.x) * (self.x - to.x) + (self.y - to.y) * (self.y - to.y)).sqrt() } /// Returns the dot product of `self` and `with`. pub fn dot(&self, with: &Self) -> T { self.x * with.x + self.y * with.y } /// Returns the length(magnitude) of `self`. /// ``` /// # use vecto::Vec2; /// assert_eq!(Vec2::splat(10.0).length(), 10.0 * 2.0f32.sqrt()); /// ``` pub fn length(&self) -> T { (self.x * self.x + self.y * self.y).sqrt() } /// Returns the squared length of `self`. Faster than [`Self::length`]. /// ``` /// # use vecto::Vec2; /// assert_eq!(Vec2::splat(10.0).length_squared(), 200.0); /// ``` pub fn length_squared(&self) -> T { self.x * self.x + self.y * self.y } /// Returns the vector with a new maximum length. /// ``` /// # use vecto::{Kinda, Vec2}; /// assert!(Vec2::splat(10.).limit_length(1.0).approx_eq(Vec2::splat(1.0 / 2.0f32.sqrt()))); /// assert!(Vec2::splat(10.).limit_length(5.0).approx_eq(Vec2::splat(1.0 / 2.0f32.sqrt() * 5.0))); /// ``` #[must_use = "Does not modify in place."] pub fn limit_length(self, len: T) -> Self { let l = self.length(); if l > unsafe { T::zero() } && len < l { return (self / l) * len; } self } /// Returns the result of scaling the vector to unit length. /// Equivalent to v / v.length(). /// /// Note: This function may struggle with denormal values. /// ``` /// # use vecto::{Kinda, Vec2}; /// assert!(Vec2::RIGHT.normalized().approx_eq(Vec2::RIGHT)); /// assert!(Vec2::splat(1.0).normalized().approx_eq(Vec2::splat(0.5f32.sqrt()))); /// ``` #[must_use = "Does not modify in place."] pub fn normalized(self) -> Self { let l = self.length_squared(); if l != unsafe { T::zero() } { return self / l.sqrt(); } self } /// Rotates this vector by `angle` radians. /// ``` /// # use vecto::{Kinda, Vec2}; /// # use std::f32::consts::TAU; /// let v = Vec2::new(1.2, 3.4); /// assert!(v.rotated(TAU).approx_eq(Vec2::new(1.2, 3.4))); // full circle rotation /// assert!(v.rotated(TAU / 4.0).approx_eq(Vec2::new(-3.4, 1.2))); /// assert!(v.rotated(TAU / 3.0).approx_eq(Vec2::new(-3.5444863, -0.6607695))); /// assert!(v.rotated(TAU / 2.0).approx_eq(v.rotated(TAU / -2.0))); /// ``` #[must_use = "Does not modify in place."] pub fn rotated(self, angle: T) -> Self { Vector2::new( self.x * angle.cos() - self.y * angle.sin(), self.x * angle.sin() + self.y * angle.cos(), ) } } impl Vector2 { /// Returns a new vector with all components rounded up (towards positive infinity). #[must_use = "Does not modify in place."] pub fn ceil(self) -> Self { Self::new(self.x.ceil(), self.y.ceil()) } /// Returns a new vector with all components rounded down (towards negative infinity). #[must_use = "Does not modify in place."] pub fn floor(self) -> Self { Self::new(self.x.floor(), self.y.floor()) } }