diff --git a/src/main.zig b/src/main.zig index 3d424b4..0918966 100644 --- a/src/main.zig +++ b/src/main.zig @@ -11,7 +11,7 @@ pub fn Quantity(T: type, d: Dimensions, s: Scales) type { value: T, const Self = @This(); - pub const Vec3: type = QuantityVec3(Self); + pub const Vec3: type = QuantityVecX(3, Self); pub const ValueType: type = T; pub const dims: Dimensions = d; @@ -113,8 +113,12 @@ pub fn Quantity(T: type, d: Dimensions, s: Scales) type { return .{ .value = @intFromFloat(@round(val_f * ratio)) }; } } + pub fn vecX(self: Self, comptime len: usize) QuantityVecX(len, Self) { + return QuantityVecX(len, Self).initDefault(self.value); + } + pub fn vec3(self: Self) Vec3 { - return .{ .x = self.value, .y = self.value, .z = self.value }; + return Vec3.initDefault(self.value); } pub fn format( @@ -145,15 +149,13 @@ pub fn Quantity(T: type, d: Dimensions, s: Scales) type { }; } -pub fn QuantityVec3(Q: type) type { +pub fn QuantityVecX(comptime len: usize, comptime Q: type) type { const T = Q.ValueType; const d: Dimensions = Q.dims; const s: Scales = Q.scales; return struct { - x: T, - y: T, - z: T, + data: [len]T, const Self = @This(); pub const QuantityType = Q; @@ -161,116 +163,115 @@ pub fn QuantityVec3(Q: type) type { pub const dims: Dimensions = d; pub const scales = s; - pub const zero = Self{ .x = 0, .y = 0, .z = 0 }; - pub const one = Self{ .x = 1, .y = 1, .z = 1 }; + pub const zero = initDefault(0); + pub const one = initDefault(1); pub fn initDefault(v: T) Self { - return .{ .x = v, .y = v, .z = v }; + var data: [len]T = undefined; + for (&data) |*item| item.* = v; + return .{ .data = data }; } - pub fn add(self: Self, rhs: anytype) QuantityVec3(Quantity(T, d, s.min(@TypeOf(rhs).scales))) { + pub fn add(self: Self, rhs: anytype) QuantityVecX(len, Quantity(T, d, s.min(@TypeOf(rhs).scales))) { const Tr = @TypeOf(rhs); - // We leverage the logic in the scalar Quantity.add - const qx = (Q{ .value = self.x }).add(Tr.QuantityType{ .value = rhs.x }); - const qy = (Q{ .value = self.y }).add(Tr.QuantityType{ .value = rhs.y }); - const qz = (Q{ .value = self.z }).add(Tr.QuantityType{ .value = rhs.z }); - - return .{ - .x = qx.value, - .y = qy.value, - .z = qz.value, - }; + var res: QuantityVecX(len, Quantity(T, d, s.min(Tr.scales))) = undefined; + for (self.data, 0..) |v, i| { + const q = (Q{ .value = v }).add(Tr.QuantityType{ .value = rhs.data[i] }); + res.data[i] = q.value; + } + return res; } - pub fn sub(self: Self, rhs: anytype) QuantityVec3(Quantity(T, d, s.min(@TypeOf(rhs).scales))) { + pub fn sub(self: Self, rhs: anytype) QuantityVecX(len, Quantity(T, d, s.min(@TypeOf(rhs).scales))) { const Tr = @TypeOf(rhs); - const qx = (Q{ .value = self.x }).sub(Tr.QuantityType{ .value = rhs.x }); - const qy = (Q{ .value = self.y }).sub(Tr.QuantityType{ .value = rhs.y }); - const qz = (Q{ .value = self.z }).sub(Tr.QuantityType{ .value = rhs.z }); - - return .{ - .x = qx.value, - .y = qy.value, - .z = qz.value, - }; + var res: QuantityVecX(len, Quantity(T, d, s.min(Tr.scales))) = undefined; + for (self.data, 0..) |v, i| { + const q = (Q{ .value = v }).sub(Tr.QuantityType{ .value = rhs.data[i] }); + res.data[i] = q.value; + } + return res; } pub fn divBy( self: Self, rhs: anytype, - ) QuantityVec3(Quantity(T, d.sub(@TypeOf(rhs).dims), s.min(@TypeOf(rhs).scales))) { + ) QuantityVecX(len, Quantity(T, d.sub(@TypeOf(rhs).dims), s.min(@TypeOf(rhs).scales))) { const Tr = @TypeOf(rhs); - return .{ - .x = (Q{ .value = self.x }).divBy(Tr.QuantityType{ .value = rhs.x }).value, - .y = (Q{ .value = self.y }).divBy(Tr.QuantityType{ .value = rhs.y }).value, - .z = (Q{ .value = self.z }).divBy(Tr.QuantityType{ .value = rhs.z }).value, - }; + var res: QuantityVecX(len, Quantity(T, d.sub(Tr.dims), s.min(Tr.scales))) = undefined; + for (self.data, 0..) |v, i| { + const q = (Q{ .value = v }).divBy(Tr.QuantityType{ .value = rhs.data[i] }); + res.data[i] = q.value; + } + return res; } pub fn mulBy( self: Self, rhs: anytype, - ) QuantityVec3(Quantity(T, d.sub(@TypeOf(rhs).dims), s.min(@TypeOf(rhs).scales))) { + ) QuantityVecX(len, Quantity(T, d.add(@TypeOf(rhs).dims), s.min(@TypeOf(rhs).scales))) { const Tr = @TypeOf(rhs); - return .{ - .x = (Q{ .value = self.x }).mulBy(Tr.QuantityType{ .value = rhs.x }).value, - .y = (Q{ .value = self.y }).mulBy(Tr.QuantityType{ .value = rhs.y }).value, - .z = (Q{ .value = self.z }).mulBy(Tr.QuantityType{ .value = rhs.z }).value, - }; + var res: QuantityVecX(len, Quantity(T, d.add(Tr.dims), s.min(Tr.scales))) = undefined; + for (self.data, 0..) |v, i| { + const q = (Q{ .value = v }).mulBy(Tr.QuantityType{ .value = rhs.data[i] }); + res.data[i] = q.value; + } + return res; } pub fn divByScalar( self: Self, scalar: anytype, - ) QuantityVec3(Quantity(T, d.sub(@TypeOf(scalar).dims), s.min(@TypeOf(scalar).scales))) { - const q_x = Q{ .value = self.x }; - const q_y = Q{ .value = self.y }; - const q_z = Q{ .value = self.z }; - - return .{ - .x = q_x.divBy(scalar).value, - .y = q_y.divBy(scalar).value, - .z = q_z.divBy(scalar).value, - }; + ) QuantityVecX(len, Quantity(T, d.sub(@TypeOf(scalar).dims), s.min(@TypeOf(scalar).scales))) { + var res: QuantityVecX(len, Quantity(T, d.sub(@TypeOf(scalar).dims), s.min(@TypeOf(scalar).scales))) = undefined; + for (self.data, 0..) |v, i| { + const q = Q{ .value = v }; + res.data[i] = q.divBy(scalar).value; + } + return res; } pub fn mulByScalar( self: Self, scalar: anytype, - ) QuantityVec3(Quantity(T, d.add(@TypeOf(scalar).dims), s.min(@TypeOf(scalar).scales))) { - const q_x = Q{ .value = self.x }; - const q_y = Q{ .value = self.y }; - const q_z = Q{ .value = self.z }; - - return .{ - .x = q_x.mulBy(scalar).value, - .y = q_y.mulBy(scalar).value, - .z = q_z.mulBy(scalar).value, - }; + ) QuantityVecX(len, Quantity(T, d.add(@TypeOf(scalar).dims), s.min(@TypeOf(scalar).scales))) { + var res: QuantityVecX(len, Quantity(T, d.add(@TypeOf(scalar).dims), s.min(@TypeOf(scalar).scales))) = undefined; + for (self.data, 0..) |v, i| { + const q = Q{ .value = v }; + res.data[i] = q.mulBy(scalar).value; + } + return res; } pub fn negate(self: Self) Self { - return .{ .x = -self.x, .y = -self.y, .z = -self.z }; + var res: Self = undefined; + for (self.data, 0..) |v, i| { + res.data[i] = -v; + } + return res; } pub fn scale(self: Self, rhs: T) Self { - return .{ - .x = (Q{ .value = self.x }).scale(rhs).value, - .y = (Q{ .value = self.y }).scale(rhs).value, - .z = (Q{ .value = self.z }).scale(rhs).value, - }; + var res: Self = undefined; + for (self.data, 0..) |v, i| { + res.data[i] = (Q{ .value = v }).scale(rhs).value; + } + return res; } - pub fn to(self: Self, comptime DestQ: type) QuantityVec3(DestQ) { - return .{ - .x = (Q{ .value = self.x }).to(DestQ).value, - .y = (Q{ .value = self.y }).to(DestQ).value, - .z = (Q{ .value = self.z }).to(DestQ).value, - }; + pub fn to(self: Self, comptime DestQ: type) QuantityVecX(len, DestQ) { + var res: QuantityVecX(len, DestQ) = undefined; + for (self.data, 0..) |v, i| { + res.data[i] = (Q{ .value = v }).to(DestQ).value; + } + return res; } pub fn lengthSqr(self: Self) T { - return self.x * self.x + self.y * self.y + self.z * self.z; + var sum: T = 0; + for (self.data) |v| { + sum += v * v; + } + return sum; } pub fn length(self: Self) T { @@ -289,7 +290,12 @@ pub fn QuantityVec3(Q: type) type { } pub fn format(self: Self, writer: *std.Io.Writer) !void { - try writer.print("({d:.2}, {d:.2}, {d:.2})", .{ self.x, self.y, self.z }); + try writer.writeAll("("); + for (self.data, 0..) |v, i| { + if (i > 0) try writer.writeAll(", "); + try writer.print("{d:.2}", .{v}); + } + try writer.writeAll(")"); var iter = std.EnumSet(Dimension).initFull().iterator(); var first = true; while (iter.next()) |bu| { @@ -542,7 +548,7 @@ test "Format Quantity" { std.debug.print("Momentum: {f}\n", .{momentum}); } -test "Format Vector3" { +test "Format VectorX" { const MeterPerSecondSq = Quantity( f32, Dimensions.init(.{ .L = 1, .T = -2 }), @@ -555,123 +561,127 @@ test "Format Vector3" { ); const accel = MeterPerSecondSq.Vec3.initDefault(9.81); - const momentum = KgMeterPerSecond.Vec3{ .x = 43, .y = 0, .z = 11 }; + const momentum = KgMeterPerSecond.Vec3{ .data = .{ 43, 0, 11 } }; std.debug.print("Acceleration: {f}\n", .{accel}); std.debug.print("Momentum: {f}\n", .{momentum}); } -test "Vec3 Init and Basic Arithmetic" { +test "VecX Init and Basic Arithmetic" { const Meter = Quantity(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); const Vec3M = Meter.Vec3; // Test zero, one, initDefault const v_zero = Vec3M.zero; - try std.testing.expectEqual(0, v_zero.x); + try std.testing.expectEqual(0, v_zero.data[0]); + try std.testing.expectEqual(0, v_zero.data[1]); + try std.testing.expectEqual(0, v_zero.data[2]); const v_one = Vec3M.one; - try std.testing.expectEqual(1, v_one.x); + try std.testing.expectEqual(1, v_one.data[0]); + try std.testing.expectEqual(1, v_one.data[1]); + try std.testing.expectEqual(1, v_one.data[2]); const v_def = Vec3M.initDefault(5); - try std.testing.expectEqual(5, v_def.x); - try std.testing.expectEqual(5, v_def.y); - try std.testing.expectEqual(5, v_def.z); + try std.testing.expectEqual(5, v_def.data[0]); + try std.testing.expectEqual(5, v_def.data[1]); + try std.testing.expectEqual(5, v_def.data[2]); // Test add and sub - const v1 = Vec3M{ .x = 10, .y = 20, .z = 30 }; - const v2 = Vec3M{ .x = 2, .y = 4, .z = 6 }; + const v1 = Vec3M{ .data = .{ 10, 20, 30 } }; + const v2 = Vec3M{ .data = .{ 2, 4, 6 } }; const added = v1.add(v2); - try std.testing.expectEqual(12, added.x); - try std.testing.expectEqual(24, added.y); - try std.testing.expectEqual(36, added.z); + try std.testing.expectEqual(12, added.data[0]); + try std.testing.expectEqual(24, added.data[1]); + try std.testing.expectEqual(36, added.data[2]); const subbed = v1.sub(v2); - try std.testing.expectEqual(8, subbed.x); - try std.testing.expectEqual(16, subbed.y); - try std.testing.expectEqual(24, subbed.z); + try std.testing.expectEqual(8, subbed.data[0]); + try std.testing.expectEqual(16, subbed.data[1]); + try std.testing.expectEqual(24, subbed.data[2]); // Test negate const neg = v1.negate(); - try std.testing.expectEqual(-10, neg.x); - try std.testing.expectEqual(-20, neg.y); - try std.testing.expectEqual(-30, neg.z); + try std.testing.expectEqual(-10, neg.data[0]); + try std.testing.expectEqual(-20, neg.data[1]); + try std.testing.expectEqual(-30, neg.data[2]); } -test "Vec3 Kinematics (Scalar Mul/Div)" { +test "VecX Kinematics (Scalar Mul/Div)" { const Meter = Quantity(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); const Second = Quantity(i32, Dimensions.init(.{ .T = 1 }), Scales.init(.{})); const Vec3M = Meter.Vec3; - const pos = Vec3M{ .x = 100, .y = 200, .z = 300 }; + const pos = Vec3M{ .data = .{ 100, 200, 300 } }; const time = Second{ .value = 10 }; // Vector divided by scalar Quantity (Velocity = Position / Time) const vel = pos.divByScalar(time); - try std.testing.expectEqual(10, vel.x); - try std.testing.expectEqual(20, vel.y); - try std.testing.expectEqual(30, vel.z); + try std.testing.expectEqual(10, vel.data[0]); + try std.testing.expectEqual(20, vel.data[1]); + try std.testing.expectEqual(30, vel.data[2]); try std.testing.expectEqual(1, @TypeOf(vel).dims.get(.L)); try std.testing.expectEqual(-1, @TypeOf(vel).dims.get(.T)); // Vector multiplied by scalar Quantity (Position = Velocity * Time) const new_pos = vel.mulByScalar(time); - try std.testing.expectEqual(100, new_pos.x); - try std.testing.expectEqual(200, new_pos.y); - try std.testing.expectEqual(300, new_pos.z); + try std.testing.expectEqual(100, new_pos.data[0]); + try std.testing.expectEqual(200, new_pos.data[1]); + try std.testing.expectEqual(300, new_pos.data[2]); try std.testing.expectEqual(1, @TypeOf(new_pos).dims.get(.L)); try std.testing.expectEqual(0, @TypeOf(new_pos).dims.get(.T)); } -test "Vec3 Element-wise Math and Scaling" { +test "VecX Element-wise Math and Scaling" { const Meter = Quantity(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); const Vec3M = Meter.Vec3; - const v1 = Vec3M{ .x = 10, .y = 20, .z = 30 }; - const v2 = Vec3M{ .x = 2, .y = 5, .z = 10 }; + const v1 = Vec3M{ .data = .{ 10, 20, 30 } }; + const v2 = Vec3M{ .data = .{ 2, 5, 10 } }; // Element-wise division const div = v1.divBy(v2); - try std.testing.expectEqual(5, div.x); - try std.testing.expectEqual(4, div.y); - try std.testing.expectEqual(3, div.z); + try std.testing.expectEqual(5, div.data[0]); + try std.testing.expectEqual(4, div.data[1]); + try std.testing.expectEqual(3, div.data[2]); try std.testing.expectEqual(0, @TypeOf(div).dims.get(.L)); // M / M = Dimensionless // Scale by primitive const scaled = v1.scale(2); - try std.testing.expectEqual(20, scaled.x); - try std.testing.expectEqual(40, scaled.y); - try std.testing.expectEqual(60, scaled.z); + try std.testing.expectEqual(20, scaled.data[0]); + try std.testing.expectEqual(40, scaled.data[1]); + try std.testing.expectEqual(60, scaled.data[2]); } -test "Vec3 Conversions" { +test "VecX Conversions" { const KiloMeter = Quantity(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{ .L = .k })); const Meter = Quantity(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); - const v_km = KiloMeter.Vec3{ .x = 1, .y = 2, .z = 3 }; + const v_km = KiloMeter.Vec3{ .data = .{ 1, 2, 3 } }; const v_m = v_km.to(Meter); - try std.testing.expectEqual(1000, v_m.x); - try std.testing.expectEqual(2000, v_m.y); - try std.testing.expectEqual(3000, v_m.z); + try std.testing.expectEqual(1000, v_m.data[0]); + try std.testing.expectEqual(2000, v_m.data[1]); + try std.testing.expectEqual(3000, v_m.data[2]); // Type checking the result try std.testing.expectEqual(1, @TypeOf(v_m).dims.get(.L)); try std.testing.expectEqual(UnitScale.none, @TypeOf(v_m).scales.get(.L)); } -test "Vec3 Length" { +test "VecX Length" { const MeterInt = Quantity(i32, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); const MeterFloat = Quantity(f32, Dimensions.init(.{ .L = 1 }), Scales.init(.{})); // Integer length (using your custom isqrt) // 3-4-5 triangle on XY plane - const v_int = MeterInt.Vec3{ .x = 3, .y = 4, .z = 0 }; + const v_int = MeterInt.Vec3{ .data = .{ 3, 4, 0 } }; try std.testing.expectEqual(25, v_int.lengthSqr()); try std.testing.expectEqual(5, v_int.length()); // Float length - const v_float = MeterFloat.Vec3{ .x = 3.0, .y = 4.0, .z = 0.0 }; + const v_float = MeterFloat.Vec3{ .data = .{ 3.0, 4.0, 0.0 } }; try std.testing.expectApproxEqAbs(@as(f32, 25.0), v_float.lengthSqr(), 1e-4); try std.testing.expectApproxEqAbs(@as(f32, 5.0), v_float.length(), 1e-4); }