Skip to content

Commit 37788bf

Browse files
authored
Merge branch 'hexops:main' into vulkan-clear-fix
2 parents 77cf4c3 + 2410814 commit 37788bf

5 files changed

Lines changed: 166 additions & 2 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ Zig game engine & graphics toolkit for building high-performance, truly cross-pl
1818
## Join the community
1919

2020
Join the [Mach community on Discord](https://discord.gg/XNG3NZgCqp) to discuss this project, ask questions, get help, etc.
21+
22+
**We're here to make games and have fun, so please help keep the community focused on that.** No politics/heated topics are allowed. Unfortunately, [the political landscape today makes it such that we must also state](https://emidoots.com/2025/i-want-code-not-politics): fascists can go f*k themselves, we support LGBTQ+ and minorities here - their existence is not political.
23+

build.zig.zon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
.lazy = true,
2323
},
2424
.mach_objc = .{
25-
.url = "https://pkg.machengine.org/mach-objc/bb7ced86bf768979ff22254e31ed9934d74a0226.tar.gz",
26-
.hash = "122006526df5979b71d5f0c3f849622661d05f69a4f7065537fbc04c2ae11030f1d5",
25+
.url = "https://pkg.machengine.org/mach-objc/79b6f80c32b14948554958afe72dace261b14afc.tar.gz",
26+
.hash = "12203675829014e69be2ea7c126ecf25d403009d336b7ca5f6e7c4ccede826c8e597",
2727
.lazy = true,
2828
},
2929
.xcode_frameworks = .{

src/math/mat.zig

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
const std = @import("std");
2+
13
const mach = @import("../main.zig");
24
const testing = mach.testing;
35
const math = mach.math;
46
const vec = @import("vec.zig");
7+
const quat = @import("quat.zig");
58

69
pub fn Mat2x2(
710
comptime Scalar: type,
@@ -118,6 +121,7 @@ pub fn Mat2x2(
118121

119122
pub const mul = Shared.mul;
120123
pub const mulVec = Shared.mulVec;
124+
pub const format = Shared.format;
121125
};
122126
}
123127

@@ -258,6 +262,7 @@ pub fn Mat3x3(
258262

259263
pub const mul = Shared.mul;
260264
pub const mulVec = Shared.mulVec;
265+
pub const format = Shared.format;
261266
};
262267
}
263268

@@ -435,6 +440,22 @@ pub fn Mat4x4(
435440
);
436441
}
437442

443+
//https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/jay.htm
444+
//Requires a normalized quaternion
445+
pub inline fn rotateByQuaternion(quaternion: quat.Quat(T)) Matrix {
446+
const qx = quaternion.v.x();
447+
const qy = quaternion.v.y();
448+
const qz = quaternion.v.z();
449+
const qw = quaternion.v.w();
450+
451+
return Matrix.init(
452+
&RowVec.init(1 - 2 * qy * qy - 2 * qz * qz, 2 * qx * qy - 2 * qz * qw, 2 * qx * qz + 2 * qy * qw, 0),
453+
&RowVec.init(2 * qx * qy + 2 * qz * qw, 1 - 2 * qx * qx - 2 * qz * qz, 2 * qy * qz - 2 * qx * qw, 0),
454+
&RowVec.init(2 * qx * qz - 2 * qy * qw, 2 * qy * qz + 2 * qx * qw, 1 - 2 * qx * qx - 2 * qy * qy, 0),
455+
&RowVec.init(0, 0, 0, 1),
456+
);
457+
}
458+
438459
/// Constructs a 2D projection matrix, aka. an orthographic projection matrix.
439460
///
440461
/// First, a cuboid is defined with the parameters:
@@ -485,6 +506,7 @@ pub fn Mat4x4(
485506
pub const mulVec = Shared.mulVec;
486507
pub const eql = Shared.eql;
487508
pub const eqlApprox = Shared.eqlApprox;
509+
pub const format = Shared.format;
488510
};
489511
}
490512

@@ -542,6 +564,24 @@ pub fn MatShared(comptime RowVec: type, comptime ColVec: type, comptime Matrix:
542564
}
543565
return true;
544566
}
567+
568+
/// Custom format function for all matrix types.
569+
pub inline fn format(
570+
self: Matrix,
571+
comptime fmt: []const u8,
572+
options: std.fmt.FormatOptions,
573+
writer: anytype,
574+
) @TypeOf(writer).Error!void {
575+
const rows = @TypeOf(self).rows;
576+
try writer.print("{{", .{});
577+
inline for (0..rows) |r| {
578+
try std.fmt.formatType(self.row(r), fmt, options, writer, 1);
579+
if (r < rows - 1) {
580+
try writer.print(", ", .{});
581+
}
582+
}
583+
try writer.print("}}", .{});
584+
}
545585
};
546586
}
547587

