Initial Commit

This commit is contained in:
2025-05-05 13:59:13 -04:00
commit edfd78c944
12 changed files with 540 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

65
Cargo.lock generated Normal file
View File

@@ -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"

12
Cargo.toml Normal file
View File

@@ -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"

2
examples/custom_coord.rs Normal file
View File

@@ -0,0 +1,2 @@
fn main() {
}

155
src/basic/basic_coord2d.rs Normal file
View File

@@ -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<AxisUnit: Numeric + Copy, U> {
pub x: AxisUnit,
pub y: AxisUnit,
#[doc(hidden)]
tag: PhantomData<U>
}
/// A representation of some point on a 2D plane.
impl<AxisUnit: Numeric + Copy, U> Point2D<AxisUnit, U> {
#[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<AxisUnit: Numeric + Copy + Widen, U> Point2D<AxisUnit, U> {
#[inline]
pub fn area(&self, other: &Self) -> <AxisUnit as Widen>::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) -> <AxisUnit as Widen>::Target {
self.distance_x(other).widen() + self.distance_y(other).widen()
}
}
impl<AxisUnit: Numeric + Copy + Widen<Target: Widen + Copy>, U> Point2D<AxisUnit, U> {
fn distance_squared(&self, other: &Self) -> <<AxisUnit as Widen>::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) -> <<AxisUnit as Widen>::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 <AxisUnit: Numeric + Copy + Widen<Target: Widen<Target: SqrtUnit> + Copy>, U> Point2D<AxisUnit, U> {
fn distance(&self, other: &Self) -> <<AxisUnit as Widen>::Target as Widen>::Target {
let ds = self.distance_squared(other);
ds.sqrt_unit()
}
}
impl<AxisUnit: Numeric + Copy, U> From<(AxisUnit, AxisUnit)> for Point2D<AxisUnit, U> {
fn from(value: (AxisUnit, AxisUnit)) -> Self {
Self::new(value.0, value.1)
}
}
impl<AxisUnit: Numeric + Copy, U> From<Point2D<AxisUnit, U>> for (AxisUnit, AxisUnit) {
fn from(value: Point2D<AxisUnit, U>) -> Self {
(value.x, value.y)
}
}

View File

3
src/basic/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
mod basic_coord2d;
pub use basic_coord2d::*;

11
src/lib.rs Normal file
View File

@@ -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::*;

9
src/round.rs Normal file
View File

@@ -0,0 +1,9 @@
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[allow(clippy::enum_variant_names)]
pub enum Round {
Floor,
Ceiling,
#[default]
Round,
}

5
src/traits/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
mod numeric;
mod primitives;
pub use numeric::*;
pub use primitives::*;

86
src/traits/numeric.rs Normal file
View File

@@ -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<Self, Output = Self>
+ Sub<Self, Output = Self>
+ Mul<Self, Output = Self>
+ Div<Self, Output = Self>
+ 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<T: Integer + Signed> SignedInteger for T {}
/// UnsignedInteger is a convenience trait implemented for all Unsigned + Integer values
pub trait UnsignedInteger: Integer + Unsigned {}
impl<T: Integer + Unsigned> 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<T: Numeric>: 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<T> WidenInto<<T as Widen>::Target> for T
where
T: Widen,
<T as Widen>::Target: Numeric,
{
#[inline]
fn widen_into(self) -> <Self as Widen>::Target {
self.widen()
}
}

191
src/traits/primitives.rs Normal file
View File

@@ -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<T: PrimitiveNumSealed + Numeric> 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::<f64>::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]);