commit a0a0a04176729602f664b80cb685e342d103c9ac Author: missing <4a656666official@gmail.com> Date: Wed May 25 21:15:09 2022 -0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0a32ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# :) +.DS_Store + +# ---> Rust +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5d1bd84 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "vectorn" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d5075a3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +Permission is hereby granted, without written agreement and without +license or royalty fees, to use, copy, modify, and distribute this +software and its documentation for any purpose, provided that the +above copyright notice and the following two paragraphs appear in +all copies of this software. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR +DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN +IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO +PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8214121 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +Types representing directions and locations in 2d and 3d space. + +NOTE: `Vec3`, `Vec3Int`, and `Direction3` don't exist yet. Coming soon! + +This crate contains 6 major types: + - `Vec2`, a 2d float vector + - `Vec2Int`, a 2d integer vector + - `Direction2`, a 2d cardinal direction + - `Vec3`, a 3d float vector (TODO) + - `Vec3Int`, a 3d integer vector (TODO) + - `Direction3`, a 3d cardinal direction (TODO) + +All the types implement the expected `From`s and all the relevant operator traits. \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c6a4ecc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,473 @@ +//! Types representing directions and locations in 2d and 3d space. +//! +//! NOTE: `Vec3`, `Vec3Int`, and `Direction3` don't exist yet. Coming soon! +//! +//! This crate contains 6 major types: +//! - [`Vec2`], a 2d float vector +//! - [`Vec2Int`], a 2d integer vector +//! - [`Direction2`], a 2d cardinal direction +//! - [`Vec3`], a 3d float vector (TODO) +//! - [`Vec3Int`], a 3d integer vector (TODO) +//! - [`Direction3`], a 3d cardinal direction (TODO) +//! +//! All the types implement the expected [`From`]s and all the relevant operator traits. + +#![warn(clippy::pedantic)] +#![warn(missing_docs)] +#![allow(clippy::must_use_candidate)] + +#[cfg(test)] +mod test; + +use std::{ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}, fmt::Debug}; + +// Direction +/// A cardinal direction in a 2d plane. +/// +/// Conversions to a [`Vec2`] or [`Vec2Int`] assume that East is positive-x and South is positive-y. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Direction2 { + /// North, or Vec2::from((0, -1)) + North, + /// North, or Vec2::from((0, 1)) + South, + /// North, or Vec2::from((1, 0)) + East, + /// North, or Vec2::from((-1, 0)) + West, +} + +#[allow(clippy::enum_glob_use)] +impl Direction2 { + /// Flips this `Direction` around both the x- and y-axes. + pub fn flipped(self) -> Self { + self.flip_x().flip_y() + } + + /// Flips this `Direction` around the x-axis. + pub fn flip_x(self) -> Self { + use Direction2::*; + match self { + East => West, + West => East, + v => v, + } + } + + /// Flips this `Direction` around the y-axis. + pub fn flip_y(self) -> Self { + use Direction2::*; + match self { + North => South, + South => North, + v => v, + } + } +} + +// ...and related op impls +impl Neg for Direction2 { + type Output = Self; + + fn neg(self) -> Self::Output { + self.flipped() + } +} + +#[allow(clippy::enum_glob_use)] +impl From for Vec2 { + fn from(v: Direction2) -> Self { + use Direction2::*; + match v { + North => (0.0, -1.0).into(), + South => (0.0, 1.0).into(), + East => (1.0, 0.0).into(), + West => (-1.0, 0.0).into(), + } + } +} + +#[allow(clippy::enum_glob_use)] +impl From for Vec2Int { + fn from(v: Direction2) -> Self { + use Direction2::*; + match v { + North => (0, -1).into(), + South => (0, 1).into(), + East => (1, 0).into(), + West => (-1, 0).into(), + } + } +} + +impl Mul for Direction2 { + type Output = Vec2; + + fn mul(self, rhs: f32) -> Self::Output { + Vec2::from(self) * rhs + } +} + +impl Mul for Direction2 { + type Output = Vec2Int; + + fn mul(self, rhs: i32) -> Self::Output { + Vec2Int::from(self) * rhs + } +} + +// Vec2 +/// A set of 2 [`f32`]s representing a location or direction in the 2d plane. +#[derive(Clone, Copy, Default, PartialEq)] +pub struct Vec2 { + /// The x component of the vector. + pub x: f32, + /// The y component of the vector. + pub y: f32, +} + +impl Debug for Vec2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Vec2").field(&self.x).field(&self.y).finish() + } +} + +impl Vec2 { + /// Creates a new `Vec2` with the given x- and y-values. + /// + /// It is often simpler, and preferred, to just write `(x, y).into()`. + pub const fn new(x: f32, y: f32) -> Vec2 { + Self { x, y } + } + + /// Gets the squared magnitude of the vector. + /// + /// Useful for comparisons as it is faster to calculate than `magnitude`. + pub fn sq_magnitude(self) -> f32 { + self.x * self.x + self.y * self.y + } + + /// Gets the magnitude of the vector. + pub fn magnitude(self) -> f32 { + self.sq_magnitude().sqrt() + } + + /// Gets the squared distance from this vector to `rhs`. + /// + /// Useful for comparisons as it is faster to calculate than `dist`. + pub fn sq_dist(self, rhs: Self) -> f32 { + (self - rhs).sq_magnitude() + } + + /// Gets the distance from this vector to `rhs`. + pub fn dist(self, rhs: Self) -> f32 { + (self - rhs).magnitude() + } + + /// Normalizes the vector, making its magnitude `1`. + pub fn normalized(self) -> Self { + self / self.magnitude() + } + + /// Rounds the vector to a [`Vec2Int`]. + /// + /// This uses `as i32` under the hood, and as such comes with all the same unfortunate edge cases. Beware. + pub fn rounded(self) -> Vec2Int { + #[allow(clippy::cast_possible_truncation)] + Vec2Int { + x: self.x as i32, + y: self.y as i32, + } + } +} + +impl From<(i32, i32)> for Vec2 { + fn from(v: (i32, i32)) -> Self { + Vec2Int::from(v).to_f32() + } +} + +impl From<(f32, f32)> for Vec2 { + fn from(v: (f32, f32)) -> Self { + Self { x: v.0, y: v.1 } + } +} + +impl From for (f32, f32) { + fn from(v: Vec2) -> Self { + (v.x, v.y) + } +} + +impl PartialEq<(i32, i32)> for Vec2 { + fn eq(&self, other: &(i32, i32)) -> bool { + self == &Self::from(*other) + } +} + +impl PartialEq<(f32, f32)> for Vec2 { + fn eq(&self, other: &(f32, f32)) -> bool { + self == &Self::from(*other) + } +} + +// ...and related op impls +impl Neg for Vec2 { + type Output = Self; + + fn neg(self) -> Self::Output { + self * -1.0 + } +} + +impl Add for Vec2 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} + +impl Add for Vec2 { + type Output = Self; + + fn add(self, rhs: Direction2) -> Self::Output { + self + Self::from(rhs) + } +} + +impl AddAssign for Vec2 +where + Vec2: Add, +{ + fn add_assign(&mut self, rhs: T) { + *self = *self + rhs; + } +} + +impl Sub for Vec2 +where + Vec2: Add, +{ + type Output = Self; + + fn sub(self, rhs: T) -> Self::Output { + -(-self + rhs) + } +} + +impl SubAssign for Vec2 +where + Vec2: Sub, +{ + fn sub_assign(&mut self, rhs: T) { + *self = *self - rhs; + } +} + +impl Mul for Vec2 { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + Self { + x: self.x * rhs, + y: self.y * rhs, + } + } +} + +impl Div for Vec2 { + type Output = Self; + + fn div(self, rhs: f32) -> Self::Output { + Self { + x: self.x / rhs, + y: self.y / rhs, + } + } +} + +impl MulAssign for Vec2 { + fn mul_assign(&mut self, rhs: f32) { + *self = *self * rhs; + } +} + +impl DivAssign for Vec2 { + fn div_assign(&mut self, rhs: f32) { + *self = *self / rhs; + } +} + +// Vec2Int +/// A set of 2 [`i32`]s representing a location or direction in the 2d plane. +#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)] +pub struct Vec2Int { + /// The x component of the vector. + pub x: i32, + /// The y component of the vector. + pub y: i32, +} + +impl Debug for Vec2Int { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Vec2Int").field(&self.x).field(&self.y).finish() + } +} + +impl Vec2Int { + /// Creates a new `Vec2` with the given x- and y-values. + /// + /// It is often simpler, and preferred, to just write `(x, y).into()`. + pub const fn new(x: i32, y: i32) -> Vec2Int { + Self { x, y } + } + + /// Gets the squared magnitude of the vector. + /// + /// Useful for comparisons as it is faster to calculate than `magnitude`. + pub fn sq_magnitude(self) -> i32 { + self.x * self.x + self.y * self.y + } + + /// Gets the magnitude of the vector. + pub fn magnitude(self) -> f32 { + #[allow(clippy::cast_precision_loss)] + (self.sq_magnitude() as f32).sqrt() + } + + /// Gets the squared distance from this vector to `rhs`. + /// + /// Useful for comparisons as it is faster to calculate than `dist`. + pub fn sq_dist(self, rhs: Self) -> i32 { + (self - rhs).sq_magnitude() + } + + /// Gets the distance from this vector to `rhs`. + pub fn dist(self, rhs: Self) -> f32 { + (self - rhs).magnitude() + } + + /// Casts this vector to a [`Vec2`]. + /// + /// This uses `as f32` under the hood, and as such comes with all the same unfortunate edge cases. Beware. + pub fn to_f32(self) -> Vec2 { + #[allow(clippy::cast_precision_loss)] + Vec2 { + x: self.x as f32, + y: self.y as f32, + } + } +} + +impl From<(i32, i32)> for Vec2Int { + fn from(v: (i32, i32)) -> Self { + Self { x: v.0, y: v.1 } + } +} + +impl From for (i32, i32) { + fn from(v: Vec2Int) -> Self { + (v.x, v.y) + } +} + +impl PartialEq<(i32, i32)> for Vec2Int { + fn eq(&self, other: &(i32, i32)) -> bool { + self == &Self::from(*other) + } +} + +// ...and related op impls +impl Neg for Vec2Int { + type Output = Self; + + fn neg(self) -> Self::Output { + self * -1 + } +} + +impl Add for Vec2Int { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} + +impl Add for Vec2Int { + type Output = Self; + + fn add(self, rhs: Direction2) -> Self::Output { + self + Self::from(rhs) + } +} + +impl AddAssign for Vec2Int +where + Vec2Int: Add, +{ + fn add_assign(&mut self, rhs: T) { + *self = *self + rhs; + } +} + +impl Sub for Vec2Int +where + Vec2Int: Add, +{ + type Output = Self; + + fn sub(self, rhs: T) -> Self::Output { + -(-self + rhs) + } +} + +impl SubAssign for Vec2Int +where + Vec2Int: Sub, +{ + fn sub_assign(&mut self, rhs: T) { + *self = *self - rhs; + } +} + +impl Mul for Vec2Int { + type Output = Self; + + fn mul(self, rhs: i32) -> Self::Output { + Self { + x: self.x * rhs, + y: self.y * rhs, + } + } +} + +impl Div for Vec2Int { + type Output = Self; + + fn div(self, rhs: i32) -> Self::Output { + Self { + x: self.x / rhs, + y: self.y / rhs, + } + } +} + +impl MulAssign for Vec2Int { + fn mul_assign(&mut self, rhs: i32) { + *self = *self * rhs; + } +} + +impl DivAssign for Vec2Int { + fn div_assign(&mut self, rhs: i32) { + *self = *self / rhs; + } +} diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..1d60a39 --- /dev/null +++ b/src/test.rs @@ -0,0 +1,13 @@ +use super::*; + +#[test] +fn test() { + let vec1: Vec2 = (3, 5).into(); + let vec2: Vec2 = (7, 2).into(); + + assert_eq!(vec1, (3, 5)); + assert_eq!(vec2, (7, 2)); + assert_eq!(vec1 + vec2, (10, 7)); + assert_eq!(vec1 - vec2, (-4, 3)); + assert_eq!(vec1 * 4.0, (12, 20)); +} \ No newline at end of file