@@ -1173,3 +1213,17 @@ test "projection2D_model_to_clip_space" {
11731213
try testing.expect(math.Vec4, math.vec4(1, 0, 1, 1)).eql(mvp.mul(&math.Mat4x4.rotateY(math.degreesToRadians(90))).mulVec(&math.vec4(0, 0, 50, 1)));
11741214
try testing.expect(math.Vec4, math.vec4(0, 0, 0.5, 1)).eql(mvp.mul(&math.Mat4x4.rotateZ(math.degreesToRadians(90))).mulVec(&math.vec4(0, 0, 50, 1)));
11751215
}
1216+
1217+
test "quaternion_rotation" {
1218+
const expected = math.Mat4x4.init(
1219+
&math.vec4(0.7716905, 0.5519065, 0.3160585, 0),
1220+
&math.vec4(-0.0782971, -0.4107276, 0.9083900, 0),
1221+
&math.vec4(0.6311602, -0.7257425, -0.2737419, 0),
1222+
&math.vec4(0, 0, 0, 1),
1223+
);
1224+
1225+
const q = math.Quat.fromAxisAngle(math.vec3(0.9182788, 0.1770672, 0.3541344), 4.2384558);
1226+
const result = math.Mat4x4.rotateByQuaternion(q.normalize());
1227+
1228+
try testing.expect(bool, true).eql(expected.eqlApprox(&result, 0.0000002));
1229+
}

src/math/vec.zig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ pub fn Vec2(comptime Scalar: type) type {
6767
pub const minScalar = Shared.minScalar;
6868
pub const eqlApprox = Shared.eqlApprox;
6969
pub const eql = Shared.eql;
70+
pub const format = Shared.format;
7071
};
7172
}
7273

@@ -190,6 +191,7 @@ pub fn Vec3(comptime Scalar: type) type {
190191
pub const minScalar = Shared.minScalar;
191192
pub const eqlApprox = Shared.eqlApprox;
192193
pub const eql = Shared.eql;
194+
pub const format = Shared.format;
193195
};
194196
}
195197

@@ -269,6 +271,7 @@ pub fn Vec4(comptime Scalar: type) type {
269271
pub const minScalar = Shared.minScalar;
270272
pub const eqlApprox = Shared.eqlApprox;
271273
pub const eql = Shared.eql;
274+
pub const format = Shared.format;
272275
};
273276
}
274277

@@ -519,6 +522,16 @@ pub fn VecShared(comptime Scalar: type, comptime VecN: type) type {
519522
pub inline fn eql(a: *const VecN, b: *const VecN) bool {
520523
return a.eqlApprox(b, math.eps(Scalar));
521524
}
525+
526+
/// Custom format function for all vector types.
527+
pub inline fn format(
528+
self: VecN,
529+
comptime fmt: []const u8,
530+
options: std.fmt.FormatOptions,
531+
writer: anytype,
532+
) @TypeOf(writer).Error!void {
533+
try std.fmt.formatType(self.v, fmt, options, writer, 1);
534+
}
522535
};
523536
}
524537

src/module.zig

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,21 @@ pub fn Objects(options: ObjectsOptions, comptime T: type) type {
7070

7171
/// A bitset used to track per-field changes. Only used if options.track_fields == true.
7272
updated: ?std.bit_set.DynamicBitSetUnmanaged = if (options.track_fields) .{} else null,
73+
74+
/// Tags storage
75+
tags: std.AutoHashMapUnmanaged(TaggedObject, ?ObjectID) = .{},
7376
},
7477

7578
pub const IsMachObjects = void;
7679

7780
const Generation = u16;
7881
const Index = u32;
7982

