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 = "mat2")]

use gemath::*;
use crate::vec2::{Vec2, Meters, Pixels, World, Local, Screen};

#[cfg(test)]
mod tests {
    use super::*;
    const EPSILON: f32 = 1e-6;

    #[test]
    fn test_mat2_new() {
        let m: Mat2<(), ()> = Mat2::new(Vec2::<(), ()>::new(1.0, 2.0), Vec2::<(), ()>::new(3.0, 4.0));
        assert_eq!(m.x_col, Vec2::<(), ()>::new(1.0, 2.0));
        assert_eq!(m.y_col, Vec2::<(), ()>::new(3.0, 4.0));
    }

    #[test]
    fn test_mat2_zero() {
        let m: Mat2<(), ()> = Mat2::ZERO;
        assert_eq!(m.x_col, Vec2::<(), ()>::ZERO);
        assert_eq!(m.y_col, Vec2::<(), ()>::ZERO);
    }

    #[test]
    fn test_mat2_identity() {
        let m: Mat2<(), ()> = Mat2::IDENTITY;
        assert_eq!(m.x_col, Vec2::<(), ()>::new(1.0, 0.0));
        assert_eq!(m.y_col, Vec2::<(), ()>::new(0.0, 1.0));
    }

    #[test]
    fn test_mat2_from_cols_array() {
        let m: Mat2<(), ()> = Mat2::from_cols_array(&[1.0, 2.0, 3.0, 4.0]);
        assert_eq!(m.x_col, Vec2::<(), ()>::new(1.0, 2.0));
        assert_eq!(m.y_col, Vec2::<(), ()>::new(3.0, 4.0));
    }

    #[test]
    fn test_mat2_from_rows() {
        let m: Mat2<(), ()> = Mat2::from_rows(1.0, 3.0, 2.0, 4.0);
        assert_eq!(m.x_col, Vec2::<(), ()>::new(1.0, 2.0));
        assert_eq!(m.y_col, Vec2::<(), ()>::new(3.0, 4.0));
    }

    #[test]
    fn test_mat2_determinant() {
        let m: Mat2<(), ()> = Mat2::from_rows(1.0, 2.0, 3.0, 4.0);
        assert_eq!(m.determinant(), 1.0 * 4.0 - 2.0 * 3.0);
        let m_singular: Mat2<(), ()> = Mat2::from_rows(1.0, 2.0, 2.0, 4.0);
        assert_eq!(m_singular.determinant(), 0.0);
    }

    #[test]
    fn test_mat2_transpose() {
        let m: Mat2<(), ()> = Mat2::from_rows(1.0, 2.0, 3.0, 4.0);
        let mt = m.transpose();
        assert_eq!(mt.x_col, Vec2::<(), ()>::new(1.0, 2.0));
        assert_eq!(mt.y_col, Vec2::<(), ()>::new(3.0, 4.0));
    }

    #[test]
    fn test_mat2_inverse() {
        let m: Mat2<(), ()> = Mat2::from_rows(1.0, 2.0, 3.0, 4.0);
        let m_inv = m.inverse().unwrap();
        assert!((m_inv.x_col.x - -2.0).abs() < 1e-6);
        assert!((m_inv.x_col.y - 1.5).abs() < 1e-6);
        assert!((m_inv.y_col.x - 1.0).abs() < 1e-6);
        assert!((m_inv.y_col.y - -0.5).abs() < 1e-6);

        let m_singular: Mat2<(), ()> = Mat2::from_rows(1.0, 2.0, 2.0, 4.0);
        assert!(m_singular.inverse().is_none());
    }

    #[test]
    fn test_mat2_try_inverse_alias() {
        let m: Mat2<(), ()> = Mat2::from_rows(1.0, 2.0, 3.0, 4.0);
        assert_eq!(m.try_inverse(), m.inverse());

        let m_singular: Mat2<(), ()> = Mat2::from_rows(1.0, 2.0, 2.0, 4.0);
        assert_eq!(m_singular.try_inverse(), None);
    }

    #[test]
    fn test_mat2_eq() {
        let m1: Mat2<(), ()> = Mat2::new(Vec2::<(), ()>::new(1.0, 2.0), Vec2::<(), ()>::new(3.0, 4.0));
        let m2: Mat2<(), ()> = Mat2::new(Vec2::<(), ()>::new(1.0, 2.0), Vec2::<(), ()>::new(3.0, 4.0));
        let m3: Mat2<(), ()> = Mat2::new(Vec2::<(), ()>::new(5.0, 6.0), Vec2::<(), ()>::new(7.0, 8.0));
        assert_eq!(m1, m2);
        assert_ne!(m1, m3);
    }

