NYXMath/src/mat.zig

371 lines
14 KiB
Zig
Raw Normal View History

2026-07-30 19:48:03 +01:00
//! ----------------------------------------------------
//! ----------------------------------------------------
const std = @import("std");
const Vec = @import("vec.zig").Vec;
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn Mat(
comptime T: type,
comptime Row: usize,
comptime Col: usize,
) type {
return struct {
const Self = @This();
//
// FIELDS
//
value: [Row][Col]T,
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn zero() Self {
var result: Self = undefined;
for (0..Row) |r| {
for (0..Col) |c| {
result.value[r][c] = @as(T, 0);
}
}
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn identity() Self {
comptime if (Row != Col) @compileError("identity only defined for square matrices");
var result: Self = undefined;
for (0..Row) |r| {
for (0..Col) |c| {
result.value[r][c] = if (r == c) @as(T, 1) else @as(T, 0);
}
}
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn fromRows(rows: [Row][Col]T) Self {
return .{ .value = rows };
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn fromCols(cols: [Col][Row]T) Self {
var result: Self = undefined;
for (0..Col) |c| {
for (0..Row) |r| {
result.value[r][c] = cols[c][r];
}
}
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn row(self: Self, index: usize) Vec(T, Col) {
return Vec(T, Col).new(self.value[index]);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn col(self: Self, index: usize) Vec(T, Row) {
var data: [Row]T = undefined;
for (0..Row) |r| {
data[r] = self.value[r][index];
}
return Vec(T, Row).new(data);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn add(self: Self, other: Self) Self {
var result: Self = undefined;
for (0..Row) |r| {
for (0..Col) |c| {
result.value[r][c] = self.value[r][c] + other.value[r][c];
}
}
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn sub(self: Self, other: Self) Self {
var result: Self = undefined;
for (0..Row) |r| {
for (0..Col) |c| {
result.value[r][c] = self.value[r][c] - other.value[r][c];
}
}
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn mul(self: Self, other: Self) Self {
comptime if (Row != Col) @compileError("direct matrix multiplication requires square matrices; use mulMat for non-square");
var result: Self = undefined;
for (0..Row) |r| {
for (0..Col) |c| {
var sum: T = @as(T, 0);
for (0..Col) |k| {
sum += self.value[r][k] * other.value[k][c];
}
result.value[r][c] = sum;
}
}
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn mulVec(self: Self, v: Vec(T, Col)) Vec(T, Row) {
var result: [Row]T = undefined;
2026-07-31 14:02:06 +01:00
inline for (0..Row) |r| {
2026-07-30 19:48:03 +01:00
var sum: T = @as(T, 0);
2026-07-31 14:02:06 +01:00
inline for (0..Col) |c| {
2026-07-30 19:48:03 +01:00
sum += self.value[r][c] * v.value[c];
}
result[r] = sum;
}
return Vec(T, Row).new(result);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn mulScalar(self: Self, scalar: T) Self {
var result: Self = undefined;
for (0..Row) |r| {
for (0..Col) |c| {
result.value[r][c] = self.value[r][c] * scalar;
}
}
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn divScalar(self: Self, scalar: T) Self {
var result: Self = undefined;
for (0..Row) |r| {
for (0..Col) |c| {
result.value[r][c] = self.value[r][c] / scalar;
}
}
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn transpose(self: Self) Mat(T, Col, Row) {
var result: Mat(T, Col, Row) = undefined;
for (0..Row) |r| {
for (0..Col) |c| {
result.value[c][r] = self.value[r][c];
}
}
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn determinant(self: Self) T {
comptime if (Row != Col or Row < 1) @compileError("determinant only defined for non-empty square matrices");
if (Row == 1) return self.value[0][0];
var det: T = @as(T, 0);
for (0..Col) |c| {
det += self.value[0][c] * self.cofactor(0, c);
}
return det;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn inverse(self: Self) Self {
comptime if (Row != Col) @compileError("inverse only defined for square matrices");
const det = self.determinant();
var result: Self = undefined;
for (0..Row) |r| {
for (0..Col) |c| {
result.value[r][c] = self.cofactor(c, r) / det;
}
}
return result;
}
//
// PRIVATE
//
/// ----------------------------------------------------
/// ----------------------------------------------------
inline fn minor(self: Self, r_idx: usize, c_idx: usize) Mat(T, Row - 1, Col - 1) {
comptime if (Row < 2 or Col < 2) @compileError("minor requires at least 2x2");
var result: Mat(T, Row - 1, Col - 1) = undefined;
var ri: usize = 0;
for (0..Row) |r| {
if (r == r_idx) continue;
var ci: usize = 0;
for (0..Col) |c| {
if (c == c_idx) continue;
result.value[ri][ci] = self.value[r][c];
ci += 1;
}
ri += 1;
}
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
inline fn cofactor(self: Self, r_idx: usize, c_idx: usize) T {
const sign: T = if ((r_idx + c_idx) % 2 == 0) @as(T, 1) else -@as(T, 1);
return sign * self.minor(r_idx, c_idx).determinant();
}
//
// 4X4 TRANSFORMS
//
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn translate(x: T, y: T, z: T) Self {
comptime if (Row != 4 or Col != 4) @compileError("translate only defined for Mat4x4");
var result = identity();
result.value[0][3] = x;
result.value[1][3] = y;
result.value[2][3] = z;
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn rotateX(angle: T) Self {
comptime if (Row != 4 or Col != 4) @compileError("rotateX only defined for Mat4x4");
const c = @cos(angle);
const s = @sin(angle);
var result = identity();
result.value[1][1] = c;
result.value[1][2] = -s;
result.value[2][1] = s;
result.value[2][2] = c;
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn rotateY(angle: T) Self {
comptime if (Row != 4 or Col != 4) @compileError("rotateY only defined for Mat4x4");
const c = @cos(angle);
const s = @sin(angle);
var result = identity();
result.value[0][0] = c;
result.value[0][2] = s;
result.value[2][0] = -s;
result.value[2][2] = c;
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn rotateZ(angle: T) Self {
comptime if (Row != 4 or Col != 4) @compileError("rotateZ only defined for Mat4x4");
const c = @cos(angle);
const s = @sin(angle);
var result = identity();
result.value[0][0] = c;
result.value[0][1] = -s;
result.value[1][0] = s;
result.value[1][1] = c;
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn scale(x: T, y: T, z: T) Self {
comptime if (Row != 4 or Col != 4) @compileError("scale only defined for Mat4x4");
var result = identity();
result.value[0][0] = x;
result.value[1][1] = y;
result.value[2][2] = z;
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn lookAt(eye: Vec(T, 3), target: Vec(T, 3), up: Vec(T, 3)) Self {
comptime if (Row != 4 or Col != 4) @compileError("lookAt only defined for Mat4x4");
const f = target.sub(eye).normalize();
const s = f.cross(up).normalize();
const u = s.cross(f);
var result = identity();
result.value[0][0] = s.value[0];
result.value[0][1] = s.value[1];
result.value[0][2] = s.value[2];
result.value[1][0] = u.value[0];
result.value[1][1] = u.value[1];
result.value[1][2] = u.value[2];
result.value[2][0] = -f.value[0];
result.value[2][1] = -f.value[1];
result.value[2][2] = -f.value[2];
result.value[0][3] = -s.dot(eye);
result.value[1][3] = -u.dot(eye);
result.value[2][3] = f.dot(eye);
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn perspective(fov: T, aspect: T, near: T, far: T) Self {
comptime if (Row != 4 or Col != 4) @compileError("perspective only defined for Mat4x4");
const f = @as(T, 1) / @tan(fov / @as(T, 2));
const range_inv = @as(T, 1) / (near - far);
var result = zero();
result.value[0][0] = f / aspect;
result.value[1][1] = -f;
result.value[2][2] = far * range_inv;
result.value[2][3] = near * far * range_inv;
result.value[3][2] = -@as(T, 1);
return result;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub inline fn orthographic(left: T, right: T, bottom: T, top: T, near: T, far: T) Self {
comptime if (Row != 4 or Col != 4) @compileError("orthographic only defined for Mat4x4");
const rml = @as(T, 1) / (right - left);
const tmb = @as(T, 1) / (top - bottom);
const fmn = @as(T, 1) / (far - near);
var result = identity();
result.value[0][0] = @as(T, 2) * rml;
result.value[1][1] = -@as(T, 2) * tmb;
result.value[2][2] = -fmn;
result.value[0][3] = -(right + left) * rml;
result.value[1][3] = (top + bottom) * tmb;
result.value[2][3] = -near * fmn;
return result;
}
};
}