83+
const TaggedObject = struct {
84+
object_id: ObjectID,
85+
tag_hash: u64,
86+
};
87+
8088
const PackedID = packed struct(u64) {
8189
type_id: ObjectTypeID,
8290
generation: Generation,
@@ -275,6 +283,52 @@ pub fn Objects(options: ObjectsOptions, comptime T: type) type {
275283
if (mach.is_debug) data.set(unpacked.index, undefined);
276284
}
277285

286+
// TODO(objects): evaluate whether tag operations should ever return an error
287+
288+
/// Sets a tag on an object
289+
pub fn setTag(objs: *@This(), id: ObjectID, comptime M: type, tag: ModuleTagEnum(M), value_id: ?ObjectID) !void {
290+
_ = objs.validateAndUnpack(id, "setTag");
291+
292+
// TODO: validate that value_id is an object coming from the mach.Objects(T) list indicated by the tag value in M.mach_tags.
293+
//const value_mach_objects = moduleTagValueObjects(M, tag);
294+
295+
const tagged = TaggedObject{
296+
.object_id = id,
297+
.tag_hash = std.hash.Wyhash.hash(0, @tagName(tag)),
298+
};
299+
try objs.internal.tags.put(objs.internal.allocator, tagged, value_id);
300+
}
301+
302+
/// Removes a tag on an object
303+
pub fn removeTag(objs: *@This(), id: ObjectID, comptime M: type, tag: ModuleTagEnum(M)) void {
304+
_ = objs.validateAndUnpack(id, "removeTag");
305+
const tagged = TaggedObject{
306+
.object_id = id,
307+
.tag_hash = std.hash.Wyhash.hash(0, @tagName(tag)),
308+
};
309+
_ = objs.internal.tags.remove(tagged);
310+
}
311+
312+
/// Whether an object has a tag
313+
pub fn hasTag(objs: *@This(), id: ObjectID, comptime M: type, tag: ModuleTagEnum(M)) bool {
314+
_ = objs.validateAndUnpack(id, "hasTag");
315+
const tagged = TaggedObject{
316+
.object_id = id,
317+
.tag_hash = std.hash.Wyhash.hash(0, @tagName(tag)),
318+
};
319+
return objs.internal.tags.contains(tagged);
320+
}
321+
322+
/// Get an object's tag value, or null.
323+
pub fn getTag(objs: *@This(), id: ObjectID, comptime M: type, tag: ModuleTagEnum(M)) ?mach.ObjectID {
324+
_ = objs.validateAndUnpack(id, "getTag");
325+
const tagged = TaggedObject{
326+
.object_id = id,
327+
.tag_hash = std.hash.Wyhash.hash(0, @tagName(tag)),
328+
};
329+
return objs.internal.tags.get(tagged) orelse null;
330+
}
331+
278332
pub fn slice(objs: *@This()) Slice {
279333
return Slice{
280334
.index = 0,
@@ -497,6 +551,46 @@ fn ModuleFunctionName2(comptime M: type) type {
497551
});
498552
}
499553

554+
/// Enum describing all mach_tags for a given comptime-known module.
555+
fn ModuleTagEnum(comptime M: type) type {
556+
// TODO(object): handle duplicate enum field case in mach_tags with a more clear error?
557+
// TODO(object): improve validation error messages here
558+
validate(M);
559+
if (@typeInfo(@TypeOf(M.mach_tags)) != .@"struct") {
560+
@compileError("mach: invalid module, `pub const mach_tags must be `.{ .is_monster, .{ .renderer, mach.Renderer.objects } }`, found: " ++ @typeName(@TypeOf(M.mach_tags)));
561+
}
562+
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
563+
var i: u32 = 0;
564+
inline for (@typeInfo(@TypeOf(M.mach_tags)).@"struct".fields, 0..) |field, field_index| {
565+
const f = M.mach_tags[field_index];
566+
if (@typeInfo(field.type) == .enum_literal) {
567+
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = @tagName(f), .value = i }};
568+
i += 1;
569+
} else {
570+
if (@typeInfo(field.type) != .@"struct") {
571+
@compileError("mach: invalid module, mach_tags entry is not an enum literal or struct, found: " ++ @typeName(field.type));
572+
}
573+
// TODO(objects): validate length of struct
574+
const tag = f.@"0";
575+
const M2 = f.@"1";
576+
const object_list_tag = f.@"2";
577+
_ = object_list_tag; // autofix
578+
validate(M2);
579+
// TODO: validate that M2.object_list_tag is a mach objects list
580+
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = @tagName(tag), .value = i }};
581+
i += 1;
582+
}
583+
}
584+
return @Type(.{
585+
.@"enum" = .{
586+
.tag_type = if (enum_fields.len > 0) std.math.IntFittingRange(0, enum_fields.len - 1) else u0,
587+
.fields = enum_fields,
588+
.decls = &[_]std.builtin.Type.Declaration{},
589+
.is_exhaustive = true,
590+
},
591+
});
592+
}
593+
500594
pub fn Modules(module_lists: anytype) type {
501595
inline for (moduleTuple(module_lists)) |module| {
502596
validate(module);

0 commit comments

Comments
 (0)