From a6248dc5c2ac3bcc21a7f0b92f971def2c015c57 Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Sat, 25 Nov 2023 16:20:05 +0100 Subject: [PATCH] Dynamically switch to higher lod chunks, when otherwise a high-to-low lod border would be drawn(I have no mesh data for these). This required dynamically re-uploading some chunk meshes. Fixes #130 --- src/chunk.zig | 74 +++++++++++++++++++++++++++--------------------- src/renderer.zig | 67 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 103 insertions(+), 38 deletions(-) diff --git a/src/chunk.zig b/src/chunk.zig index f0f602ad..ad8aab4d 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -28,7 +28,7 @@ pub const chunkVolume: u31 = 1 << 3*chunkShift; pub const chunkMask: i32 = chunkSize - 1; /// Contains a bunch of constants used to describe neighboring blocks. -pub const Neighbors = struct { +pub const Neighbors = struct { // TODO: Should this be an enum? /// How many neighbors there are. pub const neighbors: u3 = 6; /// Directions → Index @@ -573,11 +573,11 @@ pub const meshing = struct { } } - fn finish(self: *PrimitiveMesh, parent: *ChunkMesh) !void { + fn finish(self: *PrimitiveMesh, parent: *ChunkMesh, isNeighborLod: [6]bool) !void { var len: usize = self.coreFaces.items.len; var neighborFaceLists: [6][]FaceData = undefined; for(0..6) |i| { - if(parent.lastNeighborsSameLod[i] == null) { + if(isNeighborLod[i]) { neighborFaceLists[i] = self.neighborFacesHigherLod[i].items; } else { neighborFaceLists[i] = self.neighborFacesSameLod[i].items; @@ -771,6 +771,7 @@ pub const meshing = struct { transparentMesh: PrimitiveMesh, lastNeighborsSameLod: [6]?*const ChunkMesh = [_]?*const ChunkMesh{null} ** 6, lastNeighborsHigherLod: [6]?*const ChunkMesh = [_]?*const ChunkMesh{null} ** 6, + forceHigherLod: [6]bool = .{false} ** 6, visibilityMask: u8 = 0xff, currentSorting: []SortingData = &.{}, sortingOutputBuffer: []FaceData = &.{}, @@ -1015,15 +1016,11 @@ pub const meshing = struct { } } if(neighborMesh != self) { - try neighborMesh.opaqueMesh.finish(neighborMesh); - try neighborMesh.voxelMesh.finish(neighborMesh); - try neighborMesh.transparentMesh.finish(neighborMesh); + try neighborMesh.uploadData(); } } self.chunk.blocks[getIndex(x, y, z)] = newBlock; - try self.opaqueMesh.finish(self); - try self.voxelMesh.finish(self); - try self.transparentMesh.finish(self); + try self.uploadData(); } pub inline fn constructFaceData(block: Block, normal: u3, x: i32, y: i32, z: i32, comptime backFace: bool) FaceData { @@ -1034,6 +1031,33 @@ pub const meshing = struct { }; } + fn clearNeighbor(self: *ChunkMesh, neighbor: u3, comptime isLod: bool) void { + self.opaqueMesh.clearNeighbor(neighbor, isLod); + self.voxelMesh.clearNeighbor(neighbor, isLod); + self.transparentMesh.clearNeighbor(neighbor, isLod); + } + + fn uploadData(self: *ChunkMesh) !void { + const isNeighborLod: [6]bool = .{ + self.lastNeighborsSameLod[0] == null or self.forceHigherLod[0], + self.lastNeighborsSameLod[1] == null or self.forceHigherLod[1], + self.lastNeighborsSameLod[2] == null or self.forceHigherLod[2], + self.lastNeighborsSameLod[3] == null or self.forceHigherLod[3], + self.lastNeighborsSameLod[4] == null or self.forceHigherLod[4], + self.lastNeighborsSameLod[5] == null or self.forceHigherLod[5], + }; + try self.opaqueMesh.finish(self, isNeighborLod); + try self.voxelMesh.finish(self, isNeighborLod); + try self.transparentMesh.finish(self, isNeighborLod); + } + + pub fn changeLodBorders(self: *ChunkMesh, forceHigherLod: [6]bool) !void { + if(!std.meta.eql(forceHigherLod, self.forceHigherLod)) { + self.forceHigherLod = forceHigherLod; + try self.uploadData(); + } + } + pub fn uploadDataAndFinishNeighbors(self: *ChunkMesh) !void { for(Neighbors.iterable) |neighbor| { const nullNeighborMesh = renderer.RenderStructure.getNeighborFromRenderThread(self.pos, self.pos.voxelSize, neighbor); @@ -1042,12 +1066,8 @@ pub const meshing = struct { if(self.lastNeighborsSameLod[neighbor] == neighborMesh) continue; self.lastNeighborsSameLod[neighbor] = neighborMesh; neighborMesh.lastNeighborsSameLod[neighbor ^ 1] = self; - self.opaqueMesh.clearNeighbor(neighbor, false); - self.voxelMesh.clearNeighbor(neighbor, false); - self.transparentMesh.clearNeighbor(neighbor, false); - neighborMesh.opaqueMesh.clearNeighbor(neighbor ^ 1, false); - neighborMesh.voxelMesh.clearNeighbor(neighbor ^ 1, false); - neighborMesh.transparentMesh.clearNeighbor(neighbor ^ 1, false); + self.clearNeighbor(neighbor, false); + neighborMesh.clearNeighbor(neighbor ^ 1, false); const x3: i32 = if(neighbor & 1 == 0) chunkMask else 0; var x1: i32 = 0; while(x1 < chunkSize): (x1 += 1) { @@ -1104,33 +1124,25 @@ pub const meshing = struct { } } } - try neighborMesh.opaqueMesh.finish(neighborMesh); - try neighborMesh.voxelMesh.finish(neighborMesh); - try neighborMesh.transparentMesh.finish(neighborMesh); + try neighborMesh.uploadData(); } else { if(self.lastNeighborsSameLod[neighbor] != null) { - self.opaqueMesh.clearNeighbor(neighbor, false); - self.voxelMesh.clearNeighbor(neighbor, false); - self.transparentMesh.clearNeighbor(neighbor, false); + self.clearNeighbor(neighbor, false); + self.lastNeighborsSameLod[neighbor] = null; } - self.lastNeighborsSameLod[neighbor] = null; } // lod border: if(self.pos.voxelSize == 1 << settings.highestLOD) continue; const neighborMesh = renderer.RenderStructure.getNeighborFromRenderThread(self.pos, 2*self.pos.voxelSize, neighbor) orelse { if(self.lastNeighborsHigherLod[neighbor] != null) { - self.opaqueMesh.clearNeighbor(neighbor, true); - self.voxelMesh.clearNeighbor(neighbor, true); - self.transparentMesh.clearNeighbor(neighbor, true); + self.clearNeighbor(neighbor, true); + self.lastNeighborsHigherLod[neighbor] = null; } - self.lastNeighborsHigherLod[neighbor] = null; continue; }; if(self.lastNeighborsHigherLod[neighbor] == neighborMesh) continue; self.lastNeighborsHigherLod[neighbor] = neighborMesh; - self.opaqueMesh.clearNeighbor(neighbor, true); - self.voxelMesh.clearNeighbor(neighbor, true); - self.transparentMesh.clearNeighbor(neighbor, true); + self.clearNeighbor(neighbor, true); const x3: i32 = if(neighbor & 1 == 0) chunkMask else 0; const offsetX = @divExact(self.pos.wx, self.pos.voxelSize) & chunkSize; const offsetY = @divExact(self.pos.wy, self.pos.voxelSize) & chunkSize; @@ -1179,9 +1191,7 @@ pub const meshing = struct { } } } - try self.opaqueMesh.finish(self); - try self.voxelMesh.finish(self); - try self.transparentMesh.finish(self); + try self.uploadData(); } pub fn render(self: *ChunkMesh, playerPosition: Vec3d) void { diff --git a/src/renderer.zig b/src/renderer.zig index d9f9c564..f8817748 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -874,6 +874,7 @@ pub const RenderStructure = struct { min: Vec2f, max: Vec2f, active: bool, + rendered: bool, }; const storageSize = 32; const storageMask = storageSize - 1; @@ -903,6 +904,7 @@ pub const RenderStructure = struct { storageList.* = try main.globalAllocator.create([storageSize*storageSize*storageSize]ChunkMeshNode); for(storageList.*) |*val| { val.mesh = null; + val.rendered = false; } } } @@ -1223,6 +1225,7 @@ pub const RenderStructure = struct { node.min = @splat(-1); node.max = @splat(1); node.active = true; + node.rendered = true; try searchList.add(.{ .node = node, .distance = 0, @@ -1235,14 +1238,17 @@ pub const RenderStructure = struct { firstPos.voxelSize *= 2; } } + var nodeList = std.ArrayList(*ChunkMeshNode).init(main.globalAllocator); + defer nodeList.deinit(); const projRotMat = game.projectionMatrix.mul(game.camera.viewMatrix); while(searchList.removeOrNull()) |data| { + try nodeList.append(data.node); data.node.active = false; const mesh = data.node.mesh.?; mesh.visibilityMask = 0xff; - try meshList.append(mesh); const relPos: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{mesh.pos.wx, mesh.pos.wy, mesh.pos.wz})) - playerPos; const relPosFloat: Vec3f = @floatCast(relPos); + var forceNeighborsLod: [6]bool = .{false} ** 6; for(chunk.Neighbors.iterable) |neighbor| continueNeighborLoop: { const component = chunk.Neighbors.extractDirectionComponent(neighbor, relPos); if(chunk.Neighbors.isPositive[neighbor] and component + @as(f64, @floatFromInt(chunk.chunkSize*mesh.pos.voxelSize)) <= 0) continue; @@ -1388,8 +1394,53 @@ pub const RenderStructure = struct { }; var lod: u3 = data.node.lod; while(lod <= settings.highestLOD) : (lod += 1) { + defer { + neighborPos.wx &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize); + neighborPos.wy &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize); + neighborPos.wz &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize); + neighborPos.voxelSize *= 2; + } const node = getNodeFromRenderThread(neighborPos); if(node.mesh != null) { + // Ensure that there are no high-to-low lod transitions, which would produce cracks. + if(lod == data.node.lod and lod != settings.highestLOD and !node.rendered) { + var isValid: bool = true; + const relPos2: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{neighborPos.wx, neighborPos.wy, neighborPos.wz})) - playerPos; + for(chunk.Neighbors.iterable) |neighbor2| { + const component2 = chunk.Neighbors.extractDirectionComponent(neighbor2, relPos2); + if(chunk.Neighbors.isPositive[neighbor2] and component2 + @as(f64, @floatFromInt(chunk.chunkSize*node.mesh.?.pos.voxelSize)) >= 0) continue; + if(!chunk.Neighbors.isPositive[neighbor2] and component2 <= 0) continue; + { // Check the chunk of same lod: + const neighborPos2 = chunk.ChunkPosition{ + .wx = neighborPos.wx + chunk.Neighbors.relX[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, + .wy = neighborPos.wy + chunk.Neighbors.relY[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, + .wz = neighborPos.wz + chunk.Neighbors.relZ[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, + .voxelSize = neighborPos.voxelSize, + }; + const node2 = getNodeFromRenderThread(neighborPos2); + if(node2.rendered) { + continue; + } + } + { // Check the chunk of higher lod + const neighborPos2 = chunk.ChunkPosition{ + .wx = neighborPos.wx + chunk.Neighbors.relX[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, + .wy = neighborPos.wy + chunk.Neighbors.relY[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, + .wz = neighborPos.wz + chunk.Neighbors.relZ[neighbor2]*chunk.chunkSize*neighborPos.voxelSize, + .voxelSize = neighborPos.voxelSize << 1, + }; + const node2 = getNodeFromRenderThread(neighborPos2); + if(node2.rendered) { + isValid = false; + break; + } + } + } + if(!isValid) { + forceNeighborsLod[neighbor] = true; + continue; + } + } if(node.active) { node.min = @min(node.min, min); node.max = @max(node.max, max); @@ -1402,17 +1453,17 @@ pub const RenderStructure = struct { .node = node, .distance = node.mesh.?.pos.getMaxDistanceSquared(playerPos), }); + node.rendered = true; } break :continueNeighborLoop; } - neighborPos.wx &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize); - neighborPos.wy &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize); - neighborPos.wz &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize); - neighborPos.voxelSize *= 2; } } + try mesh.changeLodBorders(forceNeighborsLod); } - for(meshList.items) |mesh| { + for(nodeList.items) |node| { + node.rendered = false; + const mesh = node.mesh.?; if(mesh.pos.voxelSize != @as(u31, 1) << settings.highestLOD) { const parent = getNodeFromRenderThread(.{.wx=mesh.pos.wx, .wy=mesh.pos.wy, .wz=mesh.pos.wz, .voxelSize=mesh.pos.voxelSize << 1}); if(parent.mesh) |parentMesh| { @@ -1425,6 +1476,10 @@ pub const RenderStructure = struct { try mesh.uploadDataAndFinishNeighbors(); mesh.needsNeighborUpdate = false; } + // Remove empty meshes. + if(mesh.opaqueMesh.vertexCount != 0 or mesh.voxelMesh.vertexCount != 0 or mesh.transparentMesh.vertexCount != 0) { + try meshList.append(mesh); + } } lastPx = px;