gemath 0.1.0

Type-safe game math with type-level units/spaces, typed angles, and explicit fallible ops (plus optional geometry/collision).
Documentation
# Cookbook (copy/paste snippets)

All snippets are intended to be copy-pastable. Some require specific feature flags; each section lists the minimal required features.

## Units/spaces: “make invalid math not compile”

**Requires**: `vec2` (or `full`)

```rust
use gemath::{Meters, Pixels, Screen, Vec2, World};

let p_world: Vec2<Meters, World> = Vec2::new(10.0, 20.0);
let p_screen: Vec2<Pixels, Screen> = Vec2::new(640.0, 360.0);

// let _ = p_world + p_screen;
// ^ compile error: different Unit and Space tags
```

## 2D rotation (typed angles)

**Requires**: `vec2` (or `full`)

```rust
use gemath::{Degrees, Vec2};

let v: Vec2<(), ()> = Vec2::new(1.0, 0.0);
let v90 = v.rotate_deg(Degrees(90.0));
assert!((v90 - Vec2::new(0.0, 1.0)).length() < 1e-5);
```

## TRS compose / decompose (Mat4)

**Requires**: `mat4` + `quat` (or `full`)

```rust
use gemath::{Mat4, Quat, Radians, Vec3};

let t = Vec3::new(1.0, 2.0, 3.0);
let r: Quat<(), ()> = Quat::from_axis_angle_radians(
    Vec3::new(0.0, 0.0, 1.0),
    Radians(core::f32::consts::FRAC_PI_4),
);
let s = Vec3::new(2.0, 3.0, 4.0);

let m: Mat4<(), ()> = Mat4::from_trs(t, r, s);
let (t2, _r2, s2) = m.decompose();

assert!((t2 - t).length() < 1e-5);
assert!((s2 - s).length() < 1e-4);
```

## Camera look-at (left-handed)

**Requires**: `mat4` (or `full`)

```rust
use gemath::{Mat4, Vec3};

let eye = Vec3::new(0.0, 0.0, -5.0);
let target = Vec3::new(0.0, 0.0, 0.0);
let up = Vec3::new(0.0, 1.0, 0.0);

let view: Mat4<(), ()> = Mat4::look_at_lh(eye, target, up);

// The eye should map near the origin in view space.
let eye_vs = view.transform_point(eye);
assert!(eye_vs.length() < 1e-4);
```

## Raycasts with structured hit results

**Requires**: `collision` (or `full`)

```rust
use gemath::{ray2_circle_cast, Circle, Ray2, Vec2};

let circle: Circle<(), ()> = Circle::new(Vec2::new(0.0, 0.0), 1.0);
let ray: Ray2<(), ()> = Ray2::new(Vec2::new(-2.0, 0.0), Vec2::new(1.0, 0.0));

let hit = ray2_circle_cast(ray, circle).unwrap();
assert!((hit.t - 1.0).abs() < 1e-6);
assert!((hit.point - Vec2::new(-1.0, 0.0)).length() < 1e-6);
assert!((hit.normal - Vec2::new(-1.0, 0.0)).length() < 1e-6);
```

## Allocation-backed polygons (concave point-in-polygon)

**Requires**: `polygon2` + (`std` or `alloc`)

```rust
use gemath::{Polygon2, Vec2};

let poly: Polygon2<(), ()> = Polygon2::new(vec![
    Vec2::new(0.0, 0.0),
    Vec2::new(3.0, 0.0),
    Vec2::new(3.0, 2.0),
    Vec2::new(2.0, 2.0),
    Vec2::new(2.0, 3.0),
    Vec2::new(0.0, 3.0),
]);

assert!(poly.contains_point(Vec2::new(0.5, 2.5))); // inside
assert!(!poly.contains_point(Vec2::new(2.5, 2.5))); // in the removed notch
```