const Chunk = @This(); const std = @import("std"); const math = @import("../math.zig"); const shaders = @import("../shaders.zig"); const vk = @import("vulkan"); const voxels = @import("../voxels.zig"); const Blocks = @import("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("Materials.zig"); const Matrix4x4 = math.Matrix4x4; const ObjectUniformsBuffer = GenericBuffer(void, shaders.ObjectUniforms); const Vector3 = math.Vector3; pub const chunk_size = 16; 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: Vector3, descriptor_set: vk.DescriptorSet, object_buffer: ObjectUniformsBuffer, object_count: u32, /// For debug purposes, a unique "name" per chunk instance. chunk_id: u64, pub const InitInfo = struct { origin: Vector3, descriptor_pool: vk.DescriptorPool, per_batch_descriptor_set_layout: vk.DescriptorSetLayout, }; pub const Neighbors = struct { positive_x: ?*const Chunk, negative_x: ?*const Chunk, positive_y: ?*const Chunk, negative_y: ?*const Chunk, positive_z: ?*const Chunk, negative_z: ?*const Chunk, }; pub fn init(engine: *Engine, init_info: InitInfo) !Chunk { 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: ObjectUniformsBuffer = try .init(engine, .{ .usage = .storage, .target_queue = .graphics, .array_capacity = initial_capacity, }); errdefer object_buffer.deinit(engine); 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, engine: *Engine, descriptor_pool: vk.DescriptorPool) void { std.log.scoped(.deinit).debug("Deinitializing chunk {d} with {*} and {s}#{X}", .{ self.chunk_id, engine, @typeName(vk.DescriptorPool), @intFromEnum(descriptor_pool) }); self.object_buffer.deinit(engine); engine.freeDescriptorSet(descriptor_pool, self.descriptor_set); self.* = undefined; } pub fn refresh(self: *Chunk, engine: *Engine, blocks: *const Blocks, neighbors: Neighbors, temp_allocator: std.mem.Allocator) !void { var uniforms: std.ArrayList(shaders.ObjectUniforms) = try .initCapacity(temp_allocator, initial_capacity); defer uniforms.deinit(temp_allocator); 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 center = 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, center); try uniforms.append(temp_allocator, .init(matrix, matrix, material)); } } } } } const object_count: u32 = @intCast(uniforms.items.len); if (self.object_buffer.array_capacity < object_count) { const desired_capacity = std.math.ceilPowerOfTwoAssert(u32, object_count); const new_object_buffer: ObjectUniformsBuffer = try .init(engine, .{ .usage = .storage, .target_queue = .graphics, .array_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(engine); 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.write(engine, .{ .elements = 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, center: Vector3) Matrix4x4 { return switch (side) { // zig fmt: off .positive_x => .init( 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, center.getX() + 0.5, center.getY(), center.getZ(), 1, ), .negative_x => .init( 0, -1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, center.getX() - 0.5, center.getY(), center.getZ(), 1, ), .positive_y => .init( -1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, center.getX(), center.getY() + 0.5, center.getZ(), 1, ), .negative_y => .init( 1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, center.getX(), center.getY() - 0.5, center.getZ(), 1, ), .positive_z => .init( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, center.getX(), center.getY(), center.getZ() + 0.5, 1, ), .negative_z => .init( 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, center.getX(), center.getY(), center.getZ() - 0.5, 1, ), // zig fmt: on }; }