commit edfd78c94407bdc4b01abea6bc47073ee40c0891 Author: Aaron Gorodetzky Date: Mon May 5 13:59:13 2025 -0400 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d0d4b48 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,65 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bytemuck" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "geometry" +version = "0.1.0" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cabca5c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "geometry" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +bytemuck = { version = "1.22.0", features = ["derive", "extern_crate_alloc"] } + +[lib] +name = "geometry_lib" +path = "src/lib.rs" diff --git a/examples/custom_coord.rs b/examples/custom_coord.rs new file mode 100644 index 0000000..f79c691 --- /dev/null +++ b/examples/custom_coord.rs @@ -0,0 +1,2 @@ +fn main() { +} diff --git a/src/basic/basic_coord2d.rs b/src/basic/basic_coord2d.rs new file mode 100644 index 0000000..5db3213 --- /dev/null +++ b/src/basic/basic_coord2d.rs @@ -0,0 +1,155 @@ +use std::marker::PhantomData; + +use crate::{Numeric, SqrtUnit, Widen}; + +/// A Simple 2D Coordinate with a AxisUnitag Unit +#[derive(Copy, Clone)] +pub struct Point2D { + pub x: AxisUnit, + pub y: AxisUnit, + + #[doc(hidden)] + tag: PhantomData +} + +/// A representation of some point on a 2D plane. +impl Point2D { + #[inline] + pub const fn new(x: AxisUnit, y: AxisUnit) -> Self { + Self { + x, + y, + tag: PhantomData + } + } + + #[inline] + pub fn min_point(&self, other: &Self) -> Self { + let min_x = self.min_x(other); + let min_y = self.min_y(other); + Self::new(min_x, min_y) + } + + #[inline] + pub fn max_point(&self, other: &Self) -> Self { + let max_x = self.max_x(other); + let max_y = self.max_y(other); + Self::new(max_x, max_y) + } + + #[inline] + pub fn min_x(&self, other: &Self) -> AxisUnit { + if self.x < other.x { self.x } else { other.x } + } + + #[inline] + pub fn min_y(&self, other: &Self) -> AxisUnit { + if self.y < other.y { self.y } else { other.y } + } + + #[inline] + pub fn max_x(&self, other: &Self) -> AxisUnit { + if self.x > other.x { self.x } else { other.x } + } + + #[inline] + pub fn max_y(&self, other: &Self) -> AxisUnit { + if self.y > other.y { self.y } else { other.y } + } + + #[inline] + pub fn order_x(&self, other: &Self) -> (AxisUnit, AxisUnit) { + if self.x < other.x { (self.x, other.x) } else { (other.x, self.x) } + } + + #[inline] + pub fn order_y(&self, other: &Self) -> (AxisUnit, AxisUnit) { + if self.y < other.y { (self.y, other.y) } else { (other.y, self.y) } + } + + pub fn midpoint(&self, other: &Self) -> Self { + // This function could be a lot better if we could specialize, but unfortunately we can't. + // As we are (potentially) working with discrete values, we want to avoid the situation + // where we are incorrect about the midpoint due to rounding. If we divide both operands by + // two, and they both happen to be odd, we'll be incorrect as we'll have rounded down an + // extra unit. To circumvent this, we need to do things the long way. + // + // Consider two points at x unit 3 and 9. The midpoint should be 6. But if we divide first, + // we truncate and end up with (3/2 + 9/2) = (1 + 4) = 5. So we have to add first. But if + // we add first, there is a chance we could overflow for very large numbers. To avoid this, + // we need to offset the numbers by their smallest value, ensuring the addition does not + // overflow. + let (min_x, max_x) = self.order_x(other); + let (min_y, max_y) = self.order_y(other); + + let x = min_x + ((max_x - min_x) / AxisUnit::TWO); + let y = min_y + ((max_y - min_y) / AxisUnit::TWO); + + Self::new(x, y) + } + + #[inline] + pub fn distance_x(&self, other: &Self) -> AxisUnit { + let (min_x, max_x) = self.order_x(other); + max_x - min_x + } + + #[inline] + pub fn distance_y(&self, other: &Self) -> AxisUnit { + let (min_y, max_y) = self.order_y(other); + max_y - min_y + } +} + +impl Point2D { + #[inline] + pub fn area(&self, other: &Self) -> ::Target { + let dx = self.distance_x(other); + let dy = self.distance_y(other); + + dx.widen() * dy.widen() + } + + #[inline] + pub fn manhattan_distance(&self, other: &Self) -> ::Target { + self.distance_x(other).widen() + self.distance_y(other).widen() + } +} + +impl, U> Point2D { + fn distance_squared(&self, other: &Self) -> <::Target as Widen>::Target { + let dx = self.distance_x(other).widen(); + let dy = self.distance_y(other).widen(); + + let dx2 = (dx*dx).widen(); + let dy2 = (dy*dy).widen(); + + dx2 + dy2 + } + + #[inline] + fn dot_product(&self, other: &Self) -> <::Target as Widen>::Target { + let square_x = (self.x.widen() * other.x.widen()).widen(); + let square_y = (self.x.widen() * other.y.widen()).widen(); + square_x + square_y + } +} + +impl + Copy>, U> Point2D { + fn distance(&self, other: &Self) -> <::Target as Widen>::Target { + let ds = self.distance_squared(other); + ds.sqrt_unit() + } +} + +impl From<(AxisUnit, AxisUnit)> for Point2D { + fn from(value: (AxisUnit, AxisUnit)) -> Self { + Self::new(value.0, value.1) + } +} + +impl From> for (AxisUnit, AxisUnit) { + fn from(value: Point2D) -> Self { + (value.x, value.y) + } +} diff --git a/src/basic/basic_rect2d.rs b/src/basic/basic_rect2d.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/basic/mod.rs b/src/basic/mod.rs new file mode 100644 index 0000000..52b7da2 --- /dev/null +++ b/src/basic/mod.rs @@ -0,0 +1,3 @@ +mod basic_coord2d; + +pub use basic_coord2d::*; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..22362bd --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +#![deny(clippy::all)] +#![forbid(unsafe_code)] +#![allow(dead_code)] +#![allow(clippy::module_inception)] + +mod traits; +mod round; +pub mod basic; + +pub use round::*; +pub use traits::*; diff --git a/src/round.rs b/src/round.rs new file mode 100644 index 0000000..adaca11 --- /dev/null +++ b/src/round.rs @@ -0,0 +1,9 @@ +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[allow(clippy::enum_variant_names)] +pub enum Round { + Floor, + Ceiling, + + #[default] + Round, +} diff --git a/src/traits/mod.rs b/src/traits/mod.rs new file mode 100644 index 0000000..74f24e8 --- /dev/null +++ b/src/traits/mod.rs @@ -0,0 +1,5 @@ +mod numeric; +mod primitives; + +pub use numeric::*; +pub use primitives::*; diff --git a/src/traits/numeric.rs b/src/traits/numeric.rs new file mode 100644 index 0000000..cb85819 --- /dev/null +++ b/src/traits/numeric.rs @@ -0,0 +1,86 @@ +use std::ops::{Add, Div, Mul, Shl, Shr, Sub}; +use std::hash::Hash; + +use bytemuck::Zeroable; + +/// Numeric is a partial trait that more generally represents all numeric types supported by the +/// engine. This should include both integers and floats, plus any custom types we implement. +pub trait Numeric: + Add + + Sub + + Mul + + Div + + PartialEq + + PartialOrd + + Zeroable +{ + const ZERO: Self; + const ONE: Self; + const TWO: Self; + const MAX: Self; + const MIN: Self; +} + + +// /// Integer is a trait representing any of the supported engine numeric types. +pub trait Integer: Numeric + Shl + Shr + Ord + Eq + Hash {} + +/// Float is a trait representing any of the supported engine floating point types. +pub trait Float: Numeric {} + +/// Signed is a trait representing any value supporting both positive and negative values. +pub trait Signed: Numeric {} + +/// Unsigned is a trait representing any value that cannot be negative. +pub trait Unsigned: Numeric {} + +/// SignedInteger is a convenience trait implemented for all Signed + Integer values +pub trait SignedInteger: Integer + Signed {} +impl SignedInteger for T {} + +/// UnsignedInteger is a convenience trait implemented for all Unsigned + Integer values +pub trait UnsignedInteger: Integer + Unsigned {} +impl UnsignedInteger for T {} + +/// UnitSqrt provies a universal sqrt operator for all values which can have their root taken and +/// the value represented in the unit itself. For integers, this would be equivalent to isqrt, and +/// for floats, the standard sqrt operation. +pub trait SqrtUnit { + fn sqrt_unit(self) -> Self; +} + +pub trait SqrtReal { + fn sqrt_real(self) -> f64; +} + + +/// WidenInto guarantees that a type U can be "widened" into some type T such that T: +/// 1. Can hold the result of any multiplication of two U values, including T::MAX * T::MAX. +/// 2. No data is lost when converting from U to T. +/// +/// In addition, while it isn't guaranteed, preferably any U can be turned into T with little to no +/// overhead, such as converting a u16 to u32 with `as`. +pub trait WidenInto: Numeric { + fn widen_into(self) -> T; +} + +/// Widen is similar to WidenInto, but only specifies a single associated type to widen into. This +/// is useful for code where the context doesn't care what type exactly is widened, only that the +/// resulting type is large enough to contain any single math operation on the narrower type. +pub trait Widen: Numeric { + type Target: Numeric; + + fn widen(self) -> Self::Target; +} + +// Ensure WidenInto is also implemented for T => U if the basic Widen trait is implemented. +impl WidenInto<::Target> for T +where + T: Widen, + ::Target: Numeric, +{ + #[inline] + fn widen_into(self) -> ::Target { + self.widen() + } +} diff --git a/src/traits/primitives.rs b/src/traits/primitives.rs new file mode 100644 index 0000000..ca3154e --- /dev/null +++ b/src/traits/primitives.rs @@ -0,0 +1,191 @@ +use private::PrimitiveNumSealed; + +use super::{Float, Integer, Numeric, Signed, SqrtReal, SqrtUnit, Unsigned, Widen, WidenInto}; + +mod private { + pub trait PrimitiveNumSealed: Copy + Clone {} +} + +impl PrimitiveNumSealed for u8 {} +impl PrimitiveNumSealed for u16 {} +impl PrimitiveNumSealed for u32 {} +impl PrimitiveNumSealed for u64 {} +impl PrimitiveNumSealed for u128 {} +impl PrimitiveNumSealed for usize {} +impl PrimitiveNumSealed for i8 {} +impl PrimitiveNumSealed for i16 {} +impl PrimitiveNumSealed for i32 {} +impl PrimitiveNumSealed for i64 {} +impl PrimitiveNumSealed for i128 {} +impl PrimitiveNumSealed for isize {} +impl PrimitiveNumSealed for f32 {} +impl PrimitiveNumSealed for f64 {} + +/// A marker trait used to define only primitive Rust number types. +pub trait PrimitiveNum: Numeric {} +impl PrimitiveNum for T {} + +macro_rules! impl_numeric_for_primitive_int { + ($unit:ty) => { + impl Numeric for $unit { + const ZERO: Self = 0; + const ONE: Self = 1; + const TWO: Self = 2; + const MAX: Self = <$unit>::MAX; + const MIN: Self = <$unit>::MIN; + } + }; +} + +macro_rules! impl_numeric_for_primitive_float { + ($unit:ty) => { + impl Numeric for $unit { + const ZERO: Self = 0.0; + const ONE: Self = 1.0; + const TWO: Self = 2.0; + const MAX: Self = <$unit>::MAX; + const MIN: Self = <$unit>::MIN; + } + }; +} + +impl_numeric_for_primitive_int!(u8); +impl_numeric_for_primitive_int!(u16); +impl_numeric_for_primitive_int!(u32); +impl_numeric_for_primitive_int!(u64); +impl_numeric_for_primitive_int!(u128); +impl_numeric_for_primitive_int!(usize); +impl_numeric_for_primitive_int!(i8); +impl_numeric_for_primitive_int!(i16); +impl_numeric_for_primitive_int!(i32); +impl_numeric_for_primitive_int!(i64); +impl_numeric_for_primitive_int!(i128); +impl_numeric_for_primitive_int!(isize); +impl_numeric_for_primitive_float!(f32); +impl_numeric_for_primitive_float!(f64); + +impl Integer for u8 {} +impl Integer for u16 {} +impl Integer for u32 {} +impl Integer for u64 {} +impl Integer for u128 {} +impl Integer for usize {} +impl Integer for i8 {} +impl Integer for i16 {} +impl Integer for i32 {} +impl Integer for i64 {} +impl Integer for i128 {} +impl Integer for isize {} + +impl Float for f32 {} +impl Float for f64 {} + +impl Signed for i8 {} +impl Signed for i16 {} +impl Signed for i32 {} +impl Signed for i64 {} +impl Signed for i128 {} +impl Signed for isize {} +impl Signed for f32 {} +impl Signed for f64 {} + +impl Unsigned for u8 {} +impl Unsigned for u16 {} +impl Unsigned for u32 {} +impl Unsigned for u64 {} +impl Unsigned for u128 {} +impl Unsigned for usize {} + +macro_rules! impl_unit_isqrt_unit { + ($unit:ty) => { + impl SqrtUnit for $unit { + fn sqrt_unit(self) -> Self { + self.isqrt() + } + } + }; +} + +macro_rules! impl_unit_sqrt_unit { + ($unit:ty) => { + impl SqrtUnit for $unit { + fn sqrt_unit(self) -> Self { + self.sqrt() + } + } + }; +} + +macro_rules! impl_sqrt_real { + ($unit:ty) => { + impl SqrtReal for $unit { + fn sqrt_real(self) -> f64 { + Into::::into(self).sqrt() + } + } + }; +} + +impl_unit_isqrt_unit!(u8); +impl_unit_isqrt_unit!(u16); +impl_unit_isqrt_unit!(u32); +impl_unit_isqrt_unit!(u64); +impl_unit_isqrt_unit!(u128); +impl_unit_isqrt_unit!(usize); +impl_unit_isqrt_unit!(i8); +impl_unit_isqrt_unit!(i16); +impl_unit_isqrt_unit!(i32); +impl_unit_isqrt_unit!(i64); +impl_unit_isqrt_unit!(i128); +impl_unit_isqrt_unit!(isize); +impl_unit_sqrt_unit!(f32); +impl_unit_sqrt_unit!(f64); + +impl_sqrt_real!(u8); +impl_sqrt_real!(u16); +impl_sqrt_real!(u32); +impl_sqrt_real!(i8); +impl_sqrt_real!(i16); +impl_sqrt_real!(i32); +impl_sqrt_real!(f32); +impl_sqrt_real!(f64); + +macro_rules! impl_widen_into { + ($from:ty => [$($to:ty),+]) => { + $( + impl WidenInto<$to> for $from { fn widen_into(self) -> $to { self as $to } } + )+ + }; +} + +macro_rules! impl_widen { + ($from:ty => $to:ty) => { + impl Widen for $from { + type Target=$to; + + fn widen(self) -> $to { + self as $to + } + } + }; +} + +impl_widen!(u8 => u16); +impl_widen!(u16 => u32); +impl_widen!(u32 => u64); +impl_widen!(u64 => u128); +impl_widen!(i8 => i16); +impl_widen!(i16 => i32); +impl_widen!(i32 => i64); +impl_widen!(i64 => i128); +impl_widen!(f32 => f64); + +// Additional widen options for the primitive types +impl_widen_into!(u8 => [u32, u64, u128, usize]); +impl_widen_into!(u16 => [u64, u128, usize]); +impl_widen_into!(u32 => [u128, usize]); +impl_widen_into!(u64 => [usize]); +impl_widen_into!(i8 => [i32, i64, i128, isize]); +impl_widen_into!(i16 => [i64, i128, isize]); +impl_widen_into!(i32 => [i128, isize]); +impl_widen_into!(i64 => [isize]);