From a109bd6f96c65962ff85bbef20a778b55d1e32e5 Mon Sep 17 00:00:00 2001 From: BratishkaErik Date: Fri, 14 Jan 2022 19:44:25 +0600 Subject: [PATCH] Generic vector v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alexandre ChĂȘne --- README.md | 2 +- src/generic_vector.zig | 608 +++++++++++++++++++++++++++++++++++++++++ src/main.zig | 4 +- src/mat4.zig | 270 +++++++++--------- src/quaternion.zig | 149 +++++----- src/vec2.zig | 317 --------------------- src/vec3.zig | 410 --------------------------- src/vec4.zig | 353 ------------------------ 8 files changed, 835 insertions(+), 1278 deletions(-) create mode 100644 src/generic_vector.zig delete mode 100644 src/vec2.zig delete mode 100644 src/vec3.zig delete mode 100644 src/vec4.zig diff --git a/README.md b/README.md index 6b4eeac..f45ada3 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ pub fn main () void { var projection = za.perspective(45.0, 800.0 / 600.0, 0.1, 100.0); var view = za.lookAt(Vec3.new(0.0, 0.0, -3.), Vec3.zero(), Vec3.up()); var model = Mat4.fromTranslate(Vec3.new(0.2, 0.5, 0.0)); - + var mvp = Mat4.mult(projection, view.mult(model)); } ``` diff --git a/src/generic_vector.zig b/src/generic_vector.zig new file mode 100644 index 0000000..9008194 --- /dev/null +++ b/src/generic_vector.zig @@ -0,0 +1,608 @@ +const std = @import("std"); +const root = @import("main.zig"); +const math = std.math; +const meta = std.meta; +const expectEqual = std.testing.expectEqual; +const expect = std.testing.expect; +const panic = std.debug.panic; + +pub const Vec2 = GenericVector(2, f32); +pub const Vec2_f64 = GenericVector(2, f64); +pub const Vec2_i32 = GenericVector(2, i32); +pub const Vec2_usize = GenericVector(2, usize); + +pub const Vec3 = GenericVector(3, f32); +pub const Vec3_f64 = GenericVector(3, f64); +pub const Vec3_i32 = GenericVector(3, i32); +pub const Vec3_usize = GenericVector(3, usize); + +pub const Vec4 = GenericVector(4, f32); +pub const Vec4_f64 = GenericVector(4, f64); +pub const Vec4_i32 = GenericVector(4, i32); +pub const Vec4_usize = GenericVector(4, usize); + +/// A generic vector. +pub fn GenericVector(comptime dimensions: comptime_int, comptime T: type) type { + if (@typeInfo(T) != .Float and @typeInfo(T) != .Int) { + @compileError("Vectors not implemented for " ++ @typeName(T)); + } + + if (dimensions < 2 or dimensions > 4) { + @compileError("Dimensions must be 2, 3 or 4!"); + } + + return extern struct { + const Self = @This(); + + data: meta.Vector(dimensions, T), + usingnamespace switch (dimensions) { + 2 => extern struct { + /// Construct new vector. + pub fn new(x: T, y: T) Self { + return .{ .data = [2]T{ x, y } }; + } + }, + 3 => extern struct { + /// Construct new vector. + pub fn new(x: T, y: T, z: T) Self { + return .{ .data = [3]T{ x, y, z } }; + } + + /// Shorthand for (0, 0, 1). + pub fn forward() Self { + return new(0, 0, 1); + } + + /// Shorthand for (0, 0, -1). + pub fn back() Self { + return forward().negate(); + } + + /// Construct the cross product (as vector) from two vectors. + pub fn cross(lhs: Self, rhs: Self) Self { + const lx = lhs.data[0]; + const ly = lhs.data[1]; + const lz = lhs.data[2]; + + const rx = rhs.data[0]; + const ry = rhs.data[1]; + const rz = rhs.data[2]; + + const x = (ly * rz) - (lz * ry); + const y = (lz * rx) - (lx * rz); + const z = (lx * ry) - (ly * rx); + return new(x, y, z); + } + }, + 4 => extern struct { + /// Construct new vector. + pub fn new(x: T, y: T, z: T, w: T) Self { + return .{ .data = [4]T{ x, y, z, w } }; + } + }, + else => {}, + }; + + /// Set all components to the same given value. + pub fn set(val: T) Self { + var result: [dimensions]T = undefined; + for (result) |_, i| { + result[i] = val; + } + return .{ .data = result }; + } + + /// Shorthand for (0..). + pub fn zero() Self { + return set(0); + } + + /// Shorthand for (1..). + pub fn one() Self { + return set(1); + } + + /// Shorthand for (0, 1). + pub fn up() Self { + return switch (dimensions) { + 2 => Self.new(0, 1), + 3 => Self.new(0, 1, 0), + 4 => Self.new(0, 1, 0, 0), + else => unreachable, + }; + } + + /// Shorthand for (0, -1). + pub fn down() Self { + return up().negate(); + } + + /// Shorthand for (1, 0). + pub fn right() Self { + return switch (dimensions) { + 2 => Self.new(1, 0), + 3 => Self.new(1, 0, 0), + 4 => Self.new(1, 0, 0, 0), + else => unreachable, + }; + } + + /// Shorthand for (-1, 0). + pub fn left() Self { + return right().negate(); + } + + /// Negate the given vector. + pub fn negate(vector: Self) Self { + return vector.scale(-1); + } + + /// Cast a type to another type. + /// It's like builtins: @intCast, @floatCast, @intToFloat, @floatToInt. + pub fn cast(self: Self, dest: anytype) GenericVector(dimensions, dest) { + const source_info = @typeInfo(T); + const dest_info = @typeInfo(dest); + var result: [dimensions]dest = undefined; + + if (source_info == .Float and dest_info == .Int) { + for (result) |_, i| { + result[i] = @floatToInt(dest, self.data[i]); + } + return .{ .data = result }; + } + + if (source_info == .Int and dest_info == .Float) { + for (result) |_, i| { + result[i] = @intToFloat(dest, self.data[i]); + } + return .{ .data = result }; + } + + return switch (dest_info) { + .Float => { + for (result) |_, i| { + result[i] = @floatCast(dest, self.data[i]); + } + return .{ .data = result }; + }, + .Int => { + for (result) |_, i| { + result[i] = @intCast(dest, self.data[i]); + } + return .{ .data = result }; + }, + else => panic( + "Error, dest type should be integer or float.\n", + .{}, + ), + }; + } + + /// Construct new vector from slice. + pub fn fromSlice(slice: []const T) Self { + const result = slice[0..dimensions].*; + return .{ .data = result }; + } + + /// Transform vector to array. + pub fn toArray(self: Self) [dimensions]T { + return self.data; + } + + /// Return the angle (in degrees) between two vectors. + pub fn getAngle(left_vector: Self, right_vector: Self) T { + const dot_product = dot(norm(left_vector), norm(right_vector)); + return root.toDegrees(math.acos(dot_product)); + } + + /// Return the length (magnitude) of given vector. + pub fn length(vector: Self) T { + return @sqrt(vector.dot(vector)); + } + + /// Return the distance between two points. + pub fn distance(a: Self, b: Self) T { + return length(b.sub(a)); + } + + /// Construct new normalized vector from a given one. + pub fn norm(vector: Self) Self { + const l = @splat(dimensions, vector.length()); + const result = vector.data / l; + return .{ .data = result }; + } + + /// Return true if two vectors are equals. + pub fn eql(left_vector: Self, right_vector: Self) bool { + return meta.eql(left_vector, right_vector); + } + + /// Substraction between two given vector. + pub fn sub(lhs: Self, rhs: Self) Self { + const result = lhs.data - rhs.data; + return .{ .data = result }; + } + + /// Addition betwen two given vector. + pub fn add(lhs: Self, rhs: Self) Self { + const result = lhs.data + rhs.data; + return .{ .data = result }; + } + + /// Construct vector from the max components in two vectors + pub fn max(a: Self, b: Self) Self { + const result = @maximum(a.data, b.data); + return .{ .data = result }; + } + + /// Construct vector from the min components in two vectors + pub fn min(a: Self, b: Self) Self { + const result = @minimum(a.data, b.data); + return .{ .data = result }; + } + + /// Construct new vector after multiplying each components by the given scalar + pub fn scale(vector: Self, scalar: T) Self { + const result = vector.data * @splat(dimensions, scalar); + return .{ .data = result }; + } + + /// Return the dot product between two given vector. + pub fn dot(left_vector: Self, right_vector: Self) T { + return @reduce(.Add, left_vector.data * right_vector.data); + } + + /// Linear interpolation between two vectors + pub fn lerp(left_vector: Self, right_vector: Self, t: T) Self { + var result: [dimensions]T = undefined; + for (result) |_, i| { + result[i] = root.lerp(T, left_vector.data[i], right_vector.data[i], t); + } + return .{ .data = result }; + } + }; +} + +test "zalgebra.Vectors.eql" { + // Vec2 + const a = Vec2.new(1, 2); + const b = Vec2.new(1, 2); + const c = Vec2.new(1.5, 2); + + try expectEqual(Vec2.eql(a, b), true); + try expectEqual(Vec2.eql(a, c), false); + + // Vec3 + const d = Vec3.new(1, 2, 3); + const e = Vec3.new(1, 2, 3); + const f = Vec3.new(1.5, 2, 3); + + try expectEqual(Vec3.eql(d, e), true); + try expectEqual(Vec3.eql(d, f), false); + + // Vec4 + const g = Vec4.new(1, 2, 3, 4); + const h = Vec4.new(1, 2, 3, 4); + const i = Vec4.new(1.5, 2, 3, 4); + + try expectEqual(Vec4.eql(g, h), true); + try expectEqual(Vec4.eql(g, i), false); +} + +test "zalgebra.Vectors.set" { + // Vec2 + const a = Vec2.new(2.5, 2.5); + const b = Vec2.set(2.5); + try expectEqual(a, b); + + // Vec3 + const c = Vec3.new(2.5, 2.5, 2.5); + const d = Vec3.set(2.5); + try expectEqual(c, d); + + // Vec4 + const e = Vec4.new(2.5, 2.5, 2.5, 2.5); + const f = Vec4.set(2.5); + try expectEqual(e, f); +} + +test "zalgebra.Vectors.add" { + // Vec2 + const a = Vec2.one(); + const b = Vec2.one(); + try expectEqual(a.add(b), Vec2.set(2)); + + // Vec3 + const c = Vec3.one(); + const d = Vec3.one(); + try expectEqual(c.add(d), Vec3.set(2)); + + // Vec4 + const e = Vec4.one(); + const f = Vec4.one(); + try expectEqual(e.add(f), Vec4.set(2)); +} + +test "zalgebra.Vectors.negate" { + // Vec2 + const a = Vec2.set(5); + const a_negated = Vec2.set(-5); + try expectEqual(a.negate(), a_negated); + + // Vec3 + const b = Vec3.set(5); + const b_negated = Vec3.set(-5); + try expectEqual(b.negate(), b_negated); + + // Vec4 + const c = Vec4.set(5); + const c_negated = Vec4.set(-5); + try expectEqual(c.negate(), c_negated); +} + +test "zalgebra.Vectors.getAngle" { + // Vec2 + const a = Vec2.right(); + const b = Vec2.up(); + const c = Vec2.left(); + const d = Vec2.one(); + + try expectEqual(a.getAngle(b), 90); + try expectEqual(a.getAngle(c), 180); + try expectEqual(a.getAngle(d), 45); + + // Vec3 + const e = Vec3.right(); + const f = Vec3.up(); + const g = Vec3.left(); + const h = Vec3.new(1, 1, 0); + + try expectEqual(e.getAngle(f), 90); + try expectEqual(e.getAngle(g), 180); + try expectEqual(e.getAngle(h), 45); + + // Vec4 + const i = Vec4.right(); + const j = Vec4.up(); + const k = Vec4.left(); + const l = Vec4.new(1, 1, 0, 0); + + try expectEqual(i.getAngle(j), 90); + try expectEqual(i.getAngle(k), 180); + try expectEqual(i.getAngle(l), 45); +} + +test "zalgebra.Vec3.toArray" { + const a = Vec3.up().toArray(); + const b = [_]f32{ 0, 1, 0 }; + + try expect(std.mem.eql(f32, &a, &b)); +} + +test "zalgebra.Vectors.length" { + // Vec2 + const a = Vec2.new(1.5, 2.6); + try expectEqual(a.length(), 3.00166606); + + // Vec3 + const b = Vec3.new(1.5, 2.6, 3.7); + try expectEqual(b.length(), 4.7644519); + + // Vec4 + const c = Vec4.new(1.5, 2.6, 3.7, 4.7); + try expectEqual(c.length(), 6.69253301); +} + +test "zalgebra.Vectors.distance" { + // Vec2 + const a = Vec2.zero(); + const b = Vec2.left(); + const c = Vec2.new(0, 5); + + try expectEqual(a.distance(b), 1); + try expectEqual(a.distance(c), 5); + + // Vec3 + const d = Vec3.zero(); + const e = Vec3.left(); + const f = Vec3.new(0, 5, 0); + + try expectEqual(d.distance(e), 1); + try expectEqual(d.distance(f), 5); + + // Vec4 + const g = Vec4.zero(); + const h = Vec4.left(); + const i = Vec4.new(0, 5, 0, 0); + + try expectEqual(g.distance(h), 1); + try expectEqual(g.distance(i), 5); +} + +test "zalgebra.Vectors.normalize" { + // Vec2 + const a = Vec2.new(1.5, 2.6); + const a_normalized = Vec2.new(0.499722480, 0.866185605); + try expectEqual(a.norm(), a_normalized); + + // Vec3 + const b = Vec3.new(1.5, 2.6, 3.7); + const b_normalized = Vec3.new(0.314831584, 0.545708060, 0.776584625); + try expectEqual(b.norm(), b_normalized); + + // Vec4 + const c = Vec4.new(1.5, 2.6, 3.7, 4.0); + const c_normalized = Vec4.new(0.241121411, 0.417943745, 0.594766139, 0.642990410); + try expectEqual(c.norm(), c_normalized); +} + +test "zalgebra.Vectors.scale" { + // Vec2 + const a = Vec2.new(1, 2); + const a_scaled = Vec2.new(5, 10); + try expectEqual(a.scale(5), a_scaled); + + // Vec3 + const b = Vec3.new(1, 2, 3); + const b_scaled = Vec3.new(5, 10, 15); + try expectEqual(b.scale(5), b_scaled); + + // Vec4 + const c = Vec4.new(1, 2, 3, 4); + const c_scaled = Vec4.new(5, 10, 15, 20); + try expectEqual(c.scale(5), c_scaled); +} + +test "zalgebra.Vectors.dot" { + // Vec2 + const a = Vec2.new(1.5, 2.6); + const b = Vec2.new(2.5, 3.45); + try expectEqual(a.dot(b), 12.7200002); + + // Vec3 + const c = Vec3.new(1.5, 2.6, 3.7); + const d = Vec3.new(2.5, 3.45, 1.0); + try expectEqual(c.dot(d), 16.42); + + // Vec4 + const e = Vec4.new(1.5, 2.6, 3.7, 5); + const f = Vec4.new(2.5, 3.45, 1.0, 1); + try expectEqual(e.dot(f), 21.4200000); +} + +test "zalgebra.Vectors.lerp" { + // Vec2 + const a = Vec2.new(-10, 0); + const b = Vec2.set(10); + try expectEqual(Vec2.lerp(a, b, 0.5), Vec2.new(0, 5)); + + // Vec3 + const c = Vec3.new(-10, 0, -10); + const d = Vec3.set(10); + try expectEqual(Vec3.lerp(c, d, 0.5), Vec3.new(0, 5, 0)); + + // Vec4 + const e = Vec4.new(-10, 0, -10, -10); + const f = Vec4.set(10); + try expectEqual(Vec4.lerp(e, f, 0.5), Vec4.new(0, 5, 0, 0)); +} + +test "zalgebra.Vectors.min" { + // Vec2 + const a = Vec2.new(10, -2); + const b = Vec2.new(-10, 5); + const a_b_minimum = Vec2.new(-10, -2); + try expectEqual(Vec2.min(a, b), a_b_minimum); + + // Vec3 + const c = Vec3.new(10, -2, 0); + const d = Vec3.new(-10, 5, 0); + const c_d_minimum = Vec3.new(-10, -2, 0); + try expectEqual(Vec3.min(c, d), c_d_minimum); + + // Vec4 + const e = Vec4.new(10, -2, 0, 1); + const f = Vec4.new(-10, 5, 0, 1.01); + const e_f_minimum = Vec4.new(-10, -2, 0, 1); + try expectEqual(Vec4.min(e, f), e_f_minimum); +} + +test "zalgebra.Vectors.max" { + // Vec2 + const a = Vec2.new(10, -2); + const b = Vec2.new(-10, 5); + const a_b_maximum = Vec2.new(10, 5); + try expectEqual(Vec2.max(a, b), a_b_maximum); + + // Vec3 + const c = Vec3.new(10, -2, 0); + const d = Vec3.new(-10, 5, 0); + const c_d_maximum = Vec3.new(10, 5, 0); + try expectEqual(Vec3.max(c, d), c_d_maximum); + + // Vec4 + const e = Vec4.new(10, -2, 0, 1); + const f = Vec4.new(-10, 5, 0, 1.01); + const e_f_maximum = Vec4.new(10, 5, 0, 1.01); + try expectEqual(Vec4.max(e, f), e_f_maximum); +} + +test "zalgebra.Vectors.fromSlice" { + // Vec2 + const a = [2]f32{ 2, 4 }; + try expectEqual(Vec2.fromSlice(&a), Vec2.new(2, 4)); + + // Vec3 + const b = [3]f32{ 2, 4, 3 }; + try expectEqual(Vec3.fromSlice(&b), Vec3.new(2, 4, 3)); + + // Vec4 + const c = [4]f32{ 2, 4, 3, 6 }; + try expectEqual(Vec4.fromSlice(&c), Vec4.new(2, 4, 3, 6)); +} + +test "zalgebra.Vectors.cast" { + // Vec2 + const a = Vec2_i32.new(3, 6); + const a_usize = Vec2_usize.new(3, 6); + try expectEqual(a.cast(usize), a_usize); + + const b = Vec2.new(3.5, 6.5); + const b_f64 = Vec2_f64.new(3.5, 6.5); + try expectEqual(b.cast(f64), b_f64); + + const c = Vec2_i32.new(3, 6); + const c_f32 = Vec2.new(3, 6); + try expectEqual(c.cast(f32), c_f32); + + const d = Vec2.new(3, 6); + const d_i32 = Vec2_i32.new(3, 6); + try expectEqual(d.cast(i32), d_i32); + + // Vec3 + const e = Vec3_i32.new(3, 6, 2); + const e_usize = Vec3_usize.new(3, 6, 2); + try expectEqual(e.cast(usize), e_usize); + + const f = Vec3.new(3.5, 6.5, 2); + const f_f64 = Vec3_f64.new(3.5, 6.5, 2); + try expectEqual(f.cast(f64), f_f64); + + const g = Vec3_i32.new(3, 6, 2); + const g_f32 = Vec3.new(3, 6, 2); + try expectEqual(g.cast(f32), g_f32); + + const h = Vec3.new(3, 6, 2); + const h_i32 = Vec3_i32.new(3, 6, 2); + try expectEqual(h.cast(i32), h_i32); + + // Vec4 + const i = Vec4_i32.new(3, 6, 2, 0); + const i_usize = Vec4_usize.new(3, 6, 2, 0); + try expectEqual(i.cast(usize), i_usize); + + const j = Vec4.new(3.5, 6.5, 2, 0); + const j_f64 = Vec4_f64.new(3.5, 6.5, 2, 0); + try expectEqual(j.cast(f64), j_f64); + + const k = Vec4_i32.new(3, 6, 2, 0); + const k_f32 = Vec4.new(3, 6, 2, 0); + try expectEqual(k.cast(f32), k_f32); + + const l = Vec4.new(3, 6, 2, 0); + const l_i32 = Vec4_i32.new(3, 6, 2, 0); + try expectEqual(l.cast(i32), l_i32); +} + +test "zalgebra.Vectors.cross" { + // Only for Vec3 + const a = Vec3.new(1.5, 2.6, 3.7); + const b = Vec3.new(2.5, 3.45, 1.0); + const c = Vec3.new(1.5, 2.6, 3.7); + + const result_1 = Vec3.cross(a, c); + const result_2 = Vec3.cross(a, b); + + try expectEqual(result_1, Vec3.zero()); + try expectEqual(result_2, Vec3.new(-10.1650009, 7.75, -1.32499980)); +} diff --git a/src/main.zig b/src/main.zig index e2e6dcb..171543d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,9 +4,7 @@ const std = @import("std"); const expectEqual = std.testing.expectEqual; const math = std.math; -pub usingnamespace @import("vec2.zig"); -pub usingnamespace @import("vec3.zig"); -pub usingnamespace @import("vec4.zig"); +pub usingnamespace @import("generic_vector.zig"); pub usingnamespace @import("mat4.zig"); pub usingnamespace @import("quaternion.zig"); diff --git a/src/mat4.zig b/src/mat4.zig index 63f9881..53df848 100644 --- a/src/mat4.zig +++ b/src/mat4.zig @@ -1,15 +1,15 @@ const std = @import("std"); -const root = @import("main.zig"); -const vec4 = @import("vec4.zig"); -const vec3 = @import("vec3.zig"); -const quat = @import("quaternion.zig"); const math = std.math; +const meta = std.meta; const mem = std.mem; const expectEqual = std.testing.expectEqual; const print = std.debug.print; -const Vec3 = vec3.Vec3; -const Vector3 = vec3.Vector3; -const Vector4 = vec4.Vector4; +const root = @import("main.zig"); +const generic_vector = @import("generic_vector.zig"); +const quat = @import("quaternion.zig"); + +const Vec3 = generic_vector.Vec3; +const GenericVector = generic_vector.GenericVector; const Quaternion = quat.Quaternion; const Quat = quat.Quat; @@ -26,11 +26,15 @@ pub fn Mat4x4(comptime T: type) type { @compileError("Mat4x4 not implemented for " ++ @typeName(T)); } + const Vector3 = GenericVector(3, T); + const Vector4 = GenericVector(4, T); + return extern struct { data: [4][4]T = mem.zeroes([4][4]T), const Self = @This(); + /// Shorthand for identity matrix. pub fn identity() Self { return .{ .data = .{ @@ -42,6 +46,18 @@ pub fn Mat4x4(comptime T: type) type { }; } + /// Shorthand for matrix with all zeros. + pub fn zero() Self { + return .{ + .data = .{ + .{ 0, 0, 0, 0 }, + .{ 0, 0, 0, 0 }, + .{ 0, 0, 0, 0 }, + .{ 0, 0, 0, 0 }, + }, + }; + } + /// Set all mat4 values to given value. pub fn set(value: T) Self { var data: [16]T = undefined; @@ -96,6 +112,7 @@ pub fn Mat4x4(comptime T: type) type { return @ptrCast(*const T, &mat.data); } + /// Return true if two matrices are equals. pub fn eql(left: Self, right: Self) bool { var col: usize = 0; @@ -111,42 +128,44 @@ pub fn Mat4x4(comptime T: type) type { return true; } - pub fn multByVec4(mat: Self, v: Vector4(T)) Vector4(T) { - var result: Vector4(T) = undefined; + pub fn multByVec4(mat: Self, v: Vector4) Vector4 { + const vx = v.data[0]; + const vy = v.data[1]; + const vz = v.data[2]; + const vw = v.data[3]; - result.x = (mat.data[0][0] * v.x) + (mat.data[1][0] * v.y) + (mat.data[2][0] * v.z) + (mat.data[3][0] * v.w); - result.y = (mat.data[0][1] * v.x) + (mat.data[1][1] * v.y) + (mat.data[2][1] * v.z) + (mat.data[3][1] * v.w); - result.z = (mat.data[0][2] * v.x) + (mat.data[1][2] * v.y) + (mat.data[2][2] * v.z) + (mat.data[3][2] * v.w); - result.w = (mat.data[0][3] * v.x) + (mat.data[1][3] * v.y) + (mat.data[2][3] * v.z) + (mat.data[3][3] * v.w); + const x = (mat.data[0][0] * vx) + (mat.data[1][0] * vy) + (mat.data[2][0] * vz) + (mat.data[3][0] * vw); + const y = (mat.data[0][1] * vx) + (mat.data[1][1] * vy) + (mat.data[2][1] * vz) + (mat.data[3][1] * vw); + const z = (mat.data[0][2] * vx) + (mat.data[1][2] * vy) + (mat.data[2][2] * vz) + (mat.data[3][2] * vw); + const w = (mat.data[0][3] * vx) + (mat.data[1][3] * vy) + (mat.data[2][3] * vz) + (mat.data[3][3] * vw); - return result; + return Vector4.new(x, y, z, w); } /// Construct 4x4 translation matrix by multiplying identity matrix and /// given translation vector. - pub fn fromTranslate(axis: Vector3(T)) Self { + pub fn fromTranslate(axis: Vector3) Self { var mat = Self.identity(); - - mat.data[3][0] = axis.x; - mat.data[3][1] = axis.y; - mat.data[3][2] = axis.z; + mat.data[3][0] = axis.data[0]; + mat.data[3][1] = axis.data[1]; + mat.data[3][2] = axis.data[2]; return mat; } /// Make a translation between the given matrix and the given axis. - pub fn translate(mat: Self, axis: Vector3(T)) Self { + pub fn translate(mat: Self, axis: Vector3) Self { const trans_mat = Self.fromTranslate(axis); return Self.mult(trans_mat, mat); } /// Get translation Vec3 from current matrix. - pub fn extractTranslation(self: Self) Vector3(T) { - return Vector3(T).new(self.data[3][0], self.data[3][1], self.data[3][2]); + pub fn extractTranslation(self: Self) Vector3 { + return Vector3.new(self.data[3][0], self.data[3][1], self.data[3][2]); } /// Construct a 4x4 matrix from given axis and angle (in degrees). - pub fn fromRotation(angle_in_degrees: T, axis: Vector3(T)) Self { + pub fn fromRotation(angle_in_degrees: T, axis: Vector3) Self { var mat = Self.identity(); const norm_axis = axis.norm(); @@ -155,55 +174,59 @@ pub fn Mat4x4(comptime T: type) type { const cos_theta = @cos(root.toRadians(angle_in_degrees)); const cos_value = 1.0 - cos_theta; - mat.data[0][0] = (norm_axis.x * norm_axis.x * cos_value) + cos_theta; - mat.data[0][1] = (norm_axis.x * norm_axis.y * cos_value) + (norm_axis.z * sin_theta); - mat.data[0][2] = (norm_axis.x * norm_axis.z * cos_value) - (norm_axis.y * sin_theta); + const x = norm_axis.data[0]; + const y = norm_axis.data[1]; + const z = norm_axis.data[2]; - mat.data[1][0] = (norm_axis.y * norm_axis.x * cos_value) - (norm_axis.z * sin_theta); - mat.data[1][1] = (norm_axis.y * norm_axis.y * cos_value) + cos_theta; - mat.data[1][2] = (norm_axis.y * norm_axis.z * cos_value) + (norm_axis.x * sin_theta); + mat.data[0][0] = (x * x * cos_value) + cos_theta; + mat.data[0][1] = (x * y * cos_value) + (z * sin_theta); + mat.data[0][2] = (x * z * cos_value) - (y * sin_theta); - mat.data[2][0] = (norm_axis.z * norm_axis.x * cos_value) + (norm_axis.y * sin_theta); - mat.data[2][1] = (norm_axis.z * norm_axis.y * cos_value) - (norm_axis.x * sin_theta); - mat.data[2][2] = (norm_axis.z * norm_axis.z * cos_value) + cos_theta; + mat.data[1][0] = (y * x * cos_value) - (z * sin_theta); + mat.data[1][1] = (y * y * cos_value) + cos_theta; + mat.data[1][2] = (y * z * cos_value) + (x * sin_theta); + + mat.data[2][0] = (z * x * cos_value) + (y * sin_theta); + mat.data[2][1] = (z * y * cos_value) - (x * sin_theta); + mat.data[2][2] = (z * z * cos_value) + cos_theta; return mat; } - pub fn rotate(mat: Self, angle_in_degrees: T, axis: Vector3(T)) Self { + pub fn rotate(mat: Self, angle_in_degrees: T, axis: Vector3) Self { const rotation_mat = Self.fromRotation(angle_in_degrees, axis); return Self.mult(mat, rotation_mat); } /// Construct a rotation matrix from euler angles (X * Y * Z). /// Order matters because matrix multiplication are NOT commutative. - pub fn fromEulerAngles(euler_angle: Vector3(T)) Self { - const x = Self.fromRotation(euler_angle.x, Vec3.right()); - const y = Self.fromRotation(euler_angle.y, Vec3.up()); - const z = Self.fromRotation(euler_angle.z, Vec3.forward()); + pub fn fromEulerAngles(euler_angle: Vector3) Self { + const x = Self.fromRotation(euler_angle.data[0], Vector3.right()); + const y = Self.fromRotation(euler_angle.data[1], Vector3.up()); + const z = Self.fromRotation(euler_angle.data[2], Vector3.forward()); return z.mult(y.mult(x)); } /// Ortho normalize given matrix. pub fn orthoNormalize(mat: Self) Self { - const column_1 = Vec3.new(mat.data[0][0], mat.data[0][1], mat.data[0][2]).norm(); - const column_2 = Vec3.new(mat.data[1][0], mat.data[1][1], mat.data[1][2]).norm(); - const column_3 = Vec3.new(mat.data[2][0], mat.data[2][1], mat.data[2][2]).norm(); + const column_1 = Vector3.new(mat.data[0][0], mat.data[0][1], mat.data[0][2]).norm(); + const column_2 = Vector3.new(mat.data[1][0], mat.data[1][1], mat.data[1][2]).norm(); + const column_3 = Vector3.new(mat.data[2][0], mat.data[2][1], mat.data[2][2]).norm(); var result = mat; - result.data[0][0] = column_1.x; - result.data[0][1] = column_1.y; - result.data[0][2] = column_1.z; + result.data[0][0] = column_1.data[0]; + result.data[0][1] = column_1.data[1]; + result.data[0][2] = column_1.data[2]; - result.data[1][0] = column_2.x; - result.data[1][1] = column_2.y; - result.data[1][2] = column_2.z; + result.data[1][0] = column_2.data[0]; + result.data[1][1] = column_2.data[1]; + result.data[1][2] = column_2.data[2]; - result.data[2][0] = column_3.x; - result.data[2][1] = column_3.y; - result.data[2][2] = column_3.z; + result.data[2][0] = column_3.data[0]; + result.data[2][1] = column_3.data[1]; + result.data[2][2] = column_3.data[2]; return result; } @@ -211,7 +234,7 @@ pub fn Mat4x4(comptime T: type) type { /// Return the rotation as Euler angles in degrees. /// Taken from Mike Day at Insomniac Games (and `glm` as the same function). /// For more details: https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf - pub fn extractEulerAngles(self: Self) Vector3(T) { + pub fn extractEulerAngles(self: Self) Vector3 { const m = self.orthoNormalize(); const theta_x = math.atan2(T, m.data[1][2], m.data[2][2]); @@ -221,30 +244,30 @@ pub fn Mat4x4(comptime T: type) type { const c1 = @cos(theta_x); const theta_z = math.atan2(T, s1 * m.data[2][0] - c1 * m.data[1][0], c1 * m.data[1][1] - s1 * m.data[2][1]); - return Vec3.new(root.toDegrees(theta_x), root.toDegrees(theta_y), root.toDegrees(theta_z)); + return Vector3.new(root.toDegrees(theta_x), root.toDegrees(theta_y), root.toDegrees(theta_z)); } - pub fn fromScale(axis: Vector3(T)) Self { + pub fn fromScale(axis: Vector3) Self { var mat = Self.identity(); - mat.data[0][0] = axis.x; - mat.data[1][1] = axis.y; - mat.data[2][2] = axis.z; + mat.data[0][0] = axis.data[0]; + mat.data[1][1] = axis.data[1]; + mat.data[2][2] = axis.data[2]; return mat; } - pub fn scale(mat: Self, axis: Vector3(T)) Self { + pub fn scale(mat: Self, axis: Vector3) Self { const scale_mat = Self.fromScale(axis); return Self.mult(scale_mat, mat); } - pub fn extractScale(mat: Self) Vector3(T) { - const scale_x = Vec3.new(mat.data[0][0], mat.data[0][1], mat.data[0][2]).length(); - const scale_y = Vec3.new(mat.data[1][0], mat.data[1][1], mat.data[1][2]).length(); - const scale_z = Vec3.new(mat.data[2][0], mat.data[2][1], mat.data[2][2]).length(); + pub fn extractScale(mat: Self) Vector3 { + const scale_x = Vector3.new(mat.data[0][0], mat.data[0][1], mat.data[0][2]); + const scale_y = Vector3.new(mat.data[1][0], mat.data[1][1], mat.data[1][2]); + const scale_z = Vector3.new(mat.data[2][0], mat.data[2][1], mat.data[2][2]); - return Vector3(T).new(scale_x, scale_y, scale_z); + return Vector3.new(scale_x.length(), scale_y.length(), scale_z.length()); } /// Construct a perspective 4x4 matrix. @@ -282,36 +305,36 @@ pub fn Mat4x4(comptime T: type) type { } /// Right-handed lookAt function. - pub fn lookAt(eye: Vector3(T), target: Vector3(T), up: Vector3(T)) Self { - const f = Vector3(T).norm(Vector3(T).sub(target, eye)); - const s = Vector3(T).norm(Vector3(T).cross(f, up)); - const u = Vector3(T).cross(s, f); + pub fn lookAt(eye: Vector3, target: Vector3, up: Vector3) Self { + const f = Vector3.sub(target, eye).norm(); + const s = Vector3.cross(f, up).norm(); + const u = Vector3.cross(s, f); var mat: Self = undefined; - mat.data[0][0] = s.x; - mat.data[0][1] = u.x; - mat.data[0][2] = -f.x; - mat.data[0][3] = 0.0; - - mat.data[1][0] = s.y; - mat.data[1][1] = u.y; - mat.data[1][2] = -f.y; - mat.data[1][3] = 0.0; - - mat.data[2][0] = s.z; - mat.data[2][1] = u.z; - mat.data[2][2] = -f.z; - mat.data[2][3] = 0.0; - - mat.data[3][0] = -Vector3(T).dot(s, eye); - mat.data[3][1] = -Vector3(T).dot(u, eye); - mat.data[3][2] = Vector3(T).dot(f, eye); - mat.data[3][3] = 1.0; + mat.data[0][0] = s.data[0]; + mat.data[0][1] = u.data[0]; + mat.data[0][2] = -f.data[0]; + mat.data[0][3] = 0; + + mat.data[1][0] = s.data[1]; + mat.data[1][1] = u.data[1]; + mat.data[1][2] = -f.data[1]; + mat.data[1][3] = 0; + + mat.data[2][0] = s.data[2]; + mat.data[2][1] = u.data[2]; + mat.data[2][2] = -f.data[2]; + mat.data[2][3] = 0; + + mat.data[3][0] = -Vector3.dot(s, eye); + mat.data[3][1] = -Vector3.dot(u, eye); + mat.data[3][2] = Vector3.dot(f, eye); + mat.data[3][3] = 1; return mat; } - /// Matrices multiplication. + /// Matrices' multiplication. /// Produce a new matrix from given two matrices. pub fn mult(left: Self, right: Self) Self { var mat = Self.identity(); @@ -330,7 +353,6 @@ pub fn Mat4x4(comptime T: type) type { mat.data[columns][rows] = sum; } } - return mat; } @@ -398,16 +420,16 @@ pub fn Mat4x4(comptime T: type) type { return inv_mat; } - /// Return 4x4 matrix from given all transform components; `translation`, `rotation` and `sclale`. + /// Return 4x4 matrix from given all transform components; `translation`, `rotation` and `scale`. /// The final order is T * R * S. /// Note: `rotation` could be `Vec3` (Euler angles) or a `quat`. - pub fn recompose(translation: Vector3(T), rotation: anytype, scaler: Vector3(T)) Self { + pub fn recompose(translation: Vector3, rotation: anytype, scalar: Vector3) Self { const t = Self.fromTranslate(translation); - const s = Self.fromScale(scaler); + const s = Self.fromScale(scalar); const r = switch (@TypeOf(rotation)) { Quaternion(T) => Quaternion(T).toMat4(rotation), - Vector3(T) => Self.fromEulerAngles(rotation), + Vector3 => Self.fromEulerAngles(rotation), else => @compileError("Recompose not implemented for " ++ @typeName(@TypeOf(rotation))), }; @@ -418,7 +440,7 @@ pub fn Mat4x4(comptime T: type) type { /// For now, the rotation returned is a quaternion. If you want to get Euler angles /// from it, just do: `returned_quat.extractEulerAngles()`. /// Note: We ortho nornalize the given matrix before extracting the rotation. - pub fn decompose(mat: Self) struct { t: Vector3(T), r: Quaternion(T), s: Vector3(T) } { + pub fn decompose(mat: Self) struct { t: Vector3, r: Quaternion(T), s: Vector3 } { const t = mat.extractTranslation(); const s = mat.extractScale(); const r = Quat.fromMat4(mat.orthoNormalize()); @@ -466,14 +488,7 @@ pub fn Mat4x4(comptime T: type) type { test "zalgebra.Mat4.eql" { const a = Mat4.identity(); const b = Mat4.identity(); - const c = Mat4{ - .data = .{ - .{ 0, 0, 0, 0 }, - .{ 0, 0, 0, 0 }, - .{ 0, 0, 0, 0 }, - .{ 0, 0, 0, 0 }, - }, - }; + const c = Mat4.zero(); try expectEqual(Mat4.eql(a, b), true); try expectEqual(Mat4.eql(a, c), false); @@ -490,7 +505,7 @@ test "zalgebra.Mat4.set" { }, }; - try expectEqual(Mat4.eql(a, b), true); + try expectEqual(a, b); } test "zalgebra.Mat4.negate" { @@ -502,7 +517,7 @@ test "zalgebra.Mat4.negate" { .{ 13, 14, 15, 16 }, }, }; - const b = Mat4{ + const a_negated = Mat4{ .data = .{ .{ -1, -2, -3, -4 }, .{ -5, 6, -7, -8 }, @@ -511,7 +526,7 @@ test "zalgebra.Mat4.negate" { }, }; - try expectEqual(Mat4.eql(a.negate(), b), true); + try expectEqual(a.negate(), a_negated); } test "zalgebra.Mat4.transpose" { @@ -532,68 +547,68 @@ test "zalgebra.Mat4.transpose" { }, }; - try expectEqual(Mat4.eql(a.transpose(), b), true); + try expectEqual(a.transpose(), b); } test "zalgebra.Mat4.fromSlice" { const data = [_]f32{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; const result = Mat4.fromSlice(&data); - try expectEqual(Mat4.eql(result, Mat4.identity()), true); + try expectEqual(result, Mat4.identity()); } test "zalgebra.Mat4.fromTranslate" { const a = Mat4.fromTranslate(Vec3.new(2, 3, 4)); - try expectEqual(Mat4.eql(a, Mat4{ + try expectEqual(a, Mat4{ .data = .{ .{ 1, 0, 0, 0 }, .{ 0, 1, 0, 0 }, .{ 0, 0, 1, 0 }, .{ 2, 3, 4, 1 }, }, - }), true); + }); } test "zalgebra.Mat4.translate" { const a = Mat4.fromTranslate(Vec3.new(2, 3, 2)); const result = Mat4.translate(a, Vec3.new(2, 3, 4)); - try expectEqual(Mat4.eql(result, Mat4{ + try expectEqual(result, Mat4{ .data = .{ .{ 1, 0, 0, 0 }, .{ 0, 1, 0, 0 }, .{ 0, 0, 1, 0 }, .{ 4, 6, 6, 1 }, }, - }), true); + }); } test "zalgebra.Mat4.fromScale" { const a = Mat4.fromScale(Vec3.new(2, 3, 4)); - try expectEqual(Mat4.eql(a, Mat4{ + try expectEqual(a, Mat4{ .data = .{ .{ 2, 0, 0, 0 }, .{ 0, 3, 0, 0 }, .{ 0, 0, 4, 0 }, .{ 0, 0, 0, 1 }, }, - }), true); + }); } test "zalgebra.Mat4.scale" { const a = Mat4.fromScale(Vec3.new(2, 3, 4)); - const result = Mat4.scale(a, Vec3.new(2, 2, 2)); + const result = Mat4.scale(a, Vec3.set(2)); - try expectEqual(Mat4.eql(result, Mat4{ + try expectEqual(result, Mat4{ .data = .{ .{ 4, 0, 0, 0 }, .{ 0, 6, 0, 0 }, .{ 0, 0, 8, 0 }, .{ 0, 0, 0, 1 }, }, - }), true); + }); } test "zalgebra.Mat4.inv" { @@ -606,63 +621,60 @@ test "zalgebra.Mat4.inv" { }, }; - try expectEqual(Mat4.eql(a.inv(), Mat4{ + try expectEqual(a.inv(), Mat4{ .data = .{ .{ -0.1666666716337204, 0, 0, 0.3333333432674408 }, .{ 0, 0.5, 0, 0 }, .{ 0, 0, 0.5, 0 }, .{ 0.3333333432674408, 0, 0, -0.1666666716337204 }, }, - }), true); + }); } test "zalgebra.Mat4.extractTranslation" { var a = Mat4.fromTranslate(Vec3.new(2, 3, 2)); a = a.translate(Vec3.new(2, 3, 2)); - try expectEqual(Vec3.eql(a.extractTranslation(), Vec3.new(4, 6, 4)), true); + try expectEqual(a.extractTranslation(), Vec3.new(4, 6, 4)); } test "zalgebra.Mat4.extractEulerAngles" { const a = Mat4.fromEulerAngles(Vec3.new(45, -5, 20)); - try expectEqual(Vec3.eql( - a.extractEulerAngles(), - Vec3.new(45.000003814697266, -4.99052524, 19.999998092651367), - ), true); + try expectEqual(a.extractEulerAngles(), Vec3.new(45.000003814697266, -4.99052524, 19.999998092651367)); } test "zalgebra.Mat4.extractScale" { var a = Mat4.fromScale(Vec3.new(2, 4, 8)); a = a.scale(Vec3.new(2, 4, 8)); - try expectEqual(Vec3.eql(a.extractScale(), Vec3.new(4, 16, 64)), true); + try expectEqual(a.extractScale(), Vec3.new(4, 16, 64)); } test "zalgebra.Mat4.recompose" { const result = Mat4.recompose( - Vec3.new(2, 2, 2), + Vec3.set(2), Vec3.new(45, 5, 0), - Vec3.new(1, 1, 1), + Vec3.one(), ); - try expectEqual(Mat4.eql(result, Mat4{ .data = .{ + try expectEqual(result, Mat4{ .data = .{ .{ 0.9961947202682495, 0, -0.08715573698282242, 0 }, .{ 0.06162841245532036, 0.7071067690849304, 0.704416036605835, 0 }, .{ 0.06162841245532036, -0.7071067690849304, 0.704416036605835, 0 }, .{ 2, 2, 2, 1 }, - } }), true); + } }); } test "zalgebra.Mat4.decompose" { const a = Mat4.recompose( Vec3.new(10, 5, 5), Vec3.new(45, 5, 0), - Vec3.new(1, 1, 1), + Vec3.set(1), ); const result = a.decompose(); - try expectEqual(result.t.eql(Vec3.new(10, 5, 5)), true); - try expectEqual(result.s.eql(Vec3.new(1, 1, 1)), true); - try expectEqual(result.r.extractEulerAngles().eql(Vec3.new(45, 5, 0.00000010712935250012379)), true); + try expectEqual(result.t, Vec3.new(10, 5, 5)); + try expectEqual(result.s, Vec3.set(1)); + try expectEqual(result.r.extractEulerAngles(), Vec3.new(45, 5, 0.00000010712935250012379)); } diff --git a/src/quaternion.zig b/src/quaternion.zig index 90b4749..d5a38fc 100644 --- a/src/quaternion.zig +++ b/src/quaternion.zig @@ -1,7 +1,7 @@ const std = @import("std"); const root = @import("main.zig"); -const vec4 = @import("vec4.zig"); -const vec3 = @import("vec3.zig"); +const meta = std.meta; +const generic_vector = @import("generic_vector.zig"); const mat4 = @import("mat4.zig"); const math = std.math; const expectApproxEqRel = std.testing.expectApproxEqRel; @@ -9,10 +9,10 @@ const expectEqual = std.testing.expectEqual; const expect = std.testing.expect; const assert = std.debug.assert; -const Vec3 = vec3.Vec3; -const Vector3 = vec3.Vector3; -const Vec4 = vec4.Vec4; -const Vector4 = vec4.Vector4; +const GenericVector = generic_vector.GenericVector; + +const Vec3 = generic_vector.Vec3; +const Vec4 = generic_vector.Vec4; const Mat4x4 = mat4.Mat4x4; pub const Quat = Quaternion(f32); @@ -24,6 +24,8 @@ pub fn Quaternion(comptime T: type) type { @compileError("Quaternion not implemented for " ++ @typeName(T)); } + const Vector3 = GenericVector(3, T); + return extern struct { w: T, x: T, @@ -42,7 +44,7 @@ pub fn Quaternion(comptime T: type) type { }; } - /// Construct most basic quaternion. + /// Shorthand for (1, 0, 0, 0). pub fn zero() Self { return Self.new(1, 0, 0, 0); } @@ -53,15 +55,17 @@ pub fn Quaternion(comptime T: type) type { return Self.new(slice[3], slice[0], slice[1], slice[2]); } - pub fn fromVec3(w: T, axis: Vector3(T)) Self { + // Construct new quaternion from given `W` component and Vector3. + pub fn fromVec3(w: T, axis: Vector3) Self { return .{ .w = w, - .x = axis.x, - .y = axis.y, - .z = axis.z, + .x = axis.data[0], + .y = axis.data[1], + .z = axis.data[2], }; } + /// Return true if two quaternions are equal. pub fn eql(left: Self, right: Self) bool { return (left.w == right.w and left.x == right.x and @@ -69,6 +73,7 @@ pub fn Quaternion(comptime T: type) type { left.z == right.z); } + /// Construct new normalized quaternion from a given one. pub fn norm(self: Self) Self { const l = length(self); assert(l != 0); @@ -81,10 +86,12 @@ pub fn Quaternion(comptime T: type) type { ); } + /// Return the length (magnitude) of quaternion. pub fn length(self: Self) T { return @sqrt(self.dot(self)); } + /// Substraction between two quaternions. pub fn sub(left: Self, right: Self) Self { return Self.new( left.w - right.w, @@ -94,6 +101,7 @@ pub fn Quaternion(comptime T: type) type { ); } + /// Addition between two quaternions. pub fn add(left: Self, right: Self) Self { return Self.new( left.w + right.w, @@ -103,6 +111,8 @@ pub fn Quaternion(comptime T: type) type { ); } + /// Quaternions' multiplication. + /// Produce a new quaternion from given two quaternions. pub fn mult(left: Self, right: Self) Self { var q: Self = undefined; @@ -114,6 +124,7 @@ pub fn Quaternion(comptime T: type) type { return q; } + /// Multiply each component by the given scalar. pub fn scale(mat: Self, scalar: T) Self { var result: Self = undefined; result.w = mat.w * scalar; @@ -124,6 +135,11 @@ pub fn Quaternion(comptime T: type) type { return result; } + /// Negate the given quaternion + pub fn negate(self: Self) Self { + return self.scale(-1); + } + /// Return the dot product between two quaternion. pub fn dot(left: Self, right: Self) T { return (left.x * right.x) + (left.y * right.y) + (left.z * right.z) + (left.w * right.w); @@ -212,31 +228,31 @@ pub fn Quaternion(comptime T: type) type { } } - return Self.scale(result, 0.5 / math.sqrt(t)); + return Self.scale(result, 0.5 / @sqrt(t)); } /// Convert all Euler angles (in degrees) to quaternion. - pub fn fromEulerAngles(axis_in_degrees: Vector3(T)) Self { - const x = Self.fromAxis(axis_in_degrees.x, Vec3.right()); - const y = Self.fromAxis(axis_in_degrees.y, Vec3.up()); - const z = Self.fromAxis(axis_in_degrees.z, Vec3.forward()); + pub fn fromEulerAngles(axis_in_degrees: Vector3) Self { + const x = Self.fromAxis(axis_in_degrees.data[0], Vector3.right()); + const y = Self.fromAxis(axis_in_degrees.data[1], Vector3.up()); + const z = Self.fromAxis(axis_in_degrees.data[2], Vector3.forward()); return z.mult(y.mult(x)); } /// Convert Euler angle around specified axis to quaternion. - pub fn fromAxis(degrees: T, axis: Vector3(T)) Self { + pub fn fromAxis(degrees: T, axis: Vector3) Self { const radians = root.toRadians(degrees); const rot_sin = @sin(radians / 2.0); - const quat_axis = axis.norm().scale(rot_sin); + const quat_axis = axis.norm().data * @splat(3, rot_sin); const w = @cos(radians / 2.0); - return Self.fromVec3(w, quat_axis); + return Self.fromVec3(w, .{ .data = quat_axis }); } /// Extract euler angles (degrees) from quaternion. - pub fn extractEulerAngles(self: Self) Vector3(T) { + pub fn extractEulerAngles(self: Self) Vector3 { const yaw = math.atan2( T, 2.0 * (self.y * self.z + self.w * self.x), @@ -251,27 +267,27 @@ pub fn Quaternion(comptime T: type) type { self.w * self.w + self.x * self.x - self.y * self.y - self.z * self.z, ); - return Vector3(T).new(root.toDegrees(yaw), root.toDegrees(pitch), root.toDegrees(roll)); + return Vector3.new(root.toDegrees(yaw), root.toDegrees(pitch), root.toDegrees(roll)); } /// Get the rotation angle (degrees) and axis for a given quaternion. // Taken from https://github.com/raysan5/raylib/blob/master/src/raymath.h#L1755 pub fn extractAxisAngle(self: Self) struct { axis: Vec3, angle: f32 } { var copy = self; - if (math.fabs(self.w) > 1.0) copy = copy.norm(); + if (math.fabs(copy.w) > 1.0) copy = copy.norm(); - var res_axis = Vec3.set(0.0); + var res_axis = Vec3.zero(); var res_angle: f32 = 2.0 * math.acos(copy.w); - var den: f32 = math.sqrt(1.0 - copy.w * copy.w); + var den: f32 = @sqrt(1.0 - copy.w * copy.w); if (den > 0.0001) { - res_axis.x = copy.x / den; - res_axis.y = copy.y / den; - res_axis.z = copy.z / den; + res_axis.data[0] = copy.x / den; + res_axis.data[1] = copy.y / den; + res_axis.data[2] = copy.z / den; } else { // This occurs when the angle is zero. // Not a problem: just set an arbitrary normalized axis. - res_axis.x = 1.0; + res_axis.data[0] = 1.0; } return .{ @@ -280,7 +296,7 @@ pub fn Quaternion(comptime T: type) type { }; } - /// Lerp between two quaternions. + /// Linear interpolation between two quaternions. pub fn lerp(left: Self, right: Self, t: f32) Self { const w = root.lerp(T, left.w, right.w, t); const x = root.lerp(T, left.x, right.x, t); @@ -300,7 +316,7 @@ pub fn Quaternion(comptime T: type) type { // We need the absolute value of the dot product to take the shortest path if (cos_theta < 0.0) { cos_theta *= -1; - right1 = right.scale(-1); + right1 = right.negate(); } if (cos_theta > ParallelThreshold) { @@ -314,12 +330,12 @@ pub fn Quaternion(comptime T: type) type { } } - /// Rotate the vector v using the sandwich product. + /// Rotate the Vector3 v using the sandwich product. /// Taken from "Foundations of Game Engine Development Vol. 1 Mathematics". - pub fn rotateVec(self: Self, v: Vector3(T)) Vector3(T) { + pub fn rotateVec(self: Self, v: Vector3) Vector3 { const q = self.norm(); - const b = Vector3(T).new(q.x, q.y, q.z); - const b2 = b.x * b.x + b.y * b.y + b.z * b.z; + const b = Vector3.new(q.x, q.y, q.z); + const b2 = Vector3.dot(b, b); return v.scale(q.w * q.w - b2).add(b.scale(v.dot(b) * 2.0)).add(b.cross(v).scale(q.w * 2.0)); } @@ -335,9 +351,18 @@ test "zalgebra.Quaternion.new" { try expectEqual(q.z, 4.7); } +test "zalgebra.Quaternion.eql" { + const q1 = Quat.new(1.5, 2.6, 3.7, 4.7); + const q2 = Quat.new(1.5, 2.6, 3.7, 4.7); + const q3 = Quat.new(2.6, 3.7, 4.8, 5.9); + + try expectEqual(Quat.eql(q1, q2), true); + try expectEqual(Quat.eql(q1, q3), false); +} + test "zalgebra.Quaternion.fromSlice" { const array = [4]f32{ 2, 3, 4, 1 }; - try expectEqual(Quat.eql(Quat.fromSlice(&array), Quat.new(1, 2, 3, 4)), true); + try expectEqual(Quat.fromSlice(&array), Quat.new(1, 2, 3, 4)); } test "zalgebra.Quaternion.fromVec3" { @@ -354,33 +379,33 @@ test "zalgebra.Quaternion.fromVec3" { const q2 = Quat.fromVec3(1.5, Vec3.new(2.6, 3.7, 4.7)); const q3 = Quat.fromVec3(1, Vec3.new(2.6, 3.7, 4.7)); - try expectEqual(q1.eql(q2), true); - try expectEqual(q1.eql(q3), false); + try expectEqual(q1, q2); + try expectEqual(Quat.eql(q1, q3), false); } test "zalgebra.Quaternion.norm" { const q1 = Quat.fromVec3(1, Vec3.new(2, 2.0, 2.0)); const q2 = Quat.fromVec3(0.2773500978946686, Vec3.new(0.5547001957893372, 0.5547001957893372, 0.5547001957893372)); - try expectEqual(q1.norm().eql(q2), true); + try expectEqual(q1.norm(), q2); } test "zalgebra.Quaternion.fromEulerAngles" { const q1 = Quat.fromEulerAngles(Vec3.new(10, 5, 45)); - const rot1 = q1.extractEulerAngles(); + const res_q1 = q1.extractEulerAngles(); const q2 = Quat.fromEulerAngles(Vec3.new(0, 55, 22)); - const rot2 = q2.toMat4().extractEulerAngles(); + const res_q2 = q2.toMat4().extractEulerAngles(); - try expectEqual(Vec3.eql(rot1, Vec3.new(9.999999046325684, 5.000000476837158, 45)), true); - try expectEqual(Vec3.eql(rot2, Vec3.new(0, 47.2450294, 22)), true); + try expectEqual(res_q1, Vec3.new(9.999999046325684, 5.000000476837158, 45)); + try expectEqual(res_q2, Vec3.new(0, 47.2450294, 22)); } test "zalgebra.Quaternion.fromAxis" { - const q1 = Quat.fromAxis(45, Vec3.new(0, 1, 0)); + const q1 = Quat.fromAxis(45, Vec3.up()); const res_q1 = q1.extractEulerAngles(); - try expectEqual(Vec3.eql(res_q1, Vec3.new(0, 45.0000076, 0)), true); + try expectEqual(res_q1, Vec3.new(0, 45.0000076, 0)); } test "zalgebra.Quaternion.extractAxisAngle" { @@ -389,9 +414,9 @@ test "zalgebra.Quaternion.extractAxisAngle" { const res = q1.extractAxisAngle(); const eps_value = comptime math.epsilon(f32); - try expect(math.approxEqRel(f32, axis.x, res.axis.x, eps_value) and - math.approxEqRel(f32, axis.y, res.axis.y, eps_value) and - math.approxEqRel(f32, axis.z, res.axis.z, eps_value)); + try expect(math.approxEqRel(f32, axis.data[0], res.axis.data[0], eps_value) and + math.approxEqRel(f32, axis.data[1], res.axis.data[1], eps_value) and + math.approxEqRel(f32, axis.data[1], res.axis.data[1], eps_value)); try expectApproxEqRel(@as(f32, 45.0000076), res.angle, eps_value); } @@ -400,35 +425,32 @@ test "zalgebra.Quaternion.extractEulerAngles" { const q1 = Quat.fromVec3(0.5, Vec3.new(0.5, 1, 0.3)); const res_q1 = q1.extractEulerAngles(); - try expectEqual(Vec3.eql(res_q1, Vec3.new(129.6000213623047, 44.427005767822266, 114.41073608398438)), true); + try expectEqual(res_q1, Vec3.new(129.6000213623047, 44.427005767822266, 114.4107360839843)); } test "zalgebra.Quaternion.rotateVec" { const eps_value = comptime std.math.epsilon(f32); - const q = Quat.fromEulerAngles(Vec3.new(45, 45, 45)); + const q = Quat.fromEulerAngles(Vec3.set(45)); const m = q.toMat4(); - const v = Vec3.new(0, 1, 0); + const v = Vec3.up(); const v1 = q.rotateVec(v); - const v2 = m.multByVec4(Vec4.new(v.x, v.y, v.z, 1.0)); + const v2 = m.multByVec4(Vec4.new(v.data[0], v.data[1], v.data[2], 1.0)); - try expect(std.math.approxEqAbs(f32, v1.x, -1.46446585e-01, eps_value)); - try expect(std.math.approxEqAbs(f32, v1.y, 8.53553473e-01, eps_value)); - try expect(std.math.approxEqAbs(f32, v1.z, 0.5, eps_value)); + try expect(std.math.approxEqAbs(f32, v1.data[0], -1.46446585e-01, eps_value)); + try expect(std.math.approxEqAbs(f32, v1.data[1], 8.53553473e-01, eps_value)); + try expect(std.math.approxEqAbs(f32, v1.data[2], 0.5, eps_value)); - try expect(std.math.approxEqAbs(f32, v1.x, v2.x, eps_value)); - try expect(std.math.approxEqAbs(f32, v1.y, v2.y, eps_value)); - try expect(std.math.approxEqAbs(f32, v1.z, v2.z, eps_value)); + try expect(std.math.approxEqAbs(f32, v1.data[0], v2.data[0], eps_value)); + try expect(std.math.approxEqAbs(f32, v1.data[1], v2.data[1], eps_value)); + try expect(std.math.approxEqAbs(f32, v1.data[2], v2.data[2], eps_value)); } test "zalgebra.Quaternion.lerp" { const eps_value = comptime std.math.epsilon(f32); var v1 = Quat.zero(); var v2 = Quat.fromAxis(180, Vec3.up()); - try expectEqual(Quat.eql( - Quat.lerp(v1, v2, 1.0), - v2, - ), true); + try expectEqual(Quat.lerp(v1, v2, 1.0), v2); var v3 = Quat.lerp(v1, v2, 0.5); var v4 = Quat.new(4.99999970e-01, 0, 4.99999970e-01, 0); try expect(std.math.approxEqAbs(f32, v3.w, v4.w, eps_value)); @@ -441,10 +463,7 @@ test "zalgebra.Quaternion.slerp" { const eps_value = comptime std.math.epsilon(f32); var v1 = Quat.zero(); var v2 = Quat.fromAxis(180, Vec3.up()); - try expectEqual(Quat.eql( - Quat.slerp(v1, v2, 1.0), - Quat.new(7.54979012e-08, 0, -1, 0), - ), true); + try expectEqual(Quat.slerp(v1, v2, 1.0), Quat.new(7.54979012e-08, 0, -1, 0)); var v3 = Quat.slerp(v1, v2, 0.5); var v4 = Quat.new(7.071067e-01, 0, -7.071067e-01, 0); try expect(std.math.approxEqAbs(f32, v3.w, v4.w, eps_value)); diff --git a/src/vec2.zig b/src/vec2.zig deleted file mode 100644 index 30f2090..0000000 --- a/src/vec2.zig +++ /dev/null @@ -1,317 +0,0 @@ -const std = @import("std"); -const root = @import("main.zig"); -const math = std.math; -const expectEqual = std.testing.expectEqual; -const panic = std.debug.panic; - -pub const Vec2 = Vector2(f32); -pub const Vec2_f64 = Vector2(f64); -pub const Vec2_i32 = Vector2(i32); -pub const Vec2_usize = Vector2(usize); - -/// A 2 dimensional vector. -pub fn Vector2(comptime T: type) type { - if (@typeInfo(T) != .Float and @typeInfo(T) != .Int) { - @compileError("Vector2 not implemented for " ++ @typeName(T)); - } - - return extern struct { - x: T, - y: T, - - const Self = @This(); - - /// Construct vector from given 2 components. - pub fn new(x: T, y: T) Self { - return .{ .x = x, .y = y }; - } - - /// Set all components to the same given value. - pub fn set(val: T) Self { - return Self.new(val, val); - } - - /// Shorthand for writing vec2.new(0, 0). - pub fn zero() Self { - return Self.set(0); - } - - /// Shorthand for writing vec2.new(1, 1). - pub fn one() Self { - return Self.set(1); - } - - /// Shorthand for writing vec2.new(0, 1). - pub fn up() Self { - return Self.new(0, 1); - } - - /// Negate the given vector. - pub fn negate(self: Self) Self { - return self.scale(-1); - } - - /// Cast a type to another type. Only for integers and floats. - /// It's like builtins: @intCast, @floatCast, @intToFloat, @floatToInt. - pub fn cast(self: Self, dest: anytype) Vector2(dest) { - const source_info = @typeInfo(T); - const dest_info = @typeInfo(dest); - - if (source_info == .Float and dest_info == .Int) { - const x = @floatToInt(dest, self.x); - const y = @floatToInt(dest, self.y); - return Vector2(dest).new(x, y); - } - - if (source_info == .Int and dest_info == .Float) { - const x = @intToFloat(dest, self.x); - const y = @intToFloat(dest, self.y); - return Vector2(dest).new(x, y); - } - - return switch (dest_info) { - .Float => { - const x = @floatCast(dest, self.x); - const y = @floatCast(dest, self.y); - return Vector2(dest).new(x, y); - }, - .Int => { - const x = @intCast(dest, self.x); - const y = @intCast(dest, self.y); - return Vector2(dest).new(x, y); - }, - else => panic( - "Error, given type should be integer or float.\n", - .{}, - ), - }; - } - - /// Construct new vector from slice. - pub fn fromSlice(slice: []const T) Self { - return Self.new(slice[0], slice[1]); - } - - /// Transform vector to array. - pub fn toArray(self: Self) [2]T { - return .{ self.x, self.y }; - } - - /// Return the angle in degrees between two vectors. - pub fn getAngle(left: Self, right: Self) T { - const dot_product = Self.dot(left.norm(), right.norm()); - return root.toDegrees(math.acos(dot_product)); - } - - /// Compute the length (magnitude) of given vector |a|. - pub fn length(self: Self) T { - return @sqrt(self.dot(self)); - } - - /// Compute the distance between two points. - pub fn distance(a: Self, b: Self) T { - return length(b.sub(a)); - } - - /// Construct new normalized vector from a given vector. - pub fn norm(self: Self) Self { - var l = length(self); - return Self.new( - self.x / l, - self.y / l, - ); - } - - pub fn eql(left: Self, right: Self) bool { - return left.x == right.x and left.y == right.y; - } - - /// Substraction between two given vector. - pub fn sub(left: Self, right: Self) Self { - return Self.new( - left.x - right.x, - left.y - right.y, - ); - } - - /// Addition betwen two given vector. - pub fn add(left: Self, right: Self) Self { - return Self.new( - left.x + right.x, - left.y + right.y, - ); - } - - /// Multiply each components by the given scalar. - pub fn scale(v: Self, scalar: T) Self { - return Self.new( - v.x * scalar, - v.y * scalar, - ); - } - - /// Return the dot product between two given vector. - pub fn dot(left: Self, right: Self) T { - return (left.x * right.x) + (left.y * right.y); - } - - /// Lerp between two vectors. - pub fn lerp(left: Self, right: Self, t: T) Self { - const x = root.lerp(T, left.x, right.x, t); - const y = root.lerp(T, left.y, right.y, t); - return Self.new(x, y); - } - - /// Construct a new vector from the min components between two vectors. - pub fn min(left: Self, right: Self) Self { - return Self.new( - @minimum(left.x, right.x), - @minimum(left.y, right.y), - ); - } - - /// Construct a new vector from the max components between two vectors. - pub fn max(left: Self, right: Self) Self { - return Self.new( - @maximum(left.x, right.x), - @maximum(left.y, right.y), - ); - } - }; -} - -test "zalgebra.Vec2.init" { - var a = Vec2.new(1.5, 2.6); - - try expectEqual(a.x, 1.5); - try expectEqual(a.y, 2.6); -} - -test "zalgebra.Vec2.set" { - var a = Vec2.new(2.5, 2.5); - var b = Vec2.set(2.5); - try expectEqual(Vec2.eql(a, b), true); -} - -test "zalgebra.Vec2.negate" { - var a = Vec2.set(5); - var b = Vec2.set(-5); - try expectEqual(Vec2.eql(a.negate(), b), true); -} - -test "zalgebra.Vec2.getAngle" { - var a = Vec2.new(1, 0); - var b = Vec2.up(); - var c = Vec2.new(-1, 0); - var d = Vec2.new(1, 1); - - try expectEqual(Vec2.getAngle(a, b), 90); - try expectEqual(Vec2.getAngle(a, c), 180); - try expectEqual(Vec2.getAngle(a, d), 45); -} - -test "zalgebra.Vec2.toArray" { - const a = Vec2.up().toArray(); - const b = [_]f32{ 0, 1 }; - - try expectEqual(std.mem.eql(f32, &a, &b), true); -} - -test "zalgebra.Vec2.eql" { - var a = Vec2.new(1, 2); - var b = Vec2.new(1, 2); - var c = Vec2.new(1.5, 2); - try expectEqual(Vec2.eql(a, b), true); - try expectEqual(Vec2.eql(a, c), false); -} - -test "zalgebra.Vec2.length" { - var a = Vec2.new(1.5, 2.6); - try expectEqual(a.length(), 3.00166606); -} - -test "zalgebra.Vec2.distance" { - var a = Vec2.new(0, 0); - var b = Vec2.new(-1, 0); - var c = Vec2.new(0, 5); - - try expectEqual(Vec2.distance(a, b), 1); - try expectEqual(Vec2.distance(a, c), 5); -} - -test "zalgebra.Vec2.normalize" { - var a = Vec2.new(1.5, 2.6); - try expectEqual(Vec2.eql(a.norm(), Vec2.new(0.499722480, 0.866185605)), true); -} - -test "zalgebra.Vec2.sub" { - var a = Vec2.new(1, 2); - var b = Vec2.new(2, 2); - try expectEqual(Vec2.eql(Vec2.sub(a, b), Vec2.new(-1, 0)), true); -} - -test "zalgebra.Vec2.add" { - var a = Vec2.new(1, 2); - var b = Vec2.new(2, 2); - try expectEqual(Vec2.eql(Vec2.add(a, b), Vec2.new(3, 4)), true); -} - -test "zalgebra.Vec2.scale" { - var a = Vec2.new(1, 2); - try expectEqual(Vec2.eql(Vec2.scale(a, 5), Vec2.new(5, 10)), true); -} - -test "zalgebra.Vec2.dot" { - var a = Vec2.new(1.5, 2.6); - var b = Vec2.new(2.5, 3.45); - - try expectEqual(Vec2.dot(a, b), 12.7200002); -} - -test "zalgebra.Vec2.lerp" { - var a = Vec2.new(-10.0, 0.0); - var b = Vec2.new(10.0, 10.0); - - try expectEqual(Vec2.eql(Vec2.lerp(a, b, 0.5), Vec2.new(0.0, 5.0)), true); -} - -test "zalgebra.Vec2.min" { - var a = Vec2.new(10.0, -2.0); - var b = Vec2.new(-10.0, 5.0); - - try expectEqual(Vec2.eql(Vec2.min(a, b), Vec2.new(-10.0, -2.0)), true); -} - -test "zalgebra.Vec2.max" { - var a = Vec2.new(10.0, -2.0); - var b = Vec2.new(-10.0, 5.0); - - try expectEqual(Vec2.eql(Vec2.max(a, b), Vec2.new(10.0, 5.0)), true); -} - -test "zalgebra.Vec2.fromSlice" { - const array = [2]f32{ 2, 4 }; - try expectEqual(Vec2.eql(Vec2.fromSlice(&array), Vec2.new(2, 4)), true); -} - -test "zalgebra.Vec2.cast" { - const a = Vec2_i32.new(3, 6); - const b = Vector2(usize).new(3, 6); - - try expectEqual(Vec2_usize.eql(a.cast(usize), b), true); - - const c = Vec2.new(3.5, 6.5); - const d = Vec2_f64.new(3.5, 6.5); - - try expectEqual(Vec2_f64.eql(c.cast(f64), d), true); - - const e = Vec2_i32.new(3, 6); - const f = Vec2.new(3.0, 6.0); - - try expectEqual(Vec2.eql(e.cast(f32), f), true); - - const g = Vec2.new(3.0, 6.0); - const h = Vec2_i32.new(3, 6); - - try expectEqual(Vec2_i32.eql(g.cast(i32), h), true); -} diff --git a/src/vec3.zig b/src/vec3.zig deleted file mode 100644 index 7a906be..0000000 --- a/src/vec3.zig +++ /dev/null @@ -1,410 +0,0 @@ -const std = @import("std"); -const root = @import("main.zig"); -const math = std.math; -const assert = std.debug.assert; -const panic = std.debug.panic; -const expectEqual = std.testing.expectEqual; - -pub const Vec3 = Vector3(f32); -pub const Vec3_f64 = Vector3(f64); -pub const Vec3_i32 = Vector3(i32); -pub const Vec3_usize = Vector3(usize); - -/// A 3 dimensional vector. -pub fn Vector3(comptime T: type) type { - if (@typeInfo(T) != .Float and @typeInfo(T) != .Int) { - @compileError("Vector3 not implemented for " ++ @typeName(T)); - } - - return extern struct { - x: T, - y: T, - z: T, - - const Self = @This(); - - /// Construct a vector from given 3 components. - pub fn new(x: T, y: T, z: T) Self { - return Self{ .x = x, .y = y, .z = z }; - } - - /// Return component from given index. - pub fn at(self: *const Self, index: i32) T { - assert(index <= 2); - - if (index == 0) { - return self.x; - } else if (index == 1) { - return self.y; - } else { - return self.z; - } - } - - /// Set all components to the same given value. - pub fn set(val: T) Self { - return Self.new(val, val, val); - } - - /// Shorthand for writing vec3.new(0, 0, 0). - pub fn zero() Self { - return Self.set(0); - } - - /// Shorthand for writing vec3.new(1, 1, 1). - pub fn one() Self { - return Self.set(1); - } - - /// Shorthand for writing vec3.new(0, 1, 0). - pub fn up() Self { - return Self.new(0, 1, 0); - } - - /// Shorthand for writing vec3.new(0, -1, 0). - pub fn down() Self { - return Self.new(0, -1, 0); - } - - /// Shorthand for writing vec3.new(1, 0, 0). - pub fn right() Self { - return Self.new(1, 0, 0); - } - - /// Shorthand for writing vec3.new(-1, 0, 0). - pub fn left() Self { - return Self.new(-1, 0, 0); - } - - /// Shorthand for writing vec3.new(0, 0, -1). - pub fn back() Self { - return Self.new(0, 0, -1); - } - - /// Shorthand for writing vec3.new(0, 0, 1). - pub fn forward() Self { - return Self.new(0, 0, 1); - } - - /// Negate the given vector. - pub fn negate(self: Self) Self { - return self.scale(-1); - } - - /// Cast a type to another type. Only for integers and floats. - /// It's like builtins: @intCast, @floatCast, @intToFloat, @floatToInt - pub fn cast(self: Self, dest: anytype) Vector3(dest) { - const source_info = @typeInfo(T); - const dest_info = @typeInfo(dest); - - if (source_info == .Float and dest_info == .Int) { - const x = @floatToInt(dest, self.x); - const y = @floatToInt(dest, self.y); - const z = @floatToInt(dest, self.z); - return Vector3(dest).new(x, y, z); - } - - if (source_info == .Int and dest_info == .Float) { - const x = @intToFloat(dest, self.x); - const y = @intToFloat(dest, self.y); - const z = @intToFloat(dest, self.z); - return Vector3(dest).new(x, y, z); - } - - return switch (dest_info) { - .Float => { - const x = @floatCast(dest, self.x); - const y = @floatCast(dest, self.y); - const z = @floatCast(dest, self.z); - return Vector3(dest).new(x, y, z); - }, - .Int => { - const x = @intCast(dest, self.x); - const y = @intCast(dest, self.y); - const z = @intCast(dest, self.z); - return Vector3(dest).new(x, y, z); - }, - else => panic( - "Error, given type should be integers or float.\n", - .{}, - ), - }; - } - - /// Construct new vector from slice. - pub fn fromSlice(slice: []const T) Self { - return Self.new(slice[0], slice[1], slice[2]); - } - - /// Transform vector to array. - pub fn toArray(self: Self) [3]T { - return .{ self.x, self.y, self.z }; - } - - /// Return the angle in degrees between two vectors. - pub fn getAngle(lhs: Self, rhs: Self) T { - const dot_product = Self.dot(lhs.norm(), rhs.norm()); - return root.toDegrees(math.acos(dot_product)); - } - - /// Compute the length (magnitude) of given vector |a|. - pub fn length(self: Self) T { - return @sqrt(self.dot(self)); - } - - /// Compute the distance between two points. - pub fn distance(a: Self, b: Self) T { - return length(b.sub(a)); - } - - /// Construct new normalized vector from a given vector. - pub fn norm(self: Self) Self { - var l = length(self); - return Self.new( - self.x / l, - self.y / l, - self.z / l, - ); - } - - pub fn eql(lhs: Self, rhs: Self) bool { - return lhs.x == rhs.x and lhs.y == rhs.y and lhs.z == rhs.z; - } - - /// Substraction between two given vector. - pub fn sub(lhs: Self, rhs: Self) Self { - return Self.new( - lhs.x - rhs.x, - lhs.y - rhs.y, - lhs.z - rhs.z, - ); - } - - /// Addition betwen two given vector. - pub fn add(lhs: Self, rhs: Self) Self { - return Self.new( - lhs.x + rhs.x, - lhs.y + rhs.y, - lhs.z + rhs.z, - ); - } - - /// Multiply each components by the given scalar. - pub fn scale(v: Self, scalar: T) Self { - return Self.new( - v.x * scalar, - v.y * scalar, - v.z * scalar, - ); - } - - /// Compute the cross product from two vector. - pub fn cross(lhs: Self, rhs: Self) Self { - return Self.new( - (lhs.y * rhs.z) - (lhs.z * rhs.y), - (lhs.z * rhs.x) - (lhs.x * rhs.z), - (lhs.x * rhs.y) - (lhs.y * rhs.x), - ); - } - - /// Return the dot product between two given vector. - pub fn dot(lhs: Self, rhs: Self) T { - return (lhs.x * rhs.x) + (lhs.y * rhs.y) + (lhs.z * rhs.z); - } - - /// Lerp between two vectors. - pub fn lerp(lhs: Self, rhs: Self, t: T) Self { - const x = root.lerp(T, lhs.x, rhs.x, t); - const y = root.lerp(T, lhs.y, rhs.y, t); - const z = root.lerp(T, lhs.z, rhs.z, t); - return Self.new(x, y, z); - } - - /// Construct a new vector from the min components between two vectors. - pub fn min(lhs: Self, rhs: Self) Self { - return Self.new( - @minimum(lhs.x, rhs.x), - @minimum(lhs.y, rhs.y), - @minimum(lhs.z, rhs.z), - ); - } - - /// Construct a new vector from the max components between two vectors. - pub fn max(lhs: Self, rhs: Self) Self { - return Self.new( - @maximum(lhs.x, rhs.x), - @maximum(lhs.y, rhs.y), - @maximum(lhs.z, rhs.z), - ); - } - }; -} - -test "zalgebra.Vec3.init" { - var a = Vec3.new(1.5, 2.6, 3.7); - - try expectEqual(a.x, 1.5); - try expectEqual(a.y, 2.6); - try expectEqual(a.z, 3.7); -} - -test "zalgebra.Vec3.set" { - var a = Vec3.new(2.5, 2.5, 2.5); - var b = Vec3.set(2.5); - try expectEqual(Vec3.eql(a, b), true); -} - -test "zalgebra.Vec3.negate" { - var a = Vec3.set(10); - var b = Vec3.set(-10); - try expectEqual(Vec3.eql(a.negate(), b), true); -} - -test "zalgebra.Vec3.getAngle" { - var a = Vec3.new(1, 0, 0); - var b = Vec3.up(); - var c = Vec3.new(-1, 0, 0); - var d = Vec3.new(1, 1, 0); - - try expectEqual(Vec3.getAngle(a, b), 90); - try expectEqual(Vec3.getAngle(a, c), 180); - try expectEqual(Vec3.getAngle(a, d), 45); -} - -test "zalgebra.Vec3.toArray" { - const a = Vec3.up().toArray(); - const b = [_]f32{ 0, 1, 0 }; - - try expectEqual(std.mem.eql(f32, &a, &b), true); -} - -test "zalgebra.Vec3.eql" { - var a = Vec3.new(1, 2, 3); - var b = Vec3.new(1, 2, 3); - var c = Vec3.new(1.5, 2, 3); - try expectEqual(Vec3.eql(a, b), true); - try expectEqual(Vec3.eql(a, c), false); -} - -test "zalgebra.Vec3.length" { - var a = Vec3.new(1.5, 2.6, 3.7); - try expectEqual(a.length(), 4.7644519); -} - -test "zalgebra.Vec3.distance" { - var a = Vec3.new(0, 0, 0); - var b = Vec3.new(-1, 0, 0); - var c = Vec3.new(0, 5, 0); - - try expectEqual(Vec3.distance(a, b), 1); - try expectEqual(Vec3.distance(a, c), 5); -} - -test "zalgebra.Vec3.normalize" { - var a = Vec3.new(1.5, 2.6, 3.7); - try expectEqual( - Vec3.eql(a.norm(), Vec3.new(0.314831584, 0.545708060, 0.776584625)), - true, - ); -} - -test "zalgebra.Vec3.sub" { - var a = Vec3.new(1, 2, 3); - var b = Vec3.new(2, 2, 3); - try expectEqual(Vec3.eql(Vec3.sub(a, b), Vec3.new(-1, 0, 0)), true); -} - -test "zalgebra.Vec3.add" { - var a = Vec3.new(1, 2, 3); - var b = Vec3.new(2, 2, 3); - try expectEqual(Vec3.eql(Vec3.add(a, b), Vec3.new(3, 4, 6)), true); -} - -test "zalgebra.Vec3.scale" { - var a = Vec3.new(1, 2, 3); - try expectEqual(Vec3.eql(Vec3.scale(a, 5), Vec3.new(5, 10, 15)), true); -} - -test "zalgebra.Vec3.cross" { - var a = Vec3.new(1.5, 2.6, 3.7); - var b = Vec3.new(2.5, 3.45, 1.0); - var c = Vec3.new(1.5, 2.6, 3.7); - - var result_1 = Vec3.cross(a, c); - var result_2 = Vec3.cross(a, b); - - try expectEqual(Vec3.eql(result_1, Vec3.new(0, 0, 0)), true); - try expectEqual(Vec3.eql( - result_2, - Vec3.new(-10.1650009, 7.75, -1.32499980), - ), true); -} - -test "zalgebra.Vec3.dot" { - var a = Vec3.new(1.5, 2.6, 3.7); - var b = Vec3.new(2.5, 3.45, 1.0); - - try expectEqual(Vec3.dot(a, b), 16.42); -} - -test "zalgebra.Vec3.lerp" { - var a = Vec3.new(-10.0, 0.0, -10.0); - var b = Vec3.new(10.0, 10.0, 10.0); - - try expectEqual(Vec3.eql( - Vec3.lerp(a, b, 0.5), - Vec3.new(0.0, 5.0, 0.0), - ), true); -} - -test "zalgebra.Vec3.min" { - var a = Vec3.new(10.0, -2.0, 0.0); - var b = Vec3.new(-10.0, 5.0, 0.0); - - try expectEqual(Vec3.eql( - Vec3.min(a, b), - Vec3.new(-10.0, -2.0, 0.0), - ), true); -} - -test "zalgebra.Vec3.max" { - var a = Vec3.new(10.0, -2.0, 0.0); - var b = Vec3.new(-10.0, 5.0, 0.0); - - try expectEqual(Vec3.eql(Vec3.max(a, b), Vec3.new(10.0, 5.0, 0.0)), true); -} - -test "zalgebra.Vec3.at" { - const t = Vec3.new(10.0, -2.0, 0.0); - - try expectEqual(t.at(0), 10.0); - try expectEqual(t.at(1), -2.0); - try expectEqual(t.at(2), 0.0); -} - -test "zalgebra.Vec3.fromSlice" { - const array = [3]f32{ 2, 1, 4 }; - try expectEqual(Vec3.eql(Vec3.fromSlice(&array), Vec3.new(2, 1, 4)), true); -} - -test "zalgebra.Vec3.cast" { - const a = Vec3_i32.new(3, 6, 2); - const b = Vec3_usize.new(3, 6, 2); - - try expectEqual(Vec3_usize.eql(a.cast(usize), b), true); - - const c = Vec3.new(3.5, 6.5, 2.0); - const d = Vec3_f64.new(3.5, 6.5, 2); - - try expectEqual(Vec3_f64.eql(c.cast(f64), d), true); - - const e = Vec3_i32.new(3, 6, 2); - const f = Vec3.new(3.0, 6.0, 2.0); - - try expectEqual(Vec3.eql(e.cast(f32), f), true); - - const g = Vec3.new(3.0, 6.0, 2.0); - const h = Vec3_i32.new(3, 6, 2); - - try expectEqual(Vec3_i32.eql(g.cast(i32), h), true); -} diff --git a/src/vec4.zig b/src/vec4.zig deleted file mode 100644 index 3556020..0000000 --- a/src/vec4.zig +++ /dev/null @@ -1,353 +0,0 @@ -const std = @import("std"); -const root = @import("main.zig"); -const math = std.math; -const panic = std.debug.panic; -const expectEqual = std.testing.expectEqual; - -pub const Vec4 = Vector4(f32); -pub const Vec4_f64 = Vector4(f64); -pub const Vec4_i32 = Vector4(i32); -pub const Vec4_usize = Vector4(usize); - -/// A 4 dimensional vector. -pub fn Vector4(comptime T: type) type { - if (@typeInfo(T) != .Float and @typeInfo(T) != .Int) { - @compileError("Vector4 not implemented for " ++ @typeName(T)); - } - - return extern struct { - x: T, - y: T, - z: T, - w: T, - - const Self = @This(); - - /// Constract vector from given 3 components. - pub fn new(x: T, y: T, z: T, w: T) Self { - return Self{ - .x = x, - .y = y, - .z = z, - .w = w, - }; - } - - /// Set all components to the same given value. - pub fn set(val: T) Self { - return Self.new(val, val, val, val); - } - - /// Shorthand for writing vec4.new(0, 0, 0, 0). - pub fn zero() Self { - return Self.set(0); - } - - /// Shorthand for writing vec4.new(1, 1, 1, 1). - pub fn one() Self { - return Self.set(1); - } - - /// Negate the given vector. - pub fn negate(self: Self) Self { - return self.scale(-1); - } - - /// Cast a type to another type. Only for integers and floats. - /// It's like builtins: @intCast, @floatCast, @intToFloat, @floatToInt - pub fn cast(self: Self, dest: anytype) Vector4(dest) { - const source_info = @typeInfo(T); - const dest_info = @typeInfo(dest); - - if (source_info == .Float and dest_info == .Int) { - const x = @floatToInt(dest, self.x); - const y = @floatToInt(dest, self.y); - const z = @floatToInt(dest, self.z); - const w = @floatToInt(dest, self.w); - return Vector4(dest).new(x, y, z, w); - } - - if (source_info == .Int and dest_info == .Float) { - const x = @intToFloat(dest, self.x); - const y = @intToFloat(dest, self.y); - const z = @intToFloat(dest, self.z); - const w = @intToFloat(dest, self.w); - return Vector4(dest).new(x, y, z, w); - } - - return switch (dest_info) { - .Float => { - const x = @floatCast(dest, self.x); - const y = @floatCast(dest, self.y); - const z = @floatCast(dest, self.z); - const w = @floatCast(dest, self.w); - return Vector4(dest).new(x, y, z, w); - }, - .Int => { - const x = @intCast(dest, self.x); - const y = @intCast(dest, self.y); - const z = @intCast(dest, self.z); - const w = @intCast(dest, self.w); - return Vector4(dest).new(x, y, z, w); - }, - else => panic( - "Error, given type should be integers or float.\n", - .{}, - ), - }; - } - - /// Construct new vector from slice. - pub fn fromSlice(slice: []const T) Self { - return Self.new(slice[0], slice[1], slice[2], slice[3]); - } - - /// Transform vector to array. - pub fn toArray(self: Self) [4]T { - return .{ self.x, self.y, self.z, self.w }; - } - - /// Compute the length (magnitude) of given vector |a|. - pub fn length(self: Self) T { - return @sqrt(self.dot(self)); - } - - /// Compute the distance between two points. - pub fn distance(a: Self, b: Self) T { - return length(b.sub(a)); - } - - /// Construct new normalized vector from a given vector. - pub fn norm(self: Self) Self { - var l = length(self); - return Self.new( - self.x / l, - self.y / l, - self.z / l, - self.w / l, - ); - } - - pub fn eql(left: Self, right: Self) bool { - return left.x == right.x and - left.y == right.y and - left.z == right.z and - left.w == right.w; - } - - /// Substraction between two given vector. - pub fn sub(left: Self, right: Self) Self { - return Self.new( - left.x - right.x, - left.y - right.y, - left.z - right.z, - left.w - right.w, - ); - } - - /// Addition betwen two given vector. - pub fn add(left: Self, right: Self) Self { - return Self.new( - left.x + right.x, - left.y + right.y, - left.z + right.z, - left.w + right.w, - ); - } - - /// Multiply each components by the given scalar. - pub fn scale(v: Self, scalar: T) Self { - return Self.new( - v.x * scalar, - v.y * scalar, - v.z * scalar, - v.w * scalar, - ); - } - - /// Return the dot product between two given vector. - pub fn dot(left: Self, right: Self) T { - return (left.x * right.x) + (left.y * right.y) + (left.z * right.z) + (left.w * right.w); - } - - /// Lerp between two vectors. - pub fn lerp(left: Self, right: Self, t: T) Self { - const x = root.lerp(T, left.x, right.x, t); - const y = root.lerp(T, left.y, right.y, t); - const z = root.lerp(T, left.z, right.z, t); - const w = root.lerp(T, left.w, right.w, t); - return Self.new(x, y, z, w); - } - - /// Construct a new vector from the min components between two vectors. - pub fn min(left: Self, right: Self) Self { - return Self.new( - @minimum(left.x, right.x), - @minimum(left.y, right.y), - @minimum(left.z, right.z), - @minimum(left.w, right.w), - ); - } - - /// Construct a new vector from the max components between two vectors. - pub fn max(left: Self, right: Self) Self { - return Self.new( - @maximum(left.x, right.x), - @maximum(left.y, right.y), - @maximum(left.z, right.z), - @maximum(left.w, right.w), - ); - } - }; -} - -test "zalgebra.Vec4.init" { - var _vec_0 = Vec4.new(1.5, 2.6, 3.7, 4.7); - - try expectEqual(_vec_0.x, 1.5); - try expectEqual(_vec_0.y, 2.6); - try expectEqual(_vec_0.z, 3.7); - try expectEqual(_vec_0.w, 4.7); -} - -test "zalgebra.Vec4.eql" { - var _vec_0 = Vec4.new(1, 2, 3, 4); - var _vec_1 = Vec4.new(1, 2, 3, 4); - var _vec_2 = Vec4.new(1, 2, 3, 5); - try expectEqual(Vec4.eql(_vec_0, _vec_1), true); - try expectEqual(Vec4.eql(_vec_0, _vec_2), false); -} - -test "zalgebra.Vec4.set" { - var _vec_0 = Vec4.new(2.5, 2.5, 2.5, 2.5); - var _vec_1 = Vec4.set(2.5); - try expectEqual(Vec4.eql(_vec_0, _vec_1), true); -} - -test "zalgebra.Vec4.negate" { - var a = Vec4.set(5); - var b = Vec4.set(-5); - try expectEqual(Vec4.eql(a.negate(), b), true); -} - -test "zalgebra.Vec4.toArray" { - const _vec_0 = Vec4.new(0, 1, 0, 1).toArray(); - const _vec_1 = [_]f32{ 0, 1, 0, 1 }; - - try expectEqual(std.mem.eql(f32, &_vec_0, &_vec_1), true); -} - -test "zalgebra.Vec4.length" { - var _vec_0 = Vec4.new(1.5, 2.6, 3.7, 4.7); - try expectEqual(_vec_0.length(), 6.69253301); -} - -test "zalgebra.Vec4.distance" { - var a = Vec4.new(0, 0, 0, 0); - var b = Vec4.new(-1, 0, 0, 0); - var c = Vec4.new(0, 5, 0, 0); - - try expectEqual(Vec4.distance(a, b), 1); - try expectEqual(Vec4.distance(a, c), 5); -} - -test "zalgebra.Vec4.normalize" { - var _vec_0 = Vec4.new(1.5, 2.6, 3.7, 4.0); - try expectEqual(Vec4.eql( - _vec_0.norm(), - Vec4.new(0.241121411, 0.417943745, 0.594766139, 0.642990410), - ), true); -} - -test "zalgebra.Vec4.sub" { - var _vec_0 = Vec4.new(1, 2, 3, 6); - var _vec_1 = Vec4.new(2, 2, 3, 5); - try expectEqual(Vec4.eql( - Vec4.sub(_vec_0, _vec_1), - Vec4.new(-1, 0, 0, 1), - ), true); -} - -test "zalgebra.Vec4.add" { - var _vec_0 = Vec4.new(1, 2, 3, 5); - var _vec_1 = Vec4.new(2, 2, 3, 6); - try expectEqual(Vec4.eql( - Vec4.add(_vec_0, _vec_1), - Vec4.new(3, 4, 6, 11), - ), true); -} - -test "zalgebra.Vec4.scale" { - var _vec_0 = Vec4.new(1, 2, 3, 4); - try expectEqual(Vec4.eql( - Vec4.scale(_vec_0, 5), - Vec4.new(5, 10, 15, 20), - ), true); -} - -test "zalgebra.Vec4.dot" { - var _vec_0 = Vec4.new(1.5, 2.6, 3.7, 5); - var _vec_1 = Vec4.new(2.5, 3.45, 1.0, 1); - - try expectEqual(Vec4.dot(_vec_0, _vec_1), 21.4200000); -} - -test "zalgebra.Vec4.lerp" { - var _vec_0 = Vec4.new(-10.0, 0.0, -10.0, -10.0); - var _vec_1 = Vec4.new(10.0, 10.0, 10.0, 10.0); - - try expectEqual(Vec4.eql( - Vec4.lerp(_vec_0, _vec_1, 0.5), - Vec4.new(0.0, 5.0, 0.0, 0.0), - ), true); -} - -test "zalgebra.Vec4.min" { - var _vec_0 = Vec4.new(10.0, -2.0, 0.0, 1.0); - var _vec_1 = Vec4.new(-10.0, 5.0, 0.0, 1.01); - - try expectEqual(Vec4.eql( - Vec4.min(_vec_0, _vec_1), - Vec4.new(-10.0, -2.0, 0.0, 1.0), - ), true); -} - -test "zalgebra.Vec4.max" { - var _vec_0 = Vec4.new(10.0, -2.0, 0.0, 1.0); - var _vec_1 = Vec4.new(-10.0, 5.0, 0.0, 1.01); - - try expectEqual(Vec4.eql( - Vec4.max(_vec_0, _vec_1), - Vec4.new(10.0, 5.0, 0.0, 1.01), - ), true); -} - -test "zalgebra.Vec4.fromSlice" { - const array = [4]f32{ 2, 4, 3, 6 }; - try expectEqual(Vec4.eql( - Vec4.fromSlice(&array), - Vec4.new(2, 4, 3, 6), - ), true); -} - -test "zalgebra.Vec4.cast" { - const a = Vec4_i32.new(3, 6, 2, 0); - const b = Vec4_usize.new(3, 6, 2, 0); - - try expectEqual(Vec4_usize.eql(a.cast(usize), b), true); - - const c = Vec4.new(3.5, 6.5, 2.0, 0); - const d = Vec4_f64.new(3.5, 6.5, 2, 0.0); - - try expectEqual(Vec4_f64.eql(c.cast(f64), d), true); - - const e = Vec4_i32.new(3, 6, 2, 0); - const f = Vec4.new(3.0, 6.0, 2.0, 0.0); - - try expectEqual(Vec4.eql(e.cast(f32), f), true); - - const g = Vec4.new(3.0, 6.0, 2.0, 0.0); - const h = Vec4_i32.new(3, 6, 2, 0); - - try expectEqual(Vec4_i32.eql(g.cast(i32), h), true); -}