gemath 0.1.0

Type-safe game math with type-level units/spaces, typed angles, and explicit fallible ops (plus optional geometry/collision).
Documentation
use crate::vec2::*;
use crate::math;
use core::marker::PhantomData;

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Aabb2<Unit: Copy = (), Space: Copy = ()> {
    pub center: Vec2<Unit, Space>,
    pub half_size: Vec2<Unit, Space>,
    #[cfg_attr(feature = "serde", serde(skip))]
    _unit: PhantomData<Unit>,
    #[cfg_attr(feature = "serde", serde(skip))]
    _space: PhantomData<Space>,
}

// Type aliases for common units and spaces
pub type Aabb2f32 = Aabb2<(),()>;
pub type Aabb2Meters = Aabb2<Meters,()>;
pub type Aabb2Pixels = Aabb2<Pixels,()>;
pub type Aabb2World = Aabb2<(),World>;
pub type Aabb2Local = Aabb2<(),Local>;
pub type Aabb2Screen = Aabb2<(),Screen>;
pub type Aabb2MetersWorld = Aabb2<Meters,World>;
pub type Aabb2PixelsScreen = Aabb2<Pixels,Screen>;

impl<Unit: Copy, Space: Copy> Aabb2<Unit, Space> {
    #[inline]
    pub const fn new(center: Vec2<Unit, Space>, half_size: Vec2<Unit, Space>) -> Self {
        Self { center, half_size, _unit: PhantomData, _space: PhantomData }
    }

    /// Returns the minimum corner of the AABB.
    pub fn min(&self) -> Vec2<Unit, Space> {
        self.center - self.half_size
    }

    /// Returns the maximum corner of the AABB.
    pub fn max(&self) -> Vec2<Unit, Space> {
        self.center + self.half_size
    }

    /// Constructs an AABB from min and max corners.
    pub fn from_min_max(min: Vec2<Unit, Space>, max: Vec2<Unit, Space>) -> Self {
        let center = (min + max) * 0.5;
        let half_size = (max - min) * 0.5;
        Self { center, half_size, _unit: PhantomData, _space: PhantomData }
    }

    /// Returns the size (width, height) of the AABB.
    pub fn size(&self) -> Vec2<Unit, Space> {
        self.half_size * 2.0
    }

    /// Returns true if the AABB is empty (any half_size <= 0).
    pub const fn is_empty(&self) -> bool {
        self.half_size.x <= 0.0 || self.half_size.y <= 0.0
    }

    /// Returns the area of the AABB.
    pub fn area(&self) -> f32 {
        let size = self.size();
        size.x * size.y
    }

    /// Returns true if the point is inside the AABB (inclusive min, exclusive max).
    pub fn contains_point(&self, point: Vec2<Unit, Space>) -> bool {
        let min = self.min();
        let max = self.max();
        point.x >= min.x && point.x < max.x && point.y >= min.y && point.y < max.y
    }

    /// Returns true if this AABB intersects with another.
    pub fn intersects(&self, other: &Self) -> bool {
        self.intersection(other).is_some()
    }

    /// Returns the intersection of this AABB with another, or None if they do not overlap.
    pub fn intersection(&self, other: &Self) -> Option<Self> {
        let min_a = self.min();
        let max_a = self.max();
        let min_b = other.min();
        let max_b = other.max();
        let min = min_a.max(min_b);
        let max = max_a.min(max_b);
        if min.x < max.x && min.y < max.y {
            Some(Self::from_min_max(min, max))
        } else {
            None
        }
    }

    /// Returns a new AABB expanded to include the given point.
    pub fn expand_to_include(&self, point: Vec2<Unit, Space>) -> Self {
        let min = self.min();
        let max = self.max();
        let new_min = min.min(point);
        let new_max = max.max(point);
        Self::from_min_max(new_min, new_max)
    }

    /// Returns the union (smallest AABB containing both) of self and other.
    pub fn union(&self, other: &Self) -> Self {
        let min = self.min().min(other.min());
        let max = self.max().max(other.max());
        Self::from_min_max(min, max)
    }

    /// Transforms the AABB by a Mat4. Returns the smallest AABB containing the transformed corners.
    /// Only the 2D part of the matrix is used (x, y, translation). Ignores z/w.
    #[cfg(feature = "mat4")]
    pub fn transform(&self, mat: &crate::mat4::Mat4) -> Self {
        let min = self.min();
        let max = self.max();
        let corners: [Vec2<Unit, Space>; 4] = [
            Vec2::new(min.x, min.y),
            Vec2::new(min.x, max.y),
            Vec2::new(max.x, min.y),
            Vec2::new(max.x, max.y),
        ];
        let mut tmin = Vec2::new(f32::INFINITY, f32::INFINITY);
        let mut tmax = Vec2::new(f32::NEG_INFINITY, f32::NEG_INFINITY);
        for &c in &corners {
            let v = mat.transform_point(crate::vec3::Vec3::new(c.x, c.y, 0.0));
            let v2 = Vec2::new(v.x, v.y);
            tmin = tmin.min(v2);
            tmax = tmax.max(v2);
        }
        Self::from_min_max(tmin, tmax)
    }

    /// Returns the closest point in the AABB to the given point.
    pub fn closest_point(&self, point: Vec2<Unit, Space>) -> Vec2<Unit, Space> {
        let min = self.min();
        let max = self.max();
        point.clamp(min, max)
    }

    /// Returns the distance from the AABB to the given point (0 if inside).
    pub fn distance(&self, point: Vec2<Unit, Space>) -> f32 {
        let min = self.min();
        let max = self.max();
        let dx = if point.x < min.x {
            min.x - point.x
        } else if point.x > max.x {
            point.x - max.x
        } else {
            0.0
        };
        let dy = if point.y < min.y {
            min.y - point.y
        } else if point.y > max.y {
            point.y - max.y
        } else {
            0.0
        };
        math::sqrt(dx * dx + dy * dy)
    }

    /// Ray-AABB intersection (slab method). Returns Some(t) for the first intersection, or None if no hit.
    /// Ray: origin + t * dir, t >= 0. Returns smallest t >= 0.
    pub fn intersect_ray(&self, origin: Vec2<Unit, Space>, dir: Vec2<Unit, Space>) -> Option<f32> {
        let min = self.min();
        let max = self.max();
        let mut tmin = (min.x - origin.x) / dir.x;
        let mut tmax = (max.x - origin.x) / dir.x;
        if tmin > tmax {
            core::mem::swap(&mut tmin, &mut tmax);
        }
        let mut tymin = (min.y - origin.y) / dir.y;
        let mut tymax = (max.y - origin.y) / dir.y;
        if tymin > tymax {
            core::mem::swap(&mut tymin, &mut tymax);
        }
        if (tmin > tymax) || (tymin > tmax) {
            return None;
        }
        tmin = tmin.max(tymin);
        tmax = tmax.min(tymax);
        if tmax < 0.0 {
            return None;
        }
        Some(if tmin >= 0.0 { tmin } else { tmax })
    }
}