Files
voxel-game/src/assets/Chunk.zig

261 lines
9.8 KiB
Zig

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
};
}