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;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|