From 5dfbc64676171c4268b3abc472ca3e09a209c47a Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Sat, 29 Nov 2025 14:49:28 +0100 Subject: [PATCH] Eliminate opposing walls --- src/Game.zig | 61 ++++++---- src/assets/Chunk.zig | 252 ++++++++++++++++++++++------------------ src/math.zig | 1 + src/math/Interator3.zig | 42 +++++++ 4 files changed, 226 insertions(+), 130 deletions(-) create mode 100644 src/math/Interator3.zig diff --git a/src/Game.zig b/src/Game.zig index d564b68..df9ac28 100644 --- a/src/Game.zig +++ b/src/Game.zig @@ -15,7 +15,7 @@ const Engine = @import("engine/Engine.zig"); const GenericBuffer = @import("engine/GenericBuffer.zig").GenericBuffer; const StagingBuffer = @import("engine/StagingBuffer.zig"); const Swapchain = @import("engine/Swapchain.zig"); -const Iterator3 = math.Iterator3; +const Interator3 = math.Interator3; const Matrix4x4 = math.Matrix4x4; const Quaternion = math.Quaternion; const Vector2 = math.Vector2; @@ -117,7 +117,7 @@ index_buffer: IndexBuffer, global_uniforms: GlobalUniformsBuffer, global_uniforms_staging_buffer: StagingBuffer, -global_uniforms_transfer_command_buffer: CommandBuffer(.graphics, .persistent), +global_uniforms_transfer_command_buffer: CommandBuffer(.transfer, .persistent), global_uniforms_transfer_semaphores: []vk.Semaphore, point_lights: PointLightBuffer, directional_lights: DirectionalLightBuffer, @@ -126,7 +126,7 @@ sampler: vk.Sampler, blocks: Blocks, materials: Materials, textures: Textures, -chunks: std.ArrayList(Chunk), +chunks: std.AutoHashMapUnmanaged([3]i16, Chunk), camera_position: Vector3 = .init(0, 0, 1.62), camera_pitch: f32 = 0, @@ -142,7 +142,7 @@ input_down: bool = false, const max_textures = 1024; const max_point_lights = 1024; const max_directional_lights = 4; -const max_objects = 1024; +const chunk_descriptor_pool = 512; const camera_near_plane = 0.1; const camera_vertical_fov_deg = 80.0; @@ -318,7 +318,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain }); errdefer global_uniforms_staging_buffer.deinit(engine); - var global_uniforms_transfer_command_buffer: CommandBuffer(.graphics, .persistent) = try .init(engine); + var global_uniforms_transfer_command_buffer: CommandBuffer(.transfer, .persistent) = try .init(engine); errdefer global_uniforms_transfer_command_buffer.deinit(engine); try global_uniforms_transfer_command_buffer.beginCommandBuffer(.{}); @@ -491,7 +491,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain engine.setObjectName(pipeline, "P", .{}); const descriptor_pool = try engine.createDescriptorPool(.{ - .max_sets = 1 + 256, + .max_sets = 1 + chunk_descriptor_pool, .pool_sizes = &.{ .{ .type = .sampler, @@ -507,7 +507,7 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain }, .{ .type = .storage_buffer, - .descriptor_count = 3 + 256, + .descriptor_count = 3 + chunk_descriptor_pool, }, }, }); @@ -603,33 +603,51 @@ pub fn init(allocator: std.mem.Allocator, engine: *Engine, swapchain: *Swapchain }, }); - var chunks: std.ArrayList(Chunk) = .empty; + var chunks: std.AutoHashMapUnmanaged([3]i16, Chunk) = .empty; errdefer { - for (chunks.items) |*chunk| { + var it = chunks.valueIterator(); + while (it.next()) |chunk| { chunk.deinit(engine, descriptor_pool); } chunks.deinit(allocator); } - var it: Iterator3 = .init(.init(-64, -64, 0), .init(64, 64, 0), .init(16, 16, 16)); - while (it.next()) |origin| { + var it = Interator3(i16).init(-10, -10, 0, 9, 9, 0); + while (it.next()) |chunk_coords| { + const origin = Vector3.init( + @floatFromInt(chunk_coords[0]), + @floatFromInt(chunk_coords[1]), + @floatFromInt(chunk_coords[2]), + ).mulScalar(16); + try chunks.ensureUnusedCapacity(allocator, 1); - chunks.appendAssumeCapacity(try Chunk.init(engine, .{ + chunks.putAssumeCapacityNoClobber(chunk_coords, try Chunk.init(engine, .{ .origin = origin, .descriptor_pool = descriptor_pool, .per_batch_descriptor_set_layout = per_batch_descriptor_set_layout, })); + const chunk = chunks.getPtr(chunk_coords).?; - const chunk = &chunks.items[chunks.items.len - 1]; - - var it2 = Iterator3.init(.init(0, 0, 0), .init(1, 1, 0), .{ .vector = @splat(1.0 / 16.0) }); - while (it2.next()) |fpos| { - const x, const y, const z = fpos.asArrayNorm(u4); + var it2 = Interator3(usize).init(0, 0, 0, 15, 15, 0); + while (it2.next()) |pos| { + const x, const y, const z = pos; const block: Blocks.Id = @enumFromInt(engine.random.intRangeLessThan(u12, 1, @intCast(blocks.blocks.items.len))); chunk.blocks[z][y][x] = block; } + } - try chunk.refresh(engine, &blocks, allocator); + var chunk_it = chunks.iterator(); + while (chunk_it.next()) |entry| { + const x, const y, const z = entry.key_ptr.*; + const chunk = entry.value_ptr; + try chunk.refresh(engine, &blocks, .{ + .positive_x = chunks.getPtr(.{ x + 1, y, z }), + .negative_x = chunks.getPtr(.{ x - 1, y, z }), + .positive_y = chunks.getPtr(.{ x, y + 1, z }), + .negative_y = chunks.getPtr(.{ x, y - 1, z }), + .positive_z = chunks.getPtr(.{ x, y, z + 1 }), + .negative_z = chunks.getPtr(.{ x, y, z - 1 }), + }, allocator); } const point_lights_data: []const PointLight = &.{}; @@ -683,7 +701,9 @@ pub fn deinit(self: *Game) void { self.vertex_buffer.deinit(self.engine); self.index_buffer.deinit(self.engine); - for (self.chunks.items) |*chunk| { + + var it = self.chunks.valueIterator(); + while (it.next()) |chunk| { chunk.deinit(self.engine, self.descriptor_pool); } self.chunks.deinit(self.allocator); @@ -916,7 +936,8 @@ fn render(self: *Game) !void { command_buffer.bindIndexBuffer(self.index_buffer.buffer, 0, .uint16); command_buffer.bindDescriptorSet(.graphics, self.pipeline_layout, 0, self.global_descriptor_set, null); - for (self.chunks.items) |chunk| { + var it = self.chunks.valueIterator(); + while (it.next()) |chunk| { chunk.draw(self.pipeline_layout, command_buffer); } } diff --git a/src/assets/Chunk.zig b/src/assets/Chunk.zig index 75dfd53..ff049e0 100644 --- a/src/assets/Chunk.zig +++ b/src/assets/Chunk.zig @@ -10,6 +10,8 @@ const CommandBuffer = @import("../engine/CommandBuffer.zig").CommandBuffer; const Engine = @import("../engine/Engine.zig"); const Game = @import("../Game.zig"); const GenericBuffer = @import("../engine/GenericBuffer.zig").GenericBuffer; +const Materials = @import("Materials.zig"); +const Orientation = @import("../voxel.zig").Orientation; const Matrix4x4 = math.Matrix4x4; const ObjectUniformsBuffer = GenericBuffer(void, Game.ObjectUniforms); @@ -20,11 +22,15 @@ 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 { @@ -33,6 +39,15 @@ pub const InitInfo = struct { 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, @@ -89,23 +104,97 @@ pub fn deinit(self: *Chunk, engine: *Engine, descriptor_pool: vk.DescriptorPool) self.* = undefined; } -pub fn refresh(self: *Chunk, engine: *Engine, blocks: *const Blocks, temp_allocator: std.mem.Allocator) !void { - var object_count: u32 = 0; +pub fn refresh(self: *Chunk, engine: *Engine, blocks: *const Blocks, neighbors: Neighbors, temp_allocator: std.mem.Allocator) !void { + var uniforms: std.ArrayList(Game.ObjectUniforms) = try .initCapacity(temp_allocator, initial_capacity); + defer uniforms.deinit(temp_allocator); - for (self.blocks) |plane| { - for (plane) |row| { - for (row) |id| { + 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 = blocks.getBlock(id).?; - object_count += @intFromBool(block.positive_x != .empty); - object_count += @intFromBool(block.negative_x != .empty); - object_count += @intFromBool(block.positive_y != .empty); - object_count += @intFromBool(block.negative_y != .empty); - object_count += @intFromBool(block.positive_z != .empty); - object_count += @intFromBool(block.negative_z != .empty); + + const center = Vector3.add(self.origin, .init(fx, fy, fz)); + const cx, const cy, const cz = center.asArray(); + + if (block.positive_x != .empty and self.getOpposite(.positive_x, ix, iy, iz, blocks, neighbors) == .empty) { + // zig fmt: off + const matrix: Matrix4x4 = .init( + 0, 1, 0, 0, + 0, 0, 1, 0, + 1, 0, 0, 0, + cx + 0.5, cy, cz, 1, + ); + // zig fmt: on + try uniforms.append(temp_allocator, .init(matrix, matrix, block.positive_x)); + } + + if (block.negative_x != .empty and self.getOpposite(.negative_x, ix, iy, iz, blocks, neighbors) == .empty) { + // zig fmt: off + const matrix: Matrix4x4 = .init( + 0, -1, 0, 0, + 0, 0, 1, 0, + -1, 0, 0, 0, + cx - 0.5, cy, cz, 1, + ); + // zig fmt: on + try uniforms.append(temp_allocator, .init(matrix, matrix, block.negative_x)); + } + + if (block.positive_y != .empty and self.getOpposite(.positive_y, ix, iy, iz, blocks, neighbors) == .empty) { + // zig fmt: off + const matrix: Matrix4x4 = .init( + -1, 0, 0, 0, + 0, 0, 1, 0, + 0, 1, 0, 0, + cx, cy + 0.5, cz, 1, + ); + // zig fmt: on + try uniforms.append(temp_allocator, .init(matrix, matrix, block.positive_y)); + } + + if (block.negative_y != .empty and self.getOpposite(.negative_y, ix, iy, iz, blocks, neighbors) == .empty) { + // zig fmt: off + const matrix: Matrix4x4 = .init( + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, -1, 0, 0, + cx, cy - 0.5, cz, 1, + ); + // zig fmt: on + try uniforms.append(temp_allocator, .init(matrix, matrix, block.negative_y)); + } + + if (block.positive_z != .empty and self.getOpposite(.positive_z, ix, iy, iz, blocks, neighbors) == .empty) { + // zig fmt: off + const matrix: Matrix4x4 = .init( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + cx, cy, cz + 0.5, 1, + ); + // zig fmt: on + try uniforms.append(temp_allocator, .init(matrix, matrix, block.positive_z)); + } + + if (block.negative_z != .empty and self.getOpposite(.negative_z, ix, iy, iz, blocks, neighbors) == .empty) { + // zig fmt: off + const matrix: Matrix4x4 = .init( + 1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, -1, 0, + cx, cy, cz - 0.5, 1, + ); + // zig fmt: on + try uniforms.append(temp_allocator, .init(matrix, matrix, block.negative_z)); + } } } } + 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, .{ @@ -140,105 +229,7 @@ pub fn refresh(self: *Chunk, engine: *Engine, blocks: *const Blocks, temp_alloca engine.setObjectName(new_object_buffer.device_memory, "DM Chunk[{d}]", .{self.chunk_id}); } - const uniforms = try temp_allocator.alloc(Game.ObjectUniforms, object_count); - var object_i: usize = 0; - defer temp_allocator.free(uniforms); - - 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 = blocks.getBlock(id).?; - - const center = Vector3.add(self.origin, .init(fx, fy, fz)); - const cx, const cy, const cz = center.asArray(); - - if (block.positive_x != .empty) { - // zig fmt: off - const matrix: Matrix4x4 = .init( - 0, 1, 0, 0, - 0, 0, 1, 0, - 1, 0, 0, 0, - cx + 0.5, cy, cz, 1, - ); - // zig fmt: on - uniforms[object_i] = .init(matrix, matrix, block.positive_x); - object_i += 1; - } - - if (block.negative_x != .empty) { - // zig fmt: off - const matrix: Matrix4x4 = .init( - 0, -1, 0, 0, - 0, 0, 1, 0, - -1, 0, 0, 0, - cx - 0.5, cy, cz, 1, - ); - // zig fmt: on - uniforms[object_i] = .init(matrix, matrix, block.negative_x); - object_i += 1; - } - - if (block.positive_y != .empty) { - // zig fmt: off - const matrix: Matrix4x4 = .init( - -1, 0, 0, 0, - 0, 0, 1, 0, - 0, 1, 0, 0, - cx, cy + 0.5, cz, 1, - ); - // zig fmt: on - uniforms[object_i] = .init(matrix, matrix, block.positive_y); - object_i += 1; - } - - if (block.negative_y != .empty) { - // zig fmt: off - const matrix: Matrix4x4 = .init( - 1, 0, 0, 0, - 0, 0, 1, 0, - 0, -1, 0, 0, - cx, cy - 0.5, cz, 1, - ); - // zig fmt: on - uniforms[object_i] = .init(matrix, matrix, block.negative_y); - object_i += 1; - } - - if (block.positive_z != .empty) { - // zig fmt: off - const matrix: Matrix4x4 = .init( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - cx, cy, cz + 0.5, 1, - ); - // zig fmt: on - uniforms[object_i] = .init(matrix, matrix, block.positive_z); - object_i += 1; - } - - if (block.negative_z != .empty) { - // zig fmt: off - const matrix: Matrix4x4 = .init( - 1, 0, 0, 0, - 0, -1, 0, 0, - 0, 0, -1, 0, - cx, cy, cz - 0.5, 1, - ); - // zig fmt: on - uniforms[object_i] = .init(matrix, matrix, block.negative_z); - object_i += 1; - } - } - } - } - - std.debug.assert(object_i == uniforms.len); - - try self.object_buffer.write(engine, .{ .elements = uniforms }); + try self.object_buffer.write(engine, .{ .elements = uniforms.items }); self.object_count = object_count; } @@ -246,3 +237,44 @@ pub fn draw(self: *const Chunk, layout: vk.PipelineLayout, command_buffer: Comma 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: 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.getBlock(self.blocks[z][y][x + 1]).?.negative_x + else if (neighbors.positive_x) |neighbor| + blocks.getBlock(neighbor.blocks[z][y][0]).?.negative_x + else + .empty, + .negative_x => if (x > 0) + blocks.getBlock(self.blocks[z][y][x - 1]).?.positive_x + else if (neighbors.negative_x) |neighbor| + blocks.getBlock(neighbor.blocks[z][y][chunk_size - 1]).?.positive_x + else + .empty, + .positive_y => if (y + 1 < chunk_size) + blocks.getBlock(self.blocks[z][y + 1][x]).?.negative_y + else if (neighbors.positive_y) |neighbor| + blocks.getBlock(neighbor.blocks[z][0][x]).?.negative_y + else + .empty, + .negative_y => if (y > 0) + blocks.getBlock(self.blocks[z][y - 1][x]).?.positive_y + else if (neighbors.negative_y) |neighbor| + blocks.getBlock(neighbor.blocks[z][chunk_size - 1][x]).?.positive_y + else + .empty, + .positive_z => if (z + 1 < chunk_size) + blocks.getBlock(self.blocks[z + 1][y][x]).?.negative_z + else if (neighbors.positive_z) |neighbor| + blocks.getBlock(neighbor.blocks[0][y][x]).?.negative_z + else + .empty, + .negative_z => if (z > 0) + blocks.getBlock(self.blocks[z - 1][y][x]).?.positive_z + else if (neighbors.negative_z) |neighbor| + blocks.getBlock(neighbor.blocks[chunk_size - 1][y][x]).?.positive_z + else + .empty, + }; +} diff --git a/src/math.zig b/src/math.zig index 5c940f5..72e5fce 100644 --- a/src/math.zig +++ b/src/math.zig @@ -1,3 +1,4 @@ +pub const Interator3 = @import("math/Interator3.zig").Interator3; pub const Iterator3 = @import("math/Iterator3.zig"); pub const Matrix4x4 = @import("math/Matrix4x4.zig").Matrix4x4; pub const Quaternion = @import("math/Quaternion.zig").Quaternion; diff --git a/src/math/Interator3.zig b/src/math/Interator3.zig new file mode 100644 index 0000000..6b779bc --- /dev/null +++ b/src/math/Interator3.zig @@ -0,0 +1,42 @@ +pub fn Interator3(comptime T: type) type { + return struct { + const Self = @This(); + const Vector = @Vector(3, T); + + min: Vector, + max: Vector, + current: ?Vector, + + pub fn init(min_x: T, min_y: T, min_z: T, max_x: T, max_y: T, max_z: T) Self { + const min: Vector = .{ min_x, min_y, min_z }; + const max: Vector = .{ max_x, max_y, max_z }; + return .{ + .min = min, + .max = max, + .current = min, + }; + } + + pub fn next(self: *Self) ?[3]T { + const current = self.current orelse return null; + var next_current = current; + + next_current[0] = next_current[0] + 1; + if (next_current[0] > self.max[0]) { + next_current[0] = self.min[0]; + next_current[1] = next_current[1] + 1; + if (next_current[1] > self.max[1]) { + next_current[1] = self.min[1]; + next_current[2] = next_current[2] + 1; + if (next_current[2] > self.max[2]) { + self.current = null; + return current; + } + } + } + + self.current = next_current; + return current; + } + }; +}