vec2
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Cargo.toml | 13 | ||||
| -rw-r--r-- | LICENSE | 21 | ||||
| -rw-r--r-- | README.md | 14 | ||||
| -rw-r--r-- | src/from.rs | 32 | ||||
| -rw-r--r-- | src/lib.rs | 244 | ||||
| -rw-r--r-- | src/ops.rs | 90 |
7 files changed, 416 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b797b77 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "vecto" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "vector2" +repository = "https://github.com/bend-n/vecto" +authors = ["bendn <[email protected]>"] +keywords = ["math", "vector2"] +categories = ["algorithms", "data-structures", "mathematics"] + +[dependencies] +umath = "0.0.7" @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 bendn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0761582 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# vecto + +smol rust lib for `Vector2`. + +- is generic over floats +- has methods +- overloads operators + +## usage + +```rust +use vecto::Vec2; +let v = Vec2::splat(5.0); +```
\ No newline at end of file diff --git a/src/from.rs b/src/from.rs new file mode 100644 index 0000000..000d243 --- /dev/null +++ b/src/from.rs @@ -0,0 +1,32 @@ +use crate::Vector2; + +impl<T> From<(T, T)> for Vector2<T> { + fn from((x, y): (T, T)) -> Self { + Self::new(x, y) + } +} + +impl<T: Copy> From<T> for Vector2<T> { + /// Splats the value. + fn from(value: T) -> Self { + Self::splat(value) + } +} + +impl<T> From<[T; 2]> for Vector2<T> { + fn from([x, y]: [T; 2]) -> Self { + Self::new(x, y) + } +} + +impl<T: Copy> TryFrom<&[T]> for Vector2<T> { + type Error = (); + /// If the slice len is 2, constructs a new vec. + fn try_from(value: &[T]) -> Result<Self, Self::Error> { + value + .len() + .eq(&2) + .then(|| Self::new(value[0], value[1])) + .ok_or(()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ae48f60 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,244 @@ +//! 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 <code>[`Vector2`]<[`f32`]></code> +pub type Vec2 = Vector2<f32>; + +/// Vector2. +#[derive(Copy, Clone, PartialEq, PartialOrd, Default, Hash, Eq, Ord)] +#[repr(C)] +pub struct Vector2<T> { + /// The vector's X component. + pub x: T, + /// The vector's Y component. + pub y: T, +} + +impl<T: std::fmt::Debug> std::fmt::Debug for Vector2<T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({:?}, {:?})", self.x, self.y) + } +} + +impl<T> Vector2<T> { + /// Construct a new [`Vector2`]. + pub const fn new(x: T, y: T) -> Self { + Self { x, y } + } +} + +impl<T: Copy> Vector2<T> { + /// 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<f64> { + /// 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<T: std::ops::Neg<Output = T>> Vector2<T> { + /// 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<T: FloatAlone> Vector2<T> { + /// 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<T: Rounding> Vector2<T> { + /// 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()) + } +} diff --git a/src/ops.rs b/src/ops.rs new file mode 100644 index 0000000..c5cb338 --- /dev/null +++ b/src/ops.rs @@ -0,0 +1,90 @@ +use crate::Vector2; +use core::ops::{ + Add as add, AddAssign as add_assign, Div as div, DivAssign as div_assign, Mul as mul, + MulAssign as mul_assign, Neg, Rem as rem, RemAssign as rem_assign, Sub as sub, + SubAssign as sub_assign, +}; + +macro_rules! op { + ($name:ident) => { + impl<T: $name<T, Output = T>> $name<Vector2<T>> for Vector2<T> { + type Output = Vector2<T>; + + fn $name(self, rhs: Vector2<T>) -> Self::Output { + Self::new(self.x.$name(rhs.x), self.y.$name(rhs.y)) + } + } + + impl<T: Copy + $name<T, Output = T>> $name<&Vector2<T>> for Vector2<T> { + type Output = Vector2<T>; + + fn $name(self, rhs: &Vector2<T>) -> Self::Output { + Self::new(self.x.$name(rhs.x), self.y.$name(rhs.y)) + } + } + + impl<T: Copy + $name<T, Output = T>> $name<T> for Vector2<T> { + type Output = Vector2<T>; + fn $name(self, rhs: T) -> Self::Output { + Self::new(self.x.$name(rhs), self.y.$name(rhs)) + } + } + + impl<T: Copy + $name<T, Output = T>> $name<&T> for Vector2<T> { + type Output = Vector2<T>; + fn $name(self, rhs: &T) -> Self::Output { + Self::new(self.x.$name(*rhs), self.y.$name(*rhs)) + } + } + }; +} +op!(add); +op!(div); +op!(mul); +op!(rem); +op!(sub); + +macro_rules! assign { + ($name:ident, $op:ident) => { + impl<T: $name<T>> $name<Vector2<T>> for Vector2<T> { + fn $name(&mut self, rhs: Vector2<T>) { + self.x.$name(rhs.x); + self.y.$name(rhs.y); + } + } + + impl<T: Copy + $name<T>> $name<&Vector2<T>> for Vector2<T> { + fn $name(&mut self, rhs: &Vector2<T>) { + self.x.$name(rhs.x); + self.y.$name(rhs.y); + } + } + + impl<T: Copy + $name<T>> $name<T> for Vector2<T> { + fn $name(&mut self, rhs: T) { + self.x.$name(rhs); + self.y.$name(rhs); + } + } + + impl<T: Copy + $name<T>> $name<&T> for Vector2<T> { + fn $name(&mut self, rhs: &T) { + self.x.$name(*rhs); + self.y.$name(*rhs); + } + } + }; +} +assign!(add_assign, add); +assign!(div_assign, div); +assign!(mul_assign, mul); +assign!(rem_assign, rem); +assign!(sub_assign, sub); + +impl<T: Neg<Output = T>> Neg for Vector2<T> { + type Output = Vector2<T>; + + fn neg(self) -> Self::Output { + Self::new(-self.x, -self.y) + } +} |