this is a BFS algorithm for propagating darkness in a minecraft-style lighting system, the lighting system consists of 6 channels, RGB for torch, RGB for sunlight
darkness propagation is as follows:
it utilizes a single queue, and loops until queue is empty: during which
it loops over the 6 cardinal directions, gets the lighting for each node position + direction, then loops over each channel, updating them according to conditionals.
updating light depends on channel conditions, hence looping over each channel.
i’m not sure how to optimize it any further, i am already using bit packing with BlockLight, packed struct, each channel consuming 5 bits
is there another algorithm that achieves the same effect at a better speed?
var prev_chunk_pos: int3 = darkness_seed.world_pos.worldToChunk();
var prev_chunk_neighbour: *LocalChunk = ChunkManager.getChunk(io, prev_chunk_pos).?;
while (queue.count > 0) {
const node: DarknessNode = queue.get().?;
std.debug.assert(node.light.equals(BlockLight.min()) == false);
// 6 cardinal directions
for (Util.directions) |d| {
const neighbour_world = node.world_pos.add(d);
const neighbour_chunk_pos = neighbour_world.worldToChunk();
// ChunkManager.getChunk() uses an expensive lock
// cache the previously used pointer and reuse it
// if neighbour_world_chunk_pos == previous_chunk_pos
const n_c: *LocalChunk = if (neighbour_chunk_pos.equals(prev_chunk_pos))
prev_chunk_neighbour
else
ChunkManager.getChunk(io, neighbour_chunk_pos) orelse continue;
prev_chunk_neighbour = n_c;
prev_chunk_pos = neighbour_chunk_pos;
const n_index: usize = int3.worldToIndex(neighbour_world);
const is_down: bool = d.y() == -1;
const n_light_original: BlockLight = n_c.getLight(n_index);
var updated_n_light: BlockLight = n_light_original;
var neighbor_propagation_light: BlockLight = BlockLight.min();
var did_remove_light: bool = false;
// 3 channels for torchlight
// 3 channels for sunlight
//loop over all the light channels and set them one by one
for (comptime std.enums.values(Channel)) |t| {
const node_channel_light: u5 = node.light.get(t);
if (node_channel_light == 0) continue;
const neighbour_channel_light: u5 = n_light_original.get(t);
const sunlight = t == .sun_red or t == .sun_green or t == .sun_blue;
// propagate darkness if neighbour:
// - isnt 0
// - either of those two conditions
// - channel is sunlight and is down and current node channel == max_channel_Size
// - neigbour is less than current node channel
if (neighbour_channel_light != 0 and
(sunlight and is_down and node_channel_light == BlockLight.max_channel_size) or
(neighbour_channel_light < node_channel_light))
{
updated_n_light = updated_n_light.set(t, n_c.getBlock(n_index).blockData().light.default.get(t));
neighbor_propagation_light = neighbor_propagation_light.set(t, neighbour_channel_light);
did_remove_light = true;
} else if (neighbour_channel_light >= node_channel_light) {
n_c.light_repair.setBit3D(n_index, true);
for (Util.directions) |nd| {
const nnw: int3 = neighbour_world.add(nd);
const nnnc: *LocalChunk = ChunkManager.getChunk(io, nnw.worldToChunk()) orelse continue;
try visited_blocks.put(allocator, nnw, {});
nnnc.light_repair.setBit3D(nnw.worldToIndex(), true);
}
}
}
if (did_remove_light) {
n_c.setLight(n_index, updated_n_light);
try visited_blocks.put(allocator, neighbour_world, {});
// light = 0, so skipped next iteration, so might as well not added.
if (neighbor_propagation_light.equals(BlockLight.min()) == false) {
try queue.put(allocator, DarknessNode{
.world_pos = neighbour_world,
.light = neighbor_propagation_light,
});
}
}
}
}