diff --git a/src/math/main.zig b/src/math/main.zig index f4a73f8663..5cdc8c9ccf 100644 --- a/src/math/main.zig +++ b/src/math/main.zig @@ -49,71 +49,92 @@ const ray = @import("ray.zig"); /// Public namespaces pub const collision = @import("collision.zig"); +pub const Vec = vec.Vec; +pub const Mat = mat.Mat; + /// Standard f32 precision types pub const Vec2 = vec.Vec2(f32); pub const Vec3 = vec.Vec3(f32); pub const Vec4 = vec.Vec4(f32); +pub fn VecN(comptime length: usize) type { + return Vec(f32, length); +} pub const Quat = q.Quat(f32); pub const Mat2x2 = mat.Mat2x2(f32); pub const Mat3x3 = mat.Mat3x3(f32); pub const Mat4x4 = mat.Mat4x4(f32); +pub fn MatMxN(comptime rows: usize, comptime cols: usize) type { + return Mat(f32, rows, cols); +} pub const Ray = ray.Ray3(f32); /// Half-precision f16 types pub const Vec2h = vec.Vec2(f16); pub const Vec3h = vec.Vec3(f16); pub const Vec4h = vec.Vec4(f16); +pub fn VecNh(comptime length: usize) type { + return Vec(f16, length); +} pub const Quath = q.Quat(f16); pub const Mat2x2h = mat.Mat2x2(f16); pub const Mat3x3h = mat.Mat3x3(f16); pub const Mat4x4h = mat.Mat4x4(f16); +pub fn MatMxNh(comptime rows: usize, comptime cols: usize) type { + return Mat(f16, rows, cols); +} pub const Rayh = ray.Ray3(f16); /// Double-precision f64 types pub const Vec2d = vec.Vec2(f64); pub const Vec3d = vec.Vec3(f64); pub const Vec4d = vec.Vec4(f64); +pub fn VecNd(comptime length: usize) type { + return Vec(f64, length); +} pub const Quatd = q.Quat(f64); pub const Mat2x2d = mat.Mat2x2(f64); pub const Mat3x3d = mat.Mat3x3(f64); pub const Mat4x4d = mat.Mat4x4(f64); +pub fn MatMxNd(comptime rows: usize, comptime cols: usize) type { + return Mat(f64, rows, cols); +} pub const Rayd = ray.Ray3(f64); /// Standard f32 precision initializers -pub const vec2 = Vec2.init; -pub const vec3 = Vec3.init; -pub const vec4 = Vec4.init; -pub const vec2FromInt = Vec2.fromInt; -pub const vec3FromInt = Vec3.fromInt; -pub const vec4FromInt = Vec4.fromInt; +pub const vec2 = Vec2.init2; +pub const vec3 = Vec3.init3; +pub const vec4 = Vec4.init4; +pub const vec2FromInt = Vec2.fromInt2; +pub const vec3FromInt = Vec3.fromInt3; +pub const vec4FromInt = Vec4.fromInt4; pub const quat = Quat.init; -pub const mat2x2 = Mat2x2.init; -pub const mat3x3 = Mat3x3.init; -pub const mat4x4 = Mat4x4.init; +pub const mat2x2 = Mat2x2.init2; +pub const mat3x3 = Mat3x3.init3; +pub const mat4x4 = Mat4x4.init4; /// Half-precision f16 initializers -pub const vec2h = Vec2h.init; -pub const vec3h = Vec3h.init; -pub const vec4h = Vec4h.init; -pub const vec2hFromInt = Vec2h.fromInt; -pub const vec3hFromInt = Vec3h.fromInt; -pub const vec4hFromInt = Vec4h.fromInt; +pub const vec2h = Vec2h.init2; +pub const vec3h = Vec3h.init3; +pub const vec4h = Vec4h.init4; +pub const vec2hFromInt = Vec2h.fromInt2; +pub const vec3hFromInt = Vec3h.fromInt3; +pub const vec4hFromInt = Vec4h.fromInt4; pub const quath = Quath.init; -pub const mat2x2h = Mat2x2h.init; -pub const mat3x3h = Mat3x3h.init; -pub const mat4x4h = Mat4x4h.init; +pub const mat2x2h = Mat2x2h.init2; +pub const mat3x3h = Mat3x3h.init3; +pub const mat4x4h = Mat4x4h.init4; /// Double-precision f64 initializers -pub const vec2d = Vec2d.init; -pub const vec3d = Vec3d.init; -pub const vec4d = Vec4d.init; -pub const vec2dFromInt = Vec2d.fromInt; -pub const vec3dFromInt = Vec3d.fromInt; -pub const vec4dFromInt = Vec4d.fromInt; +pub const vec2d = Vec2d.init2; +pub const vec3d = Vec3d.init3; +pub const vec4d = Vec4d.init4; +pub const vec2dFromInt = Vec2d.fromInt2; +pub const vec3dFromInt = Vec3d.fromInt3; +pub const vec4dFromInt = Vec4d.fromInt4; pub const quatd = Quatd.init; -pub const mat2x2d = Mat2x2d.init; -pub const mat3x3d = Mat3x3d.init; -pub const mat4x4d = Mat4x4d.init; +pub const mat2x2d = Mat2x2d.init2; +pub const mat3x3d = Mat3x3d.init3; +pub const mat4x4d = Mat4x4d.init4; test { testing.refAllDeclsRecursive(@This()); diff --git a/src/math/mat.zig b/src/math/mat.zig index 6e430a8329..ec9b2a82bb 100644 --- a/src/math/mat.zig +++ b/src/math/mat.zig @@ -6,128 +6,19 @@ const math = mach.math; const vec = @import("vec.zig"); const quat = @import("quat.zig"); -pub fn Mat2x2( - comptime Scalar: type, -) type { - return extern struct { - /// The column vectors of the matrix. - /// - /// Mach matrices use [column-major storage and column-vectors](https://machengine.org/engine/math/matrix-storage/). - /// The translation vector is stored in contiguous memory elements 12, 13, 14: - /// - /// ``` - /// [4]Vec4{ - /// vec4( 1, 0, 0, 0), - /// vec4( 0, 1, 0, 0), - /// vec4( 0, 0, 1, 0), - /// vec4(tx, ty, tz, tw), - /// } - /// ``` - /// - /// Use the init() constructor to write code which visually matches the same layout as you'd - /// see used in scientific / maths communities. - v: [cols]Vec, - - /// The number of columns, e.g. Mat3x4.cols == 3 - pub const cols = 2; - - /// The number of rows, e.g. Mat3x4.rows == 4 - pub const rows = 2; - - /// The scalar type of this matrix, e.g. Mat3x3.T == f32 - pub const T = Scalar; - - /// The underlying Vec type, e.g. Mat3x3.Vec == Vec3 - pub const Vec = vec.Vec2(Scalar); - - /// The Vec type corresponding to the number of rows, e.g. Mat3x3.RowVec == Vec3 - pub const RowVec = Vec; - - /// The Vec type corresponding to the numebr of cols, e.g. Mat3x4.ColVec = Vec4 - pub const ColVec = Vec; - - const Matrix = @This(); - - const Shared = MatShared(RowVec, ColVec, Matrix); - - /// Identity matrix - pub const ident = Matrix.init( - &RowVec.init(1, 0), - &RowVec.init(0, 1), - ); - - /// Constructs a 2x2 matrix with the given rows. For example to write a translation - /// matrix like in the left part of this equation: - /// - /// ``` - /// |1 tx| |x | |x+y*tx| - /// |0 ty| |y=1| = |ty | - /// ``` - /// - /// You would write it with the same visual layout: - /// - /// ``` - /// const m = Mat2x2.init( - /// vec3(1, tx), - /// vec3(0, ty), - /// ); - /// ``` - /// - /// Note that Mach matrices use [column-major storage and column-vectors](https://machengine.org/engine/math/matrix-storage/). - pub inline fn init(r0: *const RowVec, r1: *const RowVec) Matrix { - return .{ .v = [_]Vec{ - Vec.init(r0.x(), r1.x()), - Vec.init(r0.y(), r1.y()), - } }; - } - - /// Returns the row `i` of the matrix. - pub inline fn row(m: *const Matrix, i: usize) RowVec { - // Note: we inline RowVec.init manually here as it is faster in debug builds. - // return RowVec.init(m.v[0].v[i], m.v[1].v[i]); - return .{ .v = .{ m.v[0].v[i], m.v[1].v[i] } }; - } - - /// Returns the column `i` of the matrix. - pub inline fn col(m: *const Matrix, i: usize) RowVec { - // Note: we inline RowVec.init manually here as it is faster in debug builds. - // return RowVec.init(m.v[i].v[0], m.v[i].v[1]); - return .{ .v = .{ m.v[i].v[0], m.v[i].v[1] } }; - } - - /// Transposes the matrix. - pub inline fn transpose(m: *const Matrix) Matrix { - return .{ .v = [_]Vec{ - Vec.init(m.v[0].v[0], m.v[1].v[0]), - Vec.init(m.v[0].v[1], m.v[1].v[1]), - } }; - } - - /// Constructs a 1D matrix which scales each dimension by the given scalar. - pub inline fn scaleScalar(t: Vec.T) Matrix { - return init( - &RowVec.init(t, 0), - &RowVec.init(0, 1), - ); - } +pub fn Mat2x2(Scalar: type) type { + return Mat(Scalar, 2, 2); +} - /// Constructs a 1D matrix which translates coordinates by the given scalar. - pub inline fn translateScalar(t: Vec.T) Matrix { - return init( - &RowVec.init(1, t), - &RowVec.init(0, 1), - ); - } +pub fn Mat3x3(Scalar: type) type { + return Mat(Scalar, 3, 3); +} - pub const mul = Shared.mul; - pub const mulVec = Shared.mulVec; - pub const format = Shared.format; - }; +pub fn Mat4x4(Scalar: type) type { + return Mat(Scalar, 4, 4); } -pub fn Mat3x3( - comptime Scalar: type, -) type { +pub fn Mat(Scalar: type, comptime m: usize, comptime n: usize) type { return extern struct { /// The column vectors of the matrix. /// @@ -145,180 +36,33 @@ pub fn Mat3x3( /// /// Use the init() constructor to write code which visually matches the same layout as you'd /// see used in scientific / maths communities. - v: [cols]Vec, - - /// The number of columns, e.g. Mat3x4.cols == 3 - pub const cols = 3; - - /// The number of rows, e.g. Mat3x4.rows == 4 - pub const rows = 3; + v: [cols]ColVec, /// The scalar type of this matrix, e.g. Mat3x3.T == f32 pub const T = Scalar; - /// The underlying Vec type, e.g. Mat3x3.Vec == Vec3 - pub const Vec = vec.Vec3(Scalar); - - /// The Vec type corresponding to the number of rows, e.g. Mat3x3.RowVec == Vec3 - pub const RowVec = Vec; - - /// The Vec type corresponding to the numebr of cols, e.g. Mat3x4.ColVec = Vec4 - pub const ColVec = Vec; + /// The Vec type corresponding to the number of rows, e.g. Mat3x4.RowVec == Vec3 + pub const RowVec = vec.Vec(T, cols); - const Matrix = @This(); - - const Shared = MatShared(RowVec, ColVec, Matrix); - - /// Identity matrix - pub const ident = Matrix.init( - &RowVec.init(1, 0, 0), - &RowVec.init(0, 1, 0), - &RowVec.init(0, 0, 1), - ); - - /// Constructs a 3x3 matrix with the given rows. For example to write a translation - /// matrix like in the left part of this equation: - /// - /// ``` - /// |1 0 tx| |x | |x+z*tx| - /// |0 1 ty| |y | = |y+z*ty| - /// |0 0 tz| |z=1| |tz | - /// ``` - /// - /// You would write it with the same visual layout: - /// - /// ``` - /// const m = Mat3x3.init( - /// vec3(1, 0, tx), - /// vec3(0, 1, ty), - /// vec3(0, 0, tz), - /// ); - /// ``` - /// - /// Note that Mach matrices use [column-major storage and column-vectors](https://machengine.org/engine/math/matrix-storage/). - pub inline fn init(r0: *const RowVec, r1: *const RowVec, r2: *const RowVec) Matrix { - return .{ .v = [_]Vec{ - Vec.init(r0.x(), r1.x(), r2.x()), - Vec.init(r0.y(), r1.y(), r2.y()), - Vec.init(r0.z(), r1.z(), r2.z()), - } }; - } - - /// Returns the row `i` of the matrix. - pub inline fn row(m: *const Matrix, i: usize) RowVec { - // Note: we inline RowVec.init manually here as it is faster in debug builds. - // return RowVec.init(m.v[0].v[i], m.v[1].v[i], m.v[2].v[i]); - return .{ .v = .{ m.v[0].v[i], m.v[1].v[i], m.v[2].v[i] } }; - } - - /// Returns the column `i` of the matrix. - pub inline fn col(m: *const Matrix, i: usize) RowVec { - // Note: we inline RowVec.init manually here as it is faster in debug builds. - // return RowVec.init(m.v[i].v[0], m.v[i].v[1], m.v[i].v[2]); - return .{ .v = .{ m.v[i].v[0], m.v[i].v[1], m.v[i].v[2] } }; - } - - /// Transposes the matrix. - pub inline fn transpose(m: *const Matrix) Matrix { - return .{ .v = [_]Vec{ - Vec.init(m.v[0].v[0], m.v[1].v[0], m.v[2].v[0]), - Vec.init(m.v[0].v[1], m.v[1].v[1], m.v[2].v[1]), - Vec.init(m.v[0].v[2], m.v[1].v[2], m.v[2].v[2]), - } }; - } - - /// Constructs a 2D matrix which scales each dimension by the given vector. - pub inline fn scale(s: math.Vec2) Matrix { - return init( - &RowVec.init(s.x(), 0, 0), - &RowVec.init(0, s.y(), 0), - &RowVec.init(0, 0, 1), - ); - } - - /// Constructs a 2D matrix which scales each dimension by the given scalar. - pub inline fn scaleScalar(t: Vec.T) Matrix { - return scale(math.Vec2.splat(t)); - } - - /// Constructs a 2D matrix which translates coordinates by the given vector. - pub inline fn translate(t: math.Vec2) Matrix { - return init( - &RowVec.init(1, 0, t.x()), - &RowVec.init(0, 1, t.y()), - &RowVec.init(0, 0, 1), - ); - } - - /// Constructs a 2D matrix which translates coordinates by the given scalar. - pub inline fn translateScalar(t: Vec.T) Matrix { - return translate(math.Vec2.splat(t)); - } - - /// Returns the translation component of the matrix. - pub inline fn translation(t: Matrix) math.Vec2 { - return math.Vec2.init(t.v[2].x(), t.v[2].y()); - } - - pub const mul = Shared.mul; - pub const mulVec = Shared.mulVec; - pub const format = Shared.format; - }; -} - -pub fn Mat4x4( - comptime Scalar: type, -) type { - return extern struct { - /// The column vectors of the matrix. - /// - /// Mach matrices use [column-major storage and column-vectors](https://machengine.org/engine/math/matrix-storage/). - /// The translation vector is stored in contiguous memory elements 12, 13, 14: - /// - /// ``` - /// [4]Vec4{ - /// vec4( 1, 0, 0, 0), - /// vec4( 0, 1, 0, 0), - /// vec4( 0, 0, 1, 0), - /// vec4(tx, ty, tz, tw), - /// } - /// ``` - /// - /// Use the init() constructor to write code which visually matches the same layout as you'd - /// see used in scientific / maths communities. - v: [cols]Vec, - - /// The number of columns, e.g. Mat3x4.cols == 3 - pub const cols = 4; - - /// The number of rows, e.g. Mat3x4.rows == 4 - pub const rows = 4; - - /// The scalar type of this matrix, e.g. Mat3x3.T == f32 - pub const T = Scalar; + /// The Vec type corresponding to the number of cols, e.g. Mat3x4.ColVec == Vec4 + pub const ColVec = vec.Vec(T, rows); - /// The underlying Vec type, e.g. Mat3x3.Vec == Vec3 - pub const Vec = vec.Vec4(Scalar); + /// The underlying Vec type, e.g. Mat3x3.MinVec == Vec3 + pub const MinVec = vec.Vec(T, @min(rows, cols)); - /// The Vec type corresponding to the number of rows, e.g. Mat3x3.RowVec == Vec3 - pub const RowVec = Vec; + /// The Vec type whose length is one less than the minimum dimension of the matrix, e.g. Mat3x3.MinVecMinusOne == Vec2 + /// Useful for certain functions which return these, like translation() + pub const MinVecMinusOne = vec.Vec(T, @min(rows, cols) - 1); - /// The Vec type corresponding to the numebr of cols, e.g. Mat3x4.ColVec = Vec4 - pub const ColVec = Vec; + /// The number of rows, e.g. Mat3x4.rows == 3 + pub const rows = m; - const Matrix = @This(); + /// The number of columns, e.g. Mat3x4.cols == 4 + pub const cols = n; - const Shared = MatShared(RowVec, ColVec, Matrix); + const Self = @This(); - /// Identity matrix - pub const ident = Matrix.init( - &Vec.init(1, 0, 0, 0), - &Vec.init(0, 1, 0, 0), - &Vec.init(0, 0, 1, 0), - &Vec.init(0, 0, 0, 1), - ); - - /// Constructs a 4x4 matrix with the given rows. For example to write a translation + /// Constructs a MxN matrix with the given rows. For example to write a 4x4 translation /// matrix like in the left part of this equation: /// /// ``` @@ -331,129 +75,230 @@ pub fn Mat4x4( /// You would write it with the same visual layout: /// /// ``` - /// const m = Mat4x4.init( + /// const m = Mat4x4.init(.{ /// &vec4(1, 0, 0, tx), /// &vec4(0, 1, 0, ty), /// &vec4(0, 0, 1, tz), /// &vec4(0, 0, 0, tw), - /// ); + /// }); /// ``` /// /// Note that Mach matrices use [column-major storage and column-vectors](https://machengine.org/engine/math/matrix-storage/). - pub inline fn init(r0: *const RowVec, r1: *const RowVec, r2: *const RowVec, r3: *const RowVec) Matrix { - return .{ .v = [_]Vec{ - Vec.init(r0.x(), r1.x(), r2.x(), r3.x()), - Vec.init(r0.y(), r1.y(), r2.y(), r3.y()), - Vec.init(r0.z(), r1.z(), r2.z(), r3.z()), - Vec.init(r0.w(), r1.w(), r2.w(), r3.w()), - } }; + pub inline fn init(vecs: [rows]*const RowVec) Self { + var result: Self = undefined; + inline for (0..rows) |row_i| { + inline for (0..cols) |col_i| { + result.v[col_i].v[row_i] = vecs[row_i].v[col_i]; + } + } + return result; + } + + /// Init a 2x2 matrix + pub inline fn init2(r0: *const RowVec, r1: *const RowVec) Mat2x2(T) { + return .init(.{ r0, r1 }); + } + /// Init a 3x3 matrix + pub inline fn init3(r0: *const RowVec, r1: *const RowVec, r2: *const RowVec) Mat3x3(T) { + return .init(.{ r0, r1, r2 }); + } + /// Init a 4x4 matrix + pub inline fn init4(r0: *const RowVec, r1: *const RowVec, r2: *const RowVec, r3: *const RowVec) Mat4x4(T) { + return .init(.{ r0, r1, r2, r3 }); } + /// Identity matrix + pub const ident: Self = blk: { + var result: Self = undefined; + for (0..rows) |row_i| { + for (0..cols) |col_i| { + result.v[col_i].v[row_i] = if (row_i == col_i) 1 else 0; + } + } + break :blk result; + }; + /// Returns the row `i` of the matrix. - pub inline fn row(m: *const Matrix, i: usize) RowVec { - return RowVec{ .v = RowVec.Vector{ m.v[0].v[i], m.v[1].v[i], m.v[2].v[i], m.v[3].v[i] } }; + pub inline fn row(self: *const Self, i: usize) RowVec { + var result: RowVec = undefined; + inline for (0..cols) |col_i| { + result.v[col_i] = self.v[col_i].v[i]; + } + return result; } /// Returns the column `i` of the matrix. - pub inline fn col(m: *const Matrix, i: usize) RowVec { - return RowVec{ .v = RowVec.Vector{ m.v[i].v[0], m.v[i].v[1], m.v[i].v[2], m.v[i].v[3] } }; + pub inline fn col(self: *const Self, i: usize) ColVec { + return self.v[i]; } /// Transposes the matrix. - pub inline fn transpose(m: *const Matrix) Matrix { - return .{ .v = [_]Vec{ - Vec.init(m.v[0].v[0], m.v[1].v[0], m.v[2].v[0], m.v[3].v[0]), - Vec.init(m.v[0].v[1], m.v[1].v[1], m.v[2].v[1], m.v[3].v[1]), - Vec.init(m.v[0].v[2], m.v[1].v[2], m.v[2].v[2], m.v[3].v[2]), - Vec.init(m.v[0].v[3], m.v[1].v[3], m.v[2].v[3], m.v[3].v[3]), - } }; + pub inline fn transpose(self: *const Self) Mat(T, cols, rows) { + var result: Mat(T, cols, rows) = undefined; + for (0..rows) |row_i| { + for (0..cols) |col_i| { + result.v[row_i].v[col_i] = self.v[col_i].v[row_i]; + } + } + return result; } - /// Constructs a 3D matrix which scales each dimension by the given vector. - pub inline fn scale(s: math.Vec3) Matrix { - return init( - &RowVec.init(s.x(), 0, 0, 0), - &RowVec.init(0, s.y(), 0, 0), - &RowVec.init(0, 0, s.z(), 0), - &RowVec.init(0, 0, 0, 1), - ); + /// Constructs a matrix which scales each dimension by the given vector. + pub inline fn scale(s: MinVecMinusOne) Self { + var result: Self = .ident; + inline for (0..@TypeOf(s).n) |i| { + result.v[i].v[i] *= s.v[i]; + } + return result; } - /// Constructs a 3D matrix which scales each dimension by the given scalar. - pub inline fn scaleScalar(s: Vec.T) Matrix { - return scale(math.Vec3.splat(s)); + /// Constructs a matrix which scales each dimension by the given scalar. + pub inline fn scaleScalar(t: T) Self { + return scale(.splat(t)); } - /// Constructs a 3D matrix which translates coordinates by the given vector. - pub inline fn translate(t: math.Vec3) Matrix { - return init( - &RowVec.init(1, 0, 0, t.x()), - &RowVec.init(0, 1, 0, t.y()), - &RowVec.init(0, 0, 1, t.z()), - &RowVec.init(0, 0, 0, 1), - ); + /// Constructs a matrix which translates coordinates by the given vector. + pub inline fn translate(t: MinVecMinusOne) Self { + var result: Self = .ident; + inline for (0..@TypeOf(t).n) |i| { + result.v[cols - 1].v[i] += t.v[i]; + } + return result; } - /// Constructs a 3D matrix which translates coordinates by the given scalar. - pub inline fn translateScalar(t: Vec.T) Matrix { - return translate(math.Vec3.splat(t)); + /// Constructs a 1D matrix which translates coordinates by the given scalar. + pub inline fn translateScalar(t: T) Self { + return translate(.splat(t)); } - /// Returns the translation component of the matrix. - pub inline fn translation(t: *const Matrix) math.Vec3 { - return math.Vec3.init(t.v[3].x(), t.v[3].y(), t.v[3].z()); + pub inline fn translation(t: *const Self) MinVecMinusOne { + var result: MinVecMinusOne = undefined; + inline for (0..MinVecMinusOne.n) |i| { + result.v[i] = t.v[cols - 1].v[i]; + } + return result; } /// Constructs a 3D matrix which rotates around the X axis by `angle_radians`. - pub inline fn rotateX(angle_radians: f32) Matrix { + pub inline fn rotateX(angle_radians: f32) Mat4x4(T) { const c = math.cos(angle_radians); const s = math.sin(angle_radians); - return Matrix.init( - &RowVec.init(1, 0, 0, 0), - &RowVec.init(0, c, -s, 0), - &RowVec.init(0, s, c, 0), - &RowVec.init(0, 0, 0, 1), - ); + return .init(.{ + &.init(.{ 1, 0, 0, 0 }), + &.init(.{ 0, c, -s, 0 }), + &.init(.{ 0, s, c, 0 }), + &.init(.{ 0, 0, 0, 1 }), + }); } - /// Constructs a 3D matrix which rotates around the X axis by `angle_radians`. - pub inline fn rotateY(angle_radians: f32) Matrix { + /// Constructs a 3D matrix which rotates around the Y axis by `angle_radians`. + pub inline fn rotateY(angle_radians: f32) Mat4x4(T) { const c = math.cos(angle_radians); const s = math.sin(angle_radians); - return Matrix.init( - &RowVec.init(c, 0, s, 0), - &RowVec.init(0, 1, 0, 0), - &RowVec.init(-s, 0, c, 0), - &RowVec.init(0, 0, 0, 1), - ); + return .init(.{ + &.init(.{ c, 0, s, 0 }), + &.init(.{ 0, 1, 0, 0 }), + &.init(.{ -s, 0, c, 0 }), + &.init(.{ 0, 0, 0, 1 }), + }); } /// Constructs a 3D matrix which rotates around the Z axis by `angle_radians`. - pub inline fn rotateZ(angle_radians: f32) Matrix { + pub inline fn rotateZ(angle_radians: f32) Mat4x4(T) { const c = math.cos(angle_radians); const s = math.sin(angle_radians); - return Matrix.init( - &RowVec.init(c, -s, 0, 0), - &RowVec.init(s, c, 0, 0), - &RowVec.init(0, 0, 1, 0), - &RowVec.init(0, 0, 0, 1), - ); + return .init(.{ + &.init(.{ c, -s, 0, 0 }), + &.init(.{ s, c, 0, 0 }), + &.init(.{ 0, 0, 1, 0 }), + &.init(.{ 0, 0, 0, 1 }), + }); } - //https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/jay.htm - //Requires a normalized quaternion - pub inline fn rotateByQuaternion(quaternion: quat.Quat(T)) Matrix { + /// Constructs a 3D matrix which rotates around the X, Y, and Z axes by the given quaternion. + /// Requires a normalized quaternion. + // https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/jay.htm + pub inline fn rotateByQuaternion(quaternion: quat.Quat(T)) Mat4x4(T) { const qx = quaternion.v.x(); const qy = quaternion.v.y(); const qz = quaternion.v.z(); const qw = quaternion.v.w(); - return Matrix.init( - &RowVec.init(1 - 2 * qy * qy - 2 * qz * qz, 2 * qx * qy - 2 * qz * qw, 2 * qx * qz + 2 * qy * qw, 0), - &RowVec.init(2 * qx * qy + 2 * qz * qw, 1 - 2 * qx * qx - 2 * qz * qz, 2 * qy * qz - 2 * qx * qw, 0), - &RowVec.init(2 * qx * qz - 2 * qy * qw, 2 * qy * qz + 2 * qx * qw, 1 - 2 * qx * qx - 2 * qy * qy, 0), - &RowVec.init(0, 0, 0, 1), - ); + return .init(.{ + &.init(.{ 1 - 2 * qy * qy - 2 * qz * qz, 2 * qx * qy - 2 * qz * qw, 2 * qx * qz + 2 * qy * qw, 0 }), + &.init(.{ 2 * qx * qy + 2 * qz * qw, 1 - 2 * qx * qx - 2 * qz * qz, 2 * qy * qz - 2 * qx * qw, 0 }), + &.init(.{ 2 * qx * qz - 2 * qy * qw, 2 * qy * qz + 2 * qx * qw, 1 - 2 * qx * qx - 2 * qy * qy, 0 }), + &.init(.{ 0, 0, 0, 1 }), + }); + } + + /// Matrix multiplication a * b + /// + /// While this operation is defined for any two matrices such that + /// the number of columns in the first equal the number of rows in + /// the second, this function is only implemented for matrices where + /// the second's number of rows is equal to the first's number of + /// columns and the second's number of columns is equal to the + /// first's number of rows. For matrices where this is not the case, + /// see Mat.mulN. + pub inline fn mul(a: *const Self, b: *const Mat(T, cols, rows)) Mat(T, rows, rows) { + @setEvalBranchQuota(10000); + var result: Mat(T, rows, rows) = undefined; + inline for (0..rows) |row_i| { + inline for (0..rows) |col_i| { + result.v[col_i].v[row_i] = a.row(row_i).dot(&b.col(col_i)); + } + } + return result; + } + + /// Matrix multiplication a * b + /// + /// This function is defined for any two matrices such that the + /// number of columns in the first equal the number of rows in the + /// second. The number of columns must be provided as an argument for + /// type checking. For a version of this function without the extra + /// argument, see Mat.mul. + pub inline fn mulN(columns: comptime_int, a: *const Self, b: *const Mat(T, cols, columns)) Mat(T, rows, columns) { + @setEvalBranchQuota(10000); + var result: Mat(T, rows, columns) = undefined; + inline for (0..rows) |row_i| { + inline for (0..columns) |col_i| { + result.v[col_i].v[row_i] = a.row(row_i).dot(&b.col(col_i)); + } + } + return result; + } + + /// Matrix * Vector multiplication + pub inline fn mulVec(matrix: *const Self, vector: *const RowVec) ColVec { + var result: ColVec = undefined; + inline for (0..rows) |i| { + result.v[i] = matrix.row(i).dot(vector); + } + return result; + } + + /// Check if two matrices are approximately equal. Returns true if the absolute difference between + /// each element in matrix is less than or equal to the specified tolerance. + pub inline fn eqlApprox(a: *const Self, b: *const Self, tolerance: ColVec.T) bool { + inline for (0..rows) |row_i| { + if (!ColVec.eqlApprox(&a.v[row_i], &b.v[row_i], tolerance)) { + return false; + } + } + return true; + } + + /// Check if two matrices are approximately equal. Returns true if the absolute difference between + /// each element in matrix is less than or equal to the epsilon tolerance. + pub inline fn eql(a: *const Self, b: *const Self) bool { + inline for (0..rows) |row_i| { + if (!ColVec.eql(&a.v[row_i], &b.v[row_i])) { + return false; + } + } + return true; } /// Constructs a 2D projection matrix, aka. an orthographic projection matrix. @@ -487,14 +332,14 @@ pub fn Mat4x4( top: f32, near: f32, far: f32, - }) Matrix { - var p = Matrix.ident; - p = p.mul(&Matrix.translate(math.vec3( + }) Mat4x4(T) { + var p: Mat4x4(T) = .ident; + p = p.mul(&.translate(math.vec3( (v.right + v.left) / (v.left - v.right), // translate X so that the middle of (left, right) maps to x=0 in clip space (v.top + v.bottom) / (v.bottom - v.top), // translate Y so that the middle of (bottom, top) maps to y=0 in clip space v.far / (v.far - v.near), // translate Z so that far maps to z=0 ))); - p = p.mul(&Matrix.scale(math.vec3( + p = p.mul(&.scale(math.vec3( 2 / (v.right - v.left), // scale X so that [left, right] has a 2 unit range, e.g. [-1, +1] 2 / (v.top - v.bottom), // scale Y so that [bottom, top] has a 2 unit range, e.g. [-1, +1] 1 / (v.near - v.far), // scale Z so that [near, far] has a 1 unit range, e.g. [0, -1] @@ -502,77 +347,13 @@ pub fn Mat4x4( return p; } - pub const mul = Shared.mul; - pub const mulVec = Shared.mulVec; - pub const eql = Shared.eql; - pub const eqlApprox = Shared.eqlApprox; - pub const format = Shared.format; - }; -} - -pub fn MatShared(comptime RowVec: type, comptime ColVec: type, comptime Matrix: type) type { - return struct { - /// Matrix multiplication a*b - pub inline fn mul(a: *const Matrix, b: *const Matrix) Matrix { - @setEvalBranchQuota(10000); - var result: Matrix = undefined; - inline for (0..Matrix.rows) |row| { - inline for (0..Matrix.cols) |col| { - var sum: RowVec.T = 0.0; - inline for (0..RowVec.n) |i| { - // Note: we directly access rows/columns below as it is much faster **in - // debug builds**, instead of using these helpers: - // - // sum += a.row(row).mul(&b.col(col)).v[i]; - sum += a.v[i].v[row] * b.v[col].v[i]; - } - result.v[col].v[row] = sum; - } - } - return result; - } - - /// Matrix * Vector multiplication - pub inline fn mulVec(matrix: *const Matrix, vector: *const ColVec) ColVec { - var result = [_]ColVec.T{0} ** ColVec.n; - inline for (0..Matrix.rows) |row| { - inline for (0..ColVec.n) |i| { - result[i] += matrix.v[row].v[i] * vector.v[row]; - } - } - return ColVec{ .v = result }; - } - - /// Check if two matrices are approximately equal. Returns true if the absolute difference between - /// each element in matrix is less than or equal to the specified tolerance. - pub inline fn eqlApprox(a: *const Matrix, b: *const Matrix, tolerance: ColVec.T) bool { - inline for (0..Matrix.rows) |row| { - if (!ColVec.eqlApprox(&a.v[row], &b.v[row], tolerance)) { - return false; - } - } - return true; - } - - /// Check if two matrices are approximately equal. Returns true if the absolute difference between - /// each element in matrix is less than or equal to the epsilon tolerance. - pub inline fn eql(a: *const Matrix, b: *const Matrix) bool { - inline for (0..Matrix.rows) |row| { - if (!ColVec.eql(&a.v[row], &b.v[row])) { - return false; - } - } - return true; - } - /// Custom format function for all matrix types. pub inline fn format( - self: Matrix, + self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { - const rows = @TypeOf(self).rows; try writer.print("{{", .{}); inline for (0..rows) |r| { try std.fmt.formatType(self.row(r), fmt, options, writer, 1); @@ -613,8 +394,8 @@ test "zero_struct_overhead" { test "n" { try testing.expect(usize, 3).eql(math.Mat3x3.cols); try testing.expect(usize, 3).eql(math.Mat3x3.rows); - try testing.expect(type, math.Vec3).eql(math.Mat3x3.Vec); - try testing.expect(usize, 3).eql(math.Mat3x3.Vec.n); + try testing.expect(type, math.Vec3).eql(math.Mat3x3.MinVec); + try testing.expect(usize, 3).eql(math.Mat3x3.MinVec.n); } test "init" { @@ -624,9 +405,9 @@ test "init" { &math.vec3(0, 0, 1), )).eql(math.Mat3x3{ .v = [_]math.Vec3{ - math.Vec3.init(1, 0, 0), - math.Vec3.init(0, 1, 0), - math.Vec3.init(1337, 7331, 1), + math.vec3(1, 0, 0), + math.vec3(0, 1, 0), + math.vec3(1337, 7331, 1), }, }); } @@ -634,8 +415,8 @@ test "init" { test "Mat2x2_ident" { try testing.expect(math.Mat2x2, math.Mat2x2.ident).eql(math.Mat2x2{ .v = [_]math.Vec2{ - math.Vec2.init(1, 0), - math.Vec2.init(0, 1), + math.vec2(1, 0), + math.vec2(0, 1), }, }); } @@ -643,9 +424,9 @@ test "Mat2x2_ident" { test "Mat3x3_ident" { try testing.expect(math.Mat3x3, math.Mat3x3.ident).eql(math.Mat3x3{ .v = [_]math.Vec3{ - math.Vec3.init(1, 0, 0), - math.Vec3.init(0, 1, 0), - math.Vec3.init(0, 0, 1), + math.vec3(1, 0, 0), + math.vec3(0, 1, 0), + math.vec3(0, 0, 1), }, }); } @@ -653,16 +434,16 @@ test "Mat3x3_ident" { test "Mat4x4_ident" { try testing.expect(math.Mat4x4, math.Mat4x4.ident).eql(math.Mat4x4{ .v = [_]math.Vec4{ - math.Vec4.init(1, 0, 0, 0), - math.Vec4.init(0, 1, 0, 0), - math.Vec4.init(0, 0, 1, 0), - math.Vec4.init(0, 0, 0, 1), + math.vec4(1, 0, 0, 0), + math.vec4(0, 1, 0, 0), + math.vec4(0, 0, 1, 0), + math.vec4(0, 0, 0, 1), }, }); } test "Mat2x2_row" { - const m = math.Mat2x2.init( + const m = math.mat2x2( &math.vec2(0, 1), &math.vec2(2, 3), ); @@ -671,7 +452,7 @@ test "Mat2x2_row" { } test "Mat2x2_col" { - const m = math.Mat2x2.init( + const m = math.mat2x2( &math.vec2(0, 1), &math.vec2(2, 3), ); @@ -680,7 +461,7 @@ test "Mat2x2_col" { } test "Mat3x3_row" { - const m = math.Mat3x3.init( + const m = math.mat3x3( &math.vec3(0, 1, 2), &math.vec3(3, 4, 5), &math.vec3(6, 7, 8), @@ -691,7 +472,7 @@ test "Mat3x3_row" { } test "Mat3x3_col" { - const m = math.Mat3x3.init( + const m = math.mat3x3( &math.vec3(0, 1, 2), &math.vec3(3, 4, 5), &math.vec3(6, 7, 8), @@ -702,7 +483,7 @@ test "Mat3x3_col" { } test "Mat4x4_row" { - const m = math.Mat4x4.init( + const m = math.mat4x4( &math.vec4(0, 1, 2, 3), &math.vec4(4, 5, 6, 7), &math.vec4(8, 9, 10, 11), @@ -715,7 +496,7 @@ test "Mat4x4_row" { } test "Mat4x4_col" { - const m = math.Mat4x4.init( + const m = math.mat4x4( &math.vec4(0, 1, 2, 3), &math.vec4(4, 5, 6, 7), &math.vec4(8, 9, 10, 11), @@ -728,23 +509,23 @@ test "Mat4x4_col" { } test "Mat2x2_transpose" { - const m = math.Mat2x2.init( + const m = math.mat2x2( &math.vec2(0, 1), &math.vec2(2, 3), ); - try testing.expect(math.Mat2x2, math.Mat2x2.init( + try testing.expect(math.Mat2x2, math.mat2x2( &math.vec2(0, 2), &math.vec2(1, 3), )).eql(m.transpose()); } test "Mat3x3_transpose" { - const m = math.Mat3x3.init( + const m = math.mat3x3( &math.vec3(0, 1, 2), &math.vec3(3, 4, 5), &math.vec3(6, 7, 8), ); - try testing.expect(math.Mat3x3, math.Mat3x3.init( + try testing.expect(math.Mat3x3, math.mat3x3( &math.vec3(0, 3, 6), &math.vec3(1, 4, 7), &math.vec3(2, 5, 8), @@ -752,13 +533,13 @@ test "Mat3x3_transpose" { } test "Mat4x4_transpose" { - const m = math.Mat4x4.init( + const m = math.mat4x4( &math.vec4(0, 1, 2, 3), &math.vec4(4, 5, 6, 7), &math.vec4(8, 9, 10, 11), &math.vec4(12, 13, 14, 15), ); - try testing.expect(math.Mat4x4, math.Mat4x4.init( + try testing.expect(math.Mat4x4, math.mat4x4( &math.vec4(0, 4, 8, 12), &math.vec4(1, 5, 9, 13), &math.vec4(2, 6, 10, 14), @@ -768,7 +549,7 @@ test "Mat4x4_transpose" { test "Mat2x2_scaleScalar" { const m = math.Mat2x2.scaleScalar(2); - try testing.expect(math.Mat2x2, math.Mat2x2.init( + try testing.expect(math.Mat2x2, math.mat2x2( &math.vec2(2, 0), &math.vec2(0, 1), )).eql(m); @@ -776,7 +557,7 @@ test "Mat2x2_scaleScalar" { test "Mat3x3_scale" { const m = math.Mat3x3.scale(math.vec2(2, 3)); - try testing.expect(math.Mat3x3, math.Mat3x3.init( + try testing.expect(math.Mat3x3, math.mat3x3( &math.vec3(2, 0, 0), &math.vec3(0, 3, 0), &math.vec3(0, 0, 1), @@ -785,7 +566,7 @@ test "Mat3x3_scale" { test "Mat3x3_scaleScalar" { const m = math.Mat3x3.scaleScalar(2); - try testing.expect(math.Mat3x3, math.Mat3x3.init( + try testing.expect(math.Mat3x3, math.mat3x3( &math.vec3(2, 0, 0), &math.vec3(0, 2, 0), &math.vec3(0, 0, 1), @@ -794,7 +575,7 @@ test "Mat3x3_scaleScalar" { test "Mat4x4_scale" { const m = math.Mat4x4.scale(math.vec3(2, 3, 4)); - try testing.expect(math.Mat4x4, math.Mat4x4.init( + try testing.expect(math.Mat4x4, math.mat4x4( &math.vec4(2, 0, 0, 0), &math.vec4(0, 3, 0, 0), &math.vec4(0, 0, 4, 0), @@ -804,7 +585,7 @@ test "Mat4x4_scale" { test "Mat4x4_scaleScalar" { const m = math.Mat4x4.scaleScalar(2); - try testing.expect(math.Mat4x4, math.Mat4x4.init( + try testing.expect(math.Mat4x4, math.mat4x4( &math.vec4(2, 0, 0, 0), &math.vec4(0, 2, 0, 0), &math.vec4(0, 0, 2, 0), @@ -814,7 +595,7 @@ test "Mat4x4_scaleScalar" { test "Mat3x3_translate" { const m = math.Mat3x3.translate(math.vec2(2, 3)); - try testing.expect(math.Mat3x3, math.Mat3x3.init( + try testing.expect(math.Mat3x3, math.mat3x3( &math.vec3(1, 0, 2), &math.vec3(0, 1, 3), &math.vec3(0, 0, 1), @@ -823,7 +604,7 @@ test "Mat3x3_translate" { test "Mat4x4_translate" { const m = math.Mat4x4.translate(math.vec3(2, 3, 4)); - try testing.expect(math.Mat4x4, math.Mat4x4.init( + try testing.expect(math.Mat4x4, math.mat4x4( &math.vec4(1, 0, 0, 2), &math.vec4(0, 1, 0, 3), &math.vec4(0, 0, 1, 4), @@ -833,7 +614,7 @@ test "Mat4x4_translate" { test "Mat3x3_translateScalar" { const m = math.Mat3x3.translateScalar(2); - try testing.expect(math.Mat3x3, math.Mat3x3.init( + try testing.expect(math.Mat3x3, math.mat3x3( &math.vec3(1, 0, 2), &math.vec3(0, 1, 2), &math.vec3(0, 0, 1), @@ -842,7 +623,7 @@ test "Mat3x3_translateScalar" { test "Mat2x2_translateScalar" { const m = math.Mat2x2.translateScalar(2); - try testing.expect(math.Mat2x2, math.Mat2x2.init( + try testing.expect(math.Mat2x2, math.mat2x2( &math.vec2(1, 2), &math.vec2(0, 1), )).eql(m); @@ -850,7 +631,7 @@ test "Mat2x2_translateScalar" { test "Mat4x4_translateScalar" { const m = math.Mat4x4.translateScalar(2); - try testing.expect(math.Mat4x4, math.Mat4x4.init( + try testing.expect(math.Mat4x4, math.mat4x4( &math.vec4(1, 0, 0, 2), &math.vec4(0, 1, 0, 2), &math.vec4(0, 0, 1, 2), @@ -879,7 +660,7 @@ test "Mat2x2_mulVec_vec2_ident" { test "Mat2x2_mulVec_vec2" { const v = math.Vec2.splat(1); - const mat = math.Mat2x2.init( + const mat = math.mat2x2( &math.vec2(2, 0), &math.vec2(0, 2), ); @@ -900,7 +681,7 @@ test "Mat3x3_mulVec_vec3_ident" { test "Mat3x3_mulVec_vec3" { const v = math.Vec3.splat(1); - const mat = math.Mat3x3.init( + const mat = math.mat3x3( &math.vec3(2, 0, 0), &math.vec3(0, 2, 0), &math.vec3(0, 0, 3), @@ -913,7 +694,7 @@ test "Mat3x3_mulVec_vec3" { test "Mat4x4_mulVec_vec4" { const v = math.vec4(2, 5, 1, 8); - const mat = math.Mat4x4.init( + const mat = math.mat4x4( &math.vec4(1, 0, 2, 0), &math.vec4(0, 3, 0, 4), &math.vec4(0, 0, 5, 0), @@ -926,37 +707,57 @@ test "Mat4x4_mulVec_vec4" { } test "Mat2x2_mul" { - const a = math.Mat2x2.init( + const a = math.mat2x2( &math.vec2(4, 2), &math.vec2(7, 9), ); - const b = math.Mat2x2.init( + const b = math.mat2x2( &math.vec2(5, -7), &math.vec2(6, -3), ); const c = math.Mat2x2.mul(&a, &b); - const expected = math.Mat2x2.init( + const expected = math.mat2x2( &math.vec2(32, -34), &math.vec2(89, -76), ); try testing.expect(math.Mat2x2, expected).eql(c); } +test "Mat3x2_mul" { + const a = math.MatMxN(3, 2).init(.{ + &math.vec2(4, 2), + &math.vec2(7, 9), + &math.vec2(-1, 8), + }); + const b = math.MatMxN(2, 3).init(.{ + &math.vec3(5, -7, -8), + &math.vec3(6, -3, 2), + }); + const c = math.MatMxN(3, 2).mul(&a, &b); + + const expected = math.mat3x3( + &math.vec3(32, -34, -28), + &math.vec3(89, -76, -38), + &math.vec3(43, -17, 24), + ); + try testing.expect(math.Mat3x3, expected).eql(c); +} + test "Mat3x3_mul" { - const a = math.Mat3x3.init( + const a = math.mat3x3( &math.vec3(4, 2, -3), &math.vec3(7, 9, -8), &math.vec3(-1, 8, -8), ); - const b = math.Mat3x3.init( + const b = math.mat3x3( &math.vec3(5, -7, -8), &math.vec3(6, -3, 2), &math.vec3(-3, -4, 4), ); const c = math.Mat3x3.mul(&a, &b); - const expected = math.Mat3x3.init( + const expected = math.mat3x3( &math.vec3(41, -22, -40), &math.vec3(113, -44, -70), &math.vec3(67, 15, -8), @@ -964,14 +765,36 @@ test "Mat3x3_mul" { try testing.expect(math.Mat3x3, expected).eql(c); } +test "Mat3x4_mul" { + const a = math.MatMxN(3, 4).init(.{ + &math.vec4(10, -5, 6, -2), + &math.vec4(0, -1, 0, 9), + &math.vec4(-1, 6, -4, 8), + }); + const b = math.MatMxN(4, 3).init(.{ + &math.vec3(7, -7, -3), + &math.vec3(1, -1, -7), + &math.vec3(-10, 2, 2), + &math.vec3(10, -7, 7), + }); + const c = math.MatMxN(3, 4).mul(&a, &b); + + const expected = math.mat3x3( + &math.vec3(-15, -39, 3), + &math.vec3(89, -62, 70), + &math.vec3(119, -63, 9), + ); + try testing.expect(math.Mat3x3, expected).eql(c); +} + test "Mat4x4_mul" { - const a = math.Mat4x4.init( + const a = math.mat4x4( &math.vec4(10, -5, 6, -2), &math.vec4(0, -1, 0, 9), &math.vec4(-1, 6, -4, 8), &math.vec4(9, -8, -6, -10), ); - const b = math.Mat4x4.init( + const b = math.mat4x4( &math.vec4(7, -7, -3, -8), &math.vec4(1, -1, -7, -2), &math.vec4(-10, 2, 2, -2), @@ -979,7 +802,7 @@ test "Mat4x4_mul" { ); const c = math.Mat4x4.mul(&a, &b); - const expected = math.Mat4x4.init( + const expected = math.mat4x4( &math.vec4(-15, -39, 3, -84), &math.vec4(89, -62, 70, 11), &math.vec4(119, -63, 9, 12), @@ -988,14 +811,31 @@ test "Mat4x4_mul" { try testing.expect(math.Mat4x4, expected).eql(c); } +test "Mat1x3_mulN" { + const a = math.MatMxN(1, 3).init(.{ + &math.vec3(3, 4, 2), + }); + const b = math.MatMxN(3, 4).init(.{ + &math.vec4(13, 9, 7, 15), + &math.vec4(8, 7, 4, 6), + &math.vec4(6, 4, 0, 3), + }); + const c = math.MatMxN(1, 3).mulN(4, &a, &b); + + const expected = math.MatMxN(1, 4).init(.{ + &math.vec4(83, 63, 37, 75), + }); + try testing.expect(math.MatMxN(1, 4), expected).eql(c); +} + test "Mat4x4_eql_not_ident" { - const m1 = math.Mat4x4.init( + const m1 = math.mat4x4( &math.vec4(0, 1, 2, 3), &math.vec4(4, 5, 6, 7), &math.vec4(8, 9, 10, 11), &math.vec4(12, 13, 14, 15), ); - const m2 = math.Mat4x4.init( + const m2 = math.mat4x4( &math.vec4(0, 1, 2, 3), &math.vec4(4.5, 5, 6, 7), &math.vec4(8, 9, 10, 11), @@ -1005,13 +845,13 @@ test "Mat4x4_eql_not_ident" { } test "Mat4x4_eql_ident" { - const m1 = math.Mat4x4.init( + const m1 = math.mat4x4( &math.vec4(0, 1, 2, 3), &math.vec4(4, 5, 6, 7), &math.vec4(8, 9, 10, 11), &math.vec4(12, 13, 14, 15), ); - const m2 = math.Mat4x4.init( + const m2 = math.mat4x4( &math.vec4(0, 1, 2, 3), &math.vec4(4, 5, 6, 7), &math.vec4(8, 9, 10, 11), @@ -1021,13 +861,13 @@ test "Mat4x4_eql_ident" { } test "Mat4x4_eqlApprox_not_ident" { - const m1 = math.Mat4x4.init( + const m1 = math.mat4x4( &math.vec4(0, 1, 2, 3), &math.vec4(4, 5, 6, 7), &math.vec4(8, 9, 10, 11), &math.vec4(12, 13, 14, 15), ); - const m2 = math.Mat4x4.init( + const m2 = math.mat4x4( &math.vec4(0, 1, 2, 3), &math.vec4(4.11, 5, 6, 7), &math.vec4(8, 9, 10, 11), @@ -1037,13 +877,13 @@ test "Mat4x4_eqlApprox_not_ident" { } test "Mat4x4_eqlApprox_ident" { - const m1 = math.Mat4x4.init( + const m1 = math.mat4x4( &math.vec4(0, 1, 2, 3), &math.vec4(4, 5, 6, 7), &math.vec4(8, 9, 10, 11), &math.vec4(12, 13, 14, 15), ); - const m2 = math.Mat4x4.init( + const m2 = math.mat4x4( &math.vec4(0, 1, 2, 3), &math.vec4(4.09, 5, 6, 7), &math.vec4(8, 9, 10, 11), @@ -1215,7 +1055,7 @@ test "projection2D_model_to_clip_space" { } test "quaternion_rotation" { - const expected = math.Mat4x4.init( + const expected = math.mat4x4( &math.vec4(0.7716905, 0.5519065, 0.3160585, 0), &math.vec4(-0.0782971, -0.4107276, 0.9083900, 0), &math.vec4(0.6311602, -0.7257425, -0.2737419, 0), diff --git a/src/math/ray.zig b/src/math/ray.zig index 6362d16cf2..0ac8521243 100644 --- a/src/math/ray.zig +++ b/src/math/ray.zig @@ -155,12 +155,12 @@ pub fn Ray3(comptime Scalar: type) type { // considered a hit. // Since Ray.Hit is represented by a Vec4, t is the last // element of that vector - var hit: Hit = Vec4P.init( + var hit: Hit = Vec4P.init(.{ undefined, undefined, undefined, math.inf(f32), - ); + }); if (backface_culling) { if ((t < 0.0) or (t > hit.v[3] * det)) diff --git a/src/math/vec.zig b/src/math/vec.zig index 097d5b3021..fcc6349c3f 100644 --- a/src/math/vec.zig +++ b/src/math/vec.zig @@ -6,142 +6,98 @@ const math = mach.math; const mat = @import("mat.zig"); const quat = @import("quat.zig"); -pub const VecComponent = enum { x, y, z, w }; - pub fn Vec2(comptime Scalar: type) type { + return Vec(Scalar, 2); +} + +pub fn Vec3(comptime Scalar: type) type { + return Vec(Scalar, 3); +} + +pub fn Vec4(comptime Scalar: type) type { + return Vec(Scalar, 4); +} + +pub fn Vec(comptime Scalar: type, comptime length: usize) type { return extern struct { v: Vector, - /// The vector dimension size, e.g. Vec3.n == 3 - pub const n = 2; - /// The scalar type of this vector, e.g. Vec3.T == f32 pub const T = Scalar; // The underlying @Vector type - pub const Vector = @Vector(n, Scalar); + pub const Vector = @Vector(n, T); - const VecN = @This(); + /// The vector dimension size, e.g. Vec3.n == 3 + pub const n = length; - const Shared = VecShared(Scalar, VecN); + const Self = @This(); - pub inline fn init(xs: Scalar, ys: Scalar) VecN { - return .{ .v = .{ xs, ys } }; + pub inline fn init(v: [n]T) Self { + return .{ .v = v }; } - pub inline fn fromInt(xs: anytype, ys: anytype) VecN { - return .{ .v = .{ @floatFromInt(xs), @floatFromInt(ys) } }; + + pub inline fn init2(xs: Scalar, ys: Scalar) Vec2(T) { + return .{ .v = .{ xs, ys } }; } - pub inline fn x(v: *const VecN) Scalar { - return v.v[0]; + pub inline fn init3(xs: Scalar, ys: Scalar, zs: Scalar) Vec3(T) { + return .{ .v = .{ xs, ys, zs } }; } - pub inline fn y(v: *const VecN) Scalar { - return v.v[1]; + pub inline fn init4(xs: Scalar, ys: Scalar, zs: Scalar, ws: Scalar) Vec4(T) { + return .{ .v = .{ xs, ys, zs, ws } }; } - pub const add = Shared.add; - pub const sub = Shared.sub; - pub const div = Shared.div; - pub const mul = Shared.mul; - pub const addScalar = Shared.addScalar; - pub const subScalar = Shared.subScalar; - pub const divScalar = Shared.divScalar; - pub const mulScalar = Shared.mulScalar; - pub const less = Shared.less; - pub const lessEq = Shared.lessEq; - pub const greater = Shared.greater; - pub const greaterEq = Shared.greaterEq; - pub const splat = Shared.splat; - pub const len2 = Shared.len2; - pub const len = Shared.len; - pub const normalize = Shared.normalize; - pub const dir = Shared.dir; - pub const dist2 = Shared.dist2; - pub const dist = Shared.dist; - pub const lerp = Shared.lerp; - pub const dot = Shared.dot; - pub const max = Shared.max; - pub const min = Shared.min; - pub const inverse = Shared.inverse; - pub const negate = Shared.negate; - pub const maxScalar = Shared.maxScalar; - pub const minScalar = Shared.minScalar; - pub const eqlApprox = Shared.eqlApprox; - pub const eql = Shared.eql; - pub const format = Shared.format; - }; -} - -pub fn Vec3(comptime Scalar: type) type { - return extern struct { - v: Vector, - - /// The vector dimension size, e.g. Vec3.n == 3 - pub const n = 3; - - /// The scalar type of this vector, e.g. Vec3.T == f32 - pub const T = Scalar; - - // The underlying @Vector type - pub const Vector = @Vector(n, Scalar); - - const VecN = @This(); - - const Shared = VecShared(Scalar, VecN); + /// `v` must be a `[n]anytype` where the `anytype` is an integral type + pub inline fn fromInt(v: anytype) Self { + var result: Self = undefined; + inline for (0..n) |i| { + result.v[i] = @floatFromInt(v[i]); + } + return result; + } - pub inline fn init(xs: Scalar, ys: Scalar, zs: Scalar) VecN { - return .{ .v = .{ xs, ys, zs } }; + pub inline fn fromInt2(xs: anytype, ys: anytype) Vec2(T) { + return .{ .v = .{ @floatFromInt(xs), @floatFromInt(ys) } }; } - pub inline fn fromInt(xs: anytype, ys: anytype, zs: anytype) VecN { + pub inline fn fromInt3(xs: anytype, ys: anytype, zs: anytype) Vec3(T) { return .{ .v = .{ @floatFromInt(xs), @floatFromInt(ys), @floatFromInt(zs) } }; } - pub inline fn x(v: *const VecN) Scalar { + pub inline fn fromInt4(xs: anytype, ys: anytype, zs: anytype, ws: anytype) Vec4(T) { + return .{ .v = .{ @floatFromInt(xs), @floatFromInt(ys), @floatFromInt(zs), @floatFromInt(ws) } }; + } + + pub inline fn x(v: *const Self) T { + if (length < 1) @compileError("Can only call x() on vectors with a length of at least 1"); return v.v[0]; } - pub inline fn y(v: *const VecN) Scalar { + pub inline fn y(v: *const Self) T { + if (length < 2) @compileError("Can only call y() on vectors with a length of at least 2"); return v.v[1]; } - pub inline fn z(v: *const VecN) Scalar { + pub inline fn z(v: *const Self) T { + if (length < 3) @compileError("Can only call z() on vectors with a length of at least 3"); return v.v[2]; } - - pub inline fn swizzle( - v: *const VecN, - xc: VecComponent, - yc: VecComponent, - zc: VecComponent, - ) VecN { - return .{ .v = @shuffle(VecN.T, v.v, undefined, [3]T{ - @intFromEnum(xc), - @intFromEnum(yc), - @intFromEnum(zc), - }) }; - } - - /// Calculates the cross product between vector a and b. - /// This can be done only in 3D and required inputs are Vec3. - pub inline fn cross(a: *const VecN, b: *const VecN) VecN { - // https://gamemath.com/book/vectors.html#cross_product - const s1 = a.swizzle(.y, .z, .x) - .mul(&b.swizzle(.z, .x, .y)); - const s2 = a.swizzle(.z, .x, .y) - .mul(&b.swizzle(.y, .z, .x)); - return s1.sub(&s2); + pub inline fn w(v: *const Self) T { + if (length < 4) @compileError("Can only call w() on vectors with a length of at least 4"); + return v.v[3]; } /// Vector * Matrix multiplication - pub inline fn mulMat(vector: *const VecN, matrix: *const mat.Mat3x3(T)) VecN { - var result = [_]VecN.T{0} ** 3; - inline for (0..3) |i| { - inline for (0..3) |j| { - result[i] += vector.v[j] * matrix.v[i].v[j]; - } + /// While this operation is defined for any matrix with n columns, this + /// function is only implemented for square matrices. See Mat.mulVec + /// for multiplying non-square matrices by a vector. + pub inline fn mulMat(vector: *const Self, matrix: *const mat.Mat(T, n, n)) Self { + var result: Self = undefined; + inline for (0..n) |i| { + result.v[i] = vector.dot(&matrix.row(i)); // TODO: Benchmark this? } - return .{ .v = result }; + return result; } /// Vector * Quat multiplication /// https://github.com/greggman/wgpu-matrix/blob/main/src/vec3-impl.ts#L718 - pub inline fn mulQuat(v: *const VecN, q: *const quat.Quat(Scalar)) VecN { + pub inline fn mulQuat(v: *const Vec3(T), q: *const quat.Quat(T)) Vec3(T) { const qx = q.v.x(); const qy = q.v.y(); const qz = q.v.z(); @@ -162,182 +118,66 @@ pub fn Vec3(comptime Scalar: type) type { ); } - pub const add = Shared.add; - pub const sub = Shared.sub; - pub const div = Shared.div; - pub const mul = Shared.mul; - pub const addScalar = Shared.addScalar; - pub const subScalar = Shared.subScalar; - pub const divScalar = Shared.divScalar; - pub const mulScalar = Shared.mulScalar; - pub const less = Shared.less; - pub const lessEq = Shared.lessEq; - pub const greater = Shared.greater; - pub const greaterEq = Shared.greaterEq; - pub const splat = Shared.splat; - pub const len2 = Shared.len2; - pub const len = Shared.len; - pub const normalize = Shared.normalize; - pub const dir = Shared.dir; - pub const dist2 = Shared.dist2; - pub const dist = Shared.dist; - pub const lerp = Shared.lerp; - pub const dot = Shared.dot; - pub const max = Shared.max; - pub const min = Shared.min; - pub const inverse = Shared.inverse; - pub const negate = Shared.negate; - pub const maxScalar = Shared.maxScalar; - pub const minScalar = Shared.minScalar; - pub const eqlApprox = Shared.eqlApprox; - pub const eql = Shared.eql; - pub const format = Shared.format; - }; -} - -pub fn Vec4(comptime Scalar: type) type { - return extern struct { - v: Vector, - - /// The vector dimension size, e.g. Vec3.n == 3 - pub const n = 4; - - /// The scalar type of this vector, e.g. Vec3.T == f32 - pub const T = Scalar; - - // The underlying @Vector type - pub const Vector = @Vector(n, Scalar); - - const VecN = @This(); - - const Shared = VecShared(Scalar, VecN); - - pub inline fn init(xs: Scalar, ys: Scalar, zs: Scalar, ws: Scalar) VecN { - return .{ .v = .{ xs, ys, zs, ws } }; - } - pub inline fn fromInt(xs: anytype, ys: anytype, zs: anytype, ws: anytype) VecN { - return .{ .v = .{ @floatFromInt(xs), @floatFromInt(ys), @floatFromInt(zs), @floatFromInt(ws) } }; - } - pub inline fn x(v: *const VecN) Scalar { - return v.v[0]; - } - pub inline fn y(v: *const VecN) Scalar { - return v.v[1]; - } - pub inline fn z(v: *const VecN) Scalar { - return v.v[2]; - } - pub inline fn w(v: *const VecN) Scalar { - return v.v[3]; - } - - /// Vector * Matrix multiplication - pub inline fn mulMat(vector: *const VecN, matrix: *const mat.Mat4x4(T)) VecN { - var result = [_]VecN.T{0} ** 4; - inline for (0..4) |i| { - inline for (0..4) |j| { - result[i] += vector.v[j] * matrix.v[i].v[j]; - } - } - return .{ .v = result }; - } - - pub const add = Shared.add; - pub const sub = Shared.sub; - pub const div = Shared.div; - pub const mul = Shared.mul; - pub const addScalar = Shared.addScalar; - pub const subScalar = Shared.subScalar; - pub const divScalar = Shared.divScalar; - pub const mulScalar = Shared.mulScalar; - pub const less = Shared.less; - pub const lessEq = Shared.lessEq; - pub const greater = Shared.greater; - pub const greaterEq = Shared.greaterEq; - pub const splat = Shared.splat; - pub const len2 = Shared.len2; - pub const len = Shared.len; - pub const normalize = Shared.normalize; - pub const dir = Shared.dir; - pub const dist2 = Shared.dist2; - pub const dist = Shared.dist; - pub const lerp = Shared.lerp; - pub const dot = Shared.dot; - pub const max = Shared.max; - pub const min = Shared.min; - pub const inverse = Shared.inverse; - pub const negate = Shared.negate; - pub const maxScalar = Shared.maxScalar; - pub const minScalar = Shared.minScalar; - pub const eqlApprox = Shared.eqlApprox; - pub const eql = Shared.eql; - pub const format = Shared.format; - }; -} - -pub fn VecShared(comptime Scalar: type, comptime VecN: type) type { - return struct { /// Element-wise addition - pub inline fn add(a: *const VecN, b: *const VecN) VecN { + pub inline fn add(a: *const Self, b: *const Self) Self { return .{ .v = a.v + b.v }; } /// Element-wise subtraction - pub inline fn sub(a: *const VecN, b: *const VecN) VecN { + pub inline fn sub(a: *const Self, b: *const Self) Self { return .{ .v = a.v - b.v }; } /// Element-wise division - pub inline fn div(a: *const VecN, b: *const VecN) VecN { + pub inline fn div(a: *const Self, b: *const Self) Self { return .{ .v = a.v / b.v }; } /// Element-wise multiplication. /// /// See also .cross() - pub inline fn mul(a: *const VecN, b: *const VecN) VecN { + pub inline fn mul(a: *const Self, b: *const Self) Self { return .{ .v = a.v * b.v }; } /// Scalar addition - pub inline fn addScalar(a: *const VecN, s: Scalar) VecN { - return .{ .v = a.v + VecN.splat(s).v }; + pub inline fn addScalar(a: *const Self, s: T) Self { + return .{ .v = a.v + Self.splat(s).v }; } /// Scalar subtraction - pub inline fn subScalar(a: *const VecN, s: Scalar) VecN { - return .{ .v = a.v - VecN.splat(s).v }; + pub inline fn subScalar(a: *const Self, s: T) Self { + return .{ .v = a.v - Self.splat(s).v }; } /// Scalar division - pub inline fn divScalar(a: *const VecN, s: Scalar) VecN { - return .{ .v = a.v / VecN.splat(s).v }; + pub inline fn divScalar(a: *const Self, s: T) Self { + return .{ .v = a.v / Self.splat(s).v }; } /// Scalar multiplication. /// /// See .dot() for the dot product - pub inline fn mulScalar(a: *const VecN, s: Scalar) VecN { - return .{ .v = a.v * VecN.splat(s).v }; + pub inline fn mulScalar(a: *const Self, s: T) Self { + return .{ .v = a.v * Self.splat(s).v }; } - /// Element-wise a < b - pub inline fn less(a: *const VecN, b: *const VecN) bool { + pub inline fn less(a: *const Self, b: *const Self) bool { return @reduce(.And, a.v < b.v); } /// Element-wise a <= b - pub inline fn lessEq(a: *const VecN, b: *const VecN) bool { + pub inline fn lessEq(a: *const Self, b: *const Self) bool { return @reduce(.And, a.v <= b.v); } /// Element-wise a > b - pub inline fn greater(a: *const VecN, b: *const VecN) bool { + pub inline fn greater(a: *const Self, b: *const Self) bool { return @reduce(.And, a.v > b.v); } /// Element-wise a >= b - pub inline fn greaterEq(a: *const VecN, b: *const VecN) bool { + pub inline fn greaterEq(a: *const Self, b: *const Self) bool { return @reduce(.And, a.v >= b.v); } @@ -347,22 +187,50 @@ pub fn VecShared(comptime Scalar: type, comptime VecN: type) type { /// var v = Vec3.splat(1337.0).v; /// // v.x == 1337, v.y == 1337, v.z == 1337 /// ``` - pub inline fn splat(scalar: Scalar) VecN { + pub inline fn splat(scalar: T) Self { return .{ .v = @splat(scalar) }; } + /// Returns a vector with the components arranged according to the `comps` value: + /// + /// ``` + /// const v1 = math.vec3(1, 2, 3); + /// const v2 = v1.swizzle("zyx"); + /// // v2.x == 3, v2.y == 2, v2.z == 1 + /// ``` + /// + /// This can also be used to create vectors of a different length than the source vector: + /// + /// ``` + /// const v1: Vec3 = math.vec3(1, 2, 3); + /// const v2: Vec4 = v1.swizzle("xyzx"); + /// // v2.x == 1, v2.y == 2, v2.z == 3, v2.w == 1 + /// ``` + /// + /// Components can be xyzw (for coordinates), rgba (for colors), or stpq (for texture coordinates). + pub inline fn swizzle(v: *const Self, comptime comps: []const u8) Vec(T, comps.len) { + comptime var indices: [comps.len]i32 = undefined; + inline for (comps, 0..) |comp, i| { + indices[i] = switch (comp) { + 'x', 'r', 's' => 0, + 'y', 'g', 't' => 1, + 'z', 'b', 'p' => 2, + 'w', 'a', 'q' => 3, + else => @compileError(std.fmt.comptimePrint("Invalid vector component '{c}'. Vector components must be included in 'xyzw', 'rgba', or 'stpq'", .{comp})), + }; + if (indices[i] >= n) @compileError(std.fmt.comptimePrint("Vector component '{c}' out of range for vector of length {}", .{ comp, n })); + } + + return .{ .v = @shuffle(T, v.v, undefined, indices) }; + } + /// Computes the squared length of the vector. Faster than `len()` - pub inline fn len2(v: *const VecN) Scalar { - return switch (VecN.n) { - inline 2 => (v.x() * v.x()) + (v.y() * v.y()), - inline 3 => (v.x() * v.x()) + (v.y() * v.y()) + (v.z() * v.z()), - inline 4 => (v.x() * v.x()) + (v.y() * v.y()) + (v.z() * v.z()) + (v.w() * v.w()), - else => @compileError("Expected Vec2, Vec3, Vec4, found '" ++ @typeName(VecN) ++ "'"), - }; + pub inline fn len2(v: *const Self) T { + return v.dot(v); } /// Computes the length of the vector. - pub inline fn len(v: *const VecN) Scalar { + pub inline fn len(v: *const Self) T { return math.sqrt(len2(v)); } @@ -374,8 +242,8 @@ pub fn VecShared(comptime Scalar: type, comptime VecN: type) type { /// ``` /// math.vec3(1.0, 2.0, 3.0).normalize(v, 0.00000001); /// ``` - pub inline fn normalize(v: *const VecN, d0: Scalar) VecN { - return v.div(&VecN.splat(v.len() + d0)); + pub inline fn normalize(v: *const Self, d0: T) Self { + return v.div(&Self.splat(v.len() + d0)); } /// Returns the normalized direction vector from points a and b. @@ -386,17 +254,17 @@ pub fn VecShared(comptime Scalar: type, comptime VecN: type) type { /// ``` /// var v = a_point.dir(b_point, 0.0000001); /// ``` - pub inline fn dir(a: *const VecN, b: *const VecN, d0: Scalar) VecN { + pub inline fn dir(a: *const Self, b: *const Self, d0: T) Self { return b.sub(a).normalize(d0); } /// Calculates the squared distance between points a and b. Faster than `dist()`. - pub inline fn dist2(a: *const VecN, b: *const VecN) Scalar { + pub inline fn dist2(a: *const Self, b: *const Self) T { return b.sub(a).len2(); } /// Calculates the distance between points a and b. - pub inline fn dist(a: *const VecN, b: *const VecN) Scalar { + pub inline fn dist(a: *const Self, b: *const Self) T { return math.sqrt(a.dist2(b)); } @@ -406,111 +274,60 @@ pub fn VecShared(comptime Scalar: type, comptime VecN: type) type { /// a.lerp(b, 0.0) == a /// a.lerp(b, 1.0) == b /// ``` - pub inline fn lerp(a: *const VecN, b: *const VecN, amount: Scalar) VecN { + pub inline fn lerp(a: *const Self, b: *const Self, amount: T) Self { return a.mulScalar(1.0 - amount).add(&b.mulScalar(amount)); } /// Calculates the dot product between vector a and b and returns scalar. - pub inline fn dot(a: *const VecN, b: *const VecN) Scalar { + pub inline fn dot(a: *const Self, b: *const Self) T { return @reduce(.Add, a.v * b.v); } + /// Calculates the cross product between vector a and b. + /// This is only implemented in 3D and the required inputs are Vec3. + pub inline fn cross(a: *const Vec3(T), b: *const Vec3(T)) Vec3(T) { + // https://gamemath.com/book/vectors.html#cross_product + const s1 = a.swizzle("yzx").mul(&b.swizzle("zxy")); + const s2 = a.swizzle("zxy").mul(&b.swizzle("yzx")); + return s1.sub(&s2); + } + // Returns a new vector with the max values of two vectors - pub inline fn max(a: *const VecN, b: *const VecN) VecN { - return switch (VecN.n) { - inline 2 => VecN.init( - @max(a.x(), b.x()), - @max(a.y(), b.y()), - ), - inline 3 => VecN.init( - @max(a.x(), b.x()), - @max(a.y(), b.y()), - @max(a.z(), b.z()), - ), - inline 4 => VecN.init( - @max(a.x(), b.x()), - @max(a.y(), b.y()), - @max(a.z(), b.z()), - @max(a.w(), b.w()), - ), - else => @compileError("Expected Vec2, Vec3, Vec4, found '" ++ @typeName(VecN) ++ "'"), - }; + pub inline fn max(a: *const Self, b: *const Self) Self { + return .{ .v = @max(a.v, b.v) }; } // Returns a new vector with the min values of two vectors - pub inline fn min(a: *const VecN, b: *const VecN) VecN { - return switch (VecN.n) { - inline 2 => VecN.init( - @min(a.x(), b.x()), - @min(a.y(), b.y()), - ), - inline 3 => VecN.init( - @min(a.x(), b.x()), - @min(a.y(), b.y()), - @min(a.z(), b.z()), - ), - inline 4 => VecN.init( - @min(a.x(), b.x()), - @min(a.y(), b.y()), - @min(a.z(), b.z()), - @min(a.w(), b.w()), - ), - else => @compileError("Expected Vec2, Vec3, Vec4, found '" ++ @typeName(VecN) ++ "'"), - }; + pub inline fn min(a: *const Self, b: *const Self) Self { + return .{ .v = @min(a.v, b.v) }; } // Returns the inverse of a given vector - pub inline fn inverse(a: *const VecN) VecN { - return switch (VecN.n) { - inline 2 => .{ .v = (math.vec2(1, 1).v / a.v) }, - inline 3 => .{ .v = (math.vec3(1, 1, 1).v / a.v) }, - inline 4 => .{ .v = (math.vec4(1, 1, 1, 1).v / a.v) }, - else => @compileError("Expected Vec2, Vec3, Vec4, found '" ++ @typeName(VecN) ++ "'"), - }; + pub inline fn inverse(a: *const Self) Self { + return .{ .v = Self.splat(1).v / a.v }; } // Negates a given vector - pub inline fn negate(a: *const VecN) VecN { - return switch (VecN.n) { - inline 2 => .{ .v = math.vec2(-1, -1).v * a.v }, - inline 3 => .{ .v = math.vec3(-1, -1, -1).v * a.v }, - inline 4 => .{ .v = math.vec4(-1, -1, -1, -1).v * a.v }, - else => @compileError("Expected Vec2, Vec3, Vec4, found '" ++ @typeName(VecN) ++ "'"), - }; + pub inline fn negate(a: *const Self) Self { + return .{ .v = a.v * Self.splat(-1).v }; } // Returns the largest scalar of two vectors - pub inline fn maxScalar(a: *const VecN, b: *const VecN) Scalar { - var max_scalar: Scalar = a.v[0]; - inline for (0..VecN.n) |i| { - if (a.v[i] > max_scalar) - max_scalar = a.v[i]; - if (b.v[i] > max_scalar) - max_scalar = b.v[i]; - } - - return max_scalar; + pub inline fn maxScalar(a: *const Self, b: *const Self) T { + return @max(@reduce(.Max, a.v), @reduce(.Max, b.v)); } // Returns the smallest scalar of two vectors - pub inline fn minScalar(a: *const VecN, b: *const VecN) Scalar { - var min_scalar: Scalar = a.v[0]; - inline for (0..VecN.n) |i| { - if (a.v[i] < min_scalar) - min_scalar = a.v[i]; - if (b.v[i] < min_scalar) - min_scalar = b.v[i]; - } - - return min_scalar; + pub inline fn minScalar(a: *const Self, b: *const Self) T { + return @min(@reduce(.Min, a.v), @reduce(.Min, b.v)); } /// Checks for approximate (absolute tolerance) equality between two vectors /// of the same type and dimensions - pub inline fn eqlApprox(a: *const VecN, b: *const VecN, tolerance: Scalar) bool { + pub inline fn eqlApprox(a: *const Self, b: *const Self, tolerance: T) bool { var i: usize = 0; - while (i < VecN.n) : (i += 1) { - if (!math.eql(Scalar, a.v[i], b.v[i], tolerance)) { + while (i < n) : (i += 1) { + if (!math.eql(T, a.v[i], b.v[i], tolerance)) { return false; } } @@ -519,13 +336,13 @@ pub fn VecShared(comptime Scalar: type, comptime VecN: type) type { /// Checks for approximate (absolute epsilon tolerance) equality /// between two vectors of the same type and dimensions - pub inline fn eql(a: *const VecN, b: *const VecN) bool { - return a.eqlApprox(b, math.eps(Scalar)); + pub inline fn eql(a: *const Self, b: *const Self) bool { + return a.eqlApprox(b, math.eps(T)); } /// Custom format function for all vector types. pub inline fn format( - self: VecN, + self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, @@ -602,6 +419,10 @@ test "init" { try testing.expect(math.Vec3h, math.vec3h(1, 2, 3)).eql(math.vec3h(1, 2, 3)); } +test "fromInt" { + try testing.expect(math.Vec3h, math.vec3h(1, 2, 3)).eql(math.Vec3h.fromInt([_]u32{ 1, 2, 3 })); +} + test "splat" { try testing.expect(math.Vec3h, math.vec3h(1337, 1337, 1337)).eql(math.Vec3h.splat(1337)); } @@ -612,6 +433,27 @@ test "swizzle_singular" { try testing.expect(f32, 3).eql(math.vec3(1, 2, 3).z()); } +test "swizzle_multiple" { + try testing.expect(math.Vec3, math.vec3(3, 2, 1)).eql(math.vec3(1, 2, 3).swizzle("zyx")); + try testing.expect(math.Vec3, math.vec3(3, 1, 2)).eql(math.vec3(1, 2, 3).swizzle("zxy")); + try testing.expect(math.Vec4, math.vec4(4, 5, 5, 4)).eql(math.vec2(4, 5).swizzle("xyyx")); + try testing.expect(math.Vec2, math.vec2(4, 5)).eql(math.vec4(2, 3, 4, 5).swizzle("zw")); + + try testing.expect(math.Vec3, math.vec3(3, 2, 1)).eql(math.vec3(1, 2, 3).swizzle("bgr")); + try testing.expect(math.Vec3, math.vec3(3, 1, 2)).eql(math.vec3(1, 2, 3).swizzle("brg")); + try testing.expect(math.Vec4, math.vec4(4, 5, 5, 4)).eql(math.vec2(4, 5).swizzle("rggr")); + try testing.expect(math.Vec2, math.vec2(4, 5)).eql(math.vec4(2, 3, 4, 5).swizzle("ba")); + + try testing.expect(math.Vec3, math.vec3(3, 2, 1)).eql(math.vec3(1, 2, 3).swizzle("pts")); + try testing.expect(math.Vec3, math.vec3(3, 1, 2)).eql(math.vec3(1, 2, 3).swizzle("pst")); + try testing.expect(math.Vec4, math.vec4(4, 5, 5, 4)).eql(math.vec2(4, 5).swizzle("stts")); + try testing.expect(math.Vec2, math.vec2(4, 5)).eql(math.vec4(2, 3, 4, 5).swizzle("pq")); + + // Should be compile errors + // try testing.expect(math.Vec4, math.vec4(4, 5, 5, 4)).eql(math.vec2(4, 5).swizzle("wx")); // error: Vector component 'w' out of range for vector of length 2 + // try testing.expect(math.Vec2, math.vec2(4, 5)).eql(math.vec4(2, 3, 4, 5).swizzle("uv")); // error: Invalid vector component 'u' +} + test "len2" { try testing.expect(f32, 2).eql(math.vec2(1, 1).len2()); try testing.expect(f32, 29).eql(math.vec3(2, 3, -4).len2()); @@ -1103,7 +945,7 @@ test "dot_vec4" { } test "Mat3x3_mulMat" { - const matrix = math.Mat3x3.init( + const matrix = math.mat3x3( &math.vec3(2, 2, 2), &math.vec3(3, 4, 3), &math.vec3(1, 1, 2), @@ -1111,12 +953,12 @@ test "Mat3x3_mulMat" { const v = math.vec3(1, 2, 0); const m = math.Vec3.mulMat(&v, &matrix); - const expected = math.vec3(8, 10, 8); + const expected = math.vec3(6, 11, 3); try testing.expect(math.Vec3, expected).eql(m); } test "Mat4x4_mulMat" { - const matrix = math.Mat4x4.init( + const matrix = math.mat4x4( &math.vec4(2, 2, 2, 1), &math.vec4(3, 4, 3, 0), &math.vec4(1, 1, 2, 2), @@ -1125,7 +967,7 @@ test "Mat4x4_mulMat" { const v = math.vec4(1, 2, 0, -1); const m = math.Vec4.mulMat(&v, &matrix); - const expected = math.vec4(7, 9, 6, -1); + const expected = math.vec4(5, 11, 1, 1); try testing.expect(math.Vec4, expected).eql(m); }