summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBen Bridle <bridle.benjamin@gmail.com>2023-12-24 20:03:12 +1300
committerBen Bridle <bridle.benjamin@gmail.com>2023-12-24 21:38:49 +1300
commit8547ad9c2f2867da9b0e655dd4ff0fe78f04ffba (patch)
tree554f4514177cc8c1436ed3ec3ae44f176684f664 /src
downloadgeometry-a76d4655b2f188c53963b2a624159c7d35f2ef54.zip
First commitHEADv1.0.0main
Diffstat (limited to 'src')
-rw-r--r--src/dimensions.rs94
-rw-r--r--src/internal.rs47
-rw-r--r--src/lib.rs125
-rw-r--r--src/point.rs72
-rw-r--r--src/rect.rs193
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);