diff options
author | Ben Bridle <bridle.benjamin@gmail.com> | 2023-12-24 20:03:12 +1300 |
---|---|---|
committer | Ben Bridle <bridle.benjamin@gmail.com> | 2023-12-24 21:38:49 +1300 |
commit | 8547ad9c2f2867da9b0e655dd4ff0fe78f04ffba (patch) | |
tree | 554f4514177cc8c1436ed3ec3ae44f176684f664 /src | |
download | geometry-a76d4655b2f188c53963b2a624159c7d35f2ef54.zip |
Diffstat (limited to 'src')
-rw-r--r-- | src/dimensions.rs | 94 | ||||
-rw-r--r-- | src/internal.rs | 47 | ||||
-rw-r--r-- | src/lib.rs | 125 | ||||
-rw-r--r-- | src/point.rs | 72 | ||||
-rw-r--r-- | src/rect.rs | 193 |
5 files changed, 531 insertions, 0 deletions
diff --git a/src/dimensions.rs b/src/dimensions.rs new file mode 100644 index 0000000..feb5bab --- /dev/null +++ b/src/dimensions.rs @@ -0,0 +1,94 @@ +use crate::*; +use std::fmt; + +#[derive(Copy, Clone, PartialEq)] +pub struct Dimensions<D: Internal> { + pub width: D, + pub height: D, +} + +pub trait HasDimensions<D: Internal> { + fn dimensions(&self) -> Dimensions<D>; + + fn width(&self) -> D { + self.dimensions().width + } + + fn height(&self) -> D { + self.dimensions().height + } + + fn area_usize(&self) -> usize { + self.dimensions().width.as_usize() * self.dimensions().height.as_usize() + } + + fn area_f64(&self) -> f64 { + self.dimensions().width.as_f64() * self.dimensions().height.as_f64() + } + + fn contains_point(&self, point: Point<D>) -> bool { + point.x < self.width() && point.y < self.height() + } +} + +impl<D: Internal> Dimensions<D> { + pub const ZERO: Self = Self { width: D::ZERO, height: D::ZERO }; + + pub const fn new(width: D, height: D) -> Self { + Dimensions { width, height } + } + + pub fn is_zero(&self) -> bool { + self.width == D::ZERO && self.height == D::ZERO + } +} + +impl<D: Internal> HasDimensions<D> for Dimensions<D> { + fn dimensions(&self) -> Dimensions<D> { + *self + } +} + +impl<O: Internal, D: Internal> From<Rect<O, D>> for Dimensions<D> { + fn from(rect: Rect<O, D>) -> Self { + rect.dimensions + } +} + +impl<D: Internal> fmt::Debug for Dimensions<D> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Dimensions {{ w:{:?} h:{:?} }}", self.width, self.height) + } +} + +impl<D: Internal> std::ops::Mul<D> for Dimensions<D> { + type Output = Self; + fn mul(mut self, scale: D) -> Self { + self.width *= scale; + self.height *= scale; + return self; + } +} + +impl<D: Internal> std::ops::MulAssign<D> for Dimensions<D> { + fn mul_assign(&mut self, scale: D) { + self.width *= scale; + self.height *= scale; + } +} + +impl<D: Internal> std::ops::Div<D> for Dimensions<D> { + type Output = Self; + fn div(mut self, scale: D) -> Self { + self.width /= scale; + self.height /= scale; + return self; + } +} + +impl<D: Internal> std::ops::DivAssign<D> for Dimensions<D> { + fn div_assign(&mut self, scale: D) { + self.width /= scale; + self.height /= scale; + } +} diff --git a/src/internal.rs b/src/internal.rs new file mode 100644 index 0000000..166e25f --- /dev/null +++ b/src/internal.rs @@ -0,0 +1,47 @@ +use std::fmt; +use std::ops::{Add, Sub, AddAssign, SubAssign, Mul, MulAssign, Div, DivAssign}; + +pub trait Internal: + Copy + Clone + PartialEq + fmt::Debug + fmt::Display + PartialOrd + + Add<Output=Self> + Sub<Output=Self> + AddAssign + SubAssign + + Mul<Output=Self> + MulAssign + Div<Output=Self> + DivAssign + +{ + const ZERO: Self; + + fn as_usize(&self) -> usize; + + fn as_f64(&self) -> f64; +} + +macro_rules! impl_for_type { + ($z:expr, $t:ty) => { + impl Internal for $t { + const ZERO: Self = $z; + + fn as_usize(&self) -> usize { + *self as usize + } + + fn as_f64(&self) -> f64 { + *self as f64 + } + } + }; +} + +impl_for_type!(0, u8); +impl_for_type!(0, u16); +impl_for_type!(0, u32); +impl_for_type!(0, u64); +impl_for_type!(0, u128); +impl_for_type!(0, usize); + +impl_for_type!(0, i8); +impl_for_type!(0, i16); +impl_for_type!(0, i32); +impl_for_type!(0, i64); +impl_for_type!(0, i128); +impl_for_type!(0, isize); + +impl_for_type!(0.0, f32); +impl_for_type!(0.0, f64); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c28487f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,125 @@ +mod dimensions; +mod internal; +mod point; +mod rect; + +pub use internal::Internal; +pub use dimensions::{Dimensions, HasDimensions}; +pub use point::{Point, HasPosition}; +pub use rect::{Rect, HasRect}; + +pub enum Orientation { + Horizontal, + Vertical, +} + +// ----------------------------------------------------------------------------- +// Orphaned code + +impl Point<i32> { + pub fn rotate_around(&self, origin: Self, radians: f64) -> Self { + let sin = radians.sin(); + let cos = radians.cos(); + let x_diff = (self.x - origin.x) as f64; + let y_diff = (self.y - origin.y) as f64; + let x2 = (x_diff * cos) - (y_diff * sin); + let y2 = (x_diff * sin) + (y_diff * cos); + Point::new(x2.round() as i32 + origin.x, y2.round() as i32 + origin.y) + } + + pub fn intersect<R: Into<Rect<i32, u32>>>(&self, rect: R) -> Option<Point<i32>> { + let rect = rect.into(); + let left = self.x - rect.origin.x; + let top = self.y - rect.origin.y; + if left >= 0 && top >= 0 && rect.width() as i32 > left && rect.height() as i32 > top { + Some(Point::new(left, top)) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use crate::Point; + + #[rustfmt::skip] + macro_rules! p {($x:expr, $y:expr) => {Point::new($x, $y)};} + + fn rad(d: f64) -> f64 { + d.to_radians() + } + + #[test] + fn quarter_turns_zero_origin() { + let o = p!(0, 0); + assert_eq!(p!(10, 10), p!(10, 10)); + assert_eq!(p!(10, 0).rotate_around(o, rad(0.0)), p!(10, 0)); + assert_eq!(p!(10, 0).rotate_around(o, rad(1.0)), p!(10, 0)); + assert_eq!(p!(10, 0).rotate_around(o, rad(90.0)), p!(0, 10)); + assert_eq!(p!(10, 0).rotate_around(o, rad(180.0)), p!(-10, 0)); + assert_eq!(p!(10, 0).rotate_around(o, rad(270.0)), p!(0, -10)); + assert_eq!(p!(10, 0).rotate_around(o, rad(360.0)), p!(10, 0)); + assert_eq!(p!(10, 0).rotate_around(o, rad(450.0)), p!(0, 10)); + assert_eq!(p!(10, 0).rotate_around(o, rad(-0.0)), p!(10, 0)); + assert_eq!(p!(10, 0).rotate_around(o, rad(-1.0)), p!(10, 0)); + assert_eq!(p!(10, 0).rotate_around(o, rad(-90.0)), p!(0, -10)); + assert_eq!(p!(10, 0).rotate_around(o, rad(-450.0)), p!(0, -10)); + } + + #[test] + fn partial_turns_zero_origin() { + let o = p!(0, 0); + assert_eq!(p!(10, 0).rotate_around(o, rad(30.0)), p!(9, 5)); + assert_eq!(p!(10, 0).rotate_around(o, rad(45.0)), p!(7, 7)); + assert_eq!(p!(10, 0).rotate_around(o, rad(60.0)), p!(5, 9)); + assert_eq!(p!(10, 0).rotate_around(o, rad(120.0)), p!(-5, 9)); + } + + #[test] + fn positive_origin() { + let o = p!(10, 10); + assert_eq!(p!(15, 10).rotate_around(o, rad(0.0)), p!(15, 10)); + assert_eq!(p!(15, 10).rotate_around(o, rad(45.0)), p!(14, 14)); + assert_eq!(p!(15, 10).rotate_around(o, rad(90.0)), p!(10, 15)); + assert_eq!(p!(0, 0).rotate_around(o, rad(0.0)), p!(0, 0)); + assert_eq!(p!(0, 0).rotate_around(o, rad(45.0)), p!(10, -4)); + assert_eq!(p!(0, 0).rotate_around(o, rad(90.0)), p!(20, 0)); + } + + + use crate::Rect; + macro_rules! r { + ($x:expr,$y:expr;$w:expr,$h:expr) => { + Rect::<i32,u32>::new($x,$y,$w,$h) + }; + } + + #[test] + fn intersection() { + // Intersecting two null rects + assert_eq!(r!(0,0;0,0), r!(0,0;0,0).intersect(r!(0,0;0,0))); + // Intersecting two identical rects + assert_eq!(r!(0,0;4,4), r!(0,0;4,4).intersect(r!(0,0;4,4))); + // Intersecting two signed identical rects + assert_eq!(r!(-4,-4;4,4), r!(-4,-4;4,4).intersect(r!(-4,-4;4,4))); + // Intersecting two horizontally-displaced rects + assert_eq!(r!(2,0;2,4), r!(0,0;4,4).intersect(r!(2,0;4,4))); + assert_eq!(r!(2,0;2,4), r!(2,0;4,4).intersect(r!(0,0;4,4))); + // Intersecting two vertically-displaced rects + assert_eq!(r!(0,2;4,2), r!(0,0;4,4).intersect(r!(0,2;4,4))); + assert_eq!(r!(0,2;4,2), r!(0,2;4,4).intersect(r!(0,0;4,4))); + // Intersecting two adjacent rects + assert_eq!(r!(0,0;0,0), r!(0,0;4,4).intersect(r!(0,4;4,4))); + assert_eq!(r!(0,0;0,0), r!(0,0;4,4).intersect(r!(4,0;4,4))); + assert_eq!(r!(0,0;0,0), r!(0,0;4,4).intersect(r!(4,4;4,4))); + // Intersecting two almost-adjacent rects + assert_eq!(r!(0,0;0,0), r!(0,0;4,4).intersect(r!(0,5;4,4))); + assert_eq!(r!(0,0;0,0), r!(0,0;4,4).intersect(r!(5,0;4,4))); + assert_eq!(r!(0,0;0,0), r!(0,0;4,4).intersect(r!(5,5;4,4))); + // Intersecting two distant rects + assert_eq!(r!(0,0;0,0), r!(0,0;4,4).intersect(r!( 0,100;4,4))); + assert_eq!(r!(0,0;0,0), r!(0,0;4,4).intersect(r!(100, 0;4,4))); + assert_eq!(r!(0,0;0,0), r!(0,0;4,4).intersect(r!(100,100;4,4))); + } +} diff --git a/src/point.rs b/src/point.rs new file mode 100644 index 0000000..d7bd577 --- /dev/null +++ b/src/point.rs @@ -0,0 +1,72 @@ +use crate::*; +use std::fmt; +use std::ops::{Add, Sub, AddAssign, SubAssign}; + +#[derive(Copy, Clone, PartialEq)] +pub struct Point<O: Internal> { + pub x: O, + pub y: O, +} + +pub trait HasPosition<O: Internal> { + fn position(&self) -> Point<O>; + + fn x(&self) -> O { + self.position().x + } + fn y(&self) -> O { + self.position().y + } +} + +impl<O: Internal> Point<O> { + pub const ZERO: Self = Self { x: O::ZERO, y: O::ZERO }; + + pub const fn new(x: O, y: O) -> Point<O> { + Point { x, y } + } + + pub fn is_zero(&self) -> bool { + self.x == O::ZERO && self.y == O::ZERO + } +} + +impl<O: Internal> HasPosition<O> for Point<O> { + fn position(&self) -> Point<O> { + *self + } +} + +impl<O: Internal> Add<Point<O>> for Point<O> { + type Output = Self; + fn add(self, point: Point<O>) -> Self { + Point::new(self.x + point.x, self.y + point.y) + } +} + +impl<O: Internal> Sub<Point<O>> for Point<O> { + type Output = Self; + fn sub(self, point: Point<O>) -> Self { + Point::new(self.x - point.x, self.y - point.y) + } +} + +impl<O: Internal> AddAssign<Point<O>> for Point<O> { + fn add_assign(&mut self, point: Point<O>) { + self.x += point.x; + self.y += point.y; + } +} + +impl<O: Internal> SubAssign<Point<O>> for Point<O> { + fn sub_assign(&mut self, point: Point<O>) { + self.x -= point.x; + self.y -= point.y; + } +} + +impl<T: Internal> fmt::Debug for Point<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Point {{ x:{:?} y:{:?} }}", self.x, self.y) + } +} diff --git a/src/rect.rs b/src/rect.rs new file mode 100644 index 0000000..3f5e9e3 --- /dev/null +++ b/src/rect.rs @@ -0,0 +1,193 @@ +use crate::*; +use std::fmt; +use std::ops::{Add, Sub, AddAssign, SubAssign}; + +#[derive(Clone, Copy, PartialEq)] +pub struct Rect<O: Internal, D: Internal> { + pub origin: Point<O>, + pub dimensions: Dimensions<D>, +} + +pub trait HasRect<O: Internal, D: Internal>: HasPosition<O> + HasDimensions<D> { + fn rect(&self) -> Rect<O, D>; +} + +impl<O: Internal, D: Internal> Rect<O, D> { + pub const ZERO: Rect<O, D> = Rect::construct(Point::<O>::ZERO, Dimensions::<D>::ZERO); + + pub const fn new(origin_x: O, origin_y: O, width: D, height: D) -> Self { + let origin = Point::new(origin_x, origin_y); + let dimensions = Dimensions::new(width, height); + Self { origin, dimensions } + } + + pub const fn construct(origin: Point<O>, dimensions: Dimensions<D>) -> Self { + Self { origin, dimensions } + } +} + +impl<O: Internal, D: Internal> HasDimensions<D> for Rect<O, D> { + fn dimensions(&self) -> Dimensions<D> { self.dimensions } +} + +impl<O: Internal, D: Internal> HasPosition<O> for Rect<O, D> { + fn position(&self) -> Point<O> { self.origin } +} + +impl<O: Internal, D: Internal, T: HasDimensions<D> + HasPosition<O>> HasRect<O, D> for T { + fn rect(&self) -> Rect<O, D> { + Rect::construct(self.position(), self.dimensions()) + } +} + +impl<O:Internal, D: Internal> Add<Point<O>> for Rect<O, D> { + type Output = Rect<O,D>; + fn add(self, point: Point<O>) -> Self::Output { + Rect::construct(self.origin + point, self.dimensions) + } +} + +impl<O: Internal, D: Internal> Sub<Point<O>> for Rect<O, D> { + type Output = Rect<O,D>; + fn sub(self, point: Point<O>) -> Self::Output { + Rect::construct(self.origin - point, self.dimensions) + } +} + +impl<O: Internal, D: Internal> AddAssign<Point<O>> for Rect<O, D> { + fn add_assign(&mut self, point: Point<O>) { + self.origin += point + } +} + +impl<O: Internal, D: Internal> SubAssign<Point<O>> for Rect<O, D> { + fn sub_assign(&mut self, point: Point<O>) { + self.origin -= point + } +} + +impl<O: Internal, D: Internal> fmt::Debug for Rect<O, D> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Rect {{ x:{:?} y:{:?} w:{:?} h:{:?} }}", + self.origin.x, self.origin.y, + self.dimensions.width, self.dimensions.height, + ) + } +} + +// ----------------------------------------------------------------------------- +// Macro-generated code + +// PartialOrd versions of std::cmp::min/max +fn min<T: std::cmp::PartialOrd>(a: T, b: T) -> T { + match a < b { true => a, false => b } +} +fn max<T: std::cmp::PartialOrd>(a: T, b: T) -> T { + match a > b { true => a, false => b } +} + +macro_rules! impl_for_types { + ($O:ty, $D:ty) => { +impl Rect<$O, $D> { + pub const fn from_dimensions(dimensions: Dimensions<$D>) -> Self { + Self { + origin: Point::<$O>::ZERO, + dimensions, + } + } + + pub fn from_points(point1: Point<$O>, point2: Point<$O>) -> Self { + let (left, right) = match point1.x < point2.x { + true => (point1.x, point2.x), + false => (point2.x, point1.x), + }; + let (top, bottom) = match point1.y < point2.y { + true => (point1.y, point2.y), + false => (point2.y, point1.y), + }; + let width = (right - left) as $D; + let height = (bottom - top) as $D; + + Self { + origin: Point::new(left, top), + dimensions: Dimensions::new(width, height), + } + } + + /// Returns Rect(0,0,0,0) where no intersection exists. + pub fn intersect(&self, other: Rect<$O, $D>) -> Self { + let left = max(self.origin.x, other.origin.x); + let top = max(self.origin.y, other.origin.y); + + let right_self = self.origin.x + (self.width() as $O); + let right_other = other.origin.x + (other.width() as $O); + let bottom_self = self.origin.y + (self.height() as $O); + let bottom_other = other.origin.y + (other.height() as $O); + let right = min(right_self, right_other); + let bottom = min(bottom_self, bottom_other); + + if right <= left || bottom <= top { + Rect::<$O, $D>::ZERO + } else { + let width = (right - left) as $D; + let height = (bottom - top) as $D; + Rect::construct(Point::new(left, top), Dimensions::new(width, height)) + } + } + + pub fn contains_point(&self, point: Point<$O>) -> bool { + let left = self.origin.x; + let top = self.origin.y; + let right = left + (self.width() as $O); + let bottom = top + (self.height() as $O); + point.x >= left && point.x < right && point.y >= top && point.y < bottom + } + + pub fn include_point(&mut self, _point: Point<$O>) { + todo!() + } + + /// Return the inclusive left x-coordinate. + pub fn left(&self) -> $O { + self.origin.x + } + + /// Return the inclusive top y-coordinate. + pub fn top(&self) -> $O { + self.origin.y + } + + /// Return the exclusive right x-coordinate. + pub fn right(&self) -> $O { + self.left() + self.width() as $O + } + /// Return the exclusive bottom y-coordinate. + pub fn bottom(&self) -> $O { + self.top() + self.height() as $O + } +} + +impl From<Dimensions<$D>> for Rect<$O, $D> { + fn from(dimensions: Dimensions<$D>) -> Rect<$O, $D> { + Rect::<$O, $D>::from_dimensions(dimensions) + } +} + }; +} + +impl_for_types!(u8, u8); +impl_for_types!(u16, u16); +impl_for_types!(u32, u32); +impl_for_types!(u64, u64); +impl_for_types!(u128, u128); +impl_for_types!(usize, usize); + +impl_for_types!(i8, u8); +impl_for_types!(i16, u16); +impl_for_types!(i32, u32); +impl_for_types!(i64, u64); +impl_for_types!(i128, u128); +impl_for_types!(isize, usize); + +impl_for_types!(f32, f32); +impl_for_types!(f64, f64); |