vec2
bendn 2023-10-15
commit ad08944
-rw-r--r--.gitignore2
-rw-r--r--Cargo.toml13
-rw-r--r--LICENSE21
-rw-r--r--README.md14
-rw-r--r--src/from.rs32
-rw-r--r--src/lib.rs244
-rw-r--r--src/ops.rs90
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"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..1fafc15
--- /dev/null
+++ b/LICENSE
@@ -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)
+ }
+}