    #[test]
    fn test_mat2_add() {
        let m1: Mat2<(), ()> = Mat2::from_rows(1.0, 2.0, 3.0, 4.0);
        let m2: Mat2<(), ()> = Mat2::from_rows(5.0, 6.0, 7.0, 8.0);
        let expected: Mat2<(), ()> = Mat2::from_rows(6.0, 8.0, 10.0, 12.0);
        assert_eq!(m1 + m2, expected);
    }

    #[test]
    fn test_mat2_sub() {
        let m1: Mat2<(), ()> = Mat2::from_rows(5.0, 6.0, 7.0, 8.0);
        let m2: Mat2<(), ()> = Mat2::from_rows(1.0, 2.0, 3.0, 4.0);
        let expected: Mat2<(), ()> = Mat2::from_rows(4.0, 4.0, 4.0, 4.0);
        assert_eq!(m1 - m2, expected);
    }

    #[test]
    fn test_mat2_mul_scalar() {
        let m: Mat2<(), ()> = Mat2::from_rows(1.0, 2.0, 3.0, 4.0);
        let expected: Mat2<(), ()> = Mat2::from_rows(2.0, 4.0, 6.0, 8.0);
        assert_eq!(m * 2.0, expected);
        assert_eq!(2.0 * m, expected);
    }

    #[test]
    fn test_mat2_mul_vec2() {
        let m: Mat2<(), ()> = Mat2::from_rows(1.0, 2.0, 3.0, 4.0); // x_col=(1,3), y_col=(2,4)
        let v: Vec2<(), ()> = Vec2::new(5.0, 6.0);
        let expected: Vec2<(), ()> = Vec2::new(17.0, 39.0);
        assert_eq!(m * v, expected);
    }

    #[test]
    fn test_mat2_mul_mat2() {
        let m1: Mat2<(), ()> = Mat2::from_rows(1.0, 2.0, 3.0, 4.0); // x1=(1,3) y1=(2,4)
        let m2: Mat2<(), ()> = Mat2::from_rows(5.0, 6.0, 7.0, 8.0); // x2=(5,7) y2=(6,8)
        let expected: Mat2<(), ()> = Mat2::new(Vec2::new(19.0, 43.0), Vec2::new(22.0, 50.0));
        assert_eq!(m1 * m2, expected);
    }

    #[test]
    fn test_mat2_row_col_accessors() {
        let mut m: Mat2<(), ()> = Mat2::from_rows(1.0, 2.0, 3.0, 4.0);
        assert_eq!(m.col(0), Some(Vec2::<(), ()>::new(1.0, 3.0)));
        assert_eq!(m.col(1), Some(Vec2::<(), ()>::new(2.0, 4.0)));
        assert_eq!(m.row(0), Some(Vec2::<(), ()>::new(1.0, 2.0)));
        assert_eq!(m.row(1), Some(Vec2::<(), ()>::new(3.0, 4.0)));
        m.set_col(0, Vec2::<(), ()>::new(9.0, 8.0));
        assert_eq!(m.col(0), Some(Vec2::<(), ()>::new(9.0, 8.0)));
        m.set_row(1, Vec2::<(), ()>::new(7.0, 6.0));
        assert_eq!(m.row(1), Some(Vec2::<(), ()>::new(7.0, 6.0)));
    }

    #[test]
    fn test_mat2_is_orthonormal() {
        let m: Mat2<(), ()> = Mat2::IDENTITY;
        assert!(m.is_orthonormal());
        let m2: Mat2<(), ()> = Mat2::from_rows(1.0, 0.0, 0.0, -1.0); // 90 deg rotation
        assert!(m2.is_orthonormal());
        let m3: Mat2<(), ()> = Mat2::from_rows(2.0, 0.0, 0.0, 2.0); // Scaled
        assert!(!m3.is_orthonormal());
    }

    #[test]
    fn test_mat2_from_shear() {
        let shx = 2.0;
        let shy = 3.0;
        let m: Mat2<(), ()> = Mat2::from_shear(shx, shy);
        assert_eq!(m.x_col, Vec2::<(), ()>::new(1.0, 3.0));
        assert_eq!(m.y_col, Vec2::<(), ()>::new(2.0, 1.0));
    }

    #[test]
    fn test_mat2_from_rotation() {
        let m: Mat2<(), ()> = Mat2::from_rotation(Radians(core::f32::consts::FRAC_PI_2));
        assert!(m.is_orthonormal());

        let v = Vec2::<(), ()>::new(1.0, 0.0);
        let v_rot = m * v;
        assert!((v_rot.x - 0.0).abs() < EPSILON);
        assert!((v_rot.y - 1.0).abs() < EPSILON);
    }

