diff --git a/assets/cubyz/shaders/chunks/chunk_vertex.vs b/assets/cubyz/shaders/chunks/chunk_vertex.vs index 0e36244e..a915415e 100644 --- a/assets/cubyz/shaders/chunks/chunk_vertex.vs +++ b/assets/cubyz/shaders/chunks/chunk_vertex.vs @@ -49,12 +49,11 @@ struct ChunkData { vec4 minPos; vec4 maxPos; int voxelSize; + uint lightStart; uint vertexStartOpaque; uint faceCountsByNormalOpaque[14]; - uint lightStartOpaque; uint vertexStartTransparent; uint vertexCountTransparent; - uint lightStartTransparent; uint visibilityState; uint oldVisibilityState; }; @@ -72,11 +71,7 @@ void main() { vec3 modelPosition = vec3(chunks[chunkID].position.xyz - playerPositionInteger) - playerPositionFraction; int encodedPositionAndLightIndex = faceData[faceID].encodedPositionAndLightIndex; int textureAndQuad = faceData[faceID].textureAndQuad; -#ifdef transparent - uint lightIndex = chunks[chunkID].lightStartTransparent + 4*(encodedPositionAndLightIndex >> 16); -#else - uint lightIndex = chunks[chunkID].lightStartOpaque + 4*(encodedPositionAndLightIndex >> 16); -#endif + uint lightIndex = chunks[chunkID].lightStart + 4*(encodedPositionAndLightIndex >> 16); uint fullLight = lightData[lightIndex + vertexID]; vec3 sunLight = vec3( fullLight >> 25 & 31u, diff --git a/assets/cubyz/shaders/chunks/fillIndirectBuffer.glsl b/assets/cubyz/shaders/chunks/fillIndirectBuffer.glsl index 096f5658..b7874531 100644 --- a/assets/cubyz/shaders/chunks/fillIndirectBuffer.glsl +++ b/assets/cubyz/shaders/chunks/fillIndirectBuffer.glsl @@ -12,12 +12,11 @@ struct ChunkData { vec4 minPos; vec4 maxPos; int voxelSize; + uint lightStart; uint vertexStartOpaque; uint faceCountsByNormalOpaque[14]; - uint lightStartOpaque; uint vertexStartTransparent; uint vertexCountTransparent; - uint lightStartTransparent; uint visibilityState; uint oldVisibilityState; }; diff --git a/assets/cubyz/shaders/chunks/occlusionTestFragment.fs b/assets/cubyz/shaders/chunks/occlusionTestFragment.fs index 436e43a2..30b42ca7 100644 --- a/assets/cubyz/shaders/chunks/occlusionTestFragment.fs +++ b/assets/cubyz/shaders/chunks/occlusionTestFragment.fs @@ -9,12 +9,11 @@ struct ChunkData { vec4 minPos; vec4 maxPos; int voxelSize; + uint lightStart; uint vertexStartOpaque; uint faceCountsByNormalOpaque[14]; - uint lightStartOpaque; uint vertexStartTransparent; uint vertexCountTransparent; - uint lightStartTransparent; uint visibilityState; uint oldVisibilityState; }; diff --git a/assets/cubyz/shaders/chunks/occlusionTestVertex.vs b/assets/cubyz/shaders/chunks/occlusionTestVertex.vs index aa5a049b..14c6f384 100644 --- a/assets/cubyz/shaders/chunks/occlusionTestVertex.vs +++ b/assets/cubyz/shaders/chunks/occlusionTestVertex.vs @@ -7,12 +7,11 @@ struct ChunkData { vec4 minPos; vec4 maxPos; int voxelSize; + uint lightStart; uint vertexStartOpaque; uint faceCountsByNormalOpaque[14]; - uint lightStartOpaque; uint vertexStartTransparent; uint vertexCountTransparent; - uint lightStartTransparent; uint visibilityState; uint oldVisibilityState; }; diff --git a/src/blocks.zig b/src/blocks.zig index 06fa0b22..9fd1e2a0 100644 --- a/src/blocks.zig +++ b/src/blocks.zig @@ -604,9 +604,9 @@ pub const meshes = struct { // MARK: meshes pub fn registerBlockBreakingAnimation(assetFolder: []const u8) void { var i: usize = 0; while(true) : (i += 1) { - const path1 = std.fmt.allocPrint(main.stackAllocator.allocator, "assets/cubyz/blocks/textures/{}.png", .{i}) catch unreachable; + const path1 = std.fmt.allocPrint(main.stackAllocator.allocator, "assets/cubyz/blocks/textures/breaking/{}.png", .{i}) catch unreachable; defer main.stackAllocator.free(path1); - const path2 = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}/cubyz/blocks/textures/{}.png", .{assetFolder, i}) catch unreachable; + const path2 = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}/cubyz/blocks/textures/breaking/{}.png", .{assetFolder, i}) catch unreachable; defer main.stackAllocator.free(path2); if(!main.files.hasFile(path1) and !main.files.hasFile(path2)) break; diff --git a/src/chunk.zig b/src/chunk.zig index 75e5633b..9f8cfe73 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -43,6 +43,10 @@ pub const Neighbor = enum(u3) { // MARK: Neighbor const arr = [_]i32 {1, -1, 0, 0, 0, 0}; return arr[@intFromEnum(self)]; } + /// Index to relative position + pub fn relPos(self: Neighbor) Vec3i { + return .{self.relX(), self.relY(), self.relZ()}; + } /// Index to bitMask for bitmap direction data pub inline fn bitMask(self: Neighbor) u6 { return @as(u6, 1) << @intFromEnum(self); diff --git a/src/graphics.zig b/src/graphics.zig index 27a5f9f0..baff104f 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -2071,12 +2071,11 @@ pub fn generateBlockTexture(blockType: u16) Texture { .min = undefined, .max = undefined, .voxelSize = 1, + .lightStart = lightAllocation.start, .vertexStartOpaque = undefined, .faceCountsByNormalOpaque = undefined, - .lightStartOpaque = lightAllocation.start, .vertexStartTransparent = undefined, .vertexCountTransparent = undefined, - .lightStartTransparent = lightAllocation.start, .visibilityState = 0, .oldVisibilityState = 0, }}, &chunkAllocation); diff --git a/src/renderer.zig b/src/renderer.zig index 9e139fa1..8cf53df7 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -809,6 +809,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection pub fn breakBlock(inventory: main.items.Inventory, slot: u32, deltaTime: f64) void { if(selectedBlockPos) |selectedPos| { if(@reduce(.Or, lastSelectedBlockPos != selectedPos)) { + mesh_storage.removeBreakingAnimation(lastSelectedBlockPos); lastSelectedBlockPos = selectedPos; currentBlockProgress = 0; } @@ -830,9 +831,13 @@ pub const MeshSelection = struct { // MARK: MeshSelection } currentBlockProgress += @as(f32, @floatCast(deltaTime))/breakTime; if(currentBlockProgress < 1) { + mesh_storage.removeBreakingAnimation(lastSelectedBlockPos); + mesh_storage.addBreakingAnimation(lastSelectedBlockPos, currentBlockProgress); main.items.Inventory.Sync.ClientSide.mutex.unlock(); + return; } else { + mesh_storage.removeBreakingAnimation(lastSelectedBlockPos); currentBlockProgress = 0; } } else { diff --git a/src/renderer/chunk_meshing.zig b/src/renderer/chunk_meshing.zig index 5c746ac1..15f28be4 100644 --- a/src/renderer/chunk_meshing.zig +++ b/src/renderer/chunk_meshing.zig @@ -264,12 +264,11 @@ pub const ChunkData = extern struct { min: Vec3f align(16), max: Vec3f align(16), voxelSize: i32, + lightStart: u32, vertexStartOpaque: u32, faceCountsByNormalOpaque: [14]u32, - lightStartOpaque: u32, vertexStartTransparent: u32, vertexCountTransparent: u32, - lightStartTransparent: u32, visibilityState: u32, oldVisibilityState: u32, }; @@ -288,15 +287,12 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh neighborFacesHigherLod: [6]main.ListUnmanaged(FaceData) = [_]main.ListUnmanaged(FaceData){.{}} ** 6, optionalFaces: main.ListUnmanaged(FaceData) = .{}, completeList: []FaceData = &.{}, - lightList: []u32 = &.{}, - lightListNeedsUpload: bool = false, coreLen: u32 = 0, sameLodLens: [6]u32 = .{0} ** 6, higherLodLens: [6]u32 = .{0} ** 6, optionalLen: u32 = 0, mutex: std.Thread.Mutex = .{}, bufferAllocation: graphics.SubAllocation = .{.start = 0, .len = 0}, - lightAllocation: graphics.SubAllocation = .{.start = 0, .len = 0}, vertexCount: u31 = 0, byNormalCount: [14]u32 = .{0} ** 14, wasChanged: bool = false, @@ -305,7 +301,6 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh fn deinit(self: *PrimitiveMesh) void { faceBuffer.free(self.bufferAllocation); - lightBuffer.free(self.lightAllocation); self.coreFaces.deinit(main.globalAllocator); self.optionalFaces.deinit(main.globalAllocator); for(&self.neighborFacesSameLod) |*neighborFaces| { @@ -315,7 +310,6 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh neighborFaces.deinit(main.globalAllocator); } main.globalAllocator.free(self.completeList); - main.globalAllocator.free(self.lightList); } fn reset(self: *PrimitiveMesh) void { @@ -356,7 +350,7 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh } } - fn finish(self: *PrimitiveMesh, parent: *ChunkMesh) void { + fn finish(self: *PrimitiveMesh, parent: *ChunkMesh, lightList: *main.List(u32), lightMap: *std.AutoHashMap([4]u32, u16)) void { var len: usize = self.coreFaces.items.len; for(self.neighborFacesSameLod) |neighborFaces| { len += neighborFaces.items.len; @@ -379,10 +373,6 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh } @memcpy(completeList[i..][0..self.optionalFaces.items.len], self.optionalFaces.items); i += self.optionalFaces.items.len; - var lightList = main.List(u32).init(main.stackAllocator); - defer lightList.deinit(); - var lightMap = std.AutoHashMap([4]u32, u16).init(main.stackAllocator.allocator); - defer lightMap.deinit(); self.min = @splat(std.math.floatMax(f32)); self.max = @splat(-std.math.floatMax(f32)); @@ -411,13 +401,7 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh parent.lightingData[0].lock.unlockRead(); parent.lightingData[1].lock.unlockRead(); - const completeLightList = main.globalAllocator.alloc(u32, lightList.items.len); - @memcpy(completeLightList, lightList.items); - self.mutex.lock(); - const oldLightList = self.lightList; - self.lightList = completeLightList; - self.lightListNeedsUpload = true; const oldList = self.completeList; self.completeList = completeList; self.coreLen = @intCast(self.coreFaces.items.len); @@ -430,7 +414,6 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh self.optionalLen = @intCast(self.optionalFaces.items.len); self.mutex.unlock(); main.globalAllocator.free(oldList); - main.globalAllocator.free(oldLightList); } fn getValues(mesh: *ChunkMesh, wx: i32, wy: i32, wz: i32) [6]u8 { @@ -657,10 +640,6 @@ const PrimitiveMesh = struct { // MARK: PrimitiveMesh std.debug.assert(i == fullBuffer.len); self.vertexCount = @intCast(6*fullBuffer.len); self.wasChanged = true; - if(self.lightListNeedsUpload) { - self.lightListNeedsUpload = false; - lightBuffer.uploadData(self.lightList, &self.lightAllocation); - } } }; @@ -694,6 +673,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh lightingData: [2]*lighting.ChannelChunk, opaqueMesh: PrimitiveMesh, transparentMesh: PrimitiveMesh, + lightList: []u32 = &.{}, + lightListNeedsUpload: bool = false, + lightAllocation: graphics.SubAllocation = .{.start = 0, .len = 0}, + lastNeighborsSameLod: [6]?*const ChunkMesh = [_]?*const ChunkMesh{null} ** 6, lastNeighborsHigherLod: [6]?*const ChunkMesh = [_]?*const ChunkMesh{null} ** 6, isNeighborLod: [6]bool = .{false} ** 6, @@ -712,6 +695,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh min: Vec3f = undefined, max: Vec3f = undefined, + blockBreakingFaces: main.List(FaceData), + blockBreakingFacesSortingData: []SortingData = &.{}, + blockBreakingFacesChanged: bool = false, + pub fn init(self: *ChunkMesh, pos: chunk.ChunkPosition, ch: *chunk.Chunk) void { self.* = ChunkMesh{ .pos = pos, @@ -723,6 +710,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh lighting.ChannelChunk.init(ch, false), lighting.ChannelChunk.init(ch, true), }, + .blockBreakingFaces = .init(main.globalAllocator), }; } @@ -737,6 +725,10 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh for(self.lightingData) |lightingChunk| { lightingChunk.deinit(); } + self.blockBreakingFaces.deinit(); + main.globalAllocator.free(self.blockBreakingFacesSortingData); + main.globalAllocator.free(self.lightList); + lightBuffer.free(self.lightAllocation); } pub fn increaseRefCount(self: *ChunkMesh) void { @@ -1332,8 +1324,19 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh pub fn finishData(self: *ChunkMesh) void { main.utils.assertLocked(&self.mutex); - self.opaqueMesh.finish(self); - self.transparentMesh.finish(self); + + var lightList = main.List(u32).init(main.stackAllocator); + defer lightList.deinit(); + var lightMap = std.AutoHashMap([4]u32, u16).init(main.stackAllocator.allocator); + defer lightMap.deinit(); + + self.opaqueMesh.finish(self, &lightList, &lightMap); + self.transparentMesh.finish(self, &lightList, &lightMap); + + self.lightList = main.globalAllocator.realloc(self.lightList, lightList.items.len); + @memcpy(self.lightList, lightList.items); + self.lightListNeedsUpload = true; + self.min = @min(self.opaqueMesh.min, self.transparentMesh.min); self.max = @max(self.opaqueMesh.max, self.transparentMesh.max); } @@ -1341,6 +1344,12 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh pub fn uploadData(self: *ChunkMesh) void { self.opaqueMesh.uploadData(self.isNeighborLod); self.transparentMesh.uploadData(self.isNeighborLod); + + if(self.lightListNeedsUpload) { + self.lightListNeedsUpload = false; + lightBuffer.uploadData(self.lightList, &self.lightAllocation); + } + self.uploadChunkPosition(); } @@ -1504,12 +1513,11 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh chunkBuffer.uploadData(&.{ChunkData{ .position = .{self.pos.wx, self.pos.wy, self.pos.wz}, .voxelSize = self.pos.voxelSize, + .lightStart = self.lightAllocation.start, .vertexStartOpaque = self.opaqueMesh.bufferAllocation.start*4, .faceCountsByNormalOpaque = self.opaqueMesh.byNormalCount, - .lightStartOpaque = self.opaqueMesh.lightAllocation.start, .vertexStartTransparent = self.transparentMesh.bufferAllocation.start*4, .vertexCountTransparent = self.transparentMesh.bufferAllocation.len*6, - .lightStartTransparent = self.transparentMesh.lightAllocation.start, .min = self.min, .max = self.max, .visibilityState = 0, @@ -1526,7 +1534,7 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh } pub fn prepareTransparentRendering(self: *ChunkMesh, playerPosition: Vec3d, chunkList: *main.List(u32)) void { - if(self.transparentMesh.vertexCount == 0) return; + if(self.transparentMesh.vertexCount == 0 and self.blockBreakingFaces.items.len == 0) return; var needsUpdate: bool = false; if(self.transparentMesh.wasChanged) { @@ -1552,8 +1560,8 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh } offset += neighborLen; } - self.sortingOutputBuffer = main.globalAllocator.realloc(self.sortingOutputBuffer, len); self.currentSorting = main.globalAllocator.realloc(self.currentSorting, len); + self.sortingOutputBuffer = main.globalAllocator.realloc(self.sortingOutputBuffer, len + self.blockBreakingFaces.items.len); for(0..self.transparentMesh.coreLen) |i| { self.currentSorting[i].face = self.transparentMesh.completeList[i]; } @@ -1580,6 +1588,15 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh self.lastTransparentUpdatePos = updatePos; needsUpdate = true; } + if(self.blockBreakingFacesChanged) { + self.blockBreakingFacesChanged = false; + self.sortingOutputBuffer = main.globalAllocator.realloc(self.sortingOutputBuffer, self.currentSorting.len + self.blockBreakingFaces.items.len); + self.blockBreakingFacesSortingData = main.globalAllocator.realloc(self.blockBreakingFacesSortingData, self.blockBreakingFaces.items.len); + for(0..self.blockBreakingFaces.items.len) |i| { + self.blockBreakingFacesSortingData[i].face = self.blockBreakingFaces.items[i]; + } + needsUpdate = true; + } if(needsUpdate) { for(self.currentSorting) |*val| { val.update( @@ -1588,10 +1605,13 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh updatePos[2], ); } + for(0..self.blockBreakingFaces.items.len) |i| { + self.blockBreakingFacesSortingData[i].update(updatePos[0], updatePos[1], updatePos[2]); + } // Sort by back vs front face: + var backFaceStart: usize = 0; { - var backFaceStart: usize = 0; var i: usize = 0; var culledStart: usize = self.currentSorting.len; while(culledStart > 0) { @@ -1622,6 +1642,9 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh // Sort it using bucket sort: var buckets: [34*3]u32 = undefined; @memset(&buckets, 0); + for(self.blockBreakingFacesSortingData) |val| { + buckets[34*3 - 1 - val.distance] += 1; + } for(self.currentSorting[0..self.culledSortingCount]) |val| { buckets[34*3 - 1 - val.distance] += 1; } @@ -1632,12 +1655,23 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh prefixSum += copy; } // Move it over into a new buffer: - for(0..self.culledSortingCount) |i| { + for(0..backFaceStart) |i| { const bucket = 34*3 - 1 - self.currentSorting[i].distance; self.sortingOutputBuffer[buckets[bucket]] = self.currentSorting[i].face; buckets[bucket] += 1; } - + // Block breaking faces should be drawn after front faces, but before the corresponding backfaces. + for(self.blockBreakingFacesSortingData) |val| { + const bucket = 34*3 - 1 - val.distance; + self.sortingOutputBuffer[buckets[bucket]] = val.face; + buckets[bucket] += 1; + } + for(backFaceStart..self.culledSortingCount) |i| { + const bucket = 34*3 - 1 - self.currentSorting[i].distance; + self.sortingOutputBuffer[buckets[bucket]] = self.currentSorting[i].face; + buckets[bucket] += 1; + } + self.culledSortingCount += @intCast(self.blockBreakingFaces.items.len); // Upload: faceBuffer.uploadData(self.sortingOutputBuffer[0..self.culledSortingCount], &self.transparentMesh.bufferAllocation); self.uploadChunkPosition(); diff --git a/src/renderer/mesh_storage.zig b/src/renderer/mesh_storage.zig index ef00e555..93ea9e1b 100644 --- a/src/renderer/mesh_storage.zig +++ b/src/renderer/mesh_storage.zig @@ -942,3 +942,85 @@ pub fn updateChunkMesh(mesh: *chunk.Chunk) void { pub fn updateLightMap(map: *LightMap.LightMapFragment) void { mapUpdatableList.enqueue(map); } + +// MARK: Block breaking animation + +pub fn addBreakingAnimation(pos: Vec3i, breakingProgress: f32) void { + const animationFrame: usize = @intFromFloat(breakingProgress*@as(f32, @floatFromInt(main.blocks.meshes.blockBreakingTextures.items.len))); + const texture = main.blocks.meshes.blockBreakingTextures.items[animationFrame]; + + const block = getBlock(pos[0], pos[1], pos[2]) orelse return; + const modelIndex = main.blocks.meshes.model(block); + const model = &main.models.models.items[modelIndex]; + + for(model.internalQuads) |quadIndex| { + addBreakingAnimationFace(pos, quadIndex, texture, null, block.transparent()); + } + for(&model.neighborFacingQuads, 0..) |quads, n| { + for(quads) |quadIndex| { + addBreakingAnimationFace(pos, quadIndex, texture, @enumFromInt(n), block.transparent()); + } + } +} + +fn addBreakingAnimationFace(pos: Vec3i, quadIndex: u16, texture: u16, neighbor: ?chunk.Neighbor, isTransparent: bool) void { + const worldPos = pos +% if(neighbor) |n| n.relPos() else Vec3i{0, 0, 0}; + const relPos = worldPos & @as(Vec3i, @splat(main.chunk.chunkMask)); + const mesh = getMeshAndIncreaseRefCount(.{.wx = worldPos[0], .wy = worldPos[1], .wz = worldPos[2], .voxelSize = 1}) orelse return; + defer mesh.decreaseRefCount(); + mesh.mutex.lock(); + defer mesh.mutex.unlock(); + const lightIndex = blk: { + const meshData = if(isTransparent) &mesh.transparentMesh else &mesh.opaqueMesh; + for(meshData.completeList) |face| { + if(face.position.x == relPos[0] and face.position.y == relPos[1] and face.position.z == relPos[2] and face.blockAndQuad.quadIndex == quadIndex) { + break :blk face.position.lightIndex; + } + } + // The face doesn't exist. + return; + }; + mesh.blockBreakingFacesChanged = true; + mesh.blockBreakingFaces.append(.{ + .position = .{ + .x = @intCast(relPos[0]), + .y = @intCast(relPos[1]), + .z = @intCast(relPos[2]), + .isBackFace = false, + .lightIndex = lightIndex, + }, + .blockAndQuad = .{ + .texture = texture, + .quadIndex = quadIndex, + }, + }); +} + +fn removeBreakingAnimationFace(pos: Vec3i, quadIndex: u16, neighbor: ?chunk.Neighbor) void { + const worldPos = pos +% if(neighbor) |n| n.relPos() else Vec3i{0, 0, 0}; + const relPos = worldPos & @as(Vec3i, @splat(main.chunk.chunkMask)); + const mesh = getMeshAndIncreaseRefCount(.{.wx = worldPos[0], .wy = worldPos[1], .wz = worldPos[2], .voxelSize = 1}) orelse return; + defer mesh.decreaseRefCount(); + for(mesh.blockBreakingFaces.items, 0..) |face, i| { + if(face.position.x == relPos[0] and face.position.y == relPos[1] and face.position.z == relPos[2] and face.blockAndQuad.quadIndex == quadIndex) { + _ = mesh.blockBreakingFaces.swapRemove(i); + mesh.blockBreakingFacesChanged = true; + break; + } + } +} + +pub fn removeBreakingAnimation(pos: Vec3i) void { + const block = getBlock(pos[0], pos[1], pos[2]) orelse return; + const modelIndex = main.blocks.meshes.model(block); + const model = &main.models.models.items[modelIndex]; + + for(model.internalQuads) |quadIndex| { + removeBreakingAnimationFace(pos, quadIndex, null); + } + for(&model.neighborFacingQuads, 0..) |quads, n| { + for(quads) |quadIndex| { + removeBreakingAnimationFace(pos, quadIndex, @enumFromInt(n)); + } + } +}