diff --git a/src/chunk.zig b/src/chunk.zig index 91aa1735..b37cc16c 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -52,6 +52,27 @@ pub const Neighbors = struct { pub const bitMask = [_]u6 {0x01, 0x02, 0x04, 0x08, 0x10, 0x20}; /// To iterate over all neighbors easily pub const iterable = [_]u3 {0, 1, 2, 3, 4, 5}; + /// Marks the two dimension that are orthogonal + pub const orthogonalComponents = [_]Vec3i { + .{1, 0, 1}, + .{1, 0, 1}, + .{0, 1, 1}, + .{0, 1, 1}, + .{1, 1, 0}, + .{1, 1, 0}, + }; + + pub const isPositive = [_]bool {true, false, true, false, true, false}; + pub const vectorComponent = [_]enum(u2){x = 0, y = 1, z = 2} {.y, .y, .x, .x, .z, .z}; + + pub fn extractDirectionComponent(self: u3, in: anytype) @TypeOf(in[0]) { + switch(self) { + inline else => |val| { + if(val >= 6) unreachable; + return in[@intFromEnum(vectorComponent[val])]; + } + } + } }; /// Gets the index of a given position inside this chunk. @@ -105,6 +126,25 @@ pub const ChunkPosition = struct { return dx*dx + dy*dy + dz*dz; } + pub fn getMaxDistanceSquared(self: ChunkPosition, playerPosition: Vec3d) f64 { + var halfWidth: f64 = @floatFromInt(self.voxelSize*@divExact(chunkSize, 2)); + var dx = @fabs(@as(f64, @floatFromInt(self.wx)) + halfWidth - playerPosition[0]); + var dy = @fabs(@as(f64, @floatFromInt(self.wy)) + halfWidth - playerPosition[1]); + var dz = @fabs(@as(f64, @floatFromInt(self.wz)) + halfWidth - playerPosition[2]); + dx = dx + halfWidth; + dy = dy + halfWidth; + dz = dz + halfWidth; + return dx*dx + dy*dy + dz*dz; + } + + pub fn getCenterDistanceSquared(self: ChunkPosition, playerPosition: Vec3d) f64 { + var halfWidth: f64 = @floatFromInt(self.voxelSize*@divExact(chunkSize, 2)); + var dx = @as(f64, @floatFromInt(self.wx)) + halfWidth - playerPosition[0]; + var dy = @as(f64, @floatFromInt(self.wy)) + halfWidth - playerPosition[1]; + var dz = @as(f64, @floatFromInt(self.wz)) + halfWidth - playerPosition[2]; + return dx*dx + dy*dy + dz*dz; + } + pub fn getPriority(self: ChunkPosition, playerPos: Vec3d) f32 { return -@as(f32, @floatCast(self.getMinDistanceSquared(playerPos)))/@as(f32, @floatFromInt(self.voxelSize*self.voxelSize)) + 2*@as(f32, @floatFromInt(std.math.log2_int(u31, self.voxelSize)*chunkSize*chunkSize)); } @@ -372,6 +412,8 @@ pub const meshing = struct { var vbo: c_uint = undefined; var faces: std.ArrayList(u32) = undefined; pub var faceBuffer: graphics.LargeBuffer = undefined; + pub var quadsDrawn: usize = 0; + pub var transparentQuadsDrawn: usize = 0; pub fn init() !void { shader = try Shader.initAndGetUniforms("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/chunk_fragment.fs", &uniforms); @@ -585,6 +627,17 @@ pub const meshing = struct { index: u32, distance: u32, }; + const BoundingRectToNeighborChunk = struct { + min: Vec3i = @splat(std.math.maxInt(i32)), + max: Vec3i = @splat(0), + + fn adjustToBlock(self: *BoundingRectToNeighborChunk, block: Block, pos: Vec3i, neighbor: u3) void { + if(block.viewThrough()) { + self.min = @min(self.min, pos); + self.max = @max(self.max, pos + Neighbors.orthogonalComponents[neighbor]); + } + } + }; pos: ChunkPosition, size: i32, chunk: std.atomic.Atomic(?*Chunk), @@ -599,6 +652,8 @@ pub const meshing = struct { currentSortingSwap: []SortingData = &.{}, lastTransparentUpdatePos: Vec3i = Vec3i{0, 0, 0}, + chunkBorders: [6]BoundingRectToNeighborChunk = [1]BoundingRectToNeighborChunk{.{}} ** 6, + pub fn init(allocator: Allocator, pos: ChunkPosition) ChunkMesh { return ChunkMesh{ .pos = pos, @@ -683,6 +738,19 @@ pub const meshing = struct { } } } + // Check out the borders: + x = 0; + while(x < chunkSize): (x += 1) { + var y: u8 = 0; + while(y < chunkSize): (y += 1) { + self.chunkBorders[Neighbors.dirNegX].adjustToBlock((&chunk.blocks)[getIndex(0, x, y)], .{0, x, y}, Neighbors.dirNegX); // TODO: Wait for the compiler bug to get fixed. + self.chunkBorders[Neighbors.dirPosX].adjustToBlock((&chunk.blocks)[getIndex(chunkSize-1, x, y)], .{chunkSize, x, y}, Neighbors.dirPosX); // TODO: Wait for the compiler bug to get fixed. + self.chunkBorders[Neighbors.dirDown].adjustToBlock((&chunk.blocks)[getIndex(x, 0, y)], .{x, 0, y}, Neighbors.dirDown); // TODO: Wait for the compiler bug to get fixed. + self.chunkBorders[Neighbors.dirUp].adjustToBlock((&chunk.blocks)[getIndex(x, chunkSize-1, y)], .{x, chunkSize, y}, Neighbors.dirUp); // TODO: Wait for the compiler bug to get fixed. + self.chunkBorders[Neighbors.dirNegZ].adjustToBlock((&chunk.blocks)[getIndex(x, y, 0)], .{x, y, 0}, Neighbors.dirNegZ); // TODO: Wait for the compiler bug to get fixed. + self.chunkBorders[Neighbors.dirPosZ].adjustToBlock((&chunk.blocks)[getIndex(x, y, chunkSize-1)], .{x, y, chunkSize}, Neighbors.dirPosZ); // TODO: Wait for the compiler bug to get fixed. + } + } if(self.chunk.swap(chunk, .Monotonic)) |oldChunk| { main.globalAllocator.destroy(oldChunk); @@ -948,6 +1016,7 @@ pub const meshing = struct { ); c.glUniform1i(uniforms.visibilityMask, self.visibilityMask); c.glUniform1i(uniforms.voxelSize, self.pos.voxelSize); + quadsDrawn += self.opaqueMesh.faces.items.len; c.glDrawElementsBaseVertex(c.GL_TRIANGLES, self.opaqueMesh.vertexCount, c.GL_UNSIGNED_INT, null, self.opaqueMesh.bufferAllocation.start/8*4); } @@ -1046,6 +1115,7 @@ pub const meshing = struct { ); c.glUniform1i(transparentUniforms.visibilityMask, self.visibilityMask); c.glUniform1i(transparentUniforms.voxelSize, self.pos.voxelSize); + transparentQuadsDrawn += self.transparentMesh.faces.items.len; c.glDrawElementsBaseVertex(c.GL_TRIANGLES, self.transparentMesh.vertexCount, c.GL_UNSIGNED_INT, null, self.transparentMesh.bufferAllocation.start/8*4); } }; diff --git a/src/gui/windows/debug.zig b/src/gui/windows/debug.zig index 1ab11010..50710c4e 100644 --- a/src/gui/windows/debug.zig +++ b/src/gui/windows/debug.zig @@ -40,8 +40,9 @@ fn flawedRender() !void { y += 8; try draw.print("Biome: {s}", .{main.game.world.?.playerBiome.id}, 0, y, 8, .left); y += 8; - // TODO: packet loss + try draw.print("Opaque faces: {}, Transparent faces: {}", .{main.chunk.meshing.quadsDrawn, main.chunk.meshing.transparentQuadsDrawn}, 0, y, 8, .left); y += 8; + // TODO: packet loss // TODO: Protocol statistics(maybe?) } } diff --git a/src/gui/windows/save_selection.zig b/src/gui/windows/save_selection.zig index 938879d0..e5467724 100644 --- a/src/gui/windows/save_selection.zig +++ b/src/gui/windows/save_selection.zig @@ -125,7 +125,7 @@ pub fn onOpen() Allocator.Error!void { const decodedName = try parseEscapedFolderName(entry.name); defer main.threadAllocator.free(decodedName); - const name = try buttonNameArena.allocator().dupeZ(u8, entry.name); // Null terminate, so we can later recover the string from jsut the pointer. + const name = try buttonNameArena.allocator().dupeZ(u8, entry.name); // Null terminate, so we can later recover the string from just the pointer. const buttonName = try std.fmt.allocPrint(buttonNameArena.allocator(), "Play {s}", .{decodedName}); try row.add(try Button.initText(.{0, 0}, 128, buttonName, .{.callback = &openWorld, .arg = @intFromPtr(name.ptr)})); diff --git a/src/renderer.zig b/src/renderer.zig index 41d116d9..9f46bf9e 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -18,6 +18,7 @@ const network = @import("network.zig"); const settings = @import("settings.zig"); const utils = @import("utils.zig"); const vec = @import("vec.zig"); +const Vec2f = vec.Vec2f; const Vec3i = vec.Vec3i; const Vec3f = vec.Vec3f; const Vec3d = vec.Vec3d; @@ -198,6 +199,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo // Uses FrustumCulling on the chunks. var 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)); var waterFog = Fog{.active=true, .color=.{0.0, 0.1, 0.2}, .density=0.1}; @@ -213,7 +215,9 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo // SimpleList visibleChunks = new SimpleList(new NormalChunkMesh[64]); // SimpleList visibleReduced = new SimpleList(new ReducedChunkMesh[64]); - const meshes = try RenderStructure.updateAndGetRenderChunks(game.world.?.conn, playerPos, settings.renderDistance, settings.LODFactor, frustum); + chunk.meshing.quadsDrawn = 0; + chunk.meshing.transparentQuadsDrawn = 0; + const meshes = try RenderStructure.updateAndGetRenderChunks(game.world.?.conn, playerPos, settings.renderDistance, settings.LODFactor); try sortChunks(meshes, playerPos); @@ -865,6 +869,26 @@ pub const MeshSelection = struct { } } } + + pub fn drawCube(projectionMatrix: Mat4f, viewMatrix: Mat4f, relativePositionToPlayer: Vec3d, min: Vec3f, max: Vec3f) void { + shader.bind(); + + c.glUniformMatrix4fv(uniforms.projectionMatrix, 1, c.GL_FALSE, @ptrCast(&projectionMatrix)); + c.glUniformMatrix4fv(uniforms.viewMatrix, 1, c.GL_FALSE, @ptrCast(&viewMatrix)); + + c.glUniform3f(uniforms.modelPosition, + @floatCast(relativePositionToPlayer[0]), + @floatCast(relativePositionToPlayer[1]), + @floatCast(relativePositionToPlayer[2]), + ); + c.glUniform3f(uniforms.lowerBounds, min[0], min[1], min[2]); + c.glUniform3f(uniforms.upperBounds, max[0], max[1], max[2]); + + c.glBindVertexArray(cubeVAO); + // c.glLineWidth(2); // TODO: Draw thicker lines so they are more visible. Maybe a simple shader + cube mesh is enough. + c.glDrawElements(c.GL_LINES, 12*2, c.GL_UNSIGNED_BYTE, null); + } + pub fn render(projectionMatrix: Mat4f, viewMatrix: Mat4f, playerPos: Vec3d) void { if(selectedBlockPos) |_selectedBlockPos| { var block = RenderStructure.getBlock(_selectedBlockPos[0], _selectedBlockPos[1], _selectedBlockPos[2]) orelse return; @@ -874,28 +898,7 @@ pub const MeshSelection = struct { var transformedMax = model.permutation.transform(voxelModel.max - @as(Vec3i, @splat(8))) + @as(Vec3i, @splat(8)); const min = @min(transformedMin, transformedMax); const max = @max(transformedMin ,transformedMax); - shader.bind(); - - c.glUniformMatrix4fv(uniforms.projectionMatrix, 1, c.GL_FALSE, @ptrCast(&projectionMatrix)); - c.glUniformMatrix4fv(uniforms.viewMatrix, 1, c.GL_FALSE, @ptrCast(&viewMatrix)); - c.glUniform3f(uniforms.modelPosition, - @floatCast(@as(f64, @floatFromInt(_selectedBlockPos[0])) - playerPos[0]), - @floatCast(@as(f64, @floatFromInt(_selectedBlockPos[1])) - playerPos[1]), - @floatCast(@as(f64, @floatFromInt(_selectedBlockPos[2])) - playerPos[2]) - ); - c.glUniform3f(uniforms.lowerBounds, - @as(f32, @floatFromInt(min[0]))/16.0, - @as(f32, @floatFromInt(min[1]))/16.0, - @as(f32, @floatFromInt(min[2]))/16.0 - ); - c.glUniform3f(uniforms.upperBounds, - @as(f32, @floatFromInt(max[0]))/16.0, - @as(f32, @floatFromInt(max[1]))/16.0, - @as(f32, @floatFromInt(max[2]))/16.0 - ); - - c.glBindVertexArray(cubeVAO); - c.glDrawElements(c.GL_LINES, 12*2, c.GL_UNSIGNED_BYTE, null); // TODO: Draw thicker lines so they are more visible. Maybe a simple shader + cube mesh is enough. + drawCube(projectionMatrix, viewMatrix, vec.floatFromInt(f64, _selectedBlockPos) - playerPos, vec.floatFromInt(f32, min)/@as(Vec3f, @splat(16.0)), vec.floatFromInt(f32, max)/@as(Vec3f, @splat(16.0))); } } }; @@ -905,6 +908,10 @@ pub const RenderStructure = struct { mesh: chunk.meshing.ChunkMesh, shouldBeRemoved: bool, // Internal use. drawableChildren: u32, // How many children can be renderer. If this is 8 then there is no need to render this mesh. + lod: u3, + min: Vec2f, + max: Vec2f, + active: bool, }; var storageLists: [settings.highestLOD + 1][]?*ChunkMeshNode = [1][]?*ChunkMeshNode{&.{}} ** (settings.highestLOD + 1); var storageListsSwap: [settings.highestLOD + 1][]?*ChunkMeshNode = [1][]?*ChunkMeshNode{&.{}} ** (settings.highestLOD + 1); @@ -963,6 +970,18 @@ pub const RenderStructure = struct { meshList.deinit(); } + fn getNodeFromRenderThread(pos: chunk.ChunkPosition) ?*ChunkMeshNode { + var lod = std.math.log2_int(u31, pos.voxelSize); + var xIndex = pos.wx-%(&lastX[lod]).* >> lod+chunk.chunkShift; + var yIndex = pos.wy-%(&lastY[lod]).* >> lod+chunk.chunkShift; + var zIndex = pos.wz-%(&lastZ[lod]).* >> lod+chunk.chunkShift; + if(xIndex < 0 or xIndex >= (&lastSize[lod]).*) return null; + if(yIndex < 0 or yIndex >= (&lastSize[lod]).*) return null; + if(zIndex < 0 or zIndex >= (&lastSize[lod]).*) return null; + var index = (xIndex*(&lastSize[lod]).* + yIndex)*(&lastSize[lod]).* + zIndex; + return storageLists[lod][@intCast(index)]; + } + fn _getNode(pos: chunk.ChunkPosition) ?*ChunkMeshNode { var lod = std.math.log2_int(u31, pos.voxelSize); lodMutex[lod].lock(); @@ -998,7 +1017,7 @@ pub const RenderStructure = struct { return &node.mesh; } - pub fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: Vec3d, renderDistance: i32, LODFactor: f32, frustum: Frustum) ![]*chunk.meshing.ChunkMesh { + pub fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: Vec3d, renderDistance: i32, LODFactor: f32) ![]*chunk.meshing.ChunkMesh { meshList.clearRetainingCapacity(); if(lastRD != renderDistance and lastFactor != LODFactor) { try network.Protocols.genericUpdate.sendRenderDistance(conn, renderDistance, LODFactor); @@ -1010,7 +1029,7 @@ pub const RenderStructure = struct { var meshRequests = std.ArrayList(chunk.ChunkPosition).init(main.threadAllocator); defer meshRequests.deinit(); - for(0..storageLists.len) |_lod| { + for(0..storageLists.len) |_lod| { // TODO: Can this be done in a more intelligent way? const lod: u5 = @intCast(_lod); var maxRenderDistance = renderDistance*chunk.chunkSize << lod; if(lod != 0) maxRenderDistance = @intFromFloat(@ceil(@as(f32, @floatFromInt(maxRenderDistance))*LODFactor)); @@ -1058,7 +1077,7 @@ pub const RenderStructure = struct { const zIndex = @divExact(z -% startZ, size); const index = (xIndex*maxSideLength + yIndex)*maxSideLength + zIndex; const pos = chunk.ChunkPosition{.wx=x, .wy=y, .wz=z, .voxelSize=@as(u31, 1)<>sizeShift & 1) | (y>>sizeShift & 1)<<1 | (z>>sizeShift & 1)<<2); - parent.mesh.visibilityMask &= ~(@as(u8, 1) << octantIndex); + parent.mesh.visibilityMask &= ~(@as(u8, 1) << octantIndex); // TODO: Find a more robust solution, that also works for the new occlusion culling. } } node.?.drawableChildren = 0; @@ -1105,7 +1113,7 @@ pub const RenderStructure = struct { if(nullMesh) |mesh| { if(mesh.shouldBeRemoved) { if(mesh.mesh.pos.voxelSize != 1 << settings.highestLOD) { - if(_getNode(.{.wx=mesh.mesh.pos.wx, .wy=mesh.mesh.pos.wy, .wz=mesh.mesh.pos.wz, .voxelSize=2*mesh.mesh.pos.voxelSize})) |parent| { + if(getNodeFromRenderThread(.{.wx=mesh.mesh.pos.wx, .wy=mesh.mesh.pos.wy, .wz=mesh.mesh.pos.wz, .voxelSize=2*mesh.mesh.pos.voxelSize})) |parent| { const octantIndex: u3 = @intCast((mesh.mesh.pos.wx>>sizeShift & 1) | (mesh.mesh.pos.wy>>sizeShift & 1)<<1 | (mesh.mesh.pos.wz>>sizeShift & 1)<<2); parent.mesh.visibilityMask |= @as(u8, 1) << octantIndex; } @@ -1134,6 +1142,232 @@ pub const RenderStructure = struct { } } + // Does occlusion using a breadth-first search that caches an on-screen visibility rectangle. + + const OcclusionData = struct { + node: *ChunkMeshNode, + distance: f64, + + pub fn compare(_: void, a: @This(), b: @This()) std.math.Order { + if(a.distance < b.distance) return .lt; + if(a.distance > b.distance) return .gt; + return .eq; + } + }; + + var floodFillList = std.PriorityQueue(OcclusionData, void, OcclusionData.compare).init(main.threadAllocator, {}); + defer floodFillList.deinit(); + { + var firstPos = chunk.ChunkPosition{ + .wx = @intFromFloat(@floor(playerPos[0])), + .wy = @intFromFloat(@floor(playerPos[1])), + .wz = @intFromFloat(@floor(playerPos[2])), + .voxelSize = 1, + }; + firstPos.wx &= ~@as(i32, chunk.chunkMask); + firstPos.wy &= ~@as(i32, chunk.chunkMask); + firstPos.wz &= ~@as(i32, chunk.chunkMask); + var lod: u3 = 0; + while(lod <= settings.highestLOD) : (lod += 1) { + if(getNodeFromRenderThread(firstPos)) |node| if(node.mesh.generated) { + node.lod = lod; + node.min = @splat(-1); + node.max = @splat(1); + node.active = true; + try floodFillList.add(.{ + .node = node, + .distance = 0, + }); + break; + }; + firstPos.wx &= ~@as(i32, firstPos.voxelSize*chunk.chunkSize); + firstPos.wy &= ~@as(i32, firstPos.voxelSize*chunk.chunkSize); + firstPos.wz &= ~@as(i32, firstPos.voxelSize*chunk.chunkSize); + firstPos.voxelSize *= 2; + } + } + + const projRotMat = game.projectionMatrix.mul(game.camera.viewMatrix); + while(floodFillList.removeOrNull()) |data| { + data.node.active = false; + const mesh = &data.node.mesh; + try meshList.append(mesh); + const relPos: Vec3d = vec.floatFromInt(f64, Vec3i{mesh.pos.wx, mesh.pos.wy, mesh.pos.wz}) - playerPos; + const relPosFloat: Vec3f = vec.floatCast(f32, relPos); + 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; + if(!chunk.Neighbors.isPositive[neighbor] and component >= 0) continue; + if(@reduce(.Or, @min(mesh.chunkBorders[neighbor].min, mesh.chunkBorders[neighbor].max) != mesh.chunkBorders[neighbor].min)) continue; // There was not a single block in the chunk. TODO: Find a better solution. + const minVec = vec.floatFromInt(f32, mesh.chunkBorders[neighbor].min*@as(Vec3i, @splat(mesh.pos.voxelSize))); + const maxVec = vec.floatFromInt(f32, mesh.chunkBorders[neighbor].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(chunk.Neighbors.vectorComponent[neighbor]) { + .x => { + cornerVector[0] = minVec[0]; + cornerVector[1] = if(a == 0) minVec[1] else maxVec[1]; + cornerVector[2] = if(b == 0) minVec[2] else maxVec[2]; + }, + .y => { + cornerVector[1] = minVec[1]; + cornerVector[0] = if(a == 0) minVec[0] else maxVec[0]; + cornerVector[2] = if(b == 0) minVec[2] else maxVec[2]; + }, + .z => { + cornerVector[2] = minVec[2]; + cornerVector[0] = if(a == 0) minVec[0] else maxVec[0]; + cornerVector[1] = if(b == 0) minVec[1] else maxVec[1]; + }, + } + 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 + chunk.Neighbors.relX[neighbor]*chunk.chunkSize*mesh.pos.voxelSize, + .wy = mesh.pos.wy + chunk.Neighbors.relY[neighbor]*chunk.chunkSize*mesh.pos.voxelSize, + .wz = mesh.pos.wz + chunk.Neighbors.relZ[neighbor]*chunk.chunkSize*mesh.pos.voxelSize, + .voxelSize = mesh.pos.voxelSize, + }; + var lod: u3 = data.node.lod; + while(lod <= settings.highestLOD) : (lod += 1) { + if(getNodeFromRenderThread(neighborPos)) |node| if(node.mesh.generated) { + if(node.active) { + node.min = @min(node.min, min); + node.max = @max(node.max, max); + } else { + node.lod = lod; + node.min = min; + node.max = max; + node.active = true; + try floodFillList.add(.{ + .node = node, + .distance = node.mesh.pos.getMaxDistanceSquared(playerPos) + }); + } + 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; + } + } + } + var i: usize = 0; while(i < clearList.items.len) { const mesh = clearList.items[i]; diff --git a/src/vec.zig b/src/vec.zig index a877b3a5..71c90809 100644 --- a/src/vec.zig +++ b/src/vec.zig @@ -10,10 +10,18 @@ pub const Vec4i = @Vector(4, i32); pub const Vec4f = @Vector(4, f32); pub const Vec4d = @Vector(4, f64); +pub inline fn combine(pos: Vec3f, w: f32) Vec4f { + return .{pos[0], pos[1], pos[2], w}; +} + pub fn xyz(self: anytype) @Vector(3, @typeInfo(@TypeOf(self)).Vector.child) { return @Vector(3, @typeInfo(@TypeOf(self)).Vector.child){self[0], self[1], self[2]}; } +pub fn xy(self: anytype) @Vector(2, @typeInfo(@TypeOf(self)).Vector.child) { + return @Vector(2, @typeInfo(@TypeOf(self)).Vector.child){self[0], self[1]}; +} + pub fn dot(self: anytype, other: @TypeOf(self)) @typeInfo(@TypeOf(self)).Vector.child { return @reduce(.Add, self*other); }