    #[test]
    fn test_mat2_from_rotation_deg() {
        let m: Mat2<(), ()> = Mat2::from_rotation_deg(Degrees(90.0));
        let v = Vec2::<(), ()>::new(1.0, 0.0);
        let v_rot = m * v;
        assert!((v_rot.x - 0.0).abs() < EPSILON);
        assert!((v_rot.y - 1.0).abs() < EPSILON);
    }

    #[test]
    fn test_mat2_from_scale() {
        let m: Mat2<(), ()> = Mat2::from_scale(Vec2::new(2.0, 3.0));
        let v = Vec2::<(), ()>::new(4.0, 5.0);
        let v_scaled = m * v;
        assert_eq!(v_scaled, Vec2::new(8.0, 15.0));
        assert_eq!(m.determinant(), 6.0);
    }

    #[test]
    fn test_mat2_from_scale_preserves_unit_and_space() {
        let m: Mat2<Meters, World> = Mat2::from_scale(Vec2::new(2.0, 3.0));
        let v: Vec2<Meters, World> = Vec2::new(4.0, 5.0);
        let out = m * v;
        assert_eq!(out, Vec2::new(8.0, 15.0));
    }
}

// --- Compile-time (const) tests/examples for Mat2 ---
const _CONST_MAT2_ID: Mat2<(), ()> = Mat2::IDENTITY;
const _CONST_MAT2_ZERO: Mat2<(), ()> = Mat2::ZERO;
const _CONST_MAT2_NEW: Mat2<(), ()> = Mat2::new(Vec2::<(), ()>::new(1.0, 2.0), Vec2::<(), ()>::new(3.0, 4.0));
const _CONST_MAT2_COLS: Mat2<(), ()> = Mat2::from_cols_array(&[1.0, 2.0, 3.0, 4.0]);
const _CONST_MAT2_ROWS: Mat2<(), ()> = Mat2::from_rows(1.0, 3.0, 2.0, 4.0);
const _CONST_MAT2_DET: f32 = _CONST_MAT2_COLS.determinant();
const _CONST_MAT2_TRANSPOSE: Mat2<(), ()> = _CONST_MAT2_COLS.transpose();
const _CONST_MAT2_SHEAR: Mat2<(), ()> = Mat2::from_shear(2.0, 3.0);
const _CONST_MAT2_ROW0: Option<Vec2<(), ()>> = _CONST_MAT2_COLS.row(0);
const _CONST_MAT2_ROW1: Option<Vec2<(), ()>> = _CONST_MAT2_COLS.row(1);
const _CONST_MAT2_COL0: Option<Vec2<(), ()>> = _CONST_MAT2_COLS.col(0);
const _CONST_MAT2_COL1: Option<Vec2<(), ()>> = _CONST_MAT2_COLS.col(1);

const _: () = {
    // Compile-time assertions for const-everything
    assert!(_CONST_MAT2_ID.x_col.x == 1.0 && _CONST_MAT2_ID.y_col.y == 1.0);
    assert!(_CONST_MAT2_ZERO.x_col.x == 0.0 && _CONST_MAT2_ZERO.y_col.y == 0.0);
    assert!(_CONST_MAT2_NEW.x_col.x == 1.0 && _CONST_MAT2_NEW.x_col.y == 2.0);
    assert!(_CONST_MAT2_NEW.y_col.x == 3.0 && _CONST_MAT2_NEW.y_col.y == 4.0);
    assert!(_CONST_MAT2_COLS.x_col.x == 1.0 && _CONST_MAT2_COLS.x_col.y == 2.0);
    assert!(_CONST_MAT2_COLS.y_col.x == 3.0 && _CONST_MAT2_COLS.y_col.y == 4.0);
    assert!(_CONST_MAT2_ROWS.x_col.x == 1.0 && _CONST_MAT2_ROWS.x_col.y == 2.0);
    assert!(_CONST_MAT2_ROWS.y_col.x == 3.0 && _CONST_MAT2_ROWS.y_col.y == 4.0);
    assert!(_CONST_MAT2_DET == -2.0);
    assert!(_CONST_MAT2_TRANSPOSE.x_col.x == 1.0 && _CONST_MAT2_TRANSPOSE.x_col.y == 3.0);
    assert!(_CONST_MAT2_TRANSPOSE.y_col.x == 2.0 && _CONST_MAT2_TRANSPOSE.y_col.y == 4.0);
    assert!(_CONST_MAT2_SHEAR.x_col.x == 1.0 && _CONST_MAT2_SHEAR.x_col.y == 3.0);
    assert!(_CONST_MAT2_SHEAR.y_col.x == 2.0 && _CONST_MAT2_SHEAR.y_col.y == 1.0);
    // Option values for row/col
    match _CONST_MAT2_ROW0 { Some(v) => assert!(v.x == 1.0 && v.y == 3.0), None => panic!("row0") }
    match _CONST_MAT2_ROW1 { Some(v) => assert!(v.x == 2.0 && v.y == 4.0), None => panic!("row1") }
    match _CONST_MAT2_COL0 { Some(v) => assert!(v.x == 1.0 && v.y == 2.0), None => panic!("col0") }
    match _CONST_MAT2_COL1 { Some(v) => assert!(v.x == 3.0 && v.y == 4.0), None => panic!("col1") }
};

