Zig utility library
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Initial Commit

IamPyu 05bc25e9

+420
+1
.envrc
··· 1 + use flake
+3
.gitignore
··· 1 + /.direnv 2 + /.zig-cache 3 + /zig-out
+21
LICENSE
··· 1 + MIT License 2 + 3 + Copyright (c) 2025 IamPyu <pyucreates@gmail.com> 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+3
README.md
··· 1 + # laz 2 + 3 + Simple linear algebra library for Zig
+26
build.zig
··· 1 + const std = @import("std"); 2 + 3 + pub fn build(b: *std.Build) void { 4 + const target = b.standardTargetOptions(.{}); 5 + const optimize = b.standardOptimizeOption(.{}); 6 + 7 + const mod = b.addModule("laz", .{ 8 + .root_source_file = b.path("src/root.zig"), 9 + .target = target, 10 + .optimize = optimize, 11 + }); 12 + 13 + const lib = b.addLibrary(.{ 14 + .name = "laz", 15 + .root_module = mod, 16 + }); 17 + 18 + b.installArtifact(lib); 19 + 20 + const mod_tests = b.addTest(.{ 21 + .root_module = mod, 22 + }); 23 + const run_mod_tests = b.addRunArtifact(mod_tests); 24 + const test_step = b.step("test", "Run tests"); 25 + test_step.dependOn(&run_mod_tests.step); 26 + }
+26
build.zig.zon
··· 1 + .{ 2 + .name = .laz, 3 + .version = "0.1.0", 4 + // Together with name, this represents a globally unique package 5 + // identifier. This field is generated by the Zig toolchain when the 6 + // package is first created, and then *never changes*. This allows 7 + // unambiguous detection of one package being an updated version of 8 + // another. 9 + // 10 + // When forking a Zig project, this id should be regenerated (delete the 11 + // field and run `zig build`) if the upstream project is still maintained. 12 + // Otherwise, the fork is *hostile*, attempting to take control over the 13 + // original project's identity. Thus it is recommended to leave the comment 14 + // on the following line intact, so that it shows up in code reviews that 15 + // modify the field. 16 + .fingerprint = 0x72ba2992beecd756, // Changing this has security and trust implications. 17 + .minimum_zig_version = "0.15.2", 18 + .dependencies = .{}, 19 + .paths = .{ 20 + "build.zig", 21 + "build.zig.zon", 22 + "src", 23 + "LICENSE", 24 + "README.md", 25 + }, 26 + }
+7
default.nix
··· 1 + {zig, lib, stdenv}: stdenv.mkDerivation { 2 + src = lib.cleanSource ./.; 3 + 4 + nativeBuildInputs = [ 5 + zig.hook 6 + ]; 7 + }
+48
flake.lock
··· 1 + { 2 + "nodes": { 3 + "flake-parts": { 4 + "inputs": { 5 + "nixpkgs-lib": [ 6 + "nixpkgs" 7 + ] 8 + }, 9 + "locked": { 10 + "lastModified": 1762440070, 11 + "narHash": "sha256-xxdepIcb39UJ94+YydGP221rjnpkDZUlykKuF54PsqI=", 12 + "owner": "hercules-ci", 13 + "repo": "flake-parts", 14 + "rev": "26d05891e14c88eb4a5d5bee659c0db5afb609d8", 15 + "type": "github" 16 + }, 17 + "original": { 18 + "owner": "hercules-ci", 19 + "repo": "flake-parts", 20 + "type": "github" 21 + } 22 + }, 23 + "nixpkgs": { 24 + "locked": { 25 + "lastModified": 1762361079, 26 + "narHash": "sha256-lz718rr1BDpZBYk7+G8cE6wee3PiBUpn8aomG/vLLiY=", 27 + "owner": "nixos", 28 + "repo": "nixpkgs", 29 + "rev": "ffcdcf99d65c61956d882df249a9be53e5902ea5", 30 + "type": "github" 31 + }, 32 + "original": { 33 + "owner": "nixos", 34 + "ref": "nixpkgs-unstable", 35 + "repo": "nixpkgs", 36 + "type": "github" 37 + } 38 + }, 39 + "root": { 40 + "inputs": { 41 + "flake-parts": "flake-parts", 42 + "nixpkgs": "nixpkgs" 43 + } 44 + } 45 + }, 46 + "root": "root", 47 + "version": 7 48 + }
+41
flake.nix
··· 1 + { 2 + description = "Zig template"; 3 + 4 + inputs = { 5 + nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable"; 6 + flake-parts = { 7 + url = "github:hercules-ci/flake-parts"; 8 + inputs.nixpkgs-lib.follows = "nixpkgs"; 9 + }; 10 + }; 11 + 12 + outputs = { 13 + self, 14 + flake-parts, 15 + ... 16 + } @ inputs: 17 + flake-parts.lib.mkFlake {inherit inputs;} { 18 + imports = []; 19 + 20 + systems = [ 21 + "x86_64-linux" 22 + ]; 23 + 24 + perSystem = { 25 + pkgs, 26 + system, 27 + ... 28 + }: { 29 + formatter = pkgs.alejandra; 30 + 31 + packages.default = pkgs.callPackage ./default.nix {}; 32 + 33 + devShells.default = pkgs.mkShell { 34 + buildInputs = with pkgs; [ 35 + zig 36 + zls 37 + ]; 38 + }; 39 + }; 40 + }; 41 + }
+244
src/root.zig
··· 1 + const std = @import("std"); 2 + const math = std.math; 3 + 4 + pub fn Matrix(T: type, rows: usize, columns: usize) type { 5 + std.debug.assert(rows > 0); 6 + std.debug.assert(columns > 0); 7 + 8 + // TODO: assert `T` is a number 9 + // const scalar_type_info = @typeInfo(T); 10 + 11 + return struct { 12 + inner: [columns][rows]T, 13 + 14 + /// Initialize a matrix 15 + pub fn init(table: [columns][rows]T) @This() { 16 + std.debug.assert(table.len == columns); 17 + std.debug.assert(table[0].len == rows); 18 + 19 + return @This(){ .inner = table }; 20 + } 21 + 22 + /// Add all elements of the matrix to a scalar 23 + pub fn add(self: *const @This(), other: *const @This()) @This() { 24 + var mat = @This(){ .inner = self.inner }; 25 + 26 + for (0..mat.inner.len) |i| { 27 + for (0..mat.inner[i].len) |j| { 28 + mat.inner[i][j] = mat.inner[i][j] + other.inner[i][j]; 29 + } 30 + } 31 + 32 + return mat; 33 + } 34 + 35 + /// Subtract all elements of the matrix from a scalar 36 + pub fn sub(self: *const @This(), other: *const @This()) @This() { 37 + var mat = @This(){ .inner = self.inner }; 38 + 39 + for (0..mat.inner.len) |i| { 40 + for (0..mat.inner[i].len) |j| { 41 + mat.inner[i][j] = mat.inner[i][j] - other.inner[i][j]; 42 + } 43 + } 44 + 45 + return mat; 46 + } 47 + 48 + /// Multiply all elements of the matrix by a scalar 49 + pub fn mul(self: *const @This(), scalar: T) @This() { 50 + var mat = @This(){ .inner = self.inner }; 51 + 52 + for (0..mat.inner.len) |i| { 53 + for (0..mat.inner[i].len) |j| { 54 + mat.inner[i][j] = mat.inner[i][j] * scalar; 55 + } 56 + } 57 + 58 + return mat; 59 + } 60 + 61 + /// Divide all elements of the matrix by a scalar 62 + pub fn div(self: *const @This(), scalar: T) @This() { 63 + var mat = @This(){ .inner = self.inner }; 64 + 65 + for (0..mat.inner.len) |i| { 66 + for (0..mat.inner[i].len) |j| { 67 + mat.inner[i][j] = mat.inner[i][j] / scalar; 68 + } 69 + } 70 + 71 + return mat; 72 + } 73 + 74 + /// Get the magnitude of the vector 75 + pub fn magnitude(self: *const @This()) T { 76 + var mag: T = 0; 77 + 78 + for (self.inner) |r| { 79 + for (r) |s| { 80 + mag += math.pow(T, s, 2); 81 + } 82 + } 83 + 84 + return math.sqrt(mag); 85 + } 86 + 87 + /// Get the distance between 2 vectors 88 + pub fn distance(self: *const @This(), other: *const @This()) T { 89 + return other.sub(self).magnitude(); 90 + } 91 + 92 + /// Normalize the vector 93 + pub fn normalize(self: *const @This()) @This() { 94 + var normalized = @This(){ .inner = self.inner }; 95 + var unorm: T = 0; 96 + 97 + for (self.inner) |r| { 98 + for (r) |s| { 99 + unorm += math.pow(T, s, 2); 100 + } 101 + } 102 + 103 + for (normalized.inner, 0..) |r, i| { 104 + for (r, 0..) |s, j| { 105 + normalized.inner[i][j] = s / math.sqrt(unorm); 106 + } 107 + } 108 + 109 + return normalized; 110 + } 111 + 112 + /// Return the dot product of 2 vectors, returns `null` if more than 1 column. 113 + pub fn dot(self: *const @This(), other: *const @This()) ?T { 114 + if (columns != 1) { 115 + return null; 116 + } 117 + 118 + var product: T = 0; 119 + 120 + for (self.inner, other.inner) |r1, r2| { 121 + for (r1, r2) |s1, s2| { 122 + product += s1 * s2; 123 + } 124 + } 125 + 126 + return product; 127 + } 128 + 129 + // TODO: add cross product 130 + 131 + /// Flatten all the elements of the matrix into a single array 132 + pub fn flatten(self: *const @This()) [rows * columns]T { 133 + var output: [rows * columns]T = undefined; 134 + 135 + var k: usize = 0; 136 + for (self.inner) |c| { 137 + for (c) |s| { 138 + output[k] = s; 139 + k += 1; 140 + } 141 + } 142 + 143 + return output; 144 + } 145 + 146 + /// Call `func` on all the elements of the matrix 147 + pub fn map(self: *const @This(), func: *const fn (T) T) @This() { 148 + var mat = @This(){ .inner = self.inner }; 149 + 150 + for (0..mat.inner.len) |i| { 151 + for (0..mat.inner[i].len) |j| { 152 + mat.inner[i][j] = func(mat.inner[i][j]); 153 + } 154 + } 155 + 156 + return mat; 157 + } 158 + }; 159 + } 160 + 161 + pub fn Vector(T: type, components: usize) type { 162 + return Matrix(T, components, 1); 163 + } 164 + 165 + /// 2-dimensional vector of `f32` 166 + pub const Vec2F = Vector(f32, 2); 167 + /// 2-dimensional vector of `f64` 168 + pub const Vec2D = Vector(f64, 2); 169 + 170 + /// 3-dimensional vector of `f32` 171 + pub const Vec3F = Vector(f32, 3); 172 + /// 3-dimensional vector of `f64` 173 + pub const Vec3D = Vector(f64, 3); 174 + 175 + /// 4-dimensional vector of `f32` 176 + pub const Vec4F = Vector(f32, 4); 177 + /// 4-dimensional vector of `f64` 178 + pub const Vec4D = Vector(f64, 4); 179 + 180 + /// 2x2 matrix of `f32` 181 + pub const Mat2F = Matrix(f32, 2, 2); 182 + /// 2x2 matrix of `f64` 183 + pub const Mat2D = Matrix(f64, 2, 2); 184 + 185 + /// 3x3 matrix of `f32` 186 + pub const Mat3F = Matrix(f32, 3, 3); 187 + /// 3x3 matrix of `f64` 188 + pub const Mat3D = Matrix(f64, 3, 3); 189 + 190 + /// 4x4 matrix of `f32` 191 + pub const Mat4F = Matrix(f32, 4, 4); 192 + /// 4x4 matrix of `f64` 193 + pub const Mat4D = Matrix(f64, 4, 4); 194 + 195 + // idk how the fuck i will, but add quaternions eventually. 196 + 197 + test "mul" { 198 + const x = Matrix(f32, 3, 5).init(.{ 199 + .{ 1, 2, 3 }, 200 + .{ 2, 4, 6 }, 201 + .{ 3, 6, 9 }, 202 + .{ 4, 8, 12 }, 203 + .{ 5, 10, 15 }, 204 + }); 205 + 206 + const new_mat = x.mul(3); 207 + 208 + try std.testing.expectEqual(.{ 3, 6, 9 }, new_mat.inner[0]); 209 + } 210 + 211 + test "flatten" { 212 + const x = Matrix(f32, 3, 3).init(.{ 213 + .{ 1, 2, 3 }, 214 + .{ 4, 5, 6 }, 215 + .{ 7, 8, 9 }, 216 + }); 217 + 218 + const array = x.flatten(); 219 + 220 + try std.testing.expectEqual(.{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }, array); 221 + } 222 + 223 + test "magnitude" { 224 + const magnitude = Vec3F.init(.{.{ 394, 329, 684 }}).magnitude(); 225 + try std.testing.expectEqual(855.1801, magnitude); 226 + } 227 + 228 + test "normalized" { 229 + const normalized = Vec2F.init(.{.{ 6, 9 }}).normalize(); 230 + try std.testing.expectEqual(1, normalized.magnitude()); 231 + } 232 + 233 + test "dot" { 234 + const x = Vec2F.init(.{.{ 3, 4 }}); 235 + const y = Vec2F.init(.{.{ 6, 9 }}); 236 + 237 + try std.testing.expectEqual(54, x.dot(&y)); 238 + } 239 + 240 + test "dist" { 241 + const x = Vec2F.init(.{.{ 4, 1 }}); 242 + const y = Vec2F.init(.{.{ 1, 4 }}); 243 + try std.testing.expectEqual(4.2426405, x.distance(&y)); 244 + }