commit 10645c98d1eb27f54a5fce8e4ce55f6b5305ae7d Author: abux Date: Thu Jul 30 19:48:03 2026 +0100 HelloWorld diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5858b8d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.zig-cache/ +zig-pkg/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..841e236 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +## NYXMath +Low cortisol comptime generic math library for game dev / game engines. Yes. + +Coord System: Vulkan + +# Includes +```zig +// --- GENERICS --- +pub const Vec = @import("vec.zig").Vec; +pub const Mat = @import("mat.zig").Mat; +pub const Color = @import("color.zig").Color; + +// --- VEC2 --- +pub const Vec2 = Vec(i32, 2); +pub const Vec2f = Vec(f32, 2); + +// --- VEC3 --- +pub const Vec3 = Vec(i32, 3); +pub const Vec3f = Vec(f32, 3); + +// --- MAT --- +pub const Mat2x2 = Mat(f32, 2, 2); +pub const Mat3x3 = Mat(f32, 3, 3); +pub const Mat4x4 = Mat(f32, 4, 4); +``` diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..4d5e19c --- /dev/null +++ b/build.zig @@ -0,0 +1,24 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // --- TARGET --- + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // --- MOD --- + const mod = b.addModule("NYXMath", .{ + .root_source_file = b.path("src/root.zig"), + + .target = target, + .optimize = optimize, + }); + + const mod_tests = b.addTest(.{ + .root_module = mod, + }); + + const run_mod_tests = b.addRunArtifact(mod_tests); + + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_mod_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..a843c07 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,17 @@ +.{ + .name = .NYXMath, + + .version = "0.0.0", + + .fingerprint = 0x82eb78004f923e04, + + .minimum_zig_version = "0.16.0", + + .dependencies = .{}, + + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/src/color.zig b/src/color.zig new file mode 100644 index 0000000..c13de48 --- /dev/null +++ b/src/color.zig @@ -0,0 +1,279 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +const std = @import("std"); +const Vec = @import("vec.zig").Vec; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub const Color = extern struct { + const Self = @This(); + + // + // FIELDS + // + + r: f32 = 0, + g: f32 = 0, + b: f32 = 0, + a: f32 = 1, + + // + // CONSTRUCTORS + // + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn rgb(r: f32, g: f32, b: f32) Self { + return .{ .r = r, .g = g, .b = b, .a = 1 }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn rgba(r: f32, g: f32, b: f32, a: f32) Self { + return .{ .r = r, .g = g, .b = b, .a = a }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn hex(comptime value: u32) Self { + const r = @as(f32, @as(u8, @truncate(value >> 16))) / 255.0; + const g = @as(f32, @as(u8, @truncate(value >> 8))) / 255.0; + const b = @as(f32, @as(u8, @truncate(value >> 0))) / 255.0; + const a = if (value > 0xFFFFFF) + @as(f32, @as(u8, @truncate(value >> 24))) / 255.0 + else + 1.0; + return .{ .r = r, .g = g, .b = b, .a = a }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn fromVec4(v: Vec(f32, 4)) Self { + return .{ .r = v.value[0], .g = v.value[1], .b = v.value[2], .a = v.value[3] }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn toVec4(self: Self) Vec(f32, 4) { + return Vec(f32, 4).new(.{ self.r, self.g, self.b, self.a }); + } + + // + // ARITHMETIC + // + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn add(self: Self, other: Self) Self { + return .{ + .r = self.r + other.r, + .g = self.g + other.g, + .b = self.b + other.b, + .a = self.a + other.a, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn sub(self: Self, other: Self) Self { + return .{ + .r = self.r - other.r, + .g = self.g - other.g, + .b = self.b - other.b, + .a = self.a - other.a, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn mul(self: Self, other: Self) Self { + return .{ + .r = self.r * other.r, + .g = self.g * other.g, + .b = self.b * other.b, + .a = self.a * other.a, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn div(self: Self, other: Self) Self { + return .{ + .r = self.r / other.r, + .g = self.g / other.g, + .b = self.b / other.b, + .a = self.a / other.a, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn scale(self: Self, scalar: f32) Self { + return .{ + .r = self.r * scalar, + .g = self.g * scalar, + .b = self.b * scalar, + .a = self.a * scalar, + }; + } + + // + // COLOR OPERATIONS + // + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn lerp(self: Self, other: Self, t: f32) Self { + return .{ + .r = self.r + (other.r - self.r) * t, + .g = self.g + (other.g - self.g) * t, + .b = self.b + (other.b - self.b) * t, + .a = self.a + (other.a - self.a) * t, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn blend(self: Self, other: Self) Self { + const out_a = other.a + self.a * (1.0 - other.a); + if (out_a == 0) return .{ .r = 0, .g = 0, .b = 0, .a = 0 }; + return .{ + .r = (other.r * other.a + self.r * self.a * (1.0 - other.a)) / out_a, + .g = (other.g * other.a + self.g * self.a * (1.0 - other.a)) / out_a, + .b = (other.b * other.a + self.b * self.a * (1.0 - other.a)) / out_a, + .a = out_a, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn premultiply(self: Self) Self { + return .{ + .r = self.r * self.a, + .g = self.g * self.a, + .b = self.b * self.a, + .a = self.a, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn luminance(self: Self) f32 { + return self.r * 0.2126 + self.g * 0.7152 + self.b * 0.0722; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn toLinear(self: Self) Self { + const srgb_to_linear = struct { + inline fn map(c: f32) f32 { + if (c <= 0.04045) return c / 12.92; + return std.math.pow(f32, (c + 0.055) / 1.055, 2.4); + } + }; + return .{ + .r = srgb_to_linear.map(self.r), + .g = srgb_to_linear.map(self.g), + .b = srgb_to_linear.map(self.b), + .a = self.a, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn toSRGB(self: Self) Self { + const linear_to_srgb = struct { + inline fn map(c: f32) f32 { + if (c <= 0.0031308) return c * 12.92; + return 1.055 * std.math.pow(f32, c, 1.0 / 2.4) - 0.055; + } + }; + return .{ + .r = linear_to_srgb.map(self.r), + .g = linear_to_srgb.map(self.g), + .b = linear_to_srgb.map(self.b), + .a = self.a, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn toHSV(self: Self) struct { h: f32, s: f32, v: f32 } { + const max = @max(self.r, @max(self.g, self.b)); + const min = @min(self.r, @min(self.g, self.b)); + const delta = max - min; + + const v = max; + const s = if (max == 0) 0 else delta / max; + + var h: f32 = 0; + if (delta != 0) { + if (max == self.r) { + h = 60.0 * @mod((self.g - self.b) / delta, 6.0); + } else if (max == self.g) { + h = 60.0 * ((self.b - self.r) / delta + 2.0); + } else { + h = 60.0 * ((self.r - self.g) / delta + 4.0); + } + } + return .{ .h = h, .s = s, .v = v }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn fromHSV(h: f32, s: f32, v: f32) Self { + const c = v * s; + const hp = h / 60.0; + const x = c * (1.0 - @abs(@mod(hp, 2.0) - 1.0)); + const m = v - c; + + var r: f32 = 0; + var g: f32 = 0; + var b: f32 = 0; + + if (hp < 1) { + r = c; + g = x; + } else if (hp < 2) { + r = x; + g = c; + } else if (hp < 3) { + g = c; + b = x; + } else if (hp < 4) { + g = x; + b = c; + } else if (hp < 5) { + r = x; + b = c; + } else { + r = c; + b = x; + } + + return .{ .r = r + m, .g = g + m, .b = b + m, .a = 1 }; + } + + // + // CONSTANTS + // + + pub const white = Self{ .r = 1, .g = 1, .b = 1, .a = 1 }; + pub const black = Self{ .r = 0, .g = 0, .b = 0, .a = 1 }; + pub const transparent = Self{ .r = 0, .g = 0, .b = 0, .a = 0 }; + + pub const red = Self{ .r = 1, .g = 0, .b = 0, .a = 1 }; + pub const green = Self{ .r = 0, .g = 1, .b = 0, .a = 1 }; + pub const blue = Self{ .r = 0, .g = 0, .b = 1, .a = 1 }; + + pub const yellow = Self{ .r = 1, .g = 1, .b = 0, .a = 1 }; + pub const cyan = Self{ .r = 0, .g = 1, .b = 1, .a = 1 }; + pub const magenta = Self{ .r = 1, .g = 0, .b = 1, .a = 1 }; + + pub const orange = Self{ .r = 1, .g = 0.55, .b = 0, .a = 1 }; + pub const purple = Self{ .r = 0.5, .g = 0, .b = 0.5, .a = 1 }; + pub const pink = Self{ .r = 1, .g = 0.75, .b = 0.8, .a = 1 }; + pub const brown = Self{ .r = 0.6, .g = 0.3, .b = 0.1, .a = 1 }; + pub const gray = Self{ .r = 0.5, .g = 0.5, .b = 0.5, .a = 1 }; +}; diff --git a/src/mat.zig b/src/mat.zig new file mode 100644 index 0000000..96d960d --- /dev/null +++ b/src/mat.zig @@ -0,0 +1,370 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +const std = @import("std"); +const Vec = @import("vec.zig").Vec; + +/// ---------------------------------------------------- +/// ---------------------------------------------------- +pub fn Mat( + comptime T: type, + comptime Row: usize, + comptime Col: usize, +) type { + return struct { + const Self = @This(); + + // + // FIELDS + // + + value: [Row][Col]T, + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn zero() Self { + var result: Self = undefined; + for (0..Row) |r| { + for (0..Col) |c| { + result.value[r][c] = @as(T, 0); + } + } + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn identity() Self { + comptime if (Row != Col) @compileError("identity only defined for square matrices"); + + var result: Self = undefined; + for (0..Row) |r| { + for (0..Col) |c| { + result.value[r][c] = if (r == c) @as(T, 1) else @as(T, 0); + } + } + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn fromRows(rows: [Row][Col]T) Self { + return .{ .value = rows }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn fromCols(cols: [Col][Row]T) Self { + var result: Self = undefined; + for (0..Col) |c| { + for (0..Row) |r| { + result.value[r][c] = cols[c][r]; + } + } + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn row(self: Self, index: usize) Vec(T, Col) { + return Vec(T, Col).new(self.value[index]); + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn col(self: Self, index: usize) Vec(T, Row) { + var data: [Row]T = undefined; + for (0..Row) |r| { + data[r] = self.value[r][index]; + } + return Vec(T, Row).new(data); + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn add(self: Self, other: Self) Self { + var result: Self = undefined; + for (0..Row) |r| { + for (0..Col) |c| { + result.value[r][c] = self.value[r][c] + other.value[r][c]; + } + } + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn sub(self: Self, other: Self) Self { + var result: Self = undefined; + for (0..Row) |r| { + for (0..Col) |c| { + result.value[r][c] = self.value[r][c] - other.value[r][c]; + } + } + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn mul(self: Self, other: Self) Self { + comptime if (Row != Col) @compileError("direct matrix multiplication requires square matrices; use mulMat for non-square"); + + var result: Self = undefined; + for (0..Row) |r| { + for (0..Col) |c| { + var sum: T = @as(T, 0); + for (0..Col) |k| { + sum += self.value[r][k] * other.value[k][c]; + } + result.value[r][c] = sum; + } + } + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn mulVec(self: Self, v: Vec(T, Col)) Vec(T, Row) { + var result: [Row]T = undefined; + for (0..Row) |r| { + var sum: T = @as(T, 0); + for (0..Col) |c| { + sum += self.value[r][c] * v.value[c]; + } + result[r] = sum; + } + return Vec(T, Row).new(result); + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn mulScalar(self: Self, scalar: T) Self { + var result: Self = undefined; + for (0..Row) |r| { + for (0..Col) |c| { + result.value[r][c] = self.value[r][c] * scalar; + } + } + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn divScalar(self: Self, scalar: T) Self { + var result: Self = undefined; + for (0..Row) |r| { + for (0..Col) |c| { + result.value[r][c] = self.value[r][c] / scalar; + } + } + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn transpose(self: Self) Mat(T, Col, Row) { + var result: Mat(T, Col, Row) = undefined; + for (0..Row) |r| { + for (0..Col) |c| { + result.value[c][r] = self.value[r][c]; + } + } + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn determinant(self: Self) T { + comptime if (Row != Col or Row < 1) @compileError("determinant only defined for non-empty square matrices"); + + if (Row == 1) return self.value[0][0]; + + var det: T = @as(T, 0); + for (0..Col) |c| { + det += self.value[0][c] * self.cofactor(0, c); + } + return det; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn inverse(self: Self) Self { + comptime if (Row != Col) @compileError("inverse only defined for square matrices"); + + const det = self.determinant(); + var result: Self = undefined; + for (0..Row) |r| { + for (0..Col) |c| { + result.value[r][c] = self.cofactor(c, r) / det; + } + } + return result; + } + + // + // PRIVATE + // + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + inline fn minor(self: Self, r_idx: usize, c_idx: usize) Mat(T, Row - 1, Col - 1) { + comptime if (Row < 2 or Col < 2) @compileError("minor requires at least 2x2"); + + var result: Mat(T, Row - 1, Col - 1) = undefined; + var ri: usize = 0; + for (0..Row) |r| { + if (r == r_idx) continue; + var ci: usize = 0; + for (0..Col) |c| { + if (c == c_idx) continue; + result.value[ri][ci] = self.value[r][c]; + ci += 1; + } + ri += 1; + } + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + inline fn cofactor(self: Self, r_idx: usize, c_idx: usize) T { + const sign: T = if ((r_idx + c_idx) % 2 == 0) @as(T, 1) else -@as(T, 1); + return sign * self.minor(r_idx, c_idx).determinant(); + } + + // + // 4X4 TRANSFORMS + // + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn translate(x: T, y: T, z: T) Self { + comptime if (Row != 4 or Col != 4) @compileError("translate only defined for Mat4x4"); + + var result = identity(); + result.value[0][3] = x; + result.value[1][3] = y; + result.value[2][3] = z; + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn rotateX(angle: T) Self { + comptime if (Row != 4 or Col != 4) @compileError("rotateX only defined for Mat4x4"); + + const c = @cos(angle); + const s = @sin(angle); + var result = identity(); + result.value[1][1] = c; + result.value[1][2] = -s; + result.value[2][1] = s; + result.value[2][2] = c; + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn rotateY(angle: T) Self { + comptime if (Row != 4 or Col != 4) @compileError("rotateY only defined for Mat4x4"); + + const c = @cos(angle); + const s = @sin(angle); + var result = identity(); + result.value[0][0] = c; + result.value[0][2] = s; + result.value[2][0] = -s; + result.value[2][2] = c; + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn rotateZ(angle: T) Self { + comptime if (Row != 4 or Col != 4) @compileError("rotateZ only defined for Mat4x4"); + + const c = @cos(angle); + const s = @sin(angle); + var result = identity(); + result.value[0][0] = c; + result.value[0][1] = -s; + result.value[1][0] = s; + result.value[1][1] = c; + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn scale(x: T, y: T, z: T) Self { + comptime if (Row != 4 or Col != 4) @compileError("scale only defined for Mat4x4"); + + var result = identity(); + result.value[0][0] = x; + result.value[1][1] = y; + result.value[2][2] = z; + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn lookAt(eye: Vec(T, 3), target: Vec(T, 3), up: Vec(T, 3)) Self { + comptime if (Row != 4 or Col != 4) @compileError("lookAt only defined for Mat4x4"); + + const f = target.sub(eye).normalize(); + const s = f.cross(up).normalize(); + const u = s.cross(f); + + var result = identity(); + result.value[0][0] = s.value[0]; + result.value[0][1] = s.value[1]; + result.value[0][2] = s.value[2]; + result.value[1][0] = u.value[0]; + result.value[1][1] = u.value[1]; + result.value[1][2] = u.value[2]; + result.value[2][0] = -f.value[0]; + result.value[2][1] = -f.value[1]; + result.value[2][2] = -f.value[2]; + + result.value[0][3] = -s.dot(eye); + result.value[1][3] = -u.dot(eye); + result.value[2][3] = f.dot(eye); + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn perspective(fov: T, aspect: T, near: T, far: T) Self { + comptime if (Row != 4 or Col != 4) @compileError("perspective only defined for Mat4x4"); + + const f = @as(T, 1) / @tan(fov / @as(T, 2)); + const range_inv = @as(T, 1) / (near - far); + + var result = zero(); + result.value[0][0] = f / aspect; + result.value[1][1] = -f; + result.value[2][2] = far * range_inv; + result.value[2][3] = near * far * range_inv; + result.value[3][2] = -@as(T, 1); + return result; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn orthographic(left: T, right: T, bottom: T, top: T, near: T, far: T) Self { + comptime if (Row != 4 or Col != 4) @compileError("orthographic only defined for Mat4x4"); + + const rml = @as(T, 1) / (right - left); + const tmb = @as(T, 1) / (top - bottom); + const fmn = @as(T, 1) / (far - near); + + var result = identity(); + result.value[0][0] = @as(T, 2) * rml; + result.value[1][1] = -@as(T, 2) * tmb; + result.value[2][2] = -fmn; + result.value[0][3] = -(right + left) * rml; + result.value[1][3] = (top + bottom) * tmb; + result.value[2][3] = -near * fmn; + return result; + } + }; +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..d19c7a0 --- /dev/null +++ b/src/root.zig @@ -0,0 +1,169 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +// +// PRIVATE +// + +const std = @import("std"); + +// +// PUBLIC +// + +// --- GENERICS --- +pub const Vec = @import("vec.zig").Vec; +pub const Mat = @import("mat.zig").Mat; +pub const Color = @import("color.zig").Color; + +// --- VEC2 --- +pub const Vec2 = Vec(i32, 2); +pub const Vec2f = Vec(f32, 2); + +// --- VEC3 --- +pub const Vec3 = Vec(i32, 3); +pub const Vec3f = Vec(f32, 3); + +// --- MAT --- +pub const Mat2x2 = Mat(f32, 2, 2); +pub const Mat3x3 = Mat(f32, 3, 3); +pub const Mat4x4 = Mat(f32, 4, 4); + +// +// TESTS +// + +test "init" { + std.debug.print("\x1b[34m--- init ---\n", .{}); + defer std.debug.print("\x1b[0m\n", .{}); + + const vec2: Vec2f = .as(12); + const vec3: Vec3f = .new(.{ 1, 2, 3 }); + + std.debug.print("Vec2: {}\n", .{vec2.value}); + std.debug.print("Vec3: {}\n", .{vec3.value}); +} + +test "math" { + std.debug.print("\x1b[35m--- math ---\n", .{}); + defer std.debug.print("\x1b[0m\n", .{}); + + const a: Vec3f = .new(.{ 1, 2, 3 }); + const b: Vec3f = .new(.{ 2, 3, 4 }); + + std.debug.print("a = {}\n", .{a.value}); + std.debug.print("b = {}\n\n", .{b.value}); + + std.debug.print("a + b = {}\n", .{a.add(b).value}); + std.debug.print("a - b = {}\n", .{a.sub(b).value}); + std.debug.print("a * b = {}\n", .{a.mul(b).value}); + std.debug.print("a / b = {:.1}\n", .{a.div(b).value}); +} + +test "mat init" { + std.debug.print("\x1b[34m--- mat init ---\n", .{}); + defer std.debug.print("\x1b[0m\n", .{}); + + const identity: Mat4x4 = .identity(); + std.debug.print("identity[0][0] = {d}\n", .{identity.value[0][0]}); + std.debug.print("identity[3][3] = {d}\n", .{identity.value[3][3]}); +} + +test "mat mul" { + std.debug.print("\x1b[35m--- mat mul ---\n", .{}); + defer std.debug.print("\x1b[0m\n", .{}); + + const t: Mat4x4 = .translate(1, 2, 3); + const rz: Mat4x4 = .rotateZ(@as(f32, std.math.pi) / 4.0); + const m = t.mul(rz); + + std.debug.print("translate * rotateZ:\n", .{}); + for (m.value, 0..) |row, i| { + std.debug.print("row {d}: {d:.4} {d:.4} {d:.4} {d:.4}\n", .{ i, row[0], row[1], row[2], row[3] }); + } +} + +test "mat det" { + std.debug.print("\x1b[34m--- mat det ---\n", .{}); + defer std.debug.print("\x1b[0m\n", .{}); + + const m: Mat3x3 = .fromRows(.{ + .{ 1, 2, 3 }, + .{ 4, 5, 6 }, + .{ 7, 8, 10 }, + }); + std.debug.print("det = {d:.2}\n", .{m.determinant()}); +} + +test "mat inverse" { + std.debug.print("\x1b[35m--- mat inverse ---\n", .{}); + defer std.debug.print("\x1b[0m\n", .{}); + + const m: Mat4x4 = .translate(5, -3, 2); + const inv = m.inverse(); + const back = m.mul(inv); + + std.debug.print("M * inv(M) should be identity:\n", .{}); + for (back.value, 0..) |row, i| { + std.debug.print("row {d}: {d:.4} {d:.4} {d:.4} {d:.4}\n", .{ i, row[0], row[1], row[2], row[3] }); + } +} + +test "mat perspective" { + std.debug.print("\x1b[34m--- mat perspective (vulkan) ---\n", .{}); + defer std.debug.print("\x1b[0m\n", .{}); + + const p: Mat4x4 = .perspective(@as(f32, std.math.pi) / 4.0, 16.0 / 9.0, 0.1, 100.0); + std.debug.print("perspective:\n", .{}); + for (p.value, 0..) |row, i| { + std.debug.print("row {d}: {d:.4} {d:.4} {d:.4} {d:.4}\n", .{ i, row[0], row[1], row[2], row[3] }); + } +} + +test "mat lookAt" { + std.debug.print("\x1b[35m--- mat lookAt ---\n", .{}); + defer std.debug.print("\x1b[0m\n", .{}); + + const eye: Vec3f = .new(.{ 0, 0, 5 }); + const target: Vec3f = .new(.{ 0, 0, 0 }); + const up: Vec3f = .new(.{ 0, 1, 0 }); + const view: Mat4x4 = .lookAt(eye, target, up); + + std.debug.print("lookAt:\n", .{}); + for (view.value, 0..) |row, i| { + std.debug.print("row {d}: {d:.4} {d:.4} {d:.4} {d:.4}\n", .{ i, row[0], row[1], row[2], row[3] }); + } +} + +test "color init" { + std.debug.print("\x1b[34m--- color init ---\n", .{}); + defer std.debug.print("\x1b[0m\n", .{}); + + const c: Color = .rgb(1, 0.5, 0); + std.debug.print("orange: r={d:.1} g={d:.1} b={d:.1} a={d:.1}\n", .{ c.r, c.g, c.b, c.a }); +} + +test "color hex" { + std.debug.print("\x1b[35m--- color hex ---\n", .{}); + defer std.debug.print("\x1b[0m\n", .{}); + + const c: Color = .hex(0xFF8800); + std.debug.print("0xFF8800: r={d:.4} g={d:.4} b={d:.4} a={d:.1}\n", .{ c.r, c.g, c.b, c.a }); +} + +test "color blend" { + std.debug.print("\x1b[34m--- color blend ---\n", .{}); + defer std.debug.print("\x1b[0m\n", .{}); + + const over = Color.red.blend(Color.green); + std.debug.print("red over green: r={d:.4} g={d:.4} b={d:.4} a={d:.1}\n", .{ over.r, over.g, over.b, over.a }); +} + +test "color hsv" { + std.debug.print("\x1b[35m--- color hsv ---\n", .{}); + defer std.debug.print("\x1b[0m\n", .{}); + + const c: Color = .fromHSV(120, 1, 1); + const hsv = c.toHSV(); + std.debug.print("fromHSV(120,1,1): r={d:.4} g={d:.4} b={d:.4} -> h={d:.1} s={d:.1} v={d:.1}\n", .{ c.r, c.g, c.b, hsv.h, hsv.s, hsv.v }); +} diff --git a/src/vec.zig b/src/vec.zig new file mode 100644 index 0000000..edbcd55 --- /dev/null +++ b/src/vec.zig @@ -0,0 +1,137 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +const std = @import("std"); + +/// ---------------------------------------------------- +/// Generic Vector +/// ---------------------------------------------------- +pub fn Vec( + comptime T: type, + comptime Len: usize, +) type { + return struct { + const Self = @This(); + + // + // FIELDS + // + + value: @Vector(Len, T), + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn as(value: T) Self { + return .{ + .value = blk: { + var data: [Len]T = undefined; + for (0..data.len) |i| { + data[i] = value; + } + break :blk data; + }, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn new(value: @Vector(Len, T)) Self { + return .{ .value = value }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn add( + self: Self, + other: Self, + ) Self { + return .{ + .value = self.value + other.value, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn sub( + self: Self, + other: Self, + ) Self { + return .{ + .value = self.value - other.value, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn mul( + self: Self, + other: Self, + ) Self { + return .{ + .value = self.value * other.value, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn div( + self: Self, + other: Self, + ) Self { + return .{ + .value = self.value / other.value, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn scale(self: Self, scalar: T) Self { + return .{ + .value = self.value * @as(@Vector(Len, T), @splat(scalar)), + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn negate(self: Self) Self { + return .{ .value = -self.value }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn dot(self: Self, other: Self) T { + return @reduce(.Add, self.value * other.value); + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn lengthSquared(self: Self) T { + return self.dot(self); + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn length(self: Self) T { + return @sqrt(self.lengthSquared()); + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn normalize(self: Self) Self { + return self.scale(@as(T, 1) / self.length()); + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub inline fn cross(self: Self, other: Self) Self { + comptime if (Len != 3) @compileError("cross product only defined for Vec3"); + return .{ + .value = .{ + self.value[1] * other.value[2] - self.value[2] * other.value[1], + self.value[2] * other.value[0] - self.value[0] * other.value[2], + self.value[0] * other.value[1] - self.value[1] * other.value[0], + }, + }; + } + }; +}