const Chunk = @This(); const std = @import("std"); const c = @import("const.zig"); const ctx = @import("AppContext.zig"); const math = @import("math.zig"); const shaders = @import("shaders.zig"); const vk = @import("vulkan"); const vm = @import("vecmath"); const voxels = @import("voxels.zig"); const Blocks = @import("assets/Blocks.zig"); const CommandBuffer = @import("engine/CommandBuffer.zig"); const Engine = @import("engine/Engine.zig"); const Game = @import("Game.zig"); const GenericBuffer = @import("engine/GenericBuffer.zig").GenericBuffer; const Materials = @import("engine/Materials.zig"); const chunk_size = c.vx_per_ck; const initial_capacity = 256; var next_chunk_id: std.atomic.Value(u64) = .init(0); /// To get the block at coordinates (x, y, z) relative to the chunk, read the /// value at `blocks[z][y][x]`. blocks: [chunk_size][chunk_size][chunk_size]Blocks.Id, origin: vm.Vector3, descriptor_set: vk.DescriptorSet, object_buffer: shaders.ObjectUniformsBuffer, object_count: u32, /// For debug purposes, a unique "name" per chunk instance. chunk_id: u64, pub const InitInfo = struct { origin: vm.Vector3, descriptor_pool: vk.DescriptorPool, per_batch_descriptor_set_layout: vk.DescriptorSetLayout, }; pub const Neighbors = std.enums.EnumFieldStruct(voxels.Orientation, ?*const Chunk, null); pub fn init(init_info: InitInfo) !Chunk { const engine = ctx.engine; const descriptor_set = try engine.allocateDescriptorSet(.{ .descriptor_pool = init_info.descriptor_pool, .set_layout = init_info.per_batch_descriptor_set_layout, }); errdefer engine.freeDescriptorSet(init_info.descriptor_pool, descriptor_set); var object_buffer: shaders.ObjectUniformsBuffer = try .init(.{ .usage = .storage, .target_queue = .graphics, .capacity = initial_capacity, }); errdefer object_buffer.deinit(); try engine.updateDescriptorSets(.{ .writes = &.{ .{ .dst_set = descriptor_set, .dst_binding = 0, .dst_array_element = 0, .descriptor_type = .storage_buffer, .descriptor_infos = .{ .buffer = &.{ .{ .buffer = object_buffer.buffer, .offset = 0, .range = vk.WHOLE_SIZE, }, }, }, }, }, }); const chunk_id = next_chunk_id.fetchAdd(1, .monotonic); engine.setObjectName(descriptor_set, "DS Chunk[{d}]", .{chunk_id}); engine.setObjectName(object_buffer.buffer, "B Chunk[{d}]", .{chunk_id}); engine.setObjectName(object_buffer.device_memory, "DM Chunk[{d}]", .{chunk_id}); return .{ .blocks = .{.{[_]Blocks.Id{.air} ** chunk_size} ** chunk_size} ** chunk_size, .origin = init_info.origin, .descriptor_set = descriptor_set, .object_buffer = object_buffer, .object_count = 0, .chunk_id = chunk_id, }; } pub fn deinit(self: *Chunk, descriptor_pool: vk.DescriptorPool) void { const engine = ctx.engine; std.log.scoped(.deinit).debug("Deinitializing chunk {d} with {s}#{X}", .{ self.chunk_id, @typeName(vk.DescriptorPool), @intFromEnum(descriptor_pool) }); self.object_buffer.deinit(); engine.freeDescriptorSet(descriptor_pool, self.descriptor_set); self.* = undefined; } pub fn refresh(self: *Chunk, blocks: *const Blocks, neighbors: Neighbors) !void { const allocator_frame = ctx.allocator_frame; const engine = ctx.engine; var uniforms: std.ArrayList(shaders.ObjectUniforms) = try .initCapacity(allocator_frame, initial_capacity); for (self.blocks, 0..) |plane, iz| { const fz: f32 = @floatFromInt(iz); for (plane, 0..) |row, iy| { const fy: f32 = @floatFromInt(iy); for (row, 0..) |id, ix| { const fx: f32 = @floatFromInt(ix); const block: *const Blocks.Definition = &blocks.array.items[id.toInt()]; const origin = vm.Vector3.add(self.origin, .init(fx, fy, fz)); inline for (@typeInfo(voxels.Orientation).@"enum".fields) |field| { const side = @field(voxels.Orientation, field.name); const material = @field(block.walls, field.name).material; if (material != .empty and self.getOpposite(side, ix, iy, iz, blocks, neighbors) == .empty) { const matrix = getMatrix(side, origin); try uniforms.append(allocator_frame, .{ .matrixOStoWS = matrix, .matrixOStoWSNormal = matrix, .material = material, }); } } } } } const object_count: u32 = @intCast(uniforms.items.len); if (self.object_buffer.capacity < object_count) { const desired_capacity = std.math.ceilPowerOfTwoAssert(u32, object_count); const new_object_buffer: shaders.ObjectUniformsBuffer = try .init(.{ .usage = .storage, .target_queue = .graphics, .capacity = desired_capacity, }); try engine.updateDescriptorSets(.{ .writes = &.{ .{ .dst_set = self.descriptor_set, .dst_binding = 0, .dst_array_element = 0, .descriptor_type = .storage_buffer, .descriptor_infos = .{ .buffer = &.{ .{ .buffer = new_object_buffer.buffer, .offset = 0, .range = vk.WHOLE_SIZE, }, }, }, }, }, }); self.object_buffer.deinit(); self.object_buffer = new_object_buffer; engine.setObjectName(new_object_buffer.buffer, "B Chunk[{d}]", .{self.chunk_id}); engine.setObjectName(new_object_buffer.device_memory, "DM Chunk[{d}]", .{self.chunk_id}); } try self.object_buffer.writeSlice(uniforms.items); self.object_count = object_count; } pub fn draw(self: *const Chunk, layout: vk.PipelineLayout, command_buffer: CommandBuffer) void { command_buffer.bindDescriptorSets(.graphics, layout, 1, &.{self.descriptor_set}, &.{}); command_buffer.drawIndexed(.{ .index_count = 6, .instance_count = self.object_count }); } fn getOpposite(self: *const Chunk, comptime side: voxels.Orientation, x: usize, y: usize, z: usize, blocks: *const Blocks, neighbors: Neighbors) Materials.Id { return switch (side) { .positive_x => if (x + 1 < chunk_size) blocks.array.items[self.blocks[z][y][x + 1].toInt()].walls.negative_x.material else if (neighbors.positive_x) |neighbor| blocks.array.items[neighbor.blocks[z][y][0].toInt()].walls.negative_x.material else .empty, .negative_x => if (x > 0) blocks.array.items[self.blocks[z][y][x - 1].toInt()].walls.positive_x.material else if (neighbors.negative_x) |neighbor| blocks.array.items[neighbor.blocks[z][y][chunk_size - 1].toInt()].walls.positive_x.material else .empty, .positive_y => if (y + 1 < chunk_size) blocks.array.items[self.blocks[z][y + 1][x].toInt()].walls.negative_y.material else if (neighbors.positive_y) |neighbor| blocks.array.items[neighbor.blocks[z][0][x].toInt()].walls.negative_y.material else .empty, .negative_y => if (y > 0) blocks.array.items[self.blocks[z][y - 1][x].toInt()].walls.positive_y.material else if (neighbors.negative_y) |neighbor| blocks.array.items[neighbor.blocks[z][chunk_size - 1][x].toInt()].walls.positive_y.material else .empty, .positive_z => if (z + 1 < chunk_size) blocks.array.items[self.blocks[z + 1][y][x].toInt()].walls.negative_z.material else if (neighbors.positive_z) |neighbor| blocks.array.items[neighbor.blocks[0][y][x].toInt()].walls.negative_z.material else .empty, .negative_z => if (z > 0) blocks.array.items[self.blocks[z - 1][y][x].toInt()].walls.positive_z.material else if (neighbors.negative_z) |neighbor| blocks.array.items[neighbor.blocks[chunk_size - 1][y][x].toInt()].walls.positive_z.material else .empty, }; } fn getMatrix(comptime side: voxels.Orientation, origin: vm.Vector3) vm.Matrix4x4 { return switch (side) { // zig fmt: off .positive_x => .init( 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, origin.x + 1, origin.y, origin.z, 1, ), .negative_x => .init( 0, -1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, origin.x, origin.y + 1, origin.z, 1, ), .positive_y => .init( -1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, origin.x + 1, origin.y + 1, origin.z, 1, ), .negative_y => .init( 1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, origin.x, origin.y, origin.z, 1, ), .positive_z => .init( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, origin.x, origin.y, origin.z + 1, 1, ), .negative_z => .init( 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, origin.x, origin.y + 1, origin.z, 1, ), // zig fmt: on }; }