Implement light propagation.

Fixes #61
This commit is contained in:
IntegratedQuantum 2023-12-04 21:49:54 +01:00
parent 77716b4679
commit 58b2a2627b
3 changed files with 170 additions and 27 deletions

View File

@ -93,7 +93,7 @@ pub const Neighbors = struct { // TODO: Should this be an enum?
};
/// Gets the index of a given position inside this chunk.
fn getIndex(x: i32, y: i32, z: i32) u32 {
pub fn getIndex(x: i32, y: i32, z: i32) u32 {
std.debug.assert((x & chunkMask) == x and (y & chunkMask) == y and (z & chunkMask) == z);
return (@as(u32, @intCast(x)) << chunkShift) | (@as(u32, @intCast(y)) << chunkShift2) | @as(u32, @intCast(z));
}
@ -609,12 +609,12 @@ pub const meshing = struct {
const z = (wz >> mesh.chunk.voxelSizeShift) & chunkMask;
const index = getIndex(x, y, z);
return .{
mesh.lightingData.*[0].data[index],
mesh.lightingData.*[1].data[index],
mesh.lightingData.*[2].data[index],
mesh.lightingData.*[3].data[index],
mesh.lightingData.*[4].data[index],
mesh.lightingData.*[5].data[index],
mesh.lightingData.*[0].data[index].load(.Unordered),
mesh.lightingData.*[1].data[index].load(.Unordered),
mesh.lightingData.*[2].data[index].load(.Unordered),
mesh.lightingData.*[3].data[index].load(.Unordered),
mesh.lightingData.*[4].data[index].load(.Unordered),
mesh.lightingData.*[5].data[index].load(.Unordered),
};
}
@ -812,9 +812,12 @@ pub const meshing = struct {
pub fn init(self: *ChunkMesh, pos: ChunkPosition, chunk: *Chunk) !void {
const lightingData = try main.globalAllocator.create([6]lighting.ChannelChunk);
for(lightingData) |*lightChunk| {
try lightChunk.init(chunk);
}
try lightingData[0].init(chunk, .sun_red);
try lightingData[1].init(chunk, .sun_green);
try lightingData[2].init(chunk, .sun_blue);
try lightingData[3].init(chunk, .red);
try lightingData[4].init(chunk, .green);
try lightingData[5].init(chunk, .blue);
self.* = ChunkMesh{
.pos = pos,
.size = chunkSize*pos.voxelSize,
@ -853,7 +856,7 @@ pub const meshing = struct {
const prevVal = self.refCount.fetchSub(1, .Monotonic);
std.debug.assert(prevVal != 0);
if(prevVal == 1) {
renderer.RenderStructure.addMeshToClearList(self) catch @panic("Out of Memory");
renderer.RenderStructure.addMeshToClearListAndDecreaseRefCount(self) catch @panic("Out of Memory");
}
}
@ -886,6 +889,8 @@ pub const meshing = struct {
self.mutex.lock();
self.opaqueMesh.reset();
self.transparentMesh.reset();
var lightEmittingBlocks = std.ArrayList([3]u8).init(main.globalAllocator);
defer lightEmittingBlocks.deinit();
var n: u32 = 0;
var x: u8 = 0;
while(x < chunkSize): (x += 1) {
@ -894,6 +899,7 @@ pub const meshing = struct {
var z: u8 = 0;
while(z < chunkSize): (z += 1) {
const block = (&self.chunk.blocks)[getIndex(x, y, z)]; // a temporary fix to a compiler performance bug. TODO: check if this was fixed.
if(block.light() != 0) try lightEmittingBlocks.append(.{x, y, z});
if(block.typ == 0) continue;
// Check all neighbors:
for(Neighbors.iterable) |i| {
@ -931,6 +937,10 @@ pub const meshing = struct {
}
}
self.mutex.unlock();
for(self.lightingData[3..]) |*lightingData| {
try lightingData.propagateLights(lightEmittingBlocks.items, true);
}
// TODO: Sunlight propagation
try self.finishNeighbors(false);
}
@ -1071,7 +1081,7 @@ pub const meshing = struct {
self.transparentMesh.clearNeighbor(neighbor, isLod);
}
fn finishData(self: *ChunkMesh) !void {
pub fn finishData(self: *ChunkMesh) !void {
std.debug.assert(!self.mutex.tryLock());
try self.opaqueMesh.finish(self);
try self.transparentMesh.finish(self);
@ -1167,7 +1177,7 @@ pub const meshing = struct {
try neighborMesh.uploadData();
} else {
neighborMesh.increaseRefCount();
try renderer.RenderStructure.addToUpdateList(neighborMesh);
try renderer.RenderStructure.addToUpdateListAndDecreaseRefCount(neighborMesh);
}
} else {
self.mutex.lock();

View File

@ -1,31 +1,164 @@
const std = @import("std");
const Atomic = std.atomic.Value;
const main = @import("root");
const blocks = main.blocks;
const chunk = main.chunk;
const Channel = enum {
red,
green,
blue,
const Channel = enum(u8) {
sun_red = 0,
sun_green = 1,
sun_blue = 2,
red = 3,
green = 4,
blue = 5,
pub fn shift(self: Channel) u5 {
switch(self) {
.red => return 16,
.green => return 8,
.blue => return 0,
.red, .sun_red => return 16,
.green, .sun_green => return 8,
.blue, .sun_blue => return 0,
}
}
};
pub const ChannelChunk = struct {
data: [chunk.chunkVolume]u8,
data: [chunk.chunkVolume]Atomic(u8),
mutex: std.Thread.Mutex,
ch: *chunk.Chunk,
channel: Channel,
pub fn init(self: *ChannelChunk, ch: *chunk.Chunk) !void {
pub fn init(self: *ChannelChunk, ch: *chunk.Chunk, channel: Channel) !void {
self.mutex = .{};
for(&self.data, 0..) |*val, i| {
val.* = if(ch.blocks[i].transparent()) 255 else 0; // TODO: Proper light propagation. This is just ambient occlusion at the moment.
self.ch = ch;
self.channel = channel;
@memset(&self.data, Atomic(u8).init(0));
}
const Entry = struct {
x: u5,
y: u5,
z: u5,
value: u8,
fn compare(_: void, a: @This(), b: @This()) std.math.Order {
if (a.value > b.value) return .gt;
if (a.value < b.value) return .lt;
return .eq;
}
};
fn propagateDirect(self: *ChannelChunk, lightQueue: *std.PriorityQueue(Entry, void, Entry.compare)) std.mem.Allocator.Error!void {
var neighborLists: [6]std.ArrayListUnmanaged(Entry) = .{.{}} ** 6;
defer {
for(&neighborLists) |*list| {
list.deinit(main.globalAllocator);
}
}
self.mutex.lock();
errdefer self.mutex.unlock();
while(lightQueue.removeOrNull()) |entry| {
const index = chunk.getIndex(entry.x, entry.y, entry.z);
if(entry.value <= self.data[index].load(.Unordered)) continue;
self.data[index].store(entry.value, .Unordered);
for(chunk.Neighbors.iterable) |neighbor| {
const nx = entry.x + chunk.Neighbors.relX[neighbor];
const ny = entry.y + chunk.Neighbors.relY[neighbor];
const nz = entry.z + chunk.Neighbors.relZ[neighbor];
var result: Entry = .{.x = @intCast(nx & chunk.chunkMask), .y = @intCast(ny & chunk.chunkMask), .z = @intCast(nz & chunk.chunkMask), .value = entry.value};
result.value -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize));
if(result.value == 0) continue;
if(nx < 0 or nx >= chunk.chunkSize or ny < 0 or ny >= chunk.chunkSize or nz < 0 or nz >= chunk.chunkSize) {
try neighborLists[neighbor].append(main.globalAllocator, result);
continue;
}
const neighborIndex = chunk.getIndex(nx, ny, nz);
var absorption: u8 = @intCast(self.ch.blocks[neighborIndex].absorption() >> self.channel.shift() & 255);
absorption *|= @intCast(self.ch.pos.voxelSize);
result.value -|= absorption;
if(result.value != 0) try lightQueue.add(result);
}
}
self.mutex.unlock();
if(main.renderer.RenderStructure.getMeshAndIncreaseRefCount(self.ch.pos)) |mesh| {
mesh.mutex.lock();
defer mesh.mutex.unlock();
try mesh.finishData();
try main.renderer.RenderStructure.addToUpdateListAndDecreaseRefCount(mesh);
}
for(0..6) |neighbor| {
if(neighborLists[neighbor].items.len == 0) continue;
const neighborMesh = main.renderer.RenderStructure.getNeighborAndIncreaseRefCount(self.ch.pos, self.ch.pos.voxelSize, @intCast(neighbor)) orelse continue;
defer neighborMesh.decreaseRefCount();
try neighborMesh.lightingData[@intFromEnum(self.channel)].propagateFromNeighbor(neighborLists[neighbor].items);
}
}
};
fn propagateFromNeighbor(self: *ChannelChunk, lights: []Entry) std.mem.Allocator.Error!void {
var lightQueue = std.PriorityQueue(Entry, void, Entry.compare).init(main.globalAllocator, {});
defer lightQueue.deinit();
for(lights) |entry| {
const index = chunk.getIndex(entry.x, entry.y, entry.z);
var result = entry;
var absorption: u8 = @intCast(self.ch.blocks[index].absorption() >> self.channel.shift() & 255);
absorption *|= @intCast(self.ch.pos.voxelSize);
result.value -|= absorption;
if(result.value != 0) try lightQueue.add(result);
}
try self.propagateDirect(&lightQueue);
}
pub fn propagateLights(self: *ChannelChunk, lights: [][3]u8, comptime checkNeighbors: bool) std.mem.Allocator.Error!void {
var lightQueue = std.PriorityQueue(Entry, void, Entry.compare).init(main.globalAllocator, {});
defer lightQueue.deinit();
for(lights) |pos| {
const index = chunk.getIndex(pos[0], pos[1], pos[2]);
try lightQueue.add(.{.x = @intCast(pos[0]), .y = @intCast(pos[1]), .z = @intCast(pos[2]), .value = @intCast(self.ch.blocks[index].light() >> self.channel.shift() & 255)});
}
if(checkNeighbors) {
for(0..6) |neighbor| {
const x3: i32 = if(neighbor & 1 == 0) chunk.chunkMask else 0;
var x1: i32 = 0;
while(x1 < chunk.chunkSize): (x1 += 1) {
var x2: i32 = 0;
while(x2 < chunk.chunkSize): (x2 += 1) {
var x: i32 = undefined;
var y: i32 = undefined;
var z: i32 = undefined;
if(chunk.Neighbors.relX[neighbor] != 0) {
x = x3;
y = x1;
z = x2;
} else if(chunk.Neighbors.relY[neighbor] != 0) {
x = x1;
y = x3;
z = x2;
} else {
x = x2;
y = x1;
z = x3;
}
const otherX = x+%chunk.Neighbors.relX[neighbor] & chunk.chunkMask;
const otherY = y+%chunk.Neighbors.relY[neighbor] & chunk.chunkMask;
const otherZ = z+%chunk.Neighbors.relZ[neighbor] & chunk.chunkMask;
const neighborMesh = main.renderer.RenderStructure.getNeighborAndIncreaseRefCount(self.ch.pos, self.ch.pos.voxelSize, @intCast(neighbor)) orelse continue;
defer neighborMesh.decreaseRefCount();
const neighborLightChunk = &neighborMesh.lightingData[@intFromEnum(self.channel)];
const index = chunk.getIndex(x, y, z);
const neighborIndex = chunk.getIndex(otherX, otherY, otherZ);
var value: u8 = neighborLightChunk.data[neighborIndex].load(.Unordered);
value -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize));
if(value == 0) continue;
var absorption: u8 = @intCast(self.ch.blocks[index].absorption() >> self.channel.shift() & 255);
absorption *|= @intCast(self.ch.pos.voxelSize);
value -|= absorption;
if(value != 0) try lightQueue.add(.{.x = @intCast(x), .y = @intCast(y), .z = @intCast(z), .value = value});
}
}
}
}
try self.propagateDirect(&lightQueue);
}
};

View File

@ -1601,14 +1601,14 @@ pub const RenderStructure = struct {
}
}
pub fn addMeshToClearList(mesh: *chunk.meshing.ChunkMesh) !void {
pub fn addMeshToClearListAndDecreaseRefCount(mesh: *chunk.meshing.ChunkMesh) !void {
std.debug.assert(mesh.refCount.load(.Monotonic) == 0);
mutex.lock();
defer mutex.unlock();
try clearList.append(mesh);
}
pub fn addToUpdateList(mesh: *chunk.meshing.ChunkMesh) !void {
pub fn addToUpdateListAndDecreaseRefCount(mesh: *chunk.meshing.ChunkMesh) !void {
std.debug.assert(mesh.refCount.load(.Monotonic) != 0);
mutex.lock();
defer mutex.unlock();