Numix
A small, ergonomic, zero-dependency math library for 2-D, 3-D and 4-D game and graphics work, written in Rust.
The entire public surface is two files: types.rs declares the plain structs, and vector.rs implements all the methods and operator overloads on them. There are no allocations — just lightweight, Copy vector and matrix types that do exactly what the names say.
Types
| Type | Fields | Use case |
|---|---|---|
Vec2<T> |
x, y: T |
2-D positions, directions, UVs |
Vec3<T> |
x, y, z: T |
3-D positions, normals, RGB colours |
Vec4<T> |
x, y, z, w: T |
Homogeneous coordinates, matrix rows |
Mat3x4<T> |
[Vec4<T>; 3] |
Compact affine transform (3 rows × 4 columns) |
Mat4x4<T> |
[Vec4<T>; 4] |
Full 4×4 transform matrix |
All types are generic over a scalar T. In practice you will use f32 for most geometry and i32 for integer grid work.
Installation
Add the crate to your Cargo.toml:
[]
= { = "../numix" }
Then import what you need:
use ;
Vec2<T>
Construction
new // from components
Arithmetic
All standard operators are implemented for any scalar T that satisfies the Scalar trait bound (Copy + Default + PartialEq + Add + Sub + Mul + Neg):
let a = new;
let b = new;
a + b // Vec2 { x: 4, y: 6 }
b - a // Vec2 { x: 2, y: 2 }
a * 2 // Vec2 { x: 2, y: 4 }
-a // Vec2 { x: -1, y: -2 }
Compound assignment requires T: AddAssign or T: SubAssign respectively:
let mut v = new;
v += new; // Vec2 { x: 4, y: 4 }
v -= new; // Vec2 { x: 3, y: 3 }
v *= 2; // Vec2 { x: 6, y: 6 } — requires T: MulAssign
Geometry
let a = new;
let b = new;
a.dot // 11 (1×3 + 2×4)
a.length_sq // 5 (1² + 2²), avoids sqrt
Swizzle
// Vec4::xyz() extracts the first three components as a Vec3
let v = new;
v.xyz // Vec3 { x: 1, y: 2, z: 3 }
Vec3<T>
Vec3<T> mirrors the Vec2<T> API and adds the 3-D cross product:
let x = new;
let y = new;
x.cross // Vec3 { x: 0, y: 0, z: 1 } (right-hand rule)
x.dot // 0
x.length_sq // 1
Cross product is anticommutative:
assert_eq!;
All arithmetic operators (+, -, *, unary -, +=, -=, *=) and Display work identically to Vec2<T>.
Vec4<T>
Vec4<T> adds a w component for homogeneous coordinates and matrix-row use:
let v = new;
v.dot // 1
v.length_sq // 30 (1+4+9+16)
v.xyz // Vec3 { x: 1, y: 2, z: 3 }
All arithmetic operators (+, -, *, unary -, +=, -=, *=) and Display are implemented.
Mat4x4<T>
A row-major 4×4 matrix. mat[r] is a Vec4<T> holding the four elements of row r.
let identity = from;
let v = new;
identity * v // Vec4 { x: 1, y: 2, z: 3, w: 4 }
You can also call .mul_vec4(v) directly if you prefer the method form.
Mat3x4<T>
A row-major 3×4 matrix (3 rows, 4 columns). The implicit last row is [0, 0, 0, 1], making it a compact representation for affine transforms that avoids storing the constant row.
mat[r] is a Vec4<T> holding the four elements of row r.
// Affine translation: rows are [1 0 0 tx], [0 1 0 ty], [0 0 1 tz]
let m = from;
// A point (w=1) is translated
m * new // Vec3 { x: 5, y: 6, z: 7 }
// A direction (w=0) is not
m * new // Vec3 { x: 1, y: 0, z: 0 }
You can also call .mul_vec4(v) directly.
Display
All four types implement Display. Formatting delegates to the inner T, so output style depends on the scalar type:
println!; // (1, 2)
println!; // (0.5, 1, -2.5)
println!; // (1, 4)
println!; // (1, 4, 7)
Running the tests
The test suite covers construction, arithmetic operators, compound assignment, dot products, cross products, xyz() extraction, matrix–vector multiplication, identity and translation matrices, and From array conversions.
Design notes
Generic over T, not specialised per precision. The Scalar trait captures the minimal operations a component type must support (Copy + Default + PartialEq + Add + Sub + Mul + Neg). In practice you will use f32 for geometry and i32 for integer grid work, but the same struct serves both without duplication.
No SIMD. The library targets readability and correctness first. If you are in a hot loop doing thousands of vectors per frame, profile before reaching for intrinsics — the compiler already auto-vectorises many of these patterns at opt-level = 3.
Copy everywhere. Vectors are small enough that passing by value is always cheaper than passing a reference, and it eliminates a whole class of borrow-checker friction in rendering code.
Mat3x4 uses a row-per-Vec4 layout. Storing three Vec4 rows makes mul_vec4 a clean sequence of three dot products and keeps the translation components in the w slot of each row — no special-casing needed.
Mat3x4::mul_vec4 respects w. Multiplying by a point (w = 1) applies the full affine transform including translation. Multiplying by a direction (w = 0) applies only the linear part. This falls out of the dot-product formulation automatically.