From b4723183fdb8c7ea887f3c0a51212dac95bc92b2 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 24 Aug 2026 15:52:39 +0200 Subject: [PATCH] feat(core): implement queue-based light recalculation on block edits --- core/src/LightEngine.cpp | 197 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 core/src/LightEngine.cpp diff --git a/core/src/LightEngine.cpp b/core/src/LightEngine.cpp new file mode 100644 index 0000000..505b84e --- /dev/null +++ b/core/src/LightEngine.cpp @@ -0,0 +1,197 @@ +#include "vf/LightEngine.h" + +#include + +namespace vf { + +namespace { + +const int offsets[6][3] = {{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, + {0, -1, 0}, {0, 0, 1}, {0, 0, -1}}; +constexpr int downDir = 3; // offsets[3] == {0, -1, 0} + +} // namespace + +LightEngine::LightEngine(World& world, const Vec3i& origin, const Vec3i& dims) + : world(world), origin(origin), dims(dims) { + std::size_t cells = static_cast(dims.x) * dims.y * dims.z; + sun.assign(cells, 0); + block.assign(cells, 0); +} + +bool LightEngine::inBox(int lx, int ly, int lz) const { + return lx >= 0 && lx < dims.x && ly >= 0 && ly < dims.y && lz >= 0 && lz < dims.z; +} + +bool LightEngine::occludesLocal(int lx, int ly, int lz) const { + return occludesLight(world.blockAt(origin.x + lx, origin.y + ly, origin.z + lz)); +} + +void LightEngine::floodAdd(std::vector& field, std::vector& queue, bool isSun) { + while (!queue.empty()) { + Node node = queue.back(); + queue.pop_back(); + uint8_t cur = field[index(node.lx, node.ly, node.lz)]; + if (cur != node.level) continue; + + for (int d = 0; d < 6; ++d) { + int nx = node.lx + offsets[d][0]; + int ny = node.ly + offsets[d][1]; + int nz = node.lz + offsets[d][2]; + if (!inBox(nx, ny, nz) || occludesLocal(nx, ny, nz)) continue; + + uint8_t target; + if (isSun && d == downDir && cur == maxLightLevel) + target = static_cast(maxLightLevel); + else + target = cur > 0 ? static_cast(cur - 1) : 0; + if (target == 0) continue; + + int ni = index(nx, ny, nz); + if (field[ni] >= target) continue; + field[ni] = target; + queue.push_back({nx, ny, nz, target}); + } + } +} + +void LightEngine::floodRemove(std::vector& field, std::vector& removals, + std::vector& reAdd, bool isSun) { + while (!removals.empty()) { + Node r = removals.back(); + removals.pop_back(); + + for (int d = 0; d < 6; ++d) { + int nx = r.lx + offsets[d][0]; + int ny = r.ly + offsets[d][1]; + int nz = r.lz + offsets[d][2]; + if (!inBox(nx, ny, nz)) continue; + + int ni = index(nx, ny, nz); + uint8_t neighbour = field[ni]; + if (neighbour == 0) continue; + + bool dependent; + if (isSun && d == downDir && r.level == maxLightLevel && neighbour == maxLightLevel) + dependent = true; // vertical sky beam cut from above + else + dependent = neighbour < r.level; + + if (dependent) { + field[ni] = 0; + removals.push_back({nx, ny, nz, neighbour}); + } else { + reAdd.push_back({nx, ny, nz, neighbour}); + } + } + } +} + +void LightEngine::updateSun(int lx, int ly, int lz) { + int i = index(lx, ly, lz); + std::vector removals{{lx, ly, lz, sun[i]}}; + std::vector reAdd; + sun[i] = 0; + floodRemove(sun, removals, reAdd, true); + + if (!occludesLocal(lx, ly, lz)) { + if (ly == dims.y - 1) { + sun[i] = static_cast(maxLightLevel); + reAdd.push_back({lx, ly, lz, sun[i]}); + } + for (int d = 0; d < 6; ++d) { + int nx = lx + offsets[d][0], ny = ly + offsets[d][1], nz = lz + offsets[d][2]; + if (!inBox(nx, ny, nz) || occludesLocal(nx, ny, nz)) continue; + uint8_t neighbour = sun[index(nx, ny, nz)]; + if (neighbour > 0) reAdd.push_back({nx, ny, nz, neighbour}); + } + } + floodAdd(sun, reAdd, true); +} + +void LightEngine::updateBlock(int lx, int ly, int lz, Block newBlock) { + int i = index(lx, ly, lz); + std::vector removals{{lx, ly, lz, block[i]}}; + std::vector reAdd; + block[i] = 0; + floodRemove(block, removals, reAdd, false); + + int emission = blockLightEmission(newBlock); + if (emission > 0) { + block[i] = static_cast(emission); + reAdd.push_back({lx, ly, lz, block[i]}); + } + if (!occludesLocal(lx, ly, lz)) { + for (int d = 0; d < 6; ++d) { + int nx = lx + offsets[d][0], ny = ly + offsets[d][1], nz = lz + offsets[d][2]; + if (!inBox(nx, ny, nz) || occludesLocal(nx, ny, nz)) continue; + uint8_t neighbour = block[index(nx, ny, nz)]; + if (neighbour > 0) reAdd.push_back({nx, ny, nz, neighbour}); + } + } + floodAdd(block, reAdd, false); +} + +void LightEngine::rebuild() { + std::fill(sun.begin(), sun.end(), 0); + std::fill(block.begin(), block.end(), 0); + + std::vector queue; + for (int lx = 0; lx < dims.x; ++lx) { + for (int lz = 0; lz < dims.z; ++lz) { + for (int ly = dims.y - 1; ly >= 0; --ly) { + if (occludesLocal(lx, ly, lz)) break; + int i = index(lx, ly, lz); + sun[i] = static_cast(maxLightLevel); + queue.push_back({lx, ly, lz, static_cast(maxLightLevel)}); + } + } + } + floodAdd(sun, queue, true); + + queue.clear(); + for (int lx = 0; lx < dims.x; ++lx) { + for (int ly = 0; ly < dims.y; ++ly) { + for (int lz = 0; lz < dims.z; ++lz) { + int emission = blockLightEmission( + world.blockAt(origin.x + lx, origin.y + ly, origin.z + lz)); + if (emission <= 0) continue; + int i = index(lx, ly, lz); + block[i] = static_cast(emission); + queue.push_back({lx, ly, lz, static_cast(emission)}); + } + } + } + floodAdd(block, queue, false); +} + +void LightEngine::setBlock(int gx, int gy, int gz, Block newBlock) { + world.setBlock(gx, gy, gz, newBlock); + int lx = gx - origin.x, ly = gy - origin.y, lz = gz - origin.z; + if (!inBox(lx, ly, lz)) return; + updateSun(lx, ly, lz); + updateBlock(lx, ly, lz, newBlock); +} + +uint8_t LightEngine::sunAt(int gx, int gy, int gz) const { + int lx = gx - origin.x, ly = gy - origin.y, lz = gz - origin.z; + if (ly >= dims.y && lx >= 0 && lx < dims.x && lz >= 0 && lz < dims.z) + return static_cast(maxLightLevel); + if (!inBox(lx, ly, lz)) return 0; + return sun[index(lx, ly, lz)]; +} + +uint8_t LightEngine::blockAt(int gx, int gy, int gz) const { + int lx = gx - origin.x, ly = gy - origin.y, lz = gz - origin.z; + if (!inBox(lx, ly, lz)) return 0; + return block[index(lx, ly, lz)]; +} + +float LightEngine::combinedAt(int gx, int gy, int gz, float dayFactor) const { + float s = static_cast(sunAt(gx, gy, gz)) * dayFactor; + float b = static_cast(blockAt(gx, gy, gz)); + float best = s > b ? s : b; + return best / static_cast(maxLightLevel); +} + +} // namespace vf