const Chunks = @This(); const std = @import("std"); const c = @import("const.zig"); const math = @import("math.zig"); const vk = @import("vulkan"); const Blocks = @import("assets/Blocks.zig"); const Chunk = @import("assets/Chunk.zig"); const Engine = @import("engine/Engine.zig"); const Iterator2 = math.Iterator2; const Vector2Int = math.Vector2Int; const Vector3Int = math.Vector3Int; chunks: std.AutoHashMapUnmanaged([3]i16, Chunk), const SweepHit = struct { normal_frac: Vector3Int, projected_distance_sv: i32, }; pub fn deinit(self: *Chunks, engine: *Engine, descriptor_pool: vk.DescriptorPool, allocator: std.mem.Allocator) void { var it = self.chunks.valueIterator(); while (it.next()) |chunk| { chunk.deinit(engine, descriptor_pool); } self.chunks.deinit(allocator); self.* = undefined; } pub fn getVoxelAt(self: *const Chunks, 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 (self.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; } } pub fn isSolid(self: *const Chunks, vx: Vector3Int) bool { const maybe_id = getVoxelAt(self, vx); // NOTE `null` is considered solid, as it's out of bounds. return maybe_id != .air; } pub fn sweepCastDown(self: *const Chunks, min_sv: Vector3Int, max_sv: Vector3Int, distance_sv: i32) ?SweepHit { const min_x_vx = c.subvoxelsToVoxels(.border_up, min_sv.getX()); const min_y_vx = c.subvoxelsToVoxels(.border_up, min_sv.getY()); const max_x_vx = c.subvoxelsToVoxels(.border_down, max_sv.getX()); const max_y_vx = c.subvoxelsToVoxels(.border_down, max_sv.getY()); const start_z_vx = c.subvoxelsToVoxels(.border_up, min_sv.getZ()) - 1; const end_z_vx = c.subvoxelsToVoxels(.border_up, min_sv.getZ() - distance_sv); var z_vx: i32 = start_z_vx; while (z_vx >= end_z_vx) : (z_vx -= 1) { const z_sv = c.voxelsToSubvoxels(z_vx + 1); var it = Iterator2(i32).init(.{ .min = .{ min_x_vx, min_y_vx }, .max = .{ max_x_vx, max_y_vx }, }); while (it.next()) |xy_vx| { const x_vx, const y_vx = xy_vx; if (!self.isSolid(.init(x_vx, y_vx, z_vx))) continue; return .{ .projected_distance_sv = distance_sv - (min_sv.getZ() - z_sv), .normal_frac = .unit_z_frac, }; } } return null; } pub fn sweepCastUp(self: *const Chunks, min_sv: Vector3Int, max_sv: Vector3Int, distance_sv: i32) ?SweepHit { const min_x_vx = c.subvoxelsToVoxels(.border_up, min_sv.getX()); const min_y_vx = c.subvoxelsToVoxels(.border_up, min_sv.getY()); const max_x_vx = c.subvoxelsToVoxels(.border_down, max_sv.getX()); const max_y_vx = c.subvoxelsToVoxels(.border_down, max_sv.getY()); const start_z_vx = c.subvoxelsToVoxels(.border_down, max_sv.getZ()) + 1; const end_z_vx = c.subvoxelsToVoxels(.border_down, max_sv.getZ() + distance_sv); var z_vx: i32 = start_z_vx; while (z_vx <= end_z_vx) : (z_vx += 1) { const z_sv = c.voxelsToSubvoxels(z_vx); var it = Iterator2(i32).init(.{ .min = .{ min_x_vx, min_y_vx }, .max = .{ max_x_vx, max_y_vx }, }); while (it.next()) |xy_vx| { const x_vx, const y_vx = xy_vx; if (!self.isSolid(.init(x_vx, y_vx, z_vx))) continue; return .{ .projected_distance_sv = distance_sv - (z_sv - max_sv.getZ()), .normal_frac = .unit_nz_frac, }; } } return null; } pub fn sweepCastHorizontal(self: *const Chunks, min_sv: Vector3Int, max_sv: Vector3Int, ray_sv: Vector2Int) ?SweepHit { const min_z_vx = c.subvoxelsToVoxels(.border_up, min_sv.getZ()); const max_z_vx = c.subvoxelsToVoxels(.border_down, max_sv.getZ()); var hit: ?SweepHit = null; var hit_distance_squared = std.math.inf(f32); 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_sv.getX() > 0) { const x0_sv = max_sv.getX(); const y0_sv = min_sv.getY(); const y1_sv = max_sv.getY(); const start_x_vx = c.subvoxelsToVoxels(.border_down, x0_sv) + 1; const end_x_vx = c.subvoxelsToVoxels(.border_down, x0_sv + ray_sv.getX()); var x_vx: i32 = start_x_vx; px: while (x_vx <= end_x_vx) : (x_vx += 1) { const x_sv = c.voxelsToSubvoxels(x_vx); const min_y_vx = c.subvoxelsToVoxels(.border_up, math.wideMulDivFloor(x_sv - x0_sv, ray_sv.getY(), ray_sv.getX()) + y0_sv); const max_y_vx = c.subvoxelsToVoxels(.border_down, math.wideMulDivCeil(x_sv - x0_sv, ray_sv.getY(), ray_sv.getX()) + y1_sv); var it = Iterator2(i32).init(.{ .min = .{ min_y_vx, min_z_vx }, .max = .{ max_y_vx, max_z_vx }, }); while (it.next()) |yz_vx| { const y_vx, const z_vx = yz_vx; if (!self.isSolid(.init(x_vx, y_vx, z_vx))) continue; const dx = x_sv - x0_sv; const fdx: f32 = @floatFromInt(dx); const fdy: f32 = fdx * fdydx; hit = .{ .projected_distance_sv = ray_sv.getX() - dx, .normal_frac = .unit_nx_frac, }; hit_distance_squared = fdx * fdx + fdy * fdy; // std.debug.print("HIT +X ({X}->{X}) at ({X}, {X}, {X}) [VX] | min_sv={X} max_sv={X} ray_sv={X} | proj_dist={X} \n", .{ start_x_vx, end_x_vx, x_vx, y_vx, z_vx, min_sv.vector, max_sv.vector, ray_sv.vector, hit.?.projected_distance_sv }); break :px; } } } // Negative X if (ray_sv.getX() < 0) { const x0_sv = min_sv.getX(); const y0_sv = min_sv.getY(); const y1_sv = max_sv.getY(); const start_x_vx = c.subvoxelsToVoxels(.border_up, x0_sv) - 1; const end_x_vx = c.subvoxelsToVoxels(.border_up, x0_sv + ray_sv.getX()); var x_vx: i32 = start_x_vx; nx: while (x_vx >= end_x_vx) : (x_vx -= 1) { const x_sv = c.voxelsToSubvoxels(x_vx + 1); const min_y_vx = c.subvoxelsToVoxels(.border_up, math.wideMulDivFloor(x_sv - x0_sv, ray_sv.getY(), ray_sv.getX()) + y0_sv); const max_y_vx = c.subvoxelsToVoxels(.border_down, math.wideMulDivCeil(x_sv - x0_sv, ray_sv.getY(), ray_sv.getX()) + y1_sv); var it = Iterator2(i32).init(.{ .min = .{ min_y_vx, min_z_vx }, .max = .{ max_y_vx, max_z_vx }, }); while (it.next()) |yz_vx| { const y_vx, const z_vx = yz_vx; if (!self.isSolid(.init(x_vx, y_vx, z_vx))) continue; const dx = x_sv - x0_sv; const fdx: f32 = @floatFromInt(dx); const fdy: f32 = fdx * fdydx; hit = .{ .projected_distance_sv = -(ray_sv.getX() - dx), .normal_frac = .unit_x_frac, }; hit_distance_squared = fdx * fdx + fdy * fdy; // std.debug.print("HIT -X ({X}->{X}) at ({X}, {X}, {X}) [VX] | min_sv={X} max_sv={X} ray_sv={X} | proj_dist={X} \n", .{ start_x_vx, end_x_vx, x_vx, y_vx, z_vx, min_sv.vector, max_sv.vector, ray_sv.vector, hit.?.projected_distance_sv }); break :nx; } } } // Positive Y if (ray_sv.getY() > 0) { const y0_sv = max_sv.getY(); const x0_sv = min_sv.getX(); const x1_sv = max_sv.getX(); const start_y_vx = c.subvoxelsToVoxels(.border_down, y0_sv) + 1; const end_y_vx = c.subvoxelsToVoxels(.border_down, y0_sv + ray_sv.getY()); var y_vx = start_y_vx; py: while (y_vx <= end_y_vx) : (y_vx += 1) { const y_sv = c.voxelsToSubvoxels(y_vx); const min_x_vx = c.subvoxelsToVoxels(.border_up, math.wideMulDivFloor(y_sv - y0_sv, ray_sv.getX(), ray_sv.getY()) + x0_sv); const max_x_vx = c.subvoxelsToVoxels(.border_down, math.wideMulDivCeil(y_sv - y0_sv, ray_sv.getX(), ray_sv.getY()) + x1_sv); var it = Iterator2(i32).init(.{ .min = .{ min_x_vx, min_z_vx }, .max = .{ max_x_vx, max_z_vx }, }); while (it.next()) |xz_vx| { const x_vx, const z_vx = xz_vx; if (!self.isSolid(.init(x_vx, y_vx, z_vx))) continue; const dy = y_sv - y0_sv; const fdy: f32 = @floatFromInt(dy); const fdx: f32 = fdy * fdxdy; const this_hit_distance_squared = fdx * fdx + fdy * fdy; 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; // std.debug.print("HIT +Y ({X}->{X}) at ({X}, {X}, {X}) [VX] | min_sv={X} max_sv={X} ray_sv={X} | proj_dist={X} \n", .{ start_y_vx, end_y_vx, x_vx, y_vx, z_vx, min_sv.vector, max_sv.vector, ray_sv.vector, hit.?.projected_distance_sv }); } break :py; } } } // Negative Y if (ray_sv.getY() < 0.0) { const y0_sv = min_sv.getY(); const x0_sv = min_sv.getX(); const x1_sv = max_sv.getX(); const start_y_vx = c.subvoxelsToVoxels(.border_up, y0_sv) - 1; const end_y_vx = c.subvoxelsToVoxels(.border_up, y0_sv + ray_sv.getY()); var y_vx = start_y_vx; ny: while (y_vx >= end_y_vx) : (y_vx -= 1) { const y_sv = c.voxelsToSubvoxels(y_vx + 1); const min_x_vx = c.subvoxelsToVoxels(.border_up, math.wideMulDivFloor(y_sv - y0_sv, ray_sv.getX(), ray_sv.getY()) + x0_sv); const max_x_vx = c.subvoxelsToVoxels(.border_down, math.wideMulDivCeil(y_sv - y0_sv, ray_sv.getX(), ray_sv.getY()) + x1_sv); var it = Iterator2(i32).init(.{ .min = .{ min_x_vx, min_z_vx }, .max = .{ max_x_vx, max_z_vx }, }); while (it.next()) |xz_vx| { const x_vx, const z_vx = xz_vx; if (!self.isSolid(.init(x_vx, y_vx, z_vx))) continue; const dy = y_sv - y0_sv; const fdy: f32 = @floatFromInt(dy); const fdx: f32 = fdy * fdxdy; const this_hit_distance_squared = fdx * fdx + fdy * fdy; 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; // std.debug.print("HIT -Y ({X}->{X}) at ({X}, {X}, {X}) [VX] | min_sv={X} max_sv={X} ray_sv={X} | proj_dist={X} \n", .{ start_y_vx, end_y_vx, x_vx, y_vx, z_vx, min_sv.vector, max_sv.vector, ray_sv.vector, hit.?.projected_distance_sv }); } break :ny; } } } return hit; }