// --- Compile-time (const) tests/examples for Mat2<Unit> type-level units ---
const _CONST_MAT2_METERS: Mat2<Meters, ()> = Mat2::new(
    Vec2::<Meters, ()>::new(1.0, 2.0),
    Vec2::<Meters, ()>::new(3.0, 4.0),
);
const _CONST_MAT2_PIXELS: Mat2<Pixels, ()> = Mat2::new(
    Vec2::<Pixels, ()>::new(10.0, 20.0),
    Vec2::<Pixels, ()>::new(30.0, 40.0),
);

const fn _make_mat2_meters() -> Mat2<Meters, ()> {
    Mat2::from_rows(1.0, 3.0, 2.0, 4.0)
}
const fn _make_mat2_pixels() -> Mat2<Pixels, ()> {
    Mat2::from_rows(10.0, 30.0, 20.0, 40.0)
}

const _CONST_MAT2_METERS2: Mat2<Meters, ()> = _make_mat2_meters();
const _CONST_MAT2_PIXELS2: Mat2<Pixels, ()> = _make_mat2_pixels();

// Compile-time type safety: the following line would fail to compile if uncommented
// const _FAIL_MAT2: Mat2<Meters, ()> = Mat2::<Pixels, ()>::new(Vec2::new(1.0, 2.0), Vec2::new(3.0, 4.0));
// const _FAIL_MAT22: Mat2<Meters, ()> = Mat2::<Meters, ()>::new(Vec2::<Meters, ()>::new(1.0, 2.0), Vec2::<Pixels, ()>::new(3.0, 4.0));

// --- Compile-time (const) tests/examples for Mat2<Unit, Space> phantom coordinate spaces ---
const _CONST_MAT2_WORLD: Mat2<(), World> = Mat2::new(
    Vec2::<(), World>::new(1.0, 2.0),
    Vec2::<(), World>::new(3.0, 4.0),
);
const _CONST_MAT2_LOCAL: Mat2<(), Local> = Mat2::new(
    Vec2::<(), Local>::new(5.0, 6.0),
    Vec2::<(), Local>::new(7.0, 8.0),
);
const _CONST_MAT2_SCREEN: Mat2<(), Screen> = Mat2::new(
    Vec2::<(), Screen>::new(9.0, 10.0),
    Vec2::<(), Screen>::new(11.0, 12.0),
);

// Compile-time type safety: the following line would fail to compile if uncommented
// const FAIL_MAT2_WORLD_LOCAL: Mat2<(), World> = Mat2::<(), Local>::new(Vec2::new(1.0, 2.0), Vec2::new(3.0, 4.0));

const _: () = {
    // Compile-time assertions for type-level units
    assert!(_CONST_MAT2_METERS.x_col.x == 1.0 && _CONST_MAT2_METERS.y_col.y == 4.0);
    assert!(_CONST_MAT2_PIXELS.x_col.x == 10.0 && _CONST_MAT2_PIXELS.y_col.y == 40.0);
    assert!(_CONST_MAT2_METERS2.x_col.x == 1.0 && _CONST_MAT2_METERS2.y_col.y == 4.0);
    assert!(_CONST_MAT2_PIXELS2.x_col.x == 10.0 && _CONST_MAT2_PIXELS2.y_col.y == 40.0);
    // Compile-time assertions for phantom coordinate spaces
    assert!(_CONST_MAT2_WORLD.x_col.x == 1.0 && _CONST_MAT2_WORLD.y_col.y == 4.0);
    assert!(_CONST_MAT2_LOCAL.x_col.x == 5.0 && _CONST_MAT2_LOCAL.y_col.y == 8.0);
    assert!(_CONST_MAT2_SCREEN.x_col.x == 9.0 && _CONST_MAT2_SCREEN.y_col.y == 12.0);
};