From 3e54b01003aa9d27de8f4ca13c9240fe785ec0e1 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Mon, 22 Apr 2019 14:38:40 +0200 Subject: Structures representing game state --- src/geometry/point.rs | 84 +++++++++++++++++++++++++++ src/geometry/rect.rs | 19 +++++++ src/geometry/vec.rs | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 257 insertions(+) create mode 100644 src/geometry/point.rs create mode 100644 src/geometry/rect.rs create mode 100644 src/geometry/vec.rs (limited to 'src/geometry') diff --git a/src/geometry/point.rs b/src/geometry/point.rs new file mode 100644 index 0000000..c34d00f --- /dev/null +++ b/src/geometry/point.rs @@ -0,0 +1,84 @@ +use crate::geometry::vec::*; + +use std::ops::*; +use num_traits::{NumOps, NumAssignOps}; +use num_traits::pow::Pow; +use num_traits::real::Real; + +macro_rules! impl_point { + ($PointN:ident { $($field:ident),+ }, $VecN:ident) => { + #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] + pub struct $PointN { + $(pub $field: T),+ + } + + impl $PointN { + pub fn new($($field: T),+) -> $PointN { + $PointN { $($field),+ } + } + } + + impl + Copy> $PointN { + pub fn distance_squared(self, other: $PointN) -> T { + (self - other).magnitude_squared() + } + } + + + impl> $PointN { + pub fn distance(self, other: $PointN) -> T { + (self - other).magnitude() + } + } + + impl Add<$VecN> for $PointN { + type Output = Self; + + fn add(self, other: $VecN) -> Self { + $PointN { + $($field: self.$field + other.$field),+ + } + } + } + + impl AddAssign<$VecN> for $PointN { + fn add_assign(&mut self, other: $VecN) { + $(self.$field += other.$field);+ + } + } + + impl Sub for $PointN { + type Output = $VecN; + + fn sub(self, other: Self) -> $VecN { + $VecN { + $($field: self.$field - other.$field),+ + } + } + } + } +} + +impl_point!(Point2d { x, y }, Vec2d); +impl_point!(Point3d { x, y, z }, Vec3d); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn distance_in_x_dimension_example() { + let a = Point2d::new(1., 1.); + let b = Point2d::new(3., 1.); + assert_eq!(a.distance(b), 2.); + assert_eq!(a.distance_squared(b), 4.); + } + + #[test] + fn distance_in_y_dimension_example() { + let a = Point2d::new(1., 1.); + let b = Point2d::new(1., 3.); + assert_eq!(b.distance(a), 2.); + assert_eq!(b.distance_squared(a), 4.); + } +} diff --git a/src/geometry/rect.rs b/src/geometry/rect.rs new file mode 100644 index 0000000..e2b1882 --- /dev/null +++ b/src/geometry/rect.rs @@ -0,0 +1,19 @@ +use crate::geometry::Point2d; +use crate::geometry::Vec2d; + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct Rectangle { + pub pos: Point2d, + pub size: Vec2d, +} + +impl Rectangle { + pub fn new(x: T, y: T, w: T, h: T) -> Rectangle { + Rectangle { + pos: Point2d::new(x, y), + size: Vec2d::new(w, h), + } + } + + // TODO: Constructor to build a rectangle centered at an x,y +} diff --git a/src/geometry/vec.rs b/src/geometry/vec.rs new file mode 100644 index 0000000..f6bd29f --- /dev/null +++ b/src/geometry/vec.rs @@ -0,0 +1,154 @@ +use std::ops::*; +use num_traits::{NumOps, NumAssignOps}; +use num_traits::pow::Pow; +use num_traits::real::Real; + +macro_rules! fold_array { + ($method:ident, { $x:expr }) => { $x }; + ($method:ident, { $x:expr, $y:expr }) => { $x.$method($y) }; + ($method:ident, { $x:expr, $y:expr, $z:expr }) => { $x.$method($y).$method($z) }; +} + +macro_rules! impl_vector { + ($VecN:ident { $($field:ident),+ }) => { + #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] + pub struct $VecN { + $(pub $field: T),+ + } + + impl $VecN { + pub fn new($($field: T),+) -> $VecN { + $VecN { $($field),+ } + } + } + + impl + Copy> $VecN { + pub fn magnitude_squared(&self) -> T { + fold_array!(add, { $(self.$field.pow(2)),+ }) + } + } + + impl> $VecN { + pub fn magnitude(&self) -> T { + self.magnitude_squared().sqrt() + } + + pub fn unit(&self) -> $VecN { + let mag = self.magnitude(); + $VecN { + $($field: self.$field / mag),+ + } + } + } + + impl $VecN { + pub fn dot(&self, other: $VecN) -> T { + fold_array!(add, { $(self.$field * other.$field),+ }) + } + } + + impl Add for $VecN { + type Output = Self; + + fn add(self, other: Self) -> Self { + $VecN { + $($field: self.$field + other.$field),+ + } + } + } + + impl AddAssign for $VecN { + fn add_assign(&mut self, other: Self) { + $(self.$field += other.$field);+ + } + } + + impl Sub for $VecN { + type Output = Self; + + fn sub(self, other: Self) -> Self { + $VecN { + $($field: self.$field - other.$field),+ + } + } + } + + impl Mul for $VecN { + type Output = Self; + + fn mul(self, rhs: T) -> Self { + $VecN { + $($field: self.$field * rhs),+ + } + } + } + + impl> Neg for $VecN { + type Output = Self; + + fn neg(self) -> Self { + $VecN { + $($field: -self.$field),+ + } + } + } + + } +} + +impl_vector!(Vec2d { x, y }); +impl_vector!(Vec3d { x, y, z }); + +impl> Vec2d { + pub fn angle(&self) -> T { + self.y.atan2(self.x) + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn right_angles_in_2d_vectors() { + use std::f32::consts::{FRAC_PI_2, PI}; + + assert_eq!(0., Vec2d::new(1., 0.).angle()); + assert_eq!(FRAC_PI_2, Vec2d::new(0., 1.).angle()); + assert_eq!(PI, Vec2d::new(-1., 0.).angle()); + assert_eq!(-FRAC_PI_2, Vec2d::new(0., -1.).angle()); + } + + #[test] + fn unit_normalizes_2d_vector() { + let before = Vec2d::new(2., 10.); + let after = before.unit(); + + assert_eq!(1., after.magnitude()); + assert_eq!(before.angle(), after.angle()); + } + + #[test] + fn unit_normalizes_3d_vector() { + let before = Vec3d::new(2., 10., -5.); + let after = before.unit(); + + assert_eq!(1., after.magnitude()); + // How to define 3d angle? 2 angles I suppose? + // assert_eq!(before.angle(), after.angle()); + } + + #[test] + fn dot_product_example_2d() { + let a = Vec2d::new(-6., 8.); + let b = Vec2d::new(5., 12.); + assert_eq!(66., a.dot(b)); + } + + #[test] + fn magnitude_squared_of_an_integer() { + let a = Vec2d::new(3, 4); + assert_eq!(25, a.magnitude_squared()); + } +} -- cgit v1.2.3