diff --git a/src/math/main.zig b/src/math/main.zig index 6b07ea22..f4a73f86 100644 --- a/src/math/main.zig +++ b/src/math/main.zig @@ -50,34 +50,34 @@ const ray = @import("ray.zig"); pub const collision = @import("collision.zig"); /// Standard f32 precision types -pub const Vec2 = vec.Vec(2, f32); -pub const Vec3 = vec.Vec(3, f32); -pub const Vec4 = vec.Vec(4, f32); +pub const Vec2 = vec.Vec2(f32); +pub const Vec3 = vec.Vec3(f32); +pub const Vec4 = vec.Vec4(f32); pub const Quat = q.Quat(f32); -pub const Mat2x2 = mat.Mat(2, 2, Vec2); -pub const Mat3x3 = mat.Mat(3, 3, Vec3); -pub const Mat4x4 = mat.Mat(4, 4, Vec4); -pub const Ray = ray.Ray(Vec3); +pub const Mat2x2 = mat.Mat2x2(f32); +pub const Mat3x3 = mat.Mat3x3(f32); +pub const Mat4x4 = mat.Mat4x4(f32); +pub const Ray = ray.Ray3(f32); /// Half-precision f16 types -pub const Vec2h = vec.Vec(2, f16); -pub const Vec3h = vec.Vec(3, f16); -pub const Vec4h = vec.Vec(4, f16); +pub const Vec2h = vec.Vec2(f16); +pub const Vec3h = vec.Vec3(f16); +pub const Vec4h = vec.Vec4(f16); pub const Quath = q.Quat(f16); -pub const Mat2x2h = mat.Mat(2, 2, Vec2h); -pub const Mat3x3h = mat.Mat(3, 3, Vec3h); -pub const Mat4x4h = mat.Mat(4, 4, Vec4h); -pub const Rayh = ray.Ray(Vec3h); +pub const Mat2x2h = mat.Mat2x2(f16); +pub const Mat3x3h = mat.Mat3x3(f16); +pub const Mat4x4h = mat.Mat4x4(f16); +pub const Rayh = ray.Ray3(f16); /// Double-precision f64 types -pub const Vec2d = vec.Vec(2, f64); -pub const Vec3d = vec.Vec(3, f64); -pub const Vec4d = vec.Vec(4, f64); +pub const Vec2d = vec.Vec2(f64); +pub const Vec3d = vec.Vec3(f64); +pub const Vec4d = vec.Vec4(f64); pub const Quatd = q.Quat(f64); -pub const Mat2x2d = mat.Mat(2, 2, Vec2d); -pub const Mat3x3d = mat.Mat(3, 3, Vec3d); -pub const Mat4x4d = mat.Mat(4, 4, Vec4d); -pub const Rayd = ray.Ray(Vec3d); +pub const Mat2x2d = mat.Mat2x2(f64); +pub const Mat3x3d = mat.Mat3x3(f64); +pub const Mat4x4d = mat.Mat4x4(f64); +pub const Rayd = ray.Ray3(f64); /// Standard f32 precision initializers pub const vec2 = Vec2.init; diff --git a/src/math/mat.zig b/src/math/mat.zig index 24ff3b92..b227f5ab 100644 --- a/src/math/mat.zig +++ b/src/math/mat.zig @@ -3,10 +3,8 @@ const testing = mach.testing; const math = mach.math; const vec = @import("vec.zig"); -pub fn Mat( - comptime n_cols: usize, - comptime n_rows: usize, - comptime Vector: type, +pub fn Mat2x2( + comptime Scalar: type, ) type { return extern struct { /// The column vectors of the matrix. @@ -28,367 +26,468 @@ pub fn Mat( v: [cols]Vec, /// The number of columns, e.g. Mat3x4.cols == 3 - pub const cols = n_cols; + pub const cols = 2; /// The number of rows, e.g. Mat3x4.rows == 4 - pub const rows = n_rows; + pub const rows = 2; /// The scalar type of this matrix, e.g. Mat3x3.T == f32 - pub const T = Vector.T; + pub const T = Scalar; /// The underlying Vec type, e.g. Mat3x3.Vec == Vec3 - pub const Vec = Vector; + pub const Vec = vec.Vec2(Scalar); /// The Vec type corresponding to the number of rows, e.g. Mat3x3.RowVec == Vec3 - pub const RowVec = vec.Vec(rows, T); + pub const RowVec = Vec; /// The Vec type corresponding to the numebr of cols, e.g. Mat3x4.ColVec = Vec4 - pub const ColVec = vec.Vec(cols, T); + pub const ColVec = Vec; const Matrix = @This(); + const Shared = MatShared(RowVec, ColVec, Matrix); + /// Identity matrix - pub const ident = switch (Matrix) { - inline math.Mat2x2, math.Mat2x2h, math.Mat2x2d => Matrix.init( - &RowVec.init(1, 0), + 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), - ), - inline math.Mat3x3, math.Mat3x3h, math.Mat3x3d => Matrix.init( - &RowVec.init(1, 0, 0), - &RowVec.init(0, 1, 0), + ); + } + + /// 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 const mul = Shared.mul; + pub const mulVec = Shared.mulVec; + }; +} + +pub fn Mat3x3( + 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 = 3; + + /// The number of rows, e.g. Mat3x4.rows == 4 + pub const rows = 3; + + /// 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; + + 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), - ), - inline math.Mat4x4, math.Mat4x4h, math.Mat4x4d => 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), - ), - else => @compileError("Expected Mat3x3, Mat4x4 found '" ++ @typeName(Matrix) ++ "'"), - }; + ); + } - pub usingnamespace switch (Matrix) { - inline math.Mat2x2, math.Mat2x2h, math.Mat2x2d => struct { - /// 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()), - } }; - } + /// 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)); + } - /// 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] } }; - } + /// 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), + ); + } - /// 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] } }; - } + /// 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)); + } - /// 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]), - } }; - } + /// 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()); + } - /// 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 const mul = Shared.mul; + pub const mulVec = Shared.mulVec; + }; +} - /// 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), - ); - } - }, - inline math.Mat3x3, math.Mat3x3h, math.Mat3x3d => struct { - /// 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()), - } }; - } +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, - /// 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] } }; - } + /// The number of columns, e.g. Mat3x4.cols == 3 + pub const cols = 4; - /// 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] } }; - } + /// The number of rows, e.g. Mat3x4.rows == 4 + pub const rows = 4; - /// 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]), - } }; - } + /// The scalar type of this matrix, e.g. Mat3x3.T == f32 + pub const T = Scalar; - /// 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), - ); - } + /// The underlying Vec type, e.g. Mat3x3.Vec == Vec3 + pub const Vec = vec.Vec4(Scalar); - /// 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)); - } + /// The Vec type corresponding to the number of rows, e.g. Mat3x3.RowVec == Vec3 + pub const RowVec = Vec; - /// 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), - ); - } + /// The Vec type corresponding to the numebr of cols, e.g. Mat3x4.ColVec = Vec4 + pub const ColVec = Vec; - /// 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)); - } + const Matrix = @This(); - /// 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()); - } - }, - inline math.Mat4x4, math.Mat4x4h, math.Mat4x4d => struct { - /// Constructs a 4x4 matrix with the given rows. For example to write a translation - /// matrix like in the left part of this equation: - /// - /// ``` - /// |1 0 0 tx| |x | |x+w*tx| - /// |0 1 0 ty| |y | = |y+w*ty| - /// |0 0 1 tz| |z | |z+w*tz| - /// |0 0 0 tw| |w=1| |tw | - /// ``` - /// - /// You would write it with the same visual layout: - /// - /// ``` - /// 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()), - } }; - } + const Shared = MatShared(RowVec, ColVec, Matrix); - /// 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] } }; - } + /// 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), + ); - /// 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] } }; - } + /// Constructs a 4x4 matrix with the given rows. For example to write a translation + /// matrix like in the left part of this equation: + /// + /// ``` + /// |1 0 0 tx| |x | |x+w*tx| + /// |0 1 0 ty| |y | = |y+w*ty| + /// |0 0 1 tz| |z | |z+w*tz| + /// |0 0 0 tw| |w=1| |tw | + /// ``` + /// + /// You would write it with the same visual layout: + /// + /// ``` + /// 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()), + } }; + } - /// 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]), - } }; - } + /// 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] } }; + } - /// 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), - ); - } + /// 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] } }; + } - /// 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)); - } + /// 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]), + } }; + } - /// 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 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 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 3D matrix which scales each dimension by the given scalar. + pub inline fn scaleScalar(s: Vec.T) Matrix { + return scale(math.Vec3.splat(s)); + } - /// 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()); - } + /// 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 3D matrix which rotates around the X axis by `angle_radians`. - pub inline fn rotateX(angle_radians: f32) Matrix { - 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), - ); - } + /// 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 3D matrix which rotates around the X axis by `angle_radians`. - pub inline fn rotateY(angle_radians: f32) Matrix { - 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), - ); - } + /// 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()); + } - /// Constructs a 3D matrix which rotates around the Z axis by `angle_radians`. - pub inline fn rotateZ(angle_radians: f32) Matrix { - 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), - ); - } + /// Constructs a 3D matrix which rotates around the X axis by `angle_radians`. + pub inline fn rotateX(angle_radians: f32) Matrix { + 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), + ); + } - /// Constructs a 2D projection matrix, aka. an orthographic projection matrix. - /// - /// First, a cuboid is defined with the parameters: - /// - /// * (right - left) defining the distance between the left and right faces of the cube - /// * (top - bottom) defining the distance between the top and bottom faces of the cube - /// * (near - far) defining the distance between the back (near) and front (far) faces of the cube - /// - /// We then need to construct a projection matrix which converts points in that - /// cuboid's space into clip space: - /// - /// https://machengine.org/engine/math/traversing-coordinate-systems/#view---clip-space - /// - /// Normally, in sysgpu/webgpu the depth buffer of floating point values would - /// have the range [0, 1] representing [near, far], i.e. a pixel very close to the - /// viewer would have a depth value of 0.0, and a pixel very far from the viewer - /// would have a depth value of 1.0. But this is an ineffective use of floating - /// point precision, a better approach is a reversed depth buffer: - /// - /// * https://webgpu.github.io/webgpu-samples/samples/reversedZ - /// * https://developer.nvidia.com/content/depth-precision-visualized - /// - /// Mach mandates the use of a reversed depth buffer, so the returned transformation - /// matrix maps to near=1 and far=0. - pub inline fn projection2D(v: struct { - left: f32, - right: f32, - bottom: f32, - top: f32, - near: f32, - far: f32, - }) Matrix { - var p = Matrix.ident; - p = p.mul(&Matrix.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( - 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] - ))); - return p; - } - }, - else => @compileError("Expected Mat3x3, Mat4x4 found '" ++ @typeName(Matrix) ++ "'"), - }; + /// Constructs a 3D matrix which rotates around the X axis by `angle_radians`. + pub inline fn rotateY(angle_radians: f32) Matrix { + 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), + ); + } + /// Constructs a 3D matrix which rotates around the Z axis by `angle_radians`. + pub inline fn rotateZ(angle_radians: f32) Matrix { + 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), + ); + } + + /// Constructs a 2D projection matrix, aka. an orthographic projection matrix. + /// + /// First, a cuboid is defined with the parameters: + /// + /// * (right - left) defining the distance between the left and right faces of the cube + /// * (top - bottom) defining the distance between the top and bottom faces of the cube + /// * (near - far) defining the distance between the back (near) and front (far) faces of the cube + /// + /// We then need to construct a projection matrix which converts points in that + /// cuboid's space into clip space: + /// + /// https://machengine.org/engine/math/traversing-coordinate-systems/#view---clip-space + /// + /// Normally, in sysgpu/webgpu the depth buffer of floating point values would + /// have the range [0, 1] representing [near, far], i.e. a pixel very close to the + /// viewer would have a depth value of 0.0, and a pixel very far from the viewer + /// would have a depth value of 1.0. But this is an ineffective use of floating + /// point precision, a better approach is a reversed depth buffer: + /// + /// * https://webgpu.github.io/webgpu-samples/samples/reversedZ + /// * https://developer.nvidia.com/content/depth-precision-visualized + /// + /// Mach mandates the use of a reversed depth buffer, so the returned transformation + /// matrix maps to near=1 and far=0. + pub inline fn projection2D(v: struct { + left: f32, + right: f32, + bottom: f32, + top: f32, + near: f32, + far: f32, + }) Matrix { + var p = Matrix.ident; + p = p.mul(&Matrix.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( + 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] + ))); + return p; + } + + pub const mul = Shared.mul; + pub const mulVec = Shared.mulVec; + }; +} + +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); @@ -417,7 +516,7 @@ pub fn Mat( result[i] += matrix.v[row].v[i] * vector.v[row]; } } - return vec.Vec(ColVec.n, ColVec.T){ .v = result }; + return ColVec{ .v = result }; } // TODO: the below code was correct in our old implementation, it just needs to be updated diff --git a/src/math/quat.zig b/src/math/quat.zig index c11ae917..4144ec5b 100644 --- a/src/math/quat.zig +++ b/src/math/quat.zig @@ -6,16 +6,16 @@ const mat = @import("mat.zig"); pub fn Quat(comptime Scalar: type) type { return extern struct { - v: vec.Vec(4, Scalar), + v: vec.Vec4(Scalar), /// The scalar type of this matrix, e.g. Mat3x3.T == f32 pub const T = Vec.T; /// The underlying Vec type, e.g. math.Vec4, math.Vec4h, math.Vec4d - pub const Vec = vec.Vec(4, Scalar); + pub const Vec = vec.Vec4(Scalar); /// The Vec type used to represent axes, e.g. math.Vec3 - pub const Axis = vec.Vec(3, Scalar); + pub const Axis = vec.Vec3(Scalar); /// Creates a quaternion from the given x, y, z, and w values pub inline fn init(x: T, y: T, z: T, w: T) Quat(T) { diff --git a/src/math/ray.zig b/src/math/ray.zig index 4d516e63..6362d16c 100644 --- a/src/math/ray.zig +++ b/src/math/ray.zig @@ -4,7 +4,9 @@ const math = mach.math; const vec = @import("vec.zig"); // A Ray in three-dimensional space -pub fn Ray(comptime Vec3P: type) type { +pub fn Ray3(comptime Scalar: type) type { + const Vec3P = vec.Vec3(Scalar); + // Floating point precision, will be either f16, f32, or f64 const P: type = Vec3P.T; @@ -34,159 +36,153 @@ pub fn Ray(comptime Vec3P: type) type { /// and w represents hit distance t pub const Hit = Vec4P; - pub usingnamespace switch (Vec3P) { - math.Vec3, math.Vec3h, math.Vec3d => struct { - // Determine the 3D vector dimension with the largest scalar - // value - fn maxDim(v: [3]P) u8 { - if (v[0] > v[1]) { - if (v[0] > v[2]) { - return 0; - } else { - return 2; - } - } else if (v[1] > v[2]) { - return 1; - } else { - return 2; - } + // Determine the 3D vector dimension with the largest scalar + // value + fn maxDim(v: [3]P) u8 { + if (v[0] > v[1]) { + if (v[0] > v[2]) { + return 0; + } else { + return 2; } + } else if (v[1] > v[2]) { + return 1; + } else { + return 2; + } + } - // Algorithm based on: - // https://www.jcgt.org/published/0002/01/05/ - /// Check for collision of a ray and a triangle in 3D space. - /// Triangle winding, which determines front- and backface of - /// the given triangle, matters if backface culling is to be - /// enabled. Without backface culling it does not matter for - /// hit detection, however the barycentric coordinates will - /// be negative in case of a backface hit. - /// On hit, will return a RayHit which contains distance t - /// and barycentric coordinates. - pub inline fn triangleIntersect( - ray: *const Ray(Vec3P), - va: *const Vec3P, - vb: *const Vec3P, - vc: *const Vec3P, - backface_culling: bool, - ) ?Hit { - const kz: u8 = maxDim([3]P{ - @abs(ray.direction.v[0]), - @abs(ray.direction.v[1]), - @abs(ray.direction.v[2]), - }); - if (ray.direction.v[kz] == 0.0) { - return null; - } - var kx: u8 = kz + 1; - if (kx == 3) - kx = 0; - var ky: u8 = kx + 1; - if (ky == 3) - ky = 0; + // Algorithm based on: + // https://www.jcgt.org/published/0002/01/05/ + /// Check for collision of a ray and a triangle in 3D space. + /// Triangle winding, which determines front- and backface of + /// the given triangle, matters if backface culling is to be + /// enabled. Without backface culling it does not matter for + /// hit detection, however the barycentric coordinates will + /// be negative in case of a backface hit. + /// On hit, will return a RayHit which contains distance t + /// and barycentric coordinates. + pub inline fn triangleIntersect( + ray: *const Ray3(P), + va: *const Vec3P, + vb: *const Vec3P, + vc: *const Vec3P, + backface_culling: bool, + ) ?Hit { + const kz: u8 = maxDim([3]P{ + @abs(ray.direction.v[0]), + @abs(ray.direction.v[1]), + @abs(ray.direction.v[2]), + }); + if (ray.direction.v[kz] == 0.0) { + return null; + } + var kx: u8 = kz + 1; + if (kx == 3) + kx = 0; + var ky: u8 = kx + 1; + if (ky == 3) + ky = 0; - if (ray.direction.v[kz] < 0.0) { - const tmp = kx; - kx = ky; - ky = tmp; - } + if (ray.direction.v[kz] < 0.0) { + const tmp = kx; + kx = ky; + ky = tmp; + } - const sx: P = ray.direction.v[kx] / ray.direction.v[kz]; - const sy: P = ray.direction.v[ky] / ray.direction.v[kz]; - const sz: P = 1.0 / ray.direction.v[kz]; + const sx: P = ray.direction.v[kx] / ray.direction.v[kz]; + const sy: P = ray.direction.v[ky] / ray.direction.v[kz]; + const sz: P = 1.0 / ray.direction.v[kz]; - const a: @Vector(3, P) = va.v - ray.origin.v; - const b: @Vector(3, P) = vb.v - ray.origin.v; - const c: @Vector(3, P) = vc.v - ray.origin.v; + const a: @Vector(3, P) = va.v - ray.origin.v; + const b: @Vector(3, P) = vb.v - ray.origin.v; + const c: @Vector(3, P) = vc.v - ray.origin.v; - const ax: P = a[kx] - sx * a[kz]; - const ay: P = a[ky] - sy * a[kz]; - const bx: P = b[kx] - sx * b[kz]; - const by: P = b[ky] - sy * b[kz]; - const cx: P = c[kx] - sx * c[kz]; - const cy: P = c[ky] - sy * c[kz]; + const ax: P = a[kx] - sx * a[kz]; + const ay: P = a[ky] - sy * a[kz]; + const bx: P = b[kx] - sx * b[kz]; + const by: P = b[ky] - sy * b[kz]; + const cx: P = c[kx] - sx * c[kz]; + const cy: P = c[ky] - sy * c[kz]; - var u: P = cx * by - cy * bx; - var v: P = ax * cy - ay * cx; - const w: P = bx * ay - by * ax; + var u: P = cx * by - cy * bx; + var v: P = ax * cy - ay * cx; + const w: P = bx * ay - by * ax; - // Double precision fallback - if (u == 0.0 or v == 0.0 or w == 0.0) { - const cxby: PP = @as(PP, @floatCast(cx)) * - @as(PP, @floatCast(by)); - const cybx: PP = @as(PP, @floatCast(cy)) * - @as(PP, @floatCast(bx)); - u = @floatCast(cxby - cybx); + // Double precision fallback + if (u == 0.0 or v == 0.0 or w == 0.0) { + const cxby: PP = @as(PP, @floatCast(cx)) * + @as(PP, @floatCast(by)); + const cybx: PP = @as(PP, @floatCast(cy)) * + @as(PP, @floatCast(bx)); + u = @floatCast(cxby - cybx); - const axcy: PP = @as(PP, @floatCast(ax)) * - @as(PP, @floatCast(cy)); - const aycx: PP = @as(PP, @floatCast(ay)) * - @as(PP, @floatCast(cx)); - v = @floatCast(axcy - aycx); + const axcy: PP = @as(PP, @floatCast(ax)) * + @as(PP, @floatCast(cy)); + const aycx: PP = @as(PP, @floatCast(ay)) * + @as(PP, @floatCast(cx)); + v = @floatCast(axcy - aycx); - const bxay: PP = @as(PP, @floatCast(bx)) * - @as(PP, @floatCast(ay)); - const byax: PP = @as(PP, @floatCast(by)) * - @as(PP, @floatCast(ax)); - v = @floatCast(bxay - byax); - } + const bxay: PP = @as(PP, @floatCast(bx)) * + @as(PP, @floatCast(ay)); + const byax: PP = @as(PP, @floatCast(by)) * + @as(PP, @floatCast(ax)); + v = @floatCast(bxay - byax); + } - if (backface_culling) { - if (u < 0.0 or v < 0.0 or w < 0.0) - return null; // no hit - } else { - if ((u < 0.0 or v < 0.0 or w < 0.0) and - (u > 0.0 or v > 0.0 or w > 0.0)) - return null; // no hit - } + if (backface_culling) { + if (u < 0.0 or v < 0.0 or w < 0.0) + return null; // no hit + } else { + if ((u < 0.0 or v < 0.0 or w < 0.0) and + (u > 0.0 or v > 0.0 or w > 0.0)) + return null; // no hit + } - var det: P = u + v + w; - if (det == 0.0) - return null; // no hit + var det: P = u + v + w; + if (det == 0.0) + return null; // no hit - // Calculate scaled z-coordinates of vertices and use them - // to calculate the hit distance - const az: P = sz * a[kz]; - const bz: P = sz * b[kz]; - const cz: P = sz * c[kz]; - var t: P = u * az + v * bz + w * cz; + // Calculate scaled z-coordinates of vertices and use them + // to calculate the hit distance + const az: P = sz * a[kz]; + const bz: P = sz * b[kz]; + const cz: P = sz * c[kz]; + var t: P = u * az + v * bz + w * cz; - // hit.t counts as a previous hit for backface culling, - // in which case triangle behind will no longer be - // considered a hit. - // Since Ray.Hit is represented by a Vec4, t is the last - // element of that vector - var hit: Hit = Vec4P.init( - undefined, - undefined, - undefined, - math.inf(f32), - ); + // hit.t counts as a previous hit for backface culling, + // in which case triangle behind will no longer be + // considered a hit. + // Since Ray.Hit is represented by a Vec4, t is the last + // element of that vector + var hit: Hit = Vec4P.init( + undefined, + undefined, + undefined, + math.inf(f32), + ); - if (backface_culling) { - if ((t < 0.0) or (t > hit.v[3] * det)) - return null; // no hit - } else { - if (det < 0) { - t = -t; - det = -det; - } - if ((t < 0.0) or (t > hit.v[3] * det)) - return null; // no hit - } - - // Normalize u, v, w and t - const rcp_det = 1.0 / det; - hit.v[0] = u * rcp_det; - hit.v[1] = v * rcp_det; - hit.v[2] = w * rcp_det; - hit.v[3] = t * rcp_det; - - return hit; + if (backface_culling) { + if ((t < 0.0) or (t > hit.v[3] * det)) + return null; // no hit + } else { + if (det < 0) { + t = -t; + det = -det; } - }, - else => @compileError("Expected Vec3, Vec3h, or Vec3d, found '" ++ - @typeName(Vec3P) ++ "'"), - }; + if ((t < 0.0) or (t > hit.v[3] * det)) + return null; // no hit + } + + // Normalize u, v, w and t + const rcp_det = 1.0 / det; + hit.v[0] = u * rcp_det; + hit.v[1] = v * rcp_det; + hit.v[2] = w * rcp_det; + hit.v[3] = t * rcp_det; + + return hit; + } }; } diff --git a/src/math/vec.zig b/src/math/vec.zig index 3b751e08..90aaa034 100644 --- a/src/math/vec.zig +++ b/src/math/vec.zig @@ -8,145 +8,272 @@ const quat = @import("quat.zig"); pub const VecComponent = enum { x, y, z, w }; -pub fn Vec(comptime n_value: usize, comptime Scalar: type) type { +pub fn Vec2(comptime Scalar: type) type { return extern struct { v: Vector, /// The vector dimension size, e.g. Vec3.n == 3 - pub const n = n_value; + 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_value, Scalar); + pub const Vector = @Vector(n, Scalar); const VecN = @This(); - pub usingnamespace switch (VecN.n) { - inline 2 => struct { - pub inline fn init(xs: Scalar, ys: Scalar) VecN { - return .{ .v = .{ xs, ys } }; - } - pub inline fn fromInt(xs: anytype, ys: anytype) VecN { - return .{ .v = .{ @floatFromInt(xs), @floatFromInt(ys) } }; - } - pub inline fn x(v: *const VecN) Scalar { - return v.v[0]; - } - pub inline fn y(v: *const VecN) Scalar { - return v.v[1]; - } - }, - inline 3 => struct { - pub inline fn init(xs: Scalar, ys: Scalar, zs: Scalar) VecN { - return .{ .v = .{ xs, ys, zs } }; - } - pub inline fn fromInt(xs: anytype, ys: anytype, zs: anytype) VecN { - return .{ .v = .{ @floatFromInt(xs), @floatFromInt(ys), @floatFromInt(zs) } }; - } - 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]; - } + const Shared = VecShared(Scalar, VecN); - 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), - }) }; - } + pub inline fn init(xs: Scalar, ys: Scalar) VecN { + return .{ .v = .{ xs, ys } }; + } + pub inline fn fromInt(xs: anytype, ys: anytype) VecN { + return .{ .v = .{ @floatFromInt(xs), @floatFromInt(ys) } }; + } + pub inline fn x(v: *const VecN) Scalar { + return v.v[0]; + } + pub inline fn y(v: *const VecN) Scalar { + return v.v[1]; + } - /// 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 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; + }; +} - /// Vector * Matrix multiplication - pub inline fn mulMat(vector: *const VecN, matrix: *const mat.Mat(3, 3, Vec(3, 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]; - } - } - return .{ .v = result }; - } +pub fn Vec3(comptime Scalar: type) type { + return extern struct { + v: Vector, - /// 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 { - const qx = q.v.x(); - const qy = q.v.y(); - const qz = q.v.z(); - const w2 = q.v.w() * 2; + /// The vector dimension size, e.g. Vec3.n == 3 + pub const n = 3; - const vx = v.x(); - const vy = v.y(); - const vz = v.z(); + /// The scalar type of this vector, e.g. Vec3.T == f32 + pub const T = Scalar; - const uv_x = qy * vz - qz * vy; - const uv_y = qz * vx - qx * vz; - const uv_z = qx * vy - qy * vx; + // The underlying @Vector type + pub const Vector = @Vector(n, Scalar); - return math.vec3( - vx + uv_x * w2 + (qy * uv_z - qz * uv_y) * 2, - vy + uv_y * w2 + (qz * uv_x - qx * uv_z) * 2, - vz + uv_z * w2 + (qz * uv_y - qy * uv_x) * 2, - ); - } - }, - inline 4 => struct { - 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]; - } + const VecN = @This(); - /// Vector * Matrix multiplication - pub inline fn mulMat(vector: *const VecN, matrix: *const mat.Mat(4, 4, Vec(4, 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 }; - } - }, - else => @compileError("Expected Vec2, Vec3, Vec4, found '" ++ @typeName(VecN) ++ "'"), - }; + const Shared = VecShared(Scalar, VecN); + pub inline fn init(xs: Scalar, ys: Scalar, zs: Scalar) VecN { + return .{ .v = .{ xs, ys, zs } }; + } + pub inline fn fromInt(xs: anytype, ys: anytype, zs: anytype) VecN { + return .{ .v = .{ @floatFromInt(xs), @floatFromInt(ys), @floatFromInt(zs) } }; + } + 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 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); + } + + /// 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]; + } + } + return .{ .v = 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 { + const qx = q.v.x(); + const qy = q.v.y(); + const qz = q.v.z(); + const w2 = q.v.w() * 2; + + const vx = v.x(); + const vy = v.y(); + const vz = v.z(); + + const uv_x = qy * vz - qz * vy; + const uv_y = qz * vx - qx * vz; + const uv_z = qx * vy - qy * vx; + + return math.vec3( + vx + uv_x * w2 + (qy * uv_z - qz * uv_y) * 2, + vy + uv_y * w2 + (qz * uv_x - qx * uv_z) * 2, + vz + uv_z * w2 + (qz * uv_y - qy * uv_x) * 2, + ); + } + + 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 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 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 { return .{ .v = a.v + b.v }; @@ -377,10 +504,10 @@ pub fn Vec(comptime n_value: usize, comptime Scalar: type) type { /// 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: T) bool { + pub inline fn eqlApprox(a: *const VecN, b: *const VecN, tolerance: Scalar) bool { var i: usize = 0; while (i < VecN.n) : (i += 1) { - if (!math.eql(T, a.v[i], b.v[i], tolerance)) { + if (!math.eql(Scalar, a.v[i], b.v[i], tolerance)) { return false; } } @@ -390,7 +517,7 @@ pub fn Vec(comptime n_value: usize, comptime Scalar: 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(T)); + return a.eqlApprox(b, math.eps(Scalar)); } }; }