Skip to content

Commit d525363

Browse files
committed
feat: add more math functions
Signed-off-by: Michael Pollind <mpollind@gmail.com>
1 parent ef9a4ea commit d525363

23 files changed

Lines changed: 1514 additions & 194 deletions

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Zig Linear Algebra (ZLA)
1+
# Zig Math Library (ZML)
22

33
[![CI](https://github.com/flying-swallow/zig-linear-algebra/actions/workflows/ci.yml/badge.svg)](https://github.com/flying-swallow/zig-linear-algebra/actions/workflows/ci.yml)
44

@@ -13,11 +13,11 @@ zig fetch --save "git+https://github.com/flying-swallow/zig-linear-algebra.git"
1313
Then in your `build.zig`:
1414

1515
```zig
16-
const zla = b.dependency("zla", .{
16+
const zml = b.dependency("zml", .{
1717
.target = target,
1818
.optimize = optimize,
1919
});
2020
21-
exe.root_module.addImport("zla", zla.module("zla"));
21+
exe.root_module.addImport("zml", zml.module("zml"));
2222
```
2323

bench/bench.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
const std = @import("std");
22
const zbench = @import("zbench");
3-
const zla = @import("zla");
3+
const zml = @import("zml");
44

55
fn benchmark_multiply(comptime size: usize) type {
66
return struct {
7-
const Matrix512 = zla.Mat(f32, size, size);
7+
const Matrix512 = zml.Mat(f32, size, size);
88
a: Matrix512,
99
b: Matrix512,
1010

@@ -40,7 +40,7 @@ fn bench_sin_cos_fused(comptime size: usize) type {
4040
}
4141

4242
pub fn run(self: *@This(), _: std.mem.Allocator) void {
43-
std.mem.doNotOptimizeAway(@call(.never_inline, zla.vec.sin_cos, .{self.angles}));
43+
std.mem.doNotOptimizeAway(@call(.never_inline, zml.vec.sin_cos, .{self.angles}));
4444
}
4545
};
4646
}

build.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pub fn build(b: *std.Build) void {
44
const target = b.standardTargetOptions(.{});
55
const optimize = b.standardOptimizeOption(.{});
66

7-
const root_module = b.addModule("zla", .{
7+
const root_module = b.addModule("zml", .{
88
.root_source_file = b.path("src/root.zig"),
99
.target = target,
1010
.optimize = optimize,
@@ -30,7 +30,7 @@ pub fn build(b: *std.Build) void {
3030
.target = target,
3131
.optimize = optimize,
3232
.imports = &.{
33-
.{ .name = "zla", .module = root_module },
33+
.{ .name = "zml", .module = root_module },
3434
.{ .name = "zbench", .module = zbench_dep.module("zbench") },
3535
},
3636
}),

build.zig.zon

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.{
2-
.name = .zla,
3-
.version = "0.0.2",
4-
.fingerprint = 0x55da41f1f0b5a9ce, // Changing this has security and trust implications.
2+
.name = .zml,
3+
.version = "0.1.0",
4+
.fingerprint = 0x32700c0d44ccd0f5, // Changing this has security and trust implications.
55
.dependencies = .{
66
.zbench = .{
77
.url = "git+https://github.com/hendriknielaender/zBench.git#f12a2262bcd9d300207890555c577da5e29de453",

src/color.zig

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
const std = @import("std");
2+
3+
const Vec3 = @Vector(3, f32);
4+
5+
fn map3(v: Vec3, comptime f: fn (f32) f32) Vec3 {
6+
return .{ f(v[0]), f(v[1]), f(v[2]) };
7+
}
8+
9+
// ---------------------------------------------------------------------------
10+
// sRGB transfer functions (IEC 61966-2-1)
11+
// ---------------------------------------------------------------------------
12+
13+
pub fn srgb_to_linear(c: f32) f32 {
14+
return if (c <= 0.04045) c / 12.92 else std.math.pow(f32, (c + 0.055) / 1.055, 2.4);
15+
}
16+
17+
pub fn linear_to_srgb(c: f32) f32 {
18+
return if (c <= 0.0031308) c * 12.92 else 1.055 * std.math.pow(f32, c, 1.0 / 2.4) - 0.055;
19+
}
20+
21+
pub fn srgb_to_linear3(c: Vec3) Vec3 {
22+
return map3(c, srgb_to_linear);
23+
}
24+
25+
pub fn linear_to_srgb3(c: Vec3) Vec3 {
26+
return map3(c, linear_to_srgb);
27+
}
28+
29+
// ---------------------------------------------------------------------------
30+
// Luminance (Rec. 709 linear-light weights)
31+
// ---------------------------------------------------------------------------
32+
33+
pub fn luminance(rgb: Vec3) f32 {
34+
return @reduce(.Add, rgb * Vec3{ 0.2126, 0.7152, 0.0722 });
35+
}
36+
37+
// ---------------------------------------------------------------------------
38+
// RGB <-> YCoCg (reversible luma/chroma transform)
39+
// ---------------------------------------------------------------------------
40+
41+
pub fn rgb_to_ycocg(rgb: Vec3) Vec3 {
42+
return .{
43+
0.25 * rgb[0] + 0.5 * rgb[1] + 0.25 * rgb[2],
44+
0.5 * rgb[0] - 0.5 * rgb[2],
45+
-0.25 * rgb[0] + 0.5 * rgb[1] - 0.25 * rgb[2],
46+
};
47+
}
48+
49+
pub fn ycocg_to_rgb(ycocg: Vec3) Vec3 {
50+
const y = ycocg[0];
51+
const co = ycocg[1];
52+
const cg = ycocg[2];
53+
return .{ y + co - cg, y + cg, y - co - cg };
54+
}
55+
56+
// ---------------------------------------------------------------------------
57+
// Tone mapping
58+
// ---------------------------------------------------------------------------
59+
60+
pub fn reinhard(x: Vec3) Vec3 {
61+
return x / (@as(Vec3, @splat(1)) + x);
62+
}
63+
64+
pub fn reinhard_inverse(x: Vec3) Vec3 {
65+
return x / (@as(Vec3, @splat(1)) - x);
66+
}
67+
68+
/// Narkowicz's fitted ACES filmic tone mapping curve.
69+
pub fn aces_film(x: Vec3) Vec3 {
70+
const a: Vec3 = @splat(2.51);
71+
const b: Vec3 = @splat(0.03);
72+
const c: Vec3 = @splat(2.43);
73+
const d: Vec3 = @splat(0.59);
74+
const e: Vec3 = @splat(0.14);
75+
const num = x * (a * x + b);
76+
const den = x * (c * x + d) + e;
77+
return @min(@max(num / den, @as(Vec3, @splat(0))), @as(Vec3, @splat(1)));
78+
}
79+
80+
// ---------------------------------------------------------------------------
81+
// PQ / SMPTE ST 2084 (Rec. 2100). Linear input normalized so 1.0 == 10000 nits.
82+
// ---------------------------------------------------------------------------
83+
84+
const pq_m1 = 0.1593017578125;
85+
const pq_m2 = 78.84375;
86+
const pq_c1 = 0.8359375;
87+
const pq_c2 = 18.8515625;
88+
const pq_c3 = 18.6875;
89+
90+
pub fn linear_to_pq(l: f32) f32 {
91+
const lm1 = std.math.pow(f32, @max(l, 0), pq_m1);
92+
return std.math.pow(f32, (pq_c1 + pq_c2 * lm1) / (1.0 + pq_c3 * lm1), pq_m2);
93+
}
94+
95+
pub fn pq_to_linear(n: f32) f32 {
96+
const nm2 = std.math.pow(f32, @max(n, 0), 1.0 / pq_m2);
97+
return std.math.pow(f32, @max(nm2 - pq_c1, 0) / (pq_c2 - pq_c3 * nm2), 1.0 / pq_m1);
98+
}
99+
100+
test "srgb round trip" {
101+
for ([_]f32{ 0, 0.001, 0.04, 0.5, 1 }) |c| {
102+
try std.testing.expectApproxEqAbs(c, linear_to_srgb(srgb_to_linear(c)), 1e-5);
103+
}
104+
const v = Vec3{ 0.1, 0.5, 0.9 };
105+
const back = linear_to_srgb3(srgb_to_linear3(v));
106+
try std.testing.expectApproxEqAbs(v[0], back[0], 1e-5);
107+
try std.testing.expectApproxEqAbs(v[2], back[2], 1e-5);
108+
}
109+
110+
test "luminance" {
111+
try std.testing.expectApproxEqAbs(@as(f32, 1), luminance(.{ 1, 1, 1 }), 1e-6);
112+
try std.testing.expectApproxEqAbs(@as(f32, 0.7152), luminance(.{ 0, 1, 0 }), 1e-6);
113+
}
114+
115+
test "ycocg round trip" {
116+
const rgb = Vec3{ 0.2, 0.6, 0.9 };
117+
const back = ycocg_to_rgb(rgb_to_ycocg(rgb));
118+
try std.testing.expectApproxEqAbs(rgb[0], back[0], 1e-6);
119+
try std.testing.expectApproxEqAbs(rgb[1], back[1], 1e-6);
120+
try std.testing.expectApproxEqAbs(rgb[2], back[2], 1e-6);
121+
}
122+
123+
test "reinhard round trip" {
124+
const x = Vec3{ 0.1, 1.0, 4.0 };
125+
const back = reinhard_inverse(reinhard(x));
126+
try std.testing.expectApproxEqAbs(x[0], back[0], 1e-5);
127+
try std.testing.expectApproxEqAbs(x[2], back[2], 1e-4);
128+
}
129+
130+
test "pq round trip" {
131+
for ([_]f32{ 0.0, 0.01, 0.1, 0.5, 1.0 }) |l| {
132+
try std.testing.expectApproxEqAbs(l, pq_to_linear(linear_to_pq(l)), 1e-4);
133+
}
134+
}

src/geometry.zig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ pub const Primative = enum {
77
Sphere,
88
OrientedBox,
99
Capsule,
10+
Frustum,
11+
};
12+
13+
/// Result of testing a primitive against a bounding volume such as a frustum.
14+
pub const IntersectionState = enum {
15+
outside,
16+
inside,
17+
partial,
1018
};
1119

1220
pub const AABB = @import("geometry/aabb.zig").AABB;
@@ -20,6 +28,9 @@ pub const Planef64 = Plane(f64);
2028
pub const Sphere = @import("geometry/sphere.zig").Sphere;
2129
pub const Capsule = @import("geometry/capsule.zig").Capsule;
2230
pub const OrientedBoundedBox = @import("geometry/obb.zig").OrientedBoundedBox;
31+
pub const Frustum = @import("geometry/frustum.zig").Frustum;
32+
pub const Frustumf32 = Frustum(f32);
33+
pub const Frustumf64 = Frustum(f64);
2334

2435

2536

@@ -33,6 +44,7 @@ test {
3344
_ = @import("geometry/sphere.zig");
3445
_ = @import("geometry/capsule.zig");
3546
_ = @import("geometry/obb.zig");
47+
_ = @import("geometry/frustum.zig");
3648
_ = @import("geometry/overlap.zig");
3749
_ = @import("geometry/ray.zig");
3850
_ = @import("geometry/contains.zig");

src/geometry/aabb.zig

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const std = @import("std");
2-
const zla = @import("../root.zig");
3-
const geometry = zla.geom;
2+
const zml = @import("../root.zig");
3+
const geometry = zml.geom;
44

55
pub fn AABB(comptime T: type) type {
66
return struct {
@@ -34,7 +34,7 @@ pub fn AABB(comptime T: type) type {
3434
}
3535

3636
pub fn get_sqr_distance_to(self: Self, point: @Vector(3, T)) T {
37-
return zla.vec.norm_sqr(self.get_closest_point(point) - point);
37+
return zml.vec.norm_sqr(self.get_closest_point(point) - point);
3838
}
3939

4040
pub fn from_two_points(p1: @Vector(3, T), p2: @Vector(3, T)) @This() {
@@ -53,7 +53,7 @@ pub fn AABB(comptime T: type) type {
5353
};
5454
}
5555

56-
pub fn transform(self: Self, mat: zla.Mat(T, 4, 4)) Self {
56+
pub fn transform(self: Self, mat: zml.Mat(T, 4, 4)) Self {
5757
const pos = mat.position();
5858
var new_min = pos;
5959
var new_max = pos;
@@ -107,7 +107,7 @@ pub fn AABB(comptime T: type) type {
107107
pub fn surface_area(self: Self) T {
108108
std.debug.assert(@reduce(.And, self.min <= self.max));
109109
const extent = self.max - self.min;
110-
return 2 * zla.vec.dot(zla.vec.swizzle(extent, "yzz"), zla.vec.swizzle(extent, "xxy"));
110+
return 2 * zml.vec.dot(zml.vec.swizzle(extent, "yzz"), zml.vec.swizzle(extent, "xxy"));
111111
}
112112

113113
pub fn volume(self: Self) T {
@@ -121,11 +121,11 @@ pub fn AABB(comptime T: type) type {
121121
test "aabb_transform" {
122122
const AABBf32 = AABB(f32);
123123
const aabb = AABBf32.from_two_points(.{ 0.0, 0.0, 0.0 }, .{ 1.0, 1.0, 1.0 });
124-
var translation: zla.Mat(f32, 4, 4) = .identity;
124+
var translation: zml.Mat(f32, 4, 4) = .identity;
125125
translation = translation.translate(.{ 1.0, 2.0, 3.0 });
126126
const transformed = aabb.transform(translation);
127-
try std.testing.expect(zla.vec.is_close_default(transformed.min, .{ 1.0, 2.0, 3.0 }));
128-
try std.testing.expect(zla.vec.is_close_default(transformed.max, .{ 2.0, 3.0, 4.0 }));
127+
try std.testing.expect(zml.vec.is_close_default(transformed.min, .{ 1.0, 2.0, 3.0 }));
128+
try std.testing.expect(zml.vec.is_close_default(transformed.max, .{ 2.0, 3.0, 4.0 }));
129129
}
130130

131131
test "surface_area" {
@@ -153,14 +153,14 @@ test "InvDirection" {
153153
test "aabb_get_support" {
154154
const aabb: AABB(f32) = .from_two_points(.{ 0.0, 0.0, 0.0 }, .{ 1.0, 1.0, 1.0 });
155155

156-
try std.testing.expect(zla.vec.is_close_default(aabb.get_support(.{ 0.5774, 0.5774, 0.5774 }), .{ 0.0, 0.0, 0.0 }));
157-
try std.testing.expect(zla.vec.is_close_default(aabb.get_support(.{ 0.5774, 0.5774, -0.5774 }), .{ 0.0, 0.0, 1.0 }));
158-
try std.testing.expect(zla.vec.is_close_default(aabb.get_support(.{ 0.5774, -0.5774, 0.5774 }), .{ 0.0, 1.0, 0.0 }));
159-
try std.testing.expect(zla.vec.is_close_default(aabb.get_support(.{ 0.5774, -0.5774, -0.5774 }), .{ 0.0, 1.0, 1.0 }));
160-
try std.testing.expect(zla.vec.is_close_default(aabb.get_support(.{ -0.5774, 0.5774, 0.5774 }), .{ 1.0, 0.0, 0.0 }));
161-
try std.testing.expect(zla.vec.is_close_default(aabb.get_support(.{ -0.5774, 0.5774, -0.5774 }), .{ 1.0, 0.0, 1.0 }));
162-
try std.testing.expect(zla.vec.is_close_default(aabb.get_support(.{ -0.5774, -0.5774, 0.5774 }), .{ 1.0, 1.0, 0.0 }));
163-
try std.testing.expect(zla.vec.is_close_default(aabb.get_support(.{ -0.5774, -0.5774, -0.5774 }), .{ 1.0, 1.0, 1.0 }));
156+
try std.testing.expect(zml.vec.is_close_default(aabb.get_support(.{ 0.5774, 0.5774, 0.5774 }), .{ 0.0, 0.0, 0.0 }));
157+
try std.testing.expect(zml.vec.is_close_default(aabb.get_support(.{ 0.5774, 0.5774, -0.5774 }), .{ 0.0, 0.0, 1.0 }));
158+
try std.testing.expect(zml.vec.is_close_default(aabb.get_support(.{ 0.5774, -0.5774, 0.5774 }), .{ 0.0, 1.0, 0.0 }));
159+
try std.testing.expect(zml.vec.is_close_default(aabb.get_support(.{ 0.5774, -0.5774, -0.5774 }), .{ 0.0, 1.0, 1.0 }));
160+
try std.testing.expect(zml.vec.is_close_default(aabb.get_support(.{ -0.5774, 0.5774, 0.5774 }), .{ 1.0, 0.0, 0.0 }));
161+
try std.testing.expect(zml.vec.is_close_default(aabb.get_support(.{ -0.5774, 0.5774, -0.5774 }), .{ 1.0, 0.0, 1.0 }));
162+
try std.testing.expect(zml.vec.is_close_default(aabb.get_support(.{ -0.5774, -0.5774, 0.5774 }), .{ 1.0, 1.0, 0.0 }));
163+
try std.testing.expect(zml.vec.is_close_default(aabb.get_support(.{ -0.5774, -0.5774, -0.5774 }), .{ 1.0, 1.0, 1.0 }));
164164
}
165165

166166
//const Plane = struct {

src/geometry/buffer_view.zig

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
const std = @import("std");
2+
const meta = @import("../meta.zig");
23

34
pub fn BufferView(comptime T: type) type {
45
return struct {
5-
pub const inner_type = switch (@typeInfo(T)) {
6-
.vector => |v| [v.len]std.meta.Child(T),
7-
.array => |a| [a.len]std.meta.Child(T),
8-
else => @compileError("Expected a vector or array type, got: " ++ @typeName(T)),
9-
};
10-
pub const num_elements = switch (@typeInfo(T)) {
11-
.vector => |v| v.len,
12-
.array => |a| a.len,
13-
else => @compileError("Expected a vector or array type, got: " ++ @typeName(T)),
14-
};
6+
pub const inner_type = [meta.lengthOf(T)]meta.Child(T);
7+
pub const num_elements = meta.lengthOf(T);
158
};
169
}

src/geometry/capsule.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const geometry = @import("../geometry.zig");
2-
const zla = @import("../root.zig");
2+
const zml = @import("../root.zig");
33

44
pub fn Capsule(comptime T: type) type {
55
return struct {
@@ -15,7 +15,7 @@ pub fn Capsule(comptime T: type) type {
1515
}
1616

1717
pub fn get_cylinder_height(self: Self) T {
18-
return zla.vec.distance(self.hemisphere_centers[0], self.hemisphere_centers[1]);
18+
return zml.vec.distance(self.hemisphere_centers[0], self.hemisphere_centers[1]);
1919
}
2020

2121
pub fn get_total_height(self: Self) T {

src/geometry/contains.zig

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const std = @import("std");
2-
const zla = @import("../root.zig");
2+
const zml = @import("../root.zig");
33

44
pub fn aabb_contains_point(a: anytype, pt: @Vector(3, @TypeOf(a).child)) bool {
55
comptime {
@@ -13,21 +13,21 @@ pub fn aabb_contains_point(a: anytype, pt: @Vector(3, @TypeOf(a).child)) bool {
1313
// std.debug.assert(@TypeOf(a).primative_type == .Capsule);
1414
// }
1515
// const ab = a.hemisphere_centers[1] - a.hemisphere_centers[0];
16-
// const t = @max(@min(zla.vec.dot(pt - a.hemisphere_centers[0], ab) / zla.vec.dot(ab, ab), @as(@TypeOf(a).child, 1)), @as(@TypeOf(a).child, 0));
16+
// const t = @max(@min(zml.vec.dot(pt - a.hemisphere_centers[0], ab) / zml.vec.dot(ab, ab), @as(@TypeOf(a).child, 1)), @as(@TypeOf(a).child, 0));
1717
// const closest_point = a.hemisphere_centers[0] + ab * @as(@Vector(3, @TypeOf(a).child), t);
18-
// return zla.vec.distance_sqr(pt, closest_point) <= a.radius * a.radius;
18+
// return zml.vec.distance_sqr(pt, closest_point) <= a.radius * a.radius;
1919
//}
2020

2121
test aabb_contains_point {
22-
const aabb: zla.geom.AABB(f32) = .from_two_points(.{ -1, -1, -1 }, .{ 1, 1, 1 });
22+
const aabb: zml.geom.AABB(f32) = .from_two_points(.{ -1, -1, -1 }, .{ 1, 1, 1 });
2323
try std.testing.expect(aabb_contains_point(aabb, .{ 0, 0, 0 }));
2424
try std.testing.expect(!aabb_contains_point(aabb, .{ 2, 0, 0 }));
2525
try std.testing.expect(!aabb_contains_point(aabb, .{ 0, -2, 0 }));
2626
try std.testing.expect(!aabb_contains_point(aabb, .{ 0, 0, 2 }));
2727
}
2828

2929
//test capsule_contains_point {
30-
// const capsule: zla.geom.Capsule(f32) = .{
30+
// const capsule: zml.geom.Capsule(f32) = .{
3131
// .hemisphere_centers = .{ .{ 0, 0, -1 }, .{ 0, 0, 1 } },
3232
// .radius = 1,
3333
// };

0 commit comments

Comments
 (0)