//! ---------------------------------------------------- //! ---------------------------------------------------- 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; inline for (0..Row) |r| { var sum: T = @as(T, 0); inline for (0..Col) |c| { 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; } }; }