gemath 0.1.0

Type-safe game math with type-level units/spaces, typed angles, and explicit fallible ops (plus optional geometry/collision).
Documentation
#![cfg(feature = "geometry")]

use gemath::*;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn ray2_point_at_and_closest_point() {
        let r: Ray2<(), ()> = Ray2::new(Vec2::new(1.0, 2.0), Vec2::new(2.0, 0.0));
        assert_eq!(r.point_at(0.0), Vec2::new(1.0, 2.0));
        assert_eq!(r.point_at(3.0), Vec2::new(7.0, 2.0));

        // Closest point from (0,0) should clamp to t=0 (origin) because ray is t>=0.
        let p = Vec2::new(0.0, 0.0);
        assert_eq!(r.closest_point(p), Vec2::new(1.0, 2.0));
        assert!((r.distance_to_point(p) - (Vec2::<(), ()>::new(-1.0, -2.0)).length()).abs() < 1e-6);
    }

    #[test]
    fn ray3_point_at_and_closest_point() {
        let r: Ray3<(), ()> = Ray3::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 2.0));
        assert_eq!(r.point_at(0.5), Vec3::new(0.0, 0.0, 1.0));

        let p = Vec3::new(1.0, 0.0, -10.0);
        // Closest clamps to t=0 (origin) due to t>=0.
        assert_eq!(r.closest_point(p), Vec3::new(0.0, 0.0, 0.0));
    }

    #[test]
    fn segment2_closest_point_distance() {
        let s: Segment2<(), ()> = Segment2::new(Vec2::new(0.0, 0.0), Vec2::new(2.0, 0.0));
        let p = Vec2::new(1.0, 3.0);
        assert_eq!(s.closest_point(p), Vec2::new(1.0, 0.0));
        assert!((s.distance_to_point(p) - 3.0).abs() < 1e-6);
        assert!((s.length() - 2.0).abs() < 1e-6);
    }

    #[test]
    fn segment3_closest_point_distance() {
        let s: Segment3<(), ()> = Segment3::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 2.0));
        let p = Vec3::new(0.0, 3.0, 1.0);
        assert_eq!(s.closest_point(p), Vec3::new(0.0, 0.0, 1.0));
        assert!((s.distance_to_point(p) - 3.0).abs() < 1e-6);
        assert!((s.length() - 2.0).abs() < 1e-6);
    }

    #[test]
    fn circle_queries() {
        let c: Circle<(), ()> = Circle::new(Vec2::new(0.0, 0.0), 2.0);
        assert!(c.contains_point(Vec2::new(0.0, 2.0)));
        assert!(!c.contains_point(Vec2::new(0.0, 2.1)));
        assert!((c.area() - (core::f32::consts::PI * 4.0)).abs() < 1e-6);

        let p = Vec2::new(3.0, 0.0);
        assert_eq!(c.closest_point(p), Vec2::new(2.0, 0.0));
        assert!((c.distance_to_point(p) - 1.0).abs() < 1e-6);
    }

    #[test]
    fn sphere_queries() {
        let s: Sphere<(), ()> = Sphere::new(Vec3::new(0.0, 0.0, 0.0), 2.0);
        assert!(s.contains_point(Vec3::new(0.0, 0.0, 2.0)));
        assert!(!s.contains_point(Vec3::new(0.0, 0.0, 2.1)));
        assert!((s.volume() - ((4.0 / 3.0) * core::f32::consts::PI * 8.0)).abs() < 1e-5);

        let p = Vec3::new(0.0, 0.0, 3.0);
        assert_eq!(s.closest_point(p), Vec3::new(0.0, 0.0, 2.0));
        assert!((s.distance_to_point(p) - 1.0).abs() < 1e-6);
    }

    #[test]
    fn plane_signed_distance_and_project() {
        // Plane z = 5 (normal +Z, through point (0,0,5))
        let plane: Plane<(), ()> = Plane::from_point_normal(Vec3::new(0.0, 0.0, 5.0), Vec3::new(0.0, 0.0, 1.0)).unwrap();
        let p_above = Vec3::new(0.0, 0.0, 7.0);
        let p_below = Vec3::new(0.0, 0.0, 4.0);
        assert!((plane.signed_distance_to_point(p_above) - 2.0).abs() < 1e-6);
        assert!((plane.signed_distance_to_point(p_below) + 1.0).abs() < 1e-6);

        let proj = plane.project_point(p_above);
        assert!((proj - Vec3::new(0.0, 0.0, 5.0)).length() < 1e-6);
    }

    #[test]
    fn capsule2_queries() {
        let cap: Capsule2<(), ()> = Capsule2::new(Vec2::new(0.0, 0.0), Vec2::new(2.0, 0.0), 1.0);
        assert!(cap.contains_point(Vec2::new(1.0, 0.5)));
        assert!(!cap.contains_point(Vec2::new(1.0, 1.01)));
        assert!((cap.distance_to_point(Vec2::new(1.0, 2.0)) - 1.0).abs() < 1e-6);

        let cp = cap.closest_point(Vec2::new(1.0, 2.0));
        // Should lie on the capsule boundary, one radius from the segment.
        assert!((cap.segment().distance_to_point(cp) - 1.0).abs() < 1e-5);
    }

    #[test]
    fn capsule3_queries() {
        let cap: Capsule3<(), ()> = Capsule3::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 2.0), 1.0);
        assert!(cap.contains_point(Vec3::new(0.5, 0.0, 1.0)));
        assert!(!cap.contains_point(Vec3::new(1.01, 0.0, 1.0)));
        assert!((cap.distance_to_point(Vec3::new(0.0, 3.0, 1.0)) - 2.0).abs() < 1e-6);

        let cp = cap.closest_point(Vec3::new(0.0, 3.0, 1.0));
        assert!((cap.segment().distance_to_point(cp) - 1.0).abs() < 1e-5);
    }
}