From 71015874a927e0b09c0b14323028747607d4595d Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Fri, 19 Dec 2025 01:36:50 +0100 Subject: [PATCH] Trying to improve collisions and failing --- src/Game.zig | 14 +- src/Player.zig | 518 ++++++++++++++++++++++------------------ src/const.zig | 27 ++- src/math.zig | 38 +++ src/math/Vector2.zig | 26 +- src/math/Vector2Int.zig | 191 +++++++++++++++ src/math/Vector2x8.zig | 2 + src/math/Vector3.zig | 27 ++- src/math/Vector3Int.zig | 199 +++++++++++++++ src/math/Vector4.zig | 17 +- 10 files changed, 805 insertions(+), 254 deletions(-) create mode 100644 src/math/Vector2Int.zig create mode 100644 src/math/Vector3Int.zig diff --git a/src/Game.zig b/src/Game.zig index 05ad90a..db89684 100644 --- a/src/Game.zig +++ b/src/Game.zig @@ -1,6 +1,7 @@ const Game = @This(); const std = @import("std"); +const c = @import("const.zig"); const glfw = @import("zglfw"); const math = @import("math.zig"); const shaders = @import("shaders.zig"); @@ -23,6 +24,7 @@ const Textures = @import("assets/Textures.zig"); const Vector2 = math.Vector2; const Vector2x8 = math.Vector2x8; const Vector3 = math.Vector3; +const Vector3Int = math.Vector3Int; allocator: std.mem.Allocator, engine: *Engine, @@ -591,7 +593,10 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain } } - const player_position = Vector3.init(0, 0, worldgen.heightF(world_seed, .zero) + 0.5); + const player_position_vx = Vector3Int.init(0, 0, worldgen.heightI(world_seed, .zero) + 1); + const player_position_sv = player_position_vx + .mulScalar(c.sv_per_vx) + .add(.init(c.sv_per_vx / 2, c.sv_per_vx / 2, 0)); var chunk_it = chunks.iterator(); while (chunk_it.next()) |entry| { @@ -655,7 +660,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain .chunks = chunks, .skybox = skybox, - .player = .init(player_position, 0, 0), + .player = .init(player_position_sv, 0, 0), }; } @@ -757,7 +762,10 @@ fn render(self: *Game) !void { @floatFromInt(extent.height), ); - const camera_position = self.player.position.add(.init(0, 0, Player.camera_height)); + const camera_position = self.player.position_sv + .asVector3() + .divScalar(c.sv_per_vx) + .add(.init(0, 0, Player.camera_height_vx)); const camera_rotation = Quaternion.mulQuaternion( .initRotationXY(self.player.yaw_rad), .initRotationYZ(self.player.pitch_rad), diff --git a/src/Player.zig b/src/Player.zig index 441a9cf..5496fa0 100644 --- a/src/Player.zig +++ b/src/Player.zig @@ -1,13 +1,17 @@ const Player = @This(); const std = @import("std"); +const c = @import("const.zig"); const glfw = @import("zglfw"); const math = @import("math.zig"); +const Blocks = @import("assets/Blocks.zig"); const Chunk = @import("assets/Chunk.zig"); const Iterator2 = math.Iterator2; const Vector2 = math.Vector2; const Vector3 = math.Vector3; +const Vector2Int = math.Vector2Int; +const Vector3Int = math.Vector3Int; const AxisState = enum { none, @@ -16,13 +20,29 @@ const AxisState = enum { both_positive, both_negative, - pub fn getComponent(self: AxisState) f32 { + pub fn getComponentFloat(self: AxisState) f32 { return switch (self) { .none => 0, .positive, .both_positive => 1, .negative, .both_negative => -1, }; } + + pub fn getComponentInt(self: AxisState) i32 { + return switch (self) { + .none => 0, + .positive, .both_positive => 1, + .negative, .both_negative => -1, + }; + } + + pub fn getComponentIntFrac(self: AxisState) i32 { + return switch (self) { + .none => 0, + .positive, .both_positive => std.math.maxInt(i32), + .negative, .both_negative => -std.math.maxInt(i32), + }; + } }; const ButtonState = struct { @@ -80,8 +100,16 @@ const Axis = struct { } } - pub fn getComponent(self: Axis) f32 { - return self.state.getComponent(); + pub fn getComponentFloat(self: Axis) f32 { + return self.state.getComponentFloat(); + } + + pub fn getComponentInt(self: Axis) i32 { + return self.state.getComponentInt(); + } + + pub fn getComponentIntFrac(self: Axis) i32 { + return self.state.getComponentIntFrac(); } }; @@ -128,16 +156,16 @@ const Button = struct { const MovementState = union(enum) { ground: struct { - horizontal_velocity: Vector2, + horizontal_velocity_sv: Vector2Int, }, air: struct { - horizontal_velocity: Vector2, - vertical_velocity: f32, + horizontal_velocity_sv: Vector2Int, + vertical_velocity_sv: i32, }, flight: void, }; -position: Vector3, +position_sv: Vector3Int, pitch_rad: f32, yaw_rad: f32, @@ -151,31 +179,30 @@ vertical_fov_deg: f32 = 80, movement_state: MovementState, -pub const camera_height = 1.62; +pub const camera_height_vx = 1.62; -pub const horizontal_speed = 11.0; -pub const vertical_speed = 7.49; +pub const horizontal_speed_sv = c.sv(11.0); +pub const vertical_speed_sv = c.sv(7.49); pub const min_pitch_rad = -0.5 * std.math.pi; pub const max_pitch_rad = 0.5 * std.math.pi; -pub const half_width = 0.4; -pub const height = 1.8; -pub const speed = 5.612; -pub const step_up = 0.53125; -pub const step_down = 0.53125; -pub const jump_velocity = 6.1237245; -pub const gravity = 15; -pub const vertical_velocity_cap = 30; -pub const ground_acceleration = 56.12; -pub const air_acceleration = 56.12; -pub const air_speed_limit = 0.5612; +pub const collision_half_width_sv = c.sv(0.4); +pub const collision_height_sv = c.sv(1.8); -const distance_from_surface = 0.0009765625; +pub const speed_sv = c.sv(5.612); +pub const step_up_sv = c.sv(0.53125); +pub const step_down_sv = c.sv(0.53125); +pub const jump_velocity_sv = c.sv(6.1237245); +pub const gravity_sv = c.sv(15); +pub const vertical_velocity_cap_sv = c.sv(30); +pub const ground_acceleration_sv = c.sv(56.12); +pub const air_acceleration_sv = c.sv(56.12); +pub const air_speed_limit_sv = c.sv(0.5612); -pub fn init(position: Vector3, pitch_rad: f32, yaw_rad: f32) Player { +pub fn init(position_sv: Vector3Int, pitch_rad: f32, yaw_rad: f32) Player { return .{ - .position = position, + .position_sv = position_sv, .pitch_rad = pitch_rad, .yaw_rad = yaw_rad, .movement_state = .flight, @@ -204,17 +231,17 @@ pub fn update(self: *Player, dt: f32, chunks: *const std.AutoHashMapUnmanaged([3 // --- GATHER INPUTS ------------------------------------------------------- - const horizontal_input_vector = blk: { + const horizontal_input_vector_frac = blk: { var ret = Vector2 - .init(self.x_input.getComponent(), self.y_input.getComponent()) + .init(self.x_input.getComponentFloat(), self.y_input.getComponentFloat()) .rotate(self.yaw_rad); const len_squared = ret.lenSquared(); if (len_squared > 1) { ret = ret.divScalar(@sqrt(len_squared)); } - break :blk ret; + break :blk ret.asVector2IntFrac(); }; - const vertical_input_vector = self.z_input.getComponent(); + const vertical_input_vector_frac = self.z_input.getComponentIntFrac(); // --- STATE TRANSITIONS --------------------------------------------------- @@ -223,8 +250,8 @@ pub fn update(self: *Player, dt: f32, chunks: *const std.AutoHashMapUnmanaged([3 if (self.jump_input.isPressed()) { self.movement_state = .{ .air = .{ - .horizontal_velocity = ground.horizontal_velocity, - .vertical_velocity = jump_velocity, + .horizontal_velocity_sv = ground.horizontal_velocity_sv, + .vertical_velocity_sv = jump_velocity_sv, }, }; } @@ -238,73 +265,88 @@ pub fn update(self: *Player, dt: f32, chunks: *const std.AutoHashMapUnmanaged([3 .ground => {}, .air => {}, .flight => { - const horizontal_velocity = horizontal_input_vector.mulScalar(horizontal_speed); - const vertical_velocity = vertical_input_vector * vertical_speed; + const horizontal_velocity_sv = horizontal_input_vector_frac.mulScalarFrac(horizontal_speed_sv); + const vertical_velocity_sv = math.mulFrac(vertical_input_vector_frac, vertical_speed_sv); - var horizontal_displacement = horizontal_velocity.mulScalar(dt); - var vertical_displacement = vertical_velocity * dt; + var horizontal_displacement_sv = horizontal_velocity_sv.mulScalarFloat(dt); + var vertical_displacement_sv = math.mulIntFloat(vertical_velocity_sv, dt); - if (vertical_displacement > 0.0) { - if (sweepCastUp(self.position, vertical_displacement, chunks)) |hit| { - vertical_displacement = hit.projected_distance - distance_from_surface; + var position_sv = self.position_sv; + + if (vertical_displacement_sv > 0) { + if (sweepCastUp(position_sv, vertical_displacement_sv, chunks)) |hit| { + vertical_displacement_sv -= hit.projected_distance_sv; } + position_sv = .add(position_sv, .init(0, 0, vertical_displacement_sv)); } var i: usize = 0; while (i < 2) : (i += 1) { - if (sweepCastHorizontal(self.position, horizontal_displacement, chunks)) |hit| { - horizontal_displacement = .add( - horizontal_displacement, - hit.normal - .asVector2() - .mulScalar(hit.projected_distance + distance_from_surface), + if (sweepCastHorizontal(position_sv, horizontal_displacement_sv, chunks)) |hit| { + const adjustment = hit.normal_frac + .asVector2Int() + .mulScalarFrac(hit.projected_distance_sv); + std.debug.print("i={} | n={} | d={} | adj={} | disp={}->", .{ + i, + hit.normal_frac.vector, + hit.projected_distance_sv, + adjustment.vector, + horizontal_displacement_sv.vector, + }); + horizontal_displacement_sv = .add( + horizontal_displacement_sv, + adjustment, ); + std.debug.print("{}\n", .{horizontal_displacement_sv.vector}); + } else { + break; } } + position_sv = .add(position_sv, horizontal_displacement_sv.asVector3Int(0)); - if (vertical_displacement < 0.0) { - if (sweepCastDown(self.position, -vertical_displacement, chunks)) |hit| { - vertical_displacement = -(hit.projected_distance - distance_from_surface); + if (vertical_displacement_sv < 0.0) { + if (sweepCastDown(position_sv, -vertical_displacement_sv, chunks)) |hit| { + vertical_displacement_sv += hit.projected_distance_sv; } + position_sv = .add(position_sv, .init(0, 0, vertical_displacement_sv)); } - const displacement = horizontal_displacement.asVector3(vertical_displacement); - self.position = .add(self.position, displacement); + self.position_sv = position_sv; }, } } const SweepHit = struct { - normal: Vector3, - projected_distance: f32, + normal_frac: Vector3Int, + projected_distance_sv: i32, }; -fn sweepCastDown(origin: Vector3, distance: f32, chunks: *const std.AutoHashMapUnmanaged([3]i16, Chunk)) ?SweepHit { - const min_origin = origin.add(.init(-half_width, -half_width, 0.0)); - const max_origin = origin.add(.init(half_width, half_width, height)); +fn sweepCastDown(origin_sv: Vector3Int, distance_sv: i32, chunks: *const std.AutoHashMapUnmanaged([3]i16, Chunk)) ?SweepHit { + const min_origin_sv = origin_sv.add(.init(-collision_half_width_sv, -collision_half_width_sv, 0)); + const max_origin_sv = origin_sv.add(.init(collision_half_width_sv, collision_half_width_sv, collision_height_sv)); - const min_x = blockCoord(.border_up, min_origin.getX()); - const min_y = blockCoord(.border_up, min_origin.getY()); - const max_x = blockCoord(.border_down, max_origin.getX()); - const max_y = blockCoord(.border_down, max_origin.getY()); + const min_x_vx = voxelFromSubvoxel(.border_up, min_origin_sv.getX()); + const min_y_vx = voxelFromSubvoxel(.border_up, min_origin_sv.getY()); + const max_x_vx = voxelFromSubvoxel(.border_down, max_origin_sv.getX()); + const max_y_vx = voxelFromSubvoxel(.border_down, max_origin_sv.getY()); - const start_z = blockCoord(.next_down_with_border, min_origin.getZ()); - const end_z = blockCoord(.border_down, min_origin.getZ() - distance); + const start_z_vx = voxelFromSubvoxel(.next_down, min_origin_sv.getZ()); + const end_z_vx = voxelFromSubvoxel(.border_up, min_origin_sv.getZ() - distance_sv); - var z: i32 = start_z; - while (z >= end_z) : (z -= 1) { - const fz = @as(f32, @floatFromInt(z)) + 1.0; + var z_vx: i32 = start_z_vx; + while (z_vx >= end_z_vx) : (z_vx -= 1) { + const z_sv = (z_vx + 1) * c.sv_per_vx; var it = Iterator2(i32).init(.{ - .min = .{ min_x, min_y }, - .max = .{ max_x, max_y }, + .min = .{ min_x_vx, min_y_vx }, + .max = .{ max_x_vx, max_y_vx }, }); - while (it.next()) |xy| { - const x, const y = xy; - if (!isSolid(chunks, x, y, z)) continue; + while (it.next()) |xy_vx| { + const x_vx, const y_vx = xy_vx; + if (!isSolid(chunks, .init(x_vx, y_vx, z_vx))) continue; return .{ - .projected_distance = distance - (min_origin.getZ() - fz), - .normal = .unit_z, + .projected_distance_sv = distance_sv - (min_origin_sv.getZ() - z_sv), + .normal_frac = .unit_z_frac, }; } } @@ -312,32 +354,32 @@ fn sweepCastDown(origin: Vector3, distance: f32, chunks: *const std.AutoHashMapU return null; } -fn sweepCastUp(origin: Vector3, distance: f32, chunks: *const std.AutoHashMapUnmanaged([3]i16, Chunk)) ?SweepHit { - const min_origin = origin.add(.init(-half_width, -half_width, 0.0)); - const max_origin = origin.add(.init(half_width, half_width, height)); +fn sweepCastUp(origin_sv: Vector3Int, distance_sv: i32, chunks: *const std.AutoHashMapUnmanaged([3]i16, Chunk)) ?SweepHit { + const min_origin_sv = origin_sv.add(.init(-collision_half_width_sv, -collision_half_width_sv, 0)); + const max_origin_sv = origin_sv.add(.init(collision_half_width_sv, collision_half_width_sv, collision_height_sv)); - const min_x = blockCoord(.border_up, min_origin.getX()); - const min_y = blockCoord(.border_up, min_origin.getY()); - const max_x = blockCoord(.border_down, max_origin.getX()); - const max_y = blockCoord(.border_down, max_origin.getY()); + const min_x_vx = voxelFromSubvoxel(.border_up, min_origin_sv.getX()); + const min_y_vx = voxelFromSubvoxel(.border_up, min_origin_sv.getY()); + const max_x_vx = voxelFromSubvoxel(.border_down, max_origin_sv.getX()); + const max_y_vx = voxelFromSubvoxel(.border_down, max_origin_sv.getY()); - const start_z = blockCoord(.next_up_with_border, max_origin.getZ()); - const end_z = blockCoord(.border_up, max_origin.getZ() + distance); + const start_z_vx = voxelFromSubvoxel(.next_up, max_origin_sv.getZ()); + const end_z_vx = voxelFromSubvoxel(.border_up, max_origin_sv.getZ() + distance_sv); - var z: i32 = start_z; - while (z <= end_z) : (z += 1) { - const fz = @as(f32, @floatFromInt(z)); + var z_vx: i32 = start_z_vx; + while (z_vx <= end_z_vx) : (z_vx += 1) { + const z_sv = z_vx * c.sv_per_vx; var it = Iterator2(i32).init(.{ - .min = .{ min_x, min_y }, - .max = .{ max_x, max_y }, + .min = .{ min_x_vx, min_y_vx }, + .max = .{ max_x_vx, max_y_vx }, }); - while (it.next()) |xy| { - const x, const y = xy; - if (!isSolid(chunks, x, y, z)) continue; + while (it.next()) |xy_vx| { + const x_vx, const y_vx = xy_vx; + if (!isSolid(chunks, .init(x_vx, y_vx, z_vx))) continue; return .{ - .projected_distance = distance - (fz - max_origin.getZ()), - .normal = .negate(.unit_z), + .projected_distance_sv = distance_sv - (z_sv - max_origin_sv.getZ()), + .normal_frac = .unit_nz_frac, }; } } @@ -345,176 +387,178 @@ fn sweepCastUp(origin: Vector3, distance: f32, chunks: *const std.AutoHashMapUnm return null; } -fn sweepCastHorizontal(origin: Vector3, ray: Vector2, chunks: *const std.AutoHashMapUnmanaged([3]i16, Chunk)) ?SweepHit { - const min_origin = origin.add(.init(-half_width, -half_width, 0)); - const max_origin = origin.add(.init(half_width, half_width, height)); +fn sweepCastHorizontal(origin_sv: Vector3Int, ray_sv: Vector2Int, chunks: *const std.AutoHashMapUnmanaged([3]i16, Chunk)) ?SweepHit { + const min_origin_sv = origin_sv.add(.init(-collision_half_width_sv, -collision_half_width_sv, 0)); + const max_origin_sv = origin_sv.add(.init(collision_half_width_sv, collision_half_width_sv, collision_height_sv)); - const min_z = blockCoord(.border_up, min_origin.getZ()); - const max_z = blockCoord(.border_down, max_origin.getZ()); + const min_z_vx = voxelFromSubvoxel(.border_up, min_origin_sv.getZ()); + const max_z_vx = voxelFromSubvoxel(.border_down, max_origin_sv.getZ()); - var closest_hit: ?SweepHit = null; - var closest_hit_distance = std.math.inf(f32); + var hit: ?SweepHit = null; + var hit_distance_squared = std.math.inf(f32); - const dydx = ray.getY() / ray.getX(); - const dxdy = ray.getX() / ray.getY(); + const fdydx: f32 = @as(f32, @floatFromInt(ray_sv.getY())) / @as(f32, @floatFromInt(ray_sv.getX())); + const fdxdy: f32 = @as(f32, @floatFromInt(ray_sv.getX())) / @as(f32, @floatFromInt(ray_sv.getY())); // Positive X - if (ray.getX() > 0.0) { - const x0 = max_origin.getX(); - const y0 = min_origin.getY(); - const y1 = max_origin.getY(); + if (ray_sv.getX() > 0) { + const x0_sv = max_origin_sv.getX(); + const y0_sv = min_origin_sv.getY(); + const y1_sv = max_origin_sv.getY(); - const start_x = blockCoord(.next_up_with_border, x0); - const end_x = blockCoord(.border_up, x0 + ray.getX()); + const start_x_vx = voxelFromSubvoxel(.next_up, x0_sv); + const end_x_vx = voxelFromSubvoxel(.border_down, x0_sv + ray_sv.getX()); - var x: i32 = start_x; - px: while (x <= end_x) : (x += 1) { - const fx = @as(f32, @floatFromInt(x)); - const min_y = blockCoord(.border_up, (fx - x0) * dydx + y0); - const max_y = blockCoord(.border_down, (fx - x0) * dydx + y1); + var x_vx: i32 = start_x_vx; + px: while (x_vx <= end_x_vx) : (x_vx += 1) { + const x_sv = x_vx * c.sv_per_vx; + const min_y_vx = voxelFromSubvoxel(.border_up, wideMulDivFloor(x_sv - x0_sv, ray_sv.getY(), ray_sv.getX()) + y0_sv); + const max_y_vx = voxelFromSubvoxel(.border_down, wideMulDivCeil(x_sv - x0_sv, ray_sv.getY(), ray_sv.getX()) + y1_sv); var it = Iterator2(i32).init(.{ - .min = .{ min_y, min_z }, - .max = .{ max_y, max_z }, + .min = .{ min_y_vx, min_z_vx }, + .max = .{ max_y_vx, max_z_vx }, }); - while (it.next()) |yz| { - const y, const z = yz; - if (!isSolid(chunks, x, y, z)) continue; + while (it.next()) |yz_vx| { + const y_vx, const z_vx = yz_vx; + if (!isSolid(chunks, .init(x_vx, y_vx, z_vx))) continue; - const dx = fx - x0; - const dy = dydx * dx; - const distance = Vector2.init(dx, dy).len(); + const dx = x_sv - x0_sv; + const fdx: f32 = @floatFromInt(dx); + const fdy: f32 = fdx * fdydx; - if (distance >= closest_hit_distance) { - break :px; - } - - closest_hit = .{ - .projected_distance = ray.getX() - dx, - .normal = .negate(.unit_x), + hit = .{ + .projected_distance_sv = ray_sv.getX() - dx, + .normal_frac = .unit_nx_frac, }; - closest_hit_distance = distance; + hit_distance_squared = fdx * fdx + fdy * fdy; + std.debug.print("+X fdx={d} fdy={d} dist2={d}\n", .{ fdx, fdy, hit_distance_squared }); + + break :px; } } } // Negative X - if (ray.getX() < 0.0) { - const x0 = min_origin.getX(); - const y0 = min_origin.getY(); - const y1 = max_origin.getY(); + if (ray_sv.getX() < 0) { + const x0_sv = min_origin_sv.getX(); + const y0_sv = min_origin_sv.getY(); + const y1_sv = max_origin_sv.getY(); - const start_x = blockCoord(.next_down_with_border, x0); - const end_x = blockCoord(.border_down, x0 + ray.getX()); + const start_x_vx = voxelFromSubvoxel(.next_down, x0_sv); + const end_x_vx = voxelFromSubvoxel(.border_up, x0_sv + ray_sv.getX()); - var x: i32 = start_x; - nx: while (x >= end_x) : (x -= 1) { - const fx = @as(f32, @floatFromInt(x)) + 1.0; - const min_y = blockCoord(.border_up, (fx - x0) * dydx + y0); - const max_y = blockCoord(.border_down, (fx - x0) * dydx + y1); + var x_vx: i32 = start_x_vx; + nx: while (x_vx >= end_x_vx) : (x_vx -= 1) { + const x_sv = (x_vx + 1) * c.sv_per_vx; + const min_y_vx = voxelFromSubvoxel(.border_up, wideMulDivFloor(x_sv - x0_sv, ray_sv.getY(), ray_sv.getX()) + y0_sv); + const max_y_vx = voxelFromSubvoxel(.border_down, wideMulDivCeil(x_sv - x0_sv, ray_sv.getY(), ray_sv.getX()) + y1_sv); var it = Iterator2(i32).init(.{ - .min = .{ min_y, min_z }, - .max = .{ max_y, max_z }, + .min = .{ min_y_vx, min_z_vx }, + .max = .{ max_y_vx, max_z_vx }, }); - while (it.next()) |yz| { - const y, const z = yz; - if (!isSolid(chunks, x, y, z)) continue; + while (it.next()) |yz_vx| { + const y_vx, const z_vx = yz_vx; + if (!isSolid(chunks, .init(x_vx, y_vx, z_vx))) continue; - const dx = fx - x0; - const dy = dydx * dx; - const distance = Vector2.init(dx, dy).len(); + const dx = x_sv - x0_sv; + const fdx: f32 = @floatFromInt(dx); + const fdy: f32 = fdx * fdydx; - if (distance >= closest_hit_distance) { - break :nx; - } - - closest_hit = .{ - .projected_distance = -(ray.getX() - dx), - .normal = .unit_x, + hit = .{ + .projected_distance_sv = -(ray_sv.getX() - dx), + .normal_frac = .unit_x_frac, }; - closest_hit_distance = distance; + hit_distance_squared = fdx * fdx + fdy * fdy; + std.debug.print("-X fdx={d} fdy={d} dist2={d}\n", .{ fdx, fdy, hit_distance_squared }); + + break :nx; } } } // Positive Y - if (ray.getY() > 0.0) { - const y0 = max_origin.getY(); - const x0 = min_origin.getX(); - const x1 = max_origin.getX(); + if (ray_sv.getY() > 0) { + const y0_sv = max_origin_sv.getY(); + const x0_sv = min_origin_sv.getX(); + const x1_sv = max_origin_sv.getX(); - const start_y: i32 = @intFromFloat(@ceil(y0)); - const end_y: i32 = @intFromFloat(@floor(y0 + ray.getY())); + const start_y_vx = voxelFromSubvoxel(.next_up, y0_sv); + const end_y_vx = voxelFromSubvoxel(.border_down, y0_sv + ray_sv.getY()); - var y: i32 = start_y; - py: while (y <= end_y) : (y += 1) { - const fy = @as(f32, @floatFromInt(y)); - const min_x: i32 = @intFromFloat(@floor(fy - y0) * dxdy + x0); - const max_x: i32 = @intFromFloat(@ceil((fy - y0) * dxdy + x1 - 1.0)); + var y_vx = start_y_vx; + py: while (y_vx <= end_y_vx) : (y_vx += 1) { + const y_sv = y_vx * c.sv_per_vx; + const min_x_vx = voxelFromSubvoxel(.border_up, wideMulDivFloor(y_sv - y0_sv, ray_sv.getX(), ray_sv.getY()) + x0_sv); + const max_x_vx = voxelFromSubvoxel(.border_down, wideMulDivCeil(y_sv - y0_sv, ray_sv.getX(), ray_sv.getY()) + x1_sv); var it = Iterator2(i32).init(.{ - .min = .{ min_x, min_z }, - .max = .{ max_x, max_z }, + .min = .{ min_x_vx, min_z_vx }, + .max = .{ max_x_vx, max_z_vx }, }); - while (it.next()) |xz| { - const x, const z = xz; - if (!isSolid(chunks, x, y, z)) continue; + while (it.next()) |xz_vx| { + const x_vx, const z_vx = xz_vx; + if (!isSolid(chunks, .init(x_vx, y_vx, z_vx))) continue; - const dy = fy - y0; - const dx = dxdy * dy; - const distance = Vector2.init(dx, dy).len(); + const dy = y_sv - y0_sv; + const fdy: f32 = @floatFromInt(dy); + const fdx: f32 = fdy * fdxdy; - if (distance >= closest_hit_distance) { - break :py; + const this_hit_distance_squared = fdx * fdx + fdy * fdy; + std.debug.print("+Y fdx={d} fdy={d} dist2={d}\n", .{ fdx, fdy, this_hit_distance_squared }); + if (this_hit_distance_squared < hit_distance_squared) { + hit = .{ + .projected_distance_sv = ray_sv.getY() - dy, + .normal_frac = .unit_ny_frac, + }; + hit_distance_squared = this_hit_distance_squared; } - closest_hit = .{ - .projected_distance = ray.getY() - dy, - .normal = .negate(.unit_y), - }; - closest_hit_distance = distance; + break :py; } } } // Negative Y - if (ray.getY() < 0.0) { - const y0 = min_origin.getY(); - const x0 = min_origin.getX(); - const x1 = max_origin.getX(); + if (ray_sv.getY() < 0.0) { + const y0_sv = min_origin_sv.getY(); + const x0_sv = min_origin_sv.getX(); + const x1_sv = max_origin_sv.getX(); - const start_y = blockCoord(.next_down_with_border, y0); - const end_y = blockCoord(.border_down, y0 + ray.getY()); + const start_y_vx = voxelFromSubvoxel(.next_down, y0_sv); + const end_y_vx = voxelFromSubvoxel(.border_up, y0_sv + ray_sv.getY()); - var y: i32 = start_y; - py: while (y >= end_y) : (y -= 1) { - const fy = @as(f32, @floatFromInt(y)) + 1.0; - const min_x: i32 = @intFromFloat(@floor(fy - y0) * dxdy + x0); - const max_x: i32 = @intFromFloat(@ceil((fy - y0) * dxdy + x1 - 1.0)); + var y_vx = start_y_vx; + ny: while (y_vx >= end_y_vx) : (y_vx -= 1) { + const y_sv = (y_vx + 1) * c.sv_per_vx; + const min_x_vx = voxelFromSubvoxel(.border_up, wideMulDivFloor(y_sv - y0_sv, ray_sv.getX(), ray_sv.getY()) + x0_sv); + const max_x_vx = voxelFromSubvoxel(.border_down, wideMulDivCeil(y_sv - y0_sv, ray_sv.getX(), ray_sv.getY()) + x1_sv); var it = Iterator2(i32).init(.{ - .min = .{ min_x, min_z }, - .max = .{ max_x, max_z }, + .min = .{ min_x_vx, min_z_vx }, + .max = .{ max_x_vx, max_z_vx }, }); - while (it.next()) |xz| { - const x, const z = xz; - if (!isSolid(chunks, x, y, z)) continue; + while (it.next()) |xz_vx| { + const x_vx, const z_vx = xz_vx; + if (!isSolid(chunks, .init(x_vx, y_vx, z_vx))) continue; - const dy = fy - y0; - const dx = dxdy * dy; - const distance = Vector2.init(dx, dy).len(); + const dy = y_sv - y0_sv; + const fdy: f32 = @floatFromInt(dy); + const fdx: f32 = fdy * fdxdy; - if (distance >= closest_hit_distance) { - break :py; + const this_hit_distance_squared = fdx * fdx + fdy * fdy; + std.debug.print("-Y fdx={d} fdy={d} dist2={d}\n", .{ fdx, fdy, this_hit_distance_squared }); + if (this_hit_distance_squared < hit_distance_squared) { + hit = .{ + .projected_distance_sv = -(ray_sv.getY() - dy), + .normal_frac = .unit_y_frac, + }; + hit_distance_squared = this_hit_distance_squared; } - closest_hit = .{ - .projected_distance = -(ray.getY() - dy), - .normal = .unit_y, - }; - closest_hit_distance = distance; + break :ny; } } } - return closest_hit; + return hit; } fn resetAllButtons(self: *Player) void { @@ -525,50 +569,52 @@ fn resetAllButtons(self: *Player) void { } } -fn isSolid(chunks: *const std.AutoHashMapUnmanaged([3]i16, Chunk), x: i32, y: i32, z: i32) bool { - const chunk_x = std.math.cast(i16, @divFloor(x, Chunk.chunk_size)) orelse return true; - const chunk_y = std.math.cast(i16, @divFloor(y, Chunk.chunk_size)) orelse return true; - const chunk_z = std.math.cast(i16, @divFloor(z, Chunk.chunk_size)) orelse return true; - if (chunks.get(.{ chunk_x, chunk_y, chunk_z })) |chunk| { - const local_x: u4 = @intCast(@mod(x, Chunk.chunk_size)); - const local_y: u4 = @intCast(@mod(y, Chunk.chunk_size)); - const local_z: u4 = @intCast(@mod(z, Chunk.chunk_size)); - return chunk.blocks[local_z][local_y][local_x] != .air; - } else { - return false; +fn getVoxelAt(chunks: *const std.AutoHashMapUnmanaged([3]i16, Chunk), vx: Vector3Int) ?Blocks.Id { + const min_ck = Vector3Int.initScalar(std.math.minInt(i16)); + const max_ck = Vector3Int.initScalar(std.math.maxInt(i16)); + const ck = vx.divScalar(c.vx_per_ck); + if (@reduce(.Or, (ck.vector < min_ck.vector) | (ck.vector > max_ck.vector))) { + return null; } + + if (chunks.get(.{ + @intCast(ck.getX()), + @intCast(ck.getY()), + @intCast(ck.getZ()), + })) |chunk| { + const ckvx = vx.modScalar(c.vx_per_ck); + return chunk.blocks[@intCast(ckvx.getZ())][@intCast(ckvx.getY())][@intCast(ckvx.getX())]; + } else { + return .air; + } +} + +fn isSolid(chunks: *const std.AutoHashMapUnmanaged([3]i16, Chunk), vx: Vector3Int) bool { + const maybe_id = getVoxelAt(chunks, vx); + // NOTE `null` is considered solid, as it's out of bounds. + return maybe_id != .air; } const RoundingMode = enum { border_down, border_up, - next_down_with_border, - next_down_without_border, - next_up_with_border, - next_up_without_border, + next_down, + next_up, }; -inline fn blockCoord(comptime rounding_mode: RoundingMode, coord: f32) i32 { - return switch (rounding_mode) { - .border_down => @intFromFloat(@ceil(coord - 1.0)), - .border_up => @intFromFloat(@floor(coord)), - .next_down_with_border => @intFromFloat(@floor(coord - 1.0)), - .next_down_without_border => @intFromFloat(@ceil(coord - 2.0)), - .next_up_with_border => @intFromFloat(@ceil(coord)), - .next_up_without_border => @intFromFloat(@floor(coord + 1.0)), - }; +inline fn wideMulDivFloor(a: i32, mul: i32, div: i32) i32 { + return @intCast(@divFloor(@as(i64, a) * @as(i64, mul), div)); } -inline fn blockCoord2(comptime rounding_mode: RoundingMode, coord: Vector2) [2]i32 { - return switch (rounding_mode) { - .border_down => @as(Vector2.Vector, @intFromFloat(@ceil(coord.sub(.one).vector))), - .border_up => @as(Vector2.Vector, @intFromFloat(@floor(coord.vector))), - }; +inline fn wideMulDivCeil(a: i32, mul: i32, div: i32) i32 { + return @intCast(@divFloor(@as(i64, a) * @as(i64, mul) + @as(i64, div) - 1, div)); } -inline fn blockCoord3(comptime rounding_mode: RoundingMode, coord: Vector3) [3]i32 { +inline fn voxelFromSubvoxel(comptime rounding_mode: RoundingMode, subvoxel: i32) i32 { return switch (rounding_mode) { - .border_down => @as(Vector3.Vector, @intFromFloat(@ceil(coord.sub(.one).vector))), - .border_up => @as(Vector3.Vector, @intFromFloat(@floor(coord.vector))), + .border_down => @divFloor(subvoxel - 1, c.sv_per_vx), + .border_up => @divFloor(subvoxel, c.sv_per_vx), + .next_down => @divFloor(subvoxel - c.sv_per_vx, c.sv_per_vx), + .next_up => @divFloor(subvoxel + (c.sv_per_vx - 1), c.sv_per_vx), }; } diff --git a/src/const.zig b/src/const.zig index 37eadf6..d32dd53 100644 --- a/src/const.zig +++ b/src/const.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const Vector2 = @import("math.zig").Vector2; +const Vector2Int = @import("math.zig").Vector2Int; pub const app_name = "voxel-game"; pub const app_version_string = @import("config").version; @@ -11,12 +11,33 @@ pub const max_frametime = 1.0 / min_framerate; pub const default_window_width = 1280; pub const default_window_height = 720; -pub const default_window_size = Vector2.init(default_window_width, default_window_height); +pub const default_window_size = Vector2Int.init(default_window_width, default_window_height); pub const min_window_width = 640; pub const min_window_height = 360; -pub const min_window_size = Vector2.init(default_window_width, default_window_height); +pub const min_window_size = Vector2Int.init(default_window_width, default_window_height); pub const window_title = "Voxel Game"; pub const temp_allocator_capacity = 16 * 1024 * 1024; + +// Coordinate spaces: +// - CS (clip space, as required by Vulkan) +// - VS (view space, looking towards +Y, +X is right and +Z is up) +// - WS (world space, origin at a corner of voxel (0, 0, 0), +Z is up) +// Units: +// - SV (subvoxel) | +// - VX (voxels) | 1 [VX] = 4096 [SV] +// - CK (chunks) | 1 [CK] = 16 [VX] +// Relative units: +// CKSV subvoxels relative to chunk | [0, 65536) +// CKVX voxels relative to chunk | [0, 16) +// VXSV subvoxels relative to voxel | [0, 4096) + +pub const sv_per_vx = 4096; +pub const vx_per_ck = 16; +pub const sv_per_ck = sv_per_vx * vx_per_ck; + +pub inline fn sv(comptime vx: comptime_float) comptime_int { + return @intFromFloat(@round(vx * sv_per_vx)); +} diff --git a/src/math.zig b/src/math.zig index 9e89b01..e0c6b65 100644 --- a/src/math.zig +++ b/src/math.zig @@ -1,10 +1,14 @@ +const std = @import("std"); + pub const Iterator2 = @import("math/Iterator2.zig").Iterator2; pub const Iterator3 = @import("math/Iterator3.zig").Iterator3; pub const Matrix4x4 = @import("math/Matrix4x4.zig").Matrix4x4; pub const Quaternion = @import("math/Quaternion.zig").Quaternion; pub const Vector2 = @import("math/Vector2.zig").Vector2; +pub const Vector2Int = @import("math/Vector2Int.zig").Vector2Int; pub const Vector2x8 = @import("math/Vector2x8.zig").Vector2x8; pub const Vector3 = @import("math/Vector3.zig").Vector3; +pub const Vector3Int = @import("math/Vector3Int.zig").Vector3Int; pub const Vector4 = @import("math/Vector4.zig").Vector4; pub const f32x8 = @Vector(8, f32); @@ -35,3 +39,37 @@ pub inline fn lerp(a: f32, b: f32, t: f32) f32 { pub const noise2 = @import("math/noise.zig").noise2; pub const noise2x8 = @import("math/noise.zig").noise2x8; + +pub inline fn asFloatFrac(frac: i32) f32 { + const numerator: f64 = @floatFromInt(frac); + const denominator: f64 = std.math.maxInt(i32); + return @floatCast(numerator / denominator); +} + +pub inline fn asIntFrac(frac: f32) i32 { + const fraction: f64 = @floatCast(frac); + const scale: f64 = std.math.maxInt(i32); + return @intFromFloat(@round(fraction * scale)); +} + +pub inline fn mulIntFloat(int: i32, float: f32) i32 { + const int_float: f64 = @floatFromInt(int); + const float_wide: f64 = @floatCast(float); + return @intFromFloat(@round(int_float * float_wide)); +} + +pub inline fn mulFrac(a: i32, b: i32) i32 { + const denominator: i64 = std.math.maxInt(i32); + const rounding_bias: i64 = denominator >> 1; + const a_wide: i64 = a; + const b_wide: i64 = b; + return @intCast(@divFloor(a_wide * b_wide + rounding_bias, denominator)); +} + +pub inline fn mulFracFrac(a: i32, b: i32) i32 { + const denominator: i64 = std.math.maxInt(i32) * std.math.maxInt(i32); + const rounding_bias: i64 = denominator >> 1; + const a_wide: i64 = a; + const b_wide: i64 = b; + return .{ .vector = @intCast(@divFloor(a_wide * b_wide + rounding_bias, denominator)) }; +} diff --git a/src/math/Vector2.zig b/src/math/Vector2.zig index 879add3..c02a072 100644 --- a/src/math/Vector2.zig +++ b/src/math/Vector2.zig @@ -1,5 +1,6 @@ const std = @import("std"); +const Vector2Int = @import("Vector2Int.zig").Vector2Int; const Vector3 = @import("Vector3.zig").Vector3; const Vector4 = @import("Vector3.zig").Vector4; @@ -7,26 +8,33 @@ pub const Vector2 = extern struct { vector: Vector, pub const Vector = @Vector(2, f32); + pub const Array = [2]f32; pub const Mask = enum(i32) { x = 0, y = 1 }; - pub const zero = Vector2.init(0, 0); - pub const one = Vector2.init(1, 1); + pub const zero = Vector2.initScalar(0); + pub const one = Vector2.initScalar(1); pub const unit_x = Vector2.init(1, 0); pub const unit_y = Vector2.init(0, 1); + pub const unit_nx = Vector2.init(-1, 0); + pub const unit_ny = Vector2.init(0, -1); // --- INIT --- + pub inline fn initScalar(scalar: f32) Vector2 { + return .{ .vector = @splat(scalar) }; + } + pub inline fn init(x: f32, y: f32) Vector2 { return .{ .vector = .{ x, y } }; } - pub inline fn initArray(array: [2]f32) Vector2 { + pub inline fn initArray(array: Array) Vector2 { return .{ .vector = array }; } // --- CONVERSION --- - pub inline fn asArray(self: Vector2) [2]f32 { + pub inline fn asArray(self: Vector2) Array { return self.vector; } @@ -35,6 +43,16 @@ pub const Vector2 = extern struct { return @as(@Vector(2, T), @intFromFloat(@round(self.vector * scale_vector))); } + pub inline fn asVector2Int(self: Vector2) Vector2Int { + return .{ .vector = @intFromFloat(@round(self.vector)) }; + } + + pub inline fn asVector2IntFrac(self: Vector2) Vector2Int { + const fraction: @Vector(2, f64) = @floatCast(self.vector); + const scale: @Vector(2, f64) = @splat(std.math.maxInt(i32)); + return .{ .vector = @intFromFloat(@round(fraction * scale)) }; + } + pub inline fn asVector3(self: Vector2, z: f32) Vector3 { const z_vector: @Vector(3, f32) = .{ undefined, undefined, z }; return .{ .vector = @shuffle(f32, self.vector, z_vector, [_]i32{ 0, 1, ~@as(i32, 2) }) }; diff --git a/src/math/Vector2Int.zig b/src/math/Vector2Int.zig new file mode 100644 index 0000000..bf420da --- /dev/null +++ b/src/math/Vector2Int.zig @@ -0,0 +1,191 @@ +const std = @import("std"); + +const Vector2 = @import("Vector2.zig").Vector2; +const Vector3Int = @import("Vector3Int.zig").Vector3Int; + +pub const Vector2Int = extern struct { + vector: Vector, + + pub const Vector = @Vector(2, i32); + pub const Array = [2]i32; + pub const Mask = enum(i32) { x = 0, y = 1 }; + + pub const zero = Vector2Int.initScalar(0); + pub const one = Vector2Int.initScalar(1); + pub const one_frac = Vector2Int.initScalar(std.math.maxInt(i32)); + pub const unit_x = Vector2Int.init(1, 0); + pub const unit_y = Vector2Int.init(0, 1); + pub const unit_nx = Vector2Int.init(-1, 0); + pub const unit_ny = Vector2Int.init(0, -1); + pub const unit_x_frac = Vector2Int.init(std.math.maxInt(i32), 0); + pub const unit_y_frac = Vector2Int.init(0, std.math.maxInt(i32)); + pub const unit_nx_frac = Vector2Int.init(-std.math.maxInt(i32), 0); + pub const unit_ny_frac = Vector2Int.init(0, -std.math.maxInt(i32)); + + // --- INIT --- + + pub inline fn initScalar(scalar: i32) Vector2Int { + return .{ .vector = @splat(scalar) }; + } + + pub inline fn init(x: i32, y: i32) Vector2Int { + return .{ .vector = .{ x, y } }; + } + + pub inline fn initArray(array: Array) Vector2Int { + return .{ .vector = array }; + } + + // --- CONVERSION --- + + pub inline fn asArray(self: Vector2Int) Array { + return self.vector; + } + + pub inline fn asVector2(self: Vector2Int) Vector2 { + return .{ .vector = @floatFromInt(self.vector) }; + } + + pub inline fn asVector2Frac(self: Vector2Int) Vector2 { + const numerator: @Vector(2, f64) = @floatFromInt(self.vector); + const denominator: @Vector(2, f64) = @splat(std.math.maxInt(i32)); + return .{ .vector = @floatCast(numerator / denominator) }; + } + + pub inline fn asVector3Int(self: Vector2Int, z: i32) Vector3Int { + const z_vector: @Vector(3, i32) = .{ undefined, undefined, z }; + return .{ .vector = @shuffle(i32, self.vector, z_vector, [_]i32{ 0, 1, ~@as(i32, 2) }) }; + } + + // --- ACCESSORS --- + + pub inline fn getX(self: Vector2Int) i32 { + return self.vector[0]; + } + + pub inline fn getY(self: Vector2Int) i32 { + return self.vector[1]; + } + + pub inline fn setX(self: Vector2Int, x: i32) Vector2Int { + const x_vector: Vector = @splat(x); + return .{ .vector = @shuffle(i32, self.vector, x_vector, .{ ~@as(i32, 0), 1 }) }; + } + + pub inline fn setY(self: Vector2Int, y: i32) Vector2Int { + const y_vector: Vector = @splat(y); + return .{ .vector = @shuffle(i32, self.vector, y_vector, .{ 0, ~@as(i32, 1) }) }; + } + + // --- COMPONENT-WISE --- + + pub inline fn add(self: Vector2Int, other: Vector2Int) Vector2Int { + return .{ .vector = self.vector + other.vector }; + } + + pub inline fn sub(self: Vector2Int, other: Vector2Int) Vector2Int { + return .{ .vector = self.vector - other.vector }; + } + + pub inline fn mul(self: Vector2Int, other: Vector2Int) Vector2Int { + return .{ .vector = self.vector * other.vector }; + } + + pub inline fn mulFrac(self: Vector2Int, other: Vector2Int) Vector2Int { + const denominator: @Vector(2, i64) = @splat(std.math.maxInt(i32)); + const rounding_bias: @Vector(2, i64) = denominator >> @as(@Vector(2, u6), @splat(1)); + const self_wide: @Vector(2, i64) = self.vector; + const other_wide: @Vector(2, i64) = other.vector; + return .{ .vector = @intCast(@divFloor(self_wide * other_wide + rounding_bias, denominator)) }; + } + + pub inline fn mulFracFrac(self: Vector2Int, other: Vector2Int) Vector2Int { + const denominator: @Vector(2, i64) = @splat(std.math.maxInt(i32) * std.math.maxInt(i32)); + const rounding_bias: @Vector(2, i64) = denominator >> @as(@Vector(2, u6), @splat(1)); + const self_wide: @Vector(2, i64) = self.vector; + const other_wide: @Vector(2, i64) = other.vector; + return .{ .vector = @intCast(@divFloor(self_wide * other_wide + rounding_bias, denominator)) }; + } + + pub inline fn div(self: Vector2Int, other: Vector2Int) Vector2Int { + return .{ .vector = @divFloor(self.vector, other.vector) }; + } + + pub inline fn mod(self: Vector2Int, other: Vector2Int) Vector2Int { + return .{ .vector = @mod(self.vector, other.vector) }; + } + + pub inline fn negate(self: Vector2Int) Vector2Int { + return .{ .vector = -self.vector }; + } + + pub inline fn mulScalar(self: Vector2Int, scalar: i32) Vector2Int { + const scalar_vector: Vector = @splat(scalar); + return .{ .vector = self.vector * scalar_vector }; + } + + pub inline fn mulScalarFloat(self: Vector2Int, scalar: f32) Vector2Int { + const self_float: @Vector(2, f64) = @floatFromInt(self.vector); + const scalar_vector_float: @Vector(2, f64) = @splat(scalar); + return .{ .vector = @intFromFloat(@round(self_float * scalar_vector_float)) }; + } + + pub inline fn mulScalarFrac(self: Vector2Int, scalar: i32) Vector2Int { + const denominator: @Vector(2, i64) = @splat(std.math.maxInt(i32)); + const rounding_bias: @Vector(2, i64) = denominator >> @as(@Vector(2, u6), @splat(1)); + const self_wide: @Vector(2, i64) = self.vector; + const scalar_wide: @Vector(2, i64) = @splat(scalar); + return .{ .vector = @intCast(@divFloor(self_wide * scalar_wide + rounding_bias, denominator)) }; + } + + pub inline fn mulScalarFracFrac(self: Vector2Int, scalar: i32) Vector2Int { + const denominator: @Vector(2, i64) = @splat(std.math.maxInt(i32) * std.math.maxInt(i32)); + const rounding_bias: @Vector(2, i64) = denominator >> @as(@Vector(2, u6), @splat(1)); + const self_wide: @Vector(2, i64) = self.vector; + const scalar_wide: @Vector(2, i64) = @splat(scalar); + return .{ .vector = @intCast(@divFloor(self_wide * scalar_wide + rounding_bias, denominator)) }; + } + + pub inline fn divScalar(self: Vector2Int, scalar: i32) Vector2Int { + const scalar_vector: Vector = @splat(scalar); + return .{ .vector = @divFloor(self.vector, scalar_vector) }; + } + + pub inline fn divScalarFloat(self: Vector2Int, scalar: f32) Vector2Int { + const self_float: @Vector(2, f64) = @floatFromInt(self.vector); + const scalar_vector_float: @Vector(2, f64) = @splat(scalar); + return .{ .vector = @intFromFloat(@round(self_float / scalar_vector_float)) }; + } + + pub inline fn modScalar(self: Vector2Int, scalar: i32) Vector2Int { + const scalar_vector: Vector = @splat(scalar); + return .{ .vector = @mod(self.vector, scalar_vector) }; + } + + // --- SWIZZLE --- + + pub inline fn swizzle2(self: Vector2Int, comptime mask: [2]Mask) Vector2Int { + return .{ .vector = @shuffle(f32, self.vector, undefined, @as([2]i32, @bitCast(mask))) }; + } + + pub inline fn swizzle3(self: Vector2Int, comptime mask: [3]Mask) Vector3Int { + return .{ .vector = @shuffle(f32, self.vector, undefined, @as([3]i32, @bitCast(mask))) }; + } + + // --- OTHER --- + + pub inline fn lenSquared(self: Vector2Int) i64 { + const self_wide: @Vector(2, i64) = self.vector; + return @reduce(.Add, self_wide * self_wide); + } + + pub inline fn dot(self: Vector2Int, other: Vector2Int) i64 { + const self_wide: @Vector(2, i64) = self.vector; + const other_wide: @Vector(2, i64) = other.vector; + return @reduce(.Add, self_wide * other_wide); + } + + pub inline fn cross(self: Vector2Int, other: Vector2Int) i64 { + return @as(i64, self.getX()) * @as(i64, other.getY()) - @as(i64, self.getY()) * @as(i64, other.getX()); + } +}; diff --git a/src/math/Vector2x8.zig b/src/math/Vector2x8.zig index 85923ab..def0c9d 100644 --- a/src/math/Vector2x8.zig +++ b/src/math/Vector2x8.zig @@ -14,6 +14,8 @@ pub const Vector2x8 = extern struct { pub const one = Vector2x8.init(ps(1), ps(1)); pub const unit_x = Vector2x8.init(ps(1), ps(0)); pub const unit_y = Vector2x8.init(ps(0), ps(1)); + pub const unit_nx = Vector2x8.init(ps(-1), ps(0)); + pub const unit_ny = Vector2x8.init(ps(0), ps(-1)); // --- INIT --- diff --git a/src/math/Vector3.zig b/src/math/Vector3.zig index 7ff5a6b..2716ff3 100644 --- a/src/math/Vector3.zig +++ b/src/math/Vector3.zig @@ -2,33 +2,42 @@ const std = @import("std"); const Quaternion = @import("Quaternion.zig").Quaternion; const Vector2 = @import("Vector2.zig").Vector2; +const Vector3Int = @import("Vector3Int.zig").Vector3Int; const Vector4 = @import("Vector4.zig").Vector4; pub const Vector3 = extern struct { vector: Vector, pub const Vector = @Vector(3, f32); + pub const Array = [3]f32; pub const Mask = enum(i32) { x = 0, y = 1, z = 2 }; - pub const zero = Vector3.init(0, 0, 0); - pub const one = Vector3.init(1, 1, 1); + pub const zero = Vector3.initScalar(0); + pub const one = Vector3.initScalar(1); pub const unit_x = Vector3.init(1, 0, 0); pub const unit_y = Vector3.init(0, 1, 0); pub const unit_z = Vector3.init(0, 0, 1); + pub const unit_nx = Vector3.init(-1, 0, 0); + pub const unit_ny = Vector3.init(0, -1, 0); + pub const unit_nz = Vector3.init(0, 0, -1); // --- INIT --- + pub inline fn initScalar(scalar: f32) Vector3 { + return .{ .vector = @splat(scalar) }; + } + pub inline fn init(x: f32, y: f32, z: f32) Vector3 { return .{ .vector = .{ x, y, z } }; } - pub inline fn initArray(array: [3]f32) Vector3 { + pub inline fn initArray(array: Array) Vector3 { return .{ .vector = array }; } // --- CONVERSION --- - pub inline fn asArray(self: Vector3) [3]f32 { + pub inline fn asArray(self: Vector3) Array { return self.vector; } @@ -37,6 +46,16 @@ pub const Vector3 = extern struct { return @as(@Vector(3, T), @intFromFloat(@round(self.vector * scale_vector))); } + pub inline fn asVector3Int(self: Vector3) Vector3Int { + return .{ .vector = @intFromFloat(@round(self.vector)) }; + } + + pub inline fn asVector2IntFrac(self: Vector3) Vector3Int { + const fraction: @Vector(3, f64) = @floatCast(self.vector); + const scale: @Vector(3, f64) = @splat(std.math.maxInt(i32)); + return .{ .vector = @intFromFloat(@round(fraction * scale)) }; + } + pub inline fn asVector2(self: Vector3) Vector2 { return .{ .vector = @shuffle(f32, self.vector, undefined, [_]i32{ 0, 1 }) }; } diff --git a/src/math/Vector3Int.zig b/src/math/Vector3Int.zig new file mode 100644 index 0000000..f15ef88 --- /dev/null +++ b/src/math/Vector3Int.zig @@ -0,0 +1,199 @@ +const std = @import("std"); + +const Vector2Int = @import("Vector2Int.zig").Vector2Int; +const Vector3 = @import("Vector3.zig").Vector3; + +pub const Vector3Int = extern struct { + vector: Vector, + + pub const Vector = @Vector(3, i32); + pub const Array = [3]i32; + pub const Mask = enum(i32) { x = 0, y = 1, z = 2 }; + + pub const zero = Vector3Int.initScalar(0); + pub const one = Vector3Int.initScalar(1); + pub const one_frac = Vector3Int.initScalar(std.math.maxInt(i32)); + pub const unit_x = Vector3Int.init(1, 0, 0); + pub const unit_y = Vector3Int.init(0, 1, 0); + pub const unit_z = Vector3Int.init(0, 0, 1); + pub const unit_nx = Vector3Int.init(-1, 0, 0); + pub const unit_ny = Vector3Int.init(0, -1, 0); + pub const unit_nz = Vector3Int.init(0, 0, -1); + pub const unit_x_frac = Vector3Int.init(std.math.maxInt(i32), 0, 0); + pub const unit_y_frac = Vector3Int.init(0, std.math.maxInt(i32), 0); + pub const unit_z_frac = Vector3Int.init(0, 0, std.math.maxInt(i32)); + pub const unit_nx_frac = Vector3Int.init(-std.math.maxInt(i32), 0, 0); + pub const unit_ny_frac = Vector3Int.init(0, -std.math.maxInt(i32), 0); + pub const unit_nz_frac = Vector3Int.init(0, 0, -std.math.maxInt(i32)); + + // --- INIT --- + + pub inline fn initScalar(scalar: i32) Vector3Int { + return .{ .vector = @splat(scalar) }; + } + + pub inline fn init(x: i32, y: i32, z: i32) Vector3Int { + return .{ .vector = .{ x, y, z } }; + } + + pub inline fn initArray(array: Array) Vector3Int { + return .{ .vector = array }; + } + + // --- CONVERSION --- + + pub inline fn asArray(self: Vector3Int) Array { + return self.vector; + } + + pub inline fn asVector3(self: Vector3Int) Vector3 { + return .{ .vector = @floatFromInt(self.vector) }; + } + + pub inline fn asVector3Frac(self: Vector3Int) Vector3 { + const numerator: @Vector(3, f64) = @floatFromInt(self.vector); + const denominator: @Vector(3, f64) = @splat(std.math.maxInt(i32)); + return .{ .vector = @floatCast(numerator / denominator) }; + } + + pub inline fn asVector2Int(self: Vector3Int) Vector2Int { + return .{ .vector = @shuffle(i32, self.vector, undefined, [_]i32{ 0, 1 }) }; + } + + // --- ACCESSORS --- + + pub inline fn getX(self: Vector3Int) i32 { + return self.vector[0]; + } + + pub inline fn getY(self: Vector3Int) i32 { + return self.vector[1]; + } + + pub inline fn getZ(self: Vector3Int) i32 { + return self.vector[2]; + } + + pub inline fn setX(self: Vector3Int, x: i32) Vector3Int { + const x_vector: Vector = @splat(x); + return .{ .vector = @shuffle(i32, self.vector, x_vector, [_]i32{ ~@as(i32, 0), 1, 2 }) }; + } + + pub inline fn setY(self: Vector3Int, y: i32) Vector3Int { + const y_vector: Vector = @splat(y); + return .{ .vector = @shuffle(i32, self.vector, y_vector, [_]i32{ 0, ~@as(i32, 1), 2 }) }; + } + + pub inline fn setZ(self: Vector3Int, z: i32) Vector3Int { + const z_vector: Vector = @splat(z); + return .{ .vector = @shuffle(i32, self.vector, z_vector, [_]i32{ 0, 1, ~@as(i32, 2) }) }; + } + + // --- COMPONENT-WISE --- + + pub inline fn add(self: Vector3Int, other: Vector3Int) Vector3Int { + return .{ .vector = self.vector + other.vector }; + } + + pub inline fn sub(self: Vector3Int, other: Vector3Int) Vector3Int { + return .{ .vector = self.vector - other.vector }; + } + + pub inline fn mul(self: Vector3Int, other: Vector3Int) Vector3Int { + return .{ .vector = self.vector * other.vector }; + } + + pub inline fn mulFrac(self: Vector3Int, other: Vector3Int) Vector3Int { + const denominator: @Vector(3, i64) = @splat(std.math.maxInt(i32)); + const rounding_bias: @Vector(3, i64) = denominator >> @as(@Vector(2, u6), @splat(1)); + const self_wide: @Vector(3, i64) = self.vector; + const other_wide: @Vector(3, i64) = other.vector; + return .{ .vector = @intCast(@divFloor(self_wide * other_wide + rounding_bias, denominator)) }; + } + + pub inline fn mulFracFrac(self: Vector3Int, other: Vector3Int) Vector3Int { + const denominator: @Vector(3, i64) = @splat(std.math.maxInt(i32) * std.math.maxInt(i32)); + const rounding_bias: @Vector(3, i64) = denominator >> @as(@Vector(2, u6), @splat(1)); + const self_wide: @Vector(3, i64) = self.vector; + const other_wide: @Vector(3, i64) = other.vector; + return .{ .vector = @intCast(@divFloor(self_wide * other_wide + rounding_bias, denominator)) }; + } + + pub inline fn div(self: Vector3Int, other: Vector3Int) Vector3Int { + return .{ .vector = @divFloor(self.vector, other.vector) }; + } + + pub inline fn mod(self: Vector3Int, other: Vector3Int) Vector3Int { + return .{ .vector = @mod(self.vector, other.vector) }; + } + + pub inline fn negate(self: Vector3Int) Vector3Int { + return .{ .vector = -self.vector }; + } + + pub inline fn mulScalar(self: Vector3Int, scalar: i32) Vector3Int { + const scalar_vector: Vector = @splat(scalar); + return .{ .vector = self.vector * scalar_vector }; + } + + pub inline fn mulScalarFloat(self: Vector3Int, scalar: f32) Vector3Int { + const self_float: @Vector(3, f64) = @floatFromInt(self.vector); + const scalar_vector_float: @Vector(3, f64) = @splat(scalar); + return .{ .vector = @intFromFloat(@round(self_float * scalar_vector_float)) }; + } + + pub inline fn mulScalarFrac(self: Vector3Int, scalar: i32) Vector3Int { + const denominator: @Vector(3, i64) = @splat(std.math.maxInt(i32)); + const rounding_bias: @Vector(3, i64) = denominator >> @as(@Vector(3, u6), @splat(1)); + const self_wide: @Vector(3, i64) = self; + const scalar_wide: @Vector(3, i64) = @splat(scalar); + return .{ .vector = @intCast(@divFloor(self_wide * scalar_wide + rounding_bias, denominator)) }; + } + + pub inline fn mulScalarFracFrac(self: Vector3Int, scalar: i32) Vector3Int { + const denominator: @Vector(3, i64) = @splat(std.math.maxInt(i32) * std.math.maxInt(i32)); + const rounding_bias: @Vector(3, i64) = denominator >> @as(@Vector(3, u6), @splat(1)); + const self_wide: @Vector(3, i64) = self; + const scalar_wide: @Vector(3, i64) = @splat(scalar); + return .{ .vector = @intCast(@divFloor(self_wide * scalar_wide + rounding_bias, denominator)) }; + } + + pub inline fn divScalar(self: Vector3Int, scalar: i32) Vector3Int { + const scalar_vector: Vector = @splat(scalar); + return .{ .vector = @divFloor(self.vector, scalar_vector) }; + } + + pub inline fn divScalarFloat(self: Vector3Int, scalar: f32) Vector3Int { + const self_float: @Vector(3, f64) = @floatFromInt(self.vector); + const scalar_vector_float: @Vector(3, f64) = @splat(scalar); + return .{ .vector = @intFromFloat(@round(self_float / scalar_vector_float)) }; + } + + pub inline fn modScalar(self: Vector3Int, scalar: i32) Vector3Int { + const scalar_vector: Vector = @splat(scalar); + return .{ .vector = @mod(self.vector, scalar_vector) }; + } + + // --- SWIZZLE --- + + pub inline fn swizzle2(self: Vector3Int, comptime mask: [2]Mask) Vector2Int { + return .{ .vector = @shuffle(f32, self.vector, undefined, @as([2]i32, @bitCast(mask))) }; + } + + pub inline fn swizzle3(self: Vector3Int, comptime mask: [3]Mask) Vector3Int { + return .{ .vector = @shuffle(f32, self.vector, undefined, @as([3]i32, @bitCast(mask))) }; + } + + // --- OTHER --- + + pub inline fn lenSquared(self: Vector3Int) i64 { + const self_wide: @Vector(2, i64) = self.vector; + return @reduce(.Add, self_wide * self_wide); + } + + pub inline fn dot(self: Vector3Int, other: Vector3Int) i64 { + const self_wide: @Vector(3, i64) = self.vector; + const other_wide: @Vector(3, i64) = other.vector; + return @reduce(.Add, self_wide * other_wide); + } +}; diff --git a/src/math/Vector4.zig b/src/math/Vector4.zig index f84f85f..c122c66 100644 --- a/src/math/Vector4.zig +++ b/src/math/Vector4.zig @@ -7,28 +7,37 @@ pub const Vector4 = extern struct { vector: Vector, pub const Vector = @Vector(4, f32); + pub const Array = [4]f32; pub const Mask = enum(i32) { x = 0, y = 1, z = 2, w = 3 }; - pub const zero = Vector4.init(0, 0, 0, 0); - pub const one = Vector4.init(1, 1, 1, 1); + pub const zero = Vector4.initScalar(0); + pub const one = Vector4.initScalar(1); pub const unit_x = Vector4.init(1, 0, 0, 0); pub const unit_y = Vector4.init(0, 1, 0, 0); pub const unit_z = Vector4.init(0, 0, 1, 0); pub const unit_w = Vector4.init(0, 0, 0, 1); + pub const unit_nx = Vector4.init(-1, 0, 0, 0); + pub const unit_ny = Vector4.init(0, -1, 0, 0); + pub const unit_nz = Vector4.init(0, 0, -1, 0); + pub const unit_nw = Vector4.init(0, 0, 0, -1); // --- INIT --- + pub inline fn initScalar(scalar: f32) Vector4 { + return .{ .vector = @splat(scalar) }; + } + pub inline fn init(x: f32, y: f32, z: f32, w: f32) Vector4 { return .{ .vector = .{ x, y, z, w } }; } - pub inline fn initArray(array: [4]f32) Vector4 { + pub inline fn initArray(array: Array) Vector4 { return .{ .vector = array }; } // --- CONVERSION --- - pub inline fn asArray(self: Vector4) [4]f32 { + pub inline fn asArray(self: Vector4) Array { return self.vector; }