diff --git a/src/models.zig b/src/models.zig index 70f043a8..83d6081b 100644 --- a/src/models.zig +++ b/src/models.zig @@ -30,6 +30,7 @@ pub const Model = struct { max: Vec3f, internalQuads: []u16, neighborFacingQuads: [6][]u16, + isNeighborOccluded: [6]bool, fn getFaceNeighbor(quad: *const QuadInfo) ?u3 { var allZero: @Vector(3, bool) = .{true, true, true}; @@ -47,6 +48,19 @@ pub const Model = struct { return null; } + fn fullyOccludesNeighbor(quad: *const QuadInfo) bool { + var zeroes: @Vector(3, u32) = .{0, 0, 0}; + var ones: @Vector(3, u32) = .{0, 0, 0}; + for(quad.corners) |corner| { + zeroes += @select(u32, approxEqAbs(corner, @splat(0), @splat(0.0001)), .{1, 1, 1}, .{0, 0, 0}); + ones += @select(u32, approxEqAbs(corner, @splat(1), @splat(0.0001)), .{1, 1, 1}, .{0, 0, 0}); + } + // For full coverage there will 2 ones and 2 zeroes for two components, while the other one is constant. + const hasTwoZeroes = zeroes == @Vector(3, u32){2, 2, 2}; + const hasTwoOnes = ones == @Vector(3, u32){2, 2, 2}; + return @popCount(@as(u3, @bitCast(hasTwoOnes))) == 2 and @popCount(@as(u3, @bitCast(hasTwoZeroes))) == 2; + } + pub fn init(quadInfos: []const QuadInfo) u16 { const modelIndex: u16 = @intCast(models.items.len); const self = models.addOne(); @@ -54,6 +68,7 @@ pub const Model = struct { var internalAmount: usize = 0; self.min = .{1, 1, 1}; self.max = .{0, 0, 0}; + self.isNeighborOccluded = .{false} ** 6; for(quadInfos) |*quad| { for(quad.corners) |corner| { self.min = @min(self.min, corner); @@ -88,6 +103,13 @@ pub const Model = struct { internalIndex += 1; } } + for(0..6) |neighbor| { + for(self.neighborFacingQuads[neighbor]) |quad| { + if(fullyOccludesNeighbor(&quads.items[quad])) { + self.isNeighborOccluded[neighbor] = true; + } + } + } return modelIndex; } diff --git a/src/renderer/chunk_meshing.zig b/src/renderer/chunk_meshing.zig index 38a3a900..cd59df22 100644 --- a/src/renderer/chunk_meshing.zig +++ b/src/renderer/chunk_meshing.zig @@ -558,12 +558,11 @@ pub const ChunkMesh = struct { fn canBeSeenThroughOtherBlock(block: Block, other: Block, neighbor: u3) bool { const rotatedModel = blocks.meshes.model(block); const model = &models.models.items[rotatedModel]; - _ = neighbor; _ = model; // TODO: Check if the neighbor model occludes this one. (maybe not that relevant) return block.typ != 0 and ( other.typ == 0 or (!std.meta.eql(block, other) and other.viewThrough()) - or blocks.meshes.model(other) != 0 + or !models.models.items[blocks.meshes.model(other)].isNeighborOccluded[neighbor ^ 1] ); } diff --git a/src/renderer/lighting.zig b/src/renderer/lighting.zig index a3bf7186..15320f59 100644 --- a/src/renderer/lighting.zig +++ b/src/renderer/lighting.zig @@ -74,6 +74,31 @@ pub const ChannelChunk = struct { return .{self.data[index][0].load(.Unordered), self.data[index][1].load(.Unordered), self.data[index][2].load(.Unordered)}; } + fn calculateIncomingOcclusion(result: *[3]u8, block: blocks.Block, voxelSize: u31, neighbor: usize) void { + if(main.models.models.items[blocks.meshes.model(block)].isNeighborOccluded[neighbor]) { + var absorption: [3]u8 = extractColor(block.absorption()); + absorption[0] *|= @intCast(voxelSize); + absorption[1] *|= @intCast(voxelSize); + absorption[2] *|= @intCast(voxelSize); + result[0] -|= absorption[0]; + result[1] -|= absorption[1]; + result[2] -|= absorption[2]; + } + } + + fn calculateOutgoingOcclusion(result: *[3]u8, block: blocks.Block, voxelSize: u31, neighbor: usize) void { + const model = &main.models.models.items[blocks.meshes.model(block)]; + if(model.isNeighborOccluded[neighbor] and !model.isNeighborOccluded[neighbor ^ 1]) { // Avoid calculating the absorption twice. + var absorption: [3]u8 = extractColor(block.absorption()); + absorption[0] *|= @intCast(voxelSize); + absorption[1] *|= @intCast(voxelSize); + absorption[2] *|= @intCast(voxelSize); + result[0] -|= absorption[0]; + result[1] -|= absorption[1]; + result[2] -|= absorption[2]; + } + } + fn propagateDirect(self: *ChannelChunk, lightQueue: *main.utils.CircularBufferQueue(Entry)) void { var neighborLists: [6]main.ListUnmanaged(Entry) = .{.{}} ** 6; defer { @@ -110,19 +135,14 @@ pub const ChannelChunk = struct { result.value[1] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); result.value[2] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); } + calculateOutgoingOcclusion(&result.value, self.ch.blocks[index], self.ch.pos.voxelSize, neighbor); if(result.value[0] == 0 and result.value[1] == 0 and result.value[2] == 0) continue; if(nx < 0 or nx >= chunk.chunkSize or ny < 0 or ny >= chunk.chunkSize or nz < 0 or nz >= chunk.chunkSize) { neighborLists[neighbor].append(main.stackAllocator, result); continue; } const neighborIndex = chunk.getIndex(nx, ny, nz); - var absorption: [3]u8 = extractColor(self.ch.blocks[neighborIndex].absorption()); - absorption[0] *|= @intCast(self.ch.pos.voxelSize); - absorption[1] *|= @intCast(self.ch.pos.voxelSize); - absorption[2] *|= @intCast(self.ch.pos.voxelSize); - result.value[0] -|= absorption[0]; - result.value[1] -|= absorption[1]; - result.value[2] -|= absorption[2]; + calculateIncomingOcclusion(&result.value, self.ch.blocks[neighborIndex], self.ch.pos.voxelSize, neighbor ^ 1); if(result.value[0] != 0 or result.value[1] != 0 or result.value[2] != 0) lightQueue.enqueue(result); } } @@ -196,18 +216,13 @@ pub const ChannelChunk = struct { result.value[1] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); result.value[2] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); } + calculateOutgoingOcclusion(&result.value, self.ch.blocks[index], self.ch.pos.voxelSize, neighbor); if(nx < 0 or nx >= chunk.chunkSize or ny < 0 or ny >= chunk.chunkSize or nz < 0 or nz >= chunk.chunkSize) { neighborLists[neighbor].append(main.stackAllocator, result); continue; } const neighborIndex = chunk.getIndex(nx, ny, nz); - var absorption: [3]u8 = extractColor(self.ch.blocks[neighborIndex].absorption()); - absorption[0] *|= @intCast(self.ch.pos.voxelSize); - absorption[1] *|= @intCast(self.ch.pos.voxelSize); - absorption[2] *|= @intCast(self.ch.pos.voxelSize); - result.value[0] -|= absorption[0]; - result.value[1] -|= absorption[1]; - result.value[2] -|= absorption[2]; + calculateIncomingOcclusion(&result.value, self.ch.blocks[neighborIndex], self.ch.pos.voxelSize, neighbor ^ 1); lightQueue.enqueue(result); } } @@ -233,13 +248,7 @@ pub const ChannelChunk = struct { for(lights) |entry| { const index = chunk.getIndex(entry.x, entry.y, entry.z); var result = entry; - var absorption: [3]u8 = extractColor(self.ch.blocks[index].absorption()); - absorption[0] *|= @intCast(self.ch.pos.voxelSize); - absorption[1] *|= @intCast(self.ch.pos.voxelSize); - absorption[2] *|= @intCast(self.ch.pos.voxelSize); - result.value[0] -|= absorption[0]; - result.value[1] -|= absorption[1]; - result.value[2] -|= absorption[2]; + calculateIncomingOcclusion(&result.value, self.ch.blocks[index], self.ch.pos.voxelSize, entry.sourceDir); if(result.value[0] != 0 or result.value[1] != 0 or result.value[2] != 0) lightQueue.enqueue(result); } self.propagateDirect(lightQueue); @@ -250,13 +259,7 @@ pub const ChannelChunk = struct { for(lights) |entry| { const index = chunk.getIndex(entry.x, entry.y, entry.z); var result = entry; - var absorption: [3]u8 = extractColor(self.ch.blocks[index].absorption()); - absorption[0] *|= @intCast(self.ch.pos.voxelSize); - absorption[1] *|= @intCast(self.ch.pos.voxelSize); - absorption[2] *|= @intCast(self.ch.pos.voxelSize); - result.value[0] -|= absorption[0]; - result.value[1] -|= absorption[1]; - result.value[2] -|= absorption[2]; + calculateIncomingOcclusion(&result.value, self.ch.blocks[index], self.ch.pos.voxelSize, entry.sourceDir); lightQueue.enqueue(result); } return self.propagateDestructive(lightQueue, constructiveEntries, false); @@ -311,14 +314,9 @@ pub const ChannelChunk = struct { value[1] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); value[2] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize)); } + calculateOutgoingOcclusion(&value, self.ch.blocks[neighborIndex], self.ch.pos.voxelSize, neighbor); if(value[0] == 0 and value[1] == 0 and value[2] == 0) continue; - var absorption: [3]u8 = extractColor(self.ch.blocks[index].absorption()); - absorption[0] *|= @intCast(self.ch.pos.voxelSize); - absorption[1] *|= @intCast(self.ch.pos.voxelSize); - absorption[2] *|= @intCast(self.ch.pos.voxelSize); - value[0] -|= absorption[0]; - value[1] -|= absorption[1]; - value[2] -|= absorption[2]; + calculateIncomingOcclusion(&value, self.ch.blocks[index], self.ch.pos.voxelSize, neighbor ^ 1); if(value[0] != 0 or value[1] != 0 or value[2] != 0) lightQueue.enqueue(.{.x = @intCast(x), .y = @intCast(y), .z = @intCast(z), .value = value, .sourceDir = @intCast(neighbor), .activeValue = 0b111}); } }