diff --git a/src/renderer.zig b/src/renderer.zig index ce5eae8f..f9b7f0d2 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -174,7 +174,6 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo // Uses FrustumCulling on the chunks. const frustum = Frustum.init(Vec3f{0, 0, 0}, game.camera.viewMatrix, lastFov, lastWidth, lastHeight); - _ = frustum; const time: u32 = @intCast(std.time.milliTimestamp() & std.math.maxInt(u32)); @@ -196,7 +195,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo chunk_meshing.quadsDrawn = 0; chunk_meshing.transparentQuadsDrawn = 0; - const meshes = mesh_storage.updateAndGetRenderChunks(world.conn, playerPos, settings.renderDistance); + const meshes = mesh_storage.updateAndGetRenderChunks(world.conn, &frustum, playerPos, settings.renderDistance); gpu_performance_measuring.startQuery(.chunk_rendering_preparation); const direction = crosshairDirection(game.camera.viewMatrix, lastFov, lastWidth, lastHeight); diff --git a/src/renderer/chunk_meshing.zig b/src/renderer/chunk_meshing.zig index d5848a0a..767b7d2c 100644 --- a/src/renderer/chunk_meshing.zig +++ b/src/renderer/chunk_meshing.zig @@ -658,17 +658,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh self.distance = @abs(fullDx) + @abs(fullDy) + @abs(fullDz); } }; - const BoundingRectToNeighborChunk = struct { - min: Vec3i = @splat(std.math.maxInt(i32)), - max: Vec3i = @splat(0), - - fn adjustToBlock(self: *BoundingRectToNeighborChunk, block: Block, pos: Vec3i, neighbor: chunk.Neighbor) void { - if(block.viewThrough()) { - self.min = @min(self.min, pos); - self.max = @max(self.max, pos + neighbor.orthogonalComponents()); - } - } - }; pos: chunk.ChunkPosition, size: i32, chunk: *chunk.Chunk, @@ -695,8 +684,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh min: Vec3f = undefined, max: Vec3f = undefined, - chunkBorders: [6]BoundingRectToNeighborChunk = [1]BoundingRectToNeighborChunk{.{}} ** 6, - pub fn init(self: *ChunkMesh, pos: chunk.ChunkPosition, ch: *chunk.Chunk) void { self.* = ChunkMesh{ .pos = pos, @@ -1184,19 +1171,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh } } - // Check out the borders: - var x: u8 = 0; - while(x < chunk.chunkSize): (x += 1) { - var y: u8 = 0; - while(y < chunk.chunkSize): (y += 1) { - self.chunkBorders[chunk.Neighbor.dirNegX.toInt()].adjustToBlock(self.chunk.data.getValue(chunk.getIndex(0, x, y)), .{0, x, y}, chunk.Neighbor.dirNegX); - self.chunkBorders[chunk.Neighbor.dirPosX.toInt()].adjustToBlock(self.chunk.data.getValue(chunk.getIndex(chunk.chunkSize-1, x, y)), .{chunk.chunkSize, x, y}, chunk.Neighbor.dirPosX); - self.chunkBorders[chunk.Neighbor.dirNegY.toInt()].adjustToBlock(self.chunk.data.getValue(chunk.getIndex(x, 0, y)), .{x, 0, y}, chunk.Neighbor.dirNegY); - self.chunkBorders[chunk.Neighbor.dirPosY.toInt()].adjustToBlock(self.chunk.data.getValue(chunk.getIndex(x, chunk.chunkSize-1, y)), .{x, chunk.chunkSize, y}, chunk.Neighbor.dirPosY); - self.chunkBorders[chunk.Neighbor.dirDown.toInt()].adjustToBlock(self.chunk.data.getValue(chunk.getIndex(x, y, 0)), .{x, y, 0}, chunk.Neighbor.dirDown); - self.chunkBorders[chunk.Neighbor.dirUp.toInt()].adjustToBlock(self.chunk.data.getValue(chunk.getIndex(x, y, chunk.chunkSize-1)), .{x, y, chunk.chunkSize}, chunk.Neighbor.dirUp); - } - } self.mutex.unlock(); self.finishNeighbors(lightRefreshList); diff --git a/src/renderer/mesh_storage.zig b/src/renderer/mesh_storage.zig index ccf348d5..63d74579 100644 --- a/src/renderer/mesh_storage.zig +++ b/src/renderer/mesh_storage.zig @@ -24,8 +24,6 @@ const chunk_meshing = @import("chunk_meshing.zig"); const ChunkMeshNode = struct { mesh: ?*chunk_meshing.ChunkMesh = null, lod: u3 = undefined, - min: Vec2f = undefined, - max: Vec2f = undefined, active: bool = false, rendered: bool = false, finishedMeshing: bool = false, // Must be synced with mesh.finishedMeshing @@ -536,7 +534,7 @@ fn createNewMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: i32, meshR } } -pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: Vec3d, renderDistance: i32) []*chunk_meshing.ChunkMesh { // MARK: updateAndGetRenderChunks() +pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, frustum: *const main.renderer.Frustum, playerPos: Vec3d, renderDistance: i32) []*chunk_meshing.ChunkMesh { // MARK: updateAndGetRenderChunks() meshList.clearRetainingCapacity(); if(lastRD != renderDistance) { network.Protocols.genericUpdate.sendRenderDistance(conn, renderDistance); @@ -565,9 +563,9 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V network.Protocols.lightMapRequest.sendRequest(conn, mapRequests.items); network.Protocols.chunkRequest.sendRequest(conn, meshRequests.items, .{lastPx, lastPy, lastPz}); - // Does occlusion using a breadth-first search that caches an on-screen visibility rectangle. + // Finds all visible chunks and lod chunks using a breadth-first search. - const OcclusionData = struct { + const SearchData = struct { node: *ChunkMeshNode, distance: f64, @@ -578,8 +576,7 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V } }; - // TODO: Is there a way to combine this with minecraft's approach? - var searchList = std.PriorityQueue(OcclusionData, void, OcclusionData.compare).init(main.stackAllocator.allocator, {}); + var searchList = std.PriorityQueue(SearchData, void, SearchData.compare).init(main.stackAllocator.allocator, {}); defer searchList.deinit(); { var firstPos = chunk.ChunkPosition{ @@ -597,8 +594,6 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V const hasMesh = node.finishedMeshing; if(hasMesh) { node.lod = lod; - node.min = @splat(-1); - node.max = @splat(1); node.active = true; node.rendered = true; searchList.add(.{ @@ -615,168 +610,32 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V } var nodeList = main.List(*ChunkMeshNode).init(main.stackAllocator); defer nodeList.deinit(); - const projRotMat = game.projectionMatrix.mul(game.camera.viewMatrix); while(searchList.removeOrNull()) |data| { std.debug.assert(data.node.finishedMeshing); nodeList.append(data.node); data.node.active = false; - mutex.lock(); - const mesh = data.node.mesh orelse { - mutex.unlock(); - continue; - }; - mesh.increaseRefCount(); - defer mesh.decreaseRefCount(); - mutex.unlock(); + const mesh = data.node.mesh.?; // No need to lock the mutex, since no other thread is allowed to overwrite the mesh (unless it's null). + const pos = mesh.pos; mesh.visibilityMask = 0xff; - const relPos: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{mesh.pos.wx, mesh.pos.wy, mesh.pos.wz})) - playerPos; + const relPos: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{pos.wx, pos.wy, pos.wz})) - playerPos; const relPosFloat: Vec3f = @floatCast(relPos); var isNeighborLod: [6]bool = .{false} ** 6; - for(chunk.Neighbor.iterable) |neighbor| continueNeighborLoop: { + neighborLoop: for(chunk.Neighbor.iterable) |neighbor| { const component = neighbor.extractDirectionComponent(relPosFloat); - if(neighbor.isPositive() and component + @as(f32, @floatFromInt(chunk.chunkSize*mesh.pos.voxelSize)) <= 0) continue; + if(neighbor.isPositive() and component + @as(f32, @floatFromInt(chunk.chunkSize*pos.voxelSize)) <= 0) continue; if(!neighbor.isPositive() and component >= 0) continue; - if(@reduce(.Or, mesh.chunkBorders[neighbor.toInt()].max < mesh.chunkBorders[neighbor.toInt()].min)) continue; // There was not a single transparent block along the chunk border. TODO: Find a better solution. - const minVec: Vec3f = @floatFromInt(mesh.chunkBorders[neighbor.toInt()].min*@as(Vec3i, @splat(mesh.pos.voxelSize))); - const maxVec: Vec3f = @floatFromInt(mesh.chunkBorders[neighbor.toInt()].max*@as(Vec3i, @splat(mesh.pos.voxelSize))); - var xyMin: Vec2f = .{10, 10}; - var xyMax: Vec2f = .{-10, -10}; - var numberOfNegatives: u8 = 0; - var corners: [5]Vec4f = undefined; - var curCorner: usize = 0; - for(0..2) |a| { - for(0..2) |b| { - - var cornerVector: Vec3f = undefined; - switch(neighbor.vectorComponent()) { - .x => { - cornerVector = @select(f32, @Vector(3, bool){true, a == 0, b == 0}, minVec, maxVec); - }, - .y => { - cornerVector = @select(f32, @Vector(3, bool){a == 0, true, b == 0}, minVec, maxVec); - }, - .z => { - cornerVector = @select(f32, @Vector(3, bool){a == 0, b == 0, true}, minVec, maxVec); - }, - } - corners[curCorner] = projRotMat.mulVec(vec.combine(relPosFloat + cornerVector, 1)); - if(corners[curCorner][3] < 0) { - numberOfNegatives += 1; - } - curCorner += 1; - } - } - switch(numberOfNegatives) { // Oh, so complicated. But this should only trigger very close to the player. - 4 => continue, - 0 => {}, - 1 => { - // Needs to duplicate the problematic corner and move it onto the projected plane. - var problematicOne: usize = 0; - for(0..curCorner) |i| { - if(corners[i][3] < 0) { - problematicOne = i; - break; - } - } - const problematicVector = corners[problematicOne]; - // The two neighbors of the quad: - const neighborA = corners[problematicOne ^ 1]; - const neighborB = corners[problematicOne ^ 2]; - // Move the problematic point towards the neighbor: - const one: Vec4f = @splat(1); - const weightA: Vec4f = @splat(problematicVector[3]/(problematicVector[3] - neighborA[3])); - var towardsA = neighborA*weightA + problematicVector*(one - weightA); - towardsA[3] = 0; // Prevent inaccuracies - const weightB: Vec4f = @splat(problematicVector[3]/(problematicVector[3] - neighborB[3])); - var towardsB = neighborB*weightB + problematicVector*(one - weightB); - towardsB[3] = 0; // Prevent inaccuracies - corners[problematicOne] = towardsA; - corners[curCorner] = towardsB; - curCorner += 1; - }, - 2 => { - // Needs to move the two problematic corners onto the projected plane. - var problematicOne: usize = undefined; - for(0..curCorner) |i| { - if(corners[i][3] < 0) { - problematicOne = i; - break; - } - } - const problematicVectorOne = corners[problematicOne]; - var problematicTwo: usize = undefined; - for(problematicOne+1..curCorner) |i| { - if(corners[i][3] < 0) { - problematicTwo = i; - break; - } - } - const problematicVectorTwo = corners[problematicTwo]; - - const commonDirection = problematicOne ^ problematicTwo; - const projectionDirection = commonDirection ^ 0b11; - // The respective neighbors: - const neighborOne = corners[problematicOne ^ projectionDirection]; - const neighborTwo = corners[problematicTwo ^ projectionDirection]; - // Move the problematic points towards the neighbor: - const one: Vec4f = @splat(1); - const weightOne: Vec4f = @splat(problematicVectorOne[3]/(problematicVectorOne[3] - neighborOne[3])); - var towardsOne = neighborOne*weightOne + problematicVectorOne*(one - weightOne); - towardsOne[3] = 0; // Prevent inaccuracies - corners[problematicOne] = towardsOne; - - const weightTwo: Vec4f = @splat(problematicVectorTwo[3]/(problematicVectorTwo[3] - neighborTwo[3])); - var towardsTwo = neighborTwo*weightTwo + problematicVectorTwo*(one - weightTwo); - towardsTwo[3] = 0; // Prevent inaccuracies - corners[problematicTwo] = towardsTwo; - }, - 3 => { - // Throw away the far problematic vector, move the other two onto the projection plane. - var neighborIndex: usize = undefined; - for(0..curCorner) |i| { - if(corners[i][3] >= 0) { - neighborIndex = i; - break; - } - } - const neighborVector = corners[neighborIndex]; - const problematicVectorOne = corners[neighborIndex ^ 1]; - const problematicVectorTwo = corners[neighborIndex ^ 2]; - // Move the problematic points towards the neighbor: - const one: Vec4f = @splat(1); - const weightOne: Vec4f = @splat(problematicVectorOne[3]/(problematicVectorOne[3] - neighborVector[3])); - var towardsOne = neighborVector*weightOne + problematicVectorOne*(one - weightOne); - towardsOne[3] = 0; // Prevent inaccuracies - - const weightTwo: Vec4f = @splat(problematicVectorTwo[3]/(problematicVectorTwo[3] - neighborVector[3])); - var towardsTwo = neighborVector*weightTwo + problematicVectorTwo*(one - weightTwo); - towardsTwo[3] = 0; // Prevent inaccuracies - - corners[0] = neighborVector; - corners[1] = towardsOne; - corners[2] = towardsTwo; - curCorner = 3; - }, - else => unreachable, - } - - for(0..curCorner) |i| { - const projected = corners[i]; - const xy = vec.xy(projected)/@as(Vec2f, @splat(@max(0, projected[3]))); - xyMin = @min(xyMin, xy); - xyMax = @max(xyMax, xy); - } - const min = @max(xyMin, data.node.min); - const max = @min(xyMax, data.node.max); - if(@reduce(.Or, min >= max)) continue; // Nothing to render. var neighborPos = chunk.ChunkPosition{ - .wx = mesh.pos.wx +% neighbor.relX()*chunk.chunkSize*mesh.pos.voxelSize, - .wy = mesh.pos.wy +% neighbor.relY()*chunk.chunkSize*mesh.pos.voxelSize, - .wz = mesh.pos.wz +% neighbor.relZ()*chunk.chunkSize*mesh.pos.voxelSize, - .voxelSize = mesh.pos.voxelSize, + .wx = pos.wx +% neighbor.relX()*chunk.chunkSize*pos.voxelSize, + .wy = pos.wy +% neighbor.relY()*chunk.chunkSize*pos.voxelSize, + .wz = pos.wz +% neighbor.relZ()*chunk.chunkSize*pos.voxelSize, + .voxelSize = pos.voxelSize, }; + if(!getNodePointer(neighborPos).active) { // Don't repeat the same frustum check all the time. + if(!frustum.testAAB(relPosFloat + @as(Vec3f, @floatFromInt(Vec3i{neighbor.relX()*chunk.chunkSize*pos.voxelSize, neighbor.relY()*chunk.chunkSize*pos.voxelSize, neighbor.relZ()*chunk.chunkSize*pos.voxelSize})), @splat(@floatFromInt(chunk.chunkSize*pos.voxelSize)))) + continue; + } var lod: u3 = data.node.lod; lodLoop: while(lod <= settings.highestLOD) : (lod += 1) { defer { @@ -824,13 +683,8 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V if(lod != data.node.lod) { isNeighborLod[neighbor.toInt()] = true; } - if(node.active) { - node.min = @min(node.min, min); - node.max = @max(node.max, max); - } else { + if(!node.active) { node.lod = lod; - node.min = min; - node.max = max; node.active = true; searchList.add(.{ .node = node, @@ -838,7 +692,7 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V }) catch unreachable; node.rendered = true; } - break :continueNeighborLoop; + continue :neighborLoop; } } } @@ -848,20 +702,12 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V node.rendered = false; if(!node.finishedMeshing) continue; - node.mutex.lock(); - const mesh = node.mesh orelse { - node.mutex.unlock(); - continue; - }; - mesh.increaseRefCount(); - defer mesh.decreaseRefCount(); - node.mutex.unlock(); + const mesh = node.mesh.?; // No need to lock the mutex, since no other thread is allowed to overwrite the mesh (unless it's null). if(mesh.pos.voxelSize != @as(u31, 1) << settings.highestLOD) { const parent = getNodePointer(.{.wx=mesh.pos.wx, .wy=mesh.pos.wy, .wz=mesh.pos.wz, .voxelSize=mesh.pos.voxelSize << 1}); - parent.mutex.lock(); - defer parent.mutex.unlock(); - if(parent.mesh) |parentMesh| { + if(parent.finishedMeshing) { + const parentMesh = parent.mesh.?; // No need to lock the mutex, since no other thread is allowed to overwrite the mesh (unless it's null). const sizeShift = chunk.chunkShift + @ctz(mesh.pos.voxelSize); const octantIndex: u3 = @intCast((mesh.pos.wx>>sizeShift & 1) | (mesh.pos.wy>>sizeShift & 1)<<1 | (mesh.pos.wz>>sizeShift & 1)<<2); parentMesh.visibilityMask &= ~(@as(u8, 1) << octantIndex);