123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- #include "LightServer.h"
-
- #include <algorithm>
-
- #include "log.h"
-
- namespace Swan {
-
- static ChunkPos lightChunkPos(TilePos pos) {
- // Same logic as in WorldPlane.cc
- return Vec2i(
- ((size_t)pos.x + (LLONG_MAX / 2) + 1) / CHUNK_WIDTH -
- ((LLONG_MAX / 2) / CHUNK_WIDTH) - 1,
- ((size_t)pos.y + (LLONG_MAX / 2) + 1) / CHUNK_HEIGHT -
- ((LLONG_MAX / 2) / CHUNK_HEIGHT) - 1);
- }
-
- static Vec2i lightRelPos(TilePos pos) {
- // Same logic as in WorldPlane.cc
- return Vec2i(
- (pos.x + (size_t)CHUNK_WIDTH * ((LLONG_MAX / 2) /
- CHUNK_WIDTH)) % CHUNK_WIDTH,
- (pos.y + (size_t)CHUNK_HEIGHT * ((LLONG_MAX / 2) /
- CHUNK_HEIGHT)) % CHUNK_HEIGHT);
- }
-
- static float attenuate(float dist) {
- float a = 1 / (1 + dist);
- return a > 1 ? 1 : a;
- }
-
- static float attenuateSquared(float squareDist) {
- return attenuate(sqrt(squareDist));
- }
-
- LightServer::LightServer(LightCallback &cb):
- cb_(cb), thread_(&LightServer::run, this) {}
-
- LightServer::~LightServer() {
- running_ = false;
- cond_.notify_one();
- thread_.join();
- }
-
- bool LightServer::tileIsSolid(TilePos pos) {
- ChunkPos cpos = lightChunkPos(pos);
- LightChunk *chunk = getChunk(cpos);
- if (chunk == nullptr) {
- return true;
- }
-
- Vec2i rpos = lightRelPos(pos);
- return chunk->blocks[rpos.y * CHUNK_WIDTH + rpos.x];
- }
-
- LightChunk *LightServer::getChunk(ChunkPos cpos) {
- if (cached_chunk_ && cached_chunk_pos_ == cpos) {
- return cached_chunk_;
- }
-
- auto it = chunks_.find(cpos);
- if (it != chunks_.end()) {
- cached_chunk_ = &it->second;
- cached_chunk_pos_ = cpos;
- return &it->second;
- }
-
- return nullptr;
- }
-
- void LightServer::processEvent(const Event &evt, std::vector<NewLightChunk> &newChunks) {
- auto markAdjacentChunksModified = [&](ChunkPos cpos) {
- for (int y = -1; y <= 1; ++y) {
- for (int x = -1; x <= 1; ++x) {
- updated_chunks_.insert(cpos + Vec2i(x, y));
- }
- }
- };
-
- auto markChunks = [&](ChunkPos cpos, Vec2i rpos, bool l, bool r, bool t, bool b) {
- updated_chunks_.insert(cpos);
- if (l) updated_chunks_.insert(cpos + Vec2i(-1, 0));
- if (r) updated_chunks_.insert(cpos + Vec2i(1, 0));
- if (t) updated_chunks_.insert(cpos + Vec2i(0, -1));
- if (b) updated_chunks_.insert(cpos + Vec2i(0, 1));
- if (l && t) updated_chunks_.insert(cpos + Vec2i(-1, -1));
- if (r && t) updated_chunks_.insert(cpos + Vec2i(1, -1));
- if (l && b) updated_chunks_.insert(cpos + Vec2i(-1, 1));
- if (r && b) updated_chunks_.insert(cpos + Vec2i(1, 1));
- };
-
- auto markChunksModified = [&](ChunkPos cpos, Vec2i rpos, float light) {
- markChunks(cpos, rpos,
- light * attenuate(rpos.x) > LIGHT_CUTOFF,
- light * attenuate(CHUNK_WIDTH - rpos.x) > LIGHT_CUTOFF,
- light * attenuate(rpos.y) > LIGHT_CUTOFF,
- light * attenuate(CHUNK_HEIGHT - rpos.y) > LIGHT_CUTOFF);
- };
-
- auto markChunksModifiedRange = [&](ChunkPos cpos, Vec2i rpos, int range) {
- markChunks(cpos, rpos,
- rpos.x <= range,
- CHUNK_WIDTH - rpos.x <= range,
- rpos.y <= range,
- CHUNK_WIDTH - rpos.y <= range);
- };
-
- if (evt.tag == Event::Tag::CHUNK_ADDED) {
- chunks_.emplace(std::piecewise_construct,
- std::forward_as_tuple(evt.pos),
- std::forward_as_tuple(std::move(newChunks[evt.i])));
- markAdjacentChunksModified(evt.pos);
- return;
- } else if (evt.tag == Event::Tag::CHUNK_REMOVED) {
- chunks_.erase(evt.pos);
- markAdjacentChunksModified(evt.pos);
- return;
- }
-
- ChunkPos cpos = lightChunkPos(evt.pos);
- LightChunk *ch = getChunk(cpos);
- if (!ch) return;
- Vec2i rpos = lightRelPos(evt.pos);
-
- switch (evt.tag) {
- case Event::Tag::BLOCK_ADDED:
- ch->blocks.set(rpos.y * CHUNK_WIDTH + rpos.x, true);
- ch->blocks_line[rpos.x] += 1;
- markChunksModifiedRange(cpos, rpos, LIGHT_CUTOFF_DIST);
- break;
-
- case Event::Tag::BLOCK_REMOVED:
- ch->blocks.set(rpos.y * CHUNK_WIDTH + rpos.x, false);
- ch->blocks_line[rpos.x] -= 1;
- markChunksModifiedRange(cpos, rpos, LIGHT_CUTOFF_DIST);
- break;
-
- case Event::Tag::LIGHT_ADDED:
- info << cpos << ": Add " << evt.f << " light to " << rpos;
- ch->light_sources[rpos] += evt.f;
- markChunksModified(cpos, rpos, ch->light_sources[rpos]);
- break;
-
- case Event::Tag::LIGHT_REMOVED:
- info << cpos << ": Remove " << evt.f << " light to " << rpos;
- markChunksModified(cpos, rpos, ch->light_sources[rpos]);
- ch->light_sources[rpos] -= evt.f;
- if (ch->light_sources[rpos] < LIGHT_CUTOFF) {
- ch->light_sources.erase(rpos);
- }
- break;
-
- // These were handled earlier
- case Event::Tag::CHUNK_ADDED:
- case Event::Tag::CHUNK_REMOVED:
- break;
- }
- }
-
- float LightServer::recalcTile(
- LightChunk &chunk, ChunkPos cpos, Vec2i rpos, TilePos base,
- std::vector<std::pair<TilePos, float>> &lights) {
- TilePos pos = rpos + base;
-
- constexpr int accuracy = 4;
- auto raycast = [&](Vec2 from, Vec2 to) {
- auto diff = to - from;
- float dist = ((Vec2)diff).length();
- Vec2 step = (Vec2)diff / (dist * accuracy);
- Vec2 currpos = from;
- TilePos currtile = TilePos(floor(currpos.x), floor(currpos.y));
- auto proceed = [&]() {
- TilePos t;
- while ((t = TilePos(floor(currpos.x), floor(currpos.y))) == currtile) {
- currpos += step;
- }
- currtile = t;
- };
-
- bool hit = false;
- Vec2i target = TilePos(floor(to.x), floor(to.y));
- while (currtile != target && (currpos - from).squareLength() <= diff.squareLength()) {
- if (tileIsSolid(currtile)) {
- hit = true;
- break;
- }
-
- proceed();
- }
-
- return hit;
- };
-
- auto diffusedRaycast = [&](Vec2 from, Vec2 to, Vec2 norm) {
- if (raycast(from, to)) {
- return 0.0f;
- }
-
- float dot = (to - from).norm().dot(norm);
- if (dot > 1) dot = 1;
- else if (dot < 0) dot = 0;
- return dot;
- };
-
- bool isSolid = tileIsSolid(pos);
-
- bool culled =
- tileIsSolid(pos + Vec2i(-1, 0)) &&
- tileIsSolid(pos + Vec2i(1, 0)) &&
- tileIsSolid(pos + Vec2i(0, -1)) &&
- tileIsSolid(pos + Vec2i(0, 1));
-
- float acc = 0;
- for (auto &[lightpos, level]: lights) {
- if (lightpos == pos) {
- acc += level;
- continue;
- }
-
- if (culled) {
- continue;
- }
-
- int squareDist = (lightpos - pos).squareLength();
- if (squareDist > LIGHT_CUTOFF_DIST * LIGHT_CUTOFF_DIST) {
- continue;
- }
-
- float light = level * attenuateSquared(squareDist);
- if (light < LIGHT_CUTOFF) {
- continue;
- }
-
- if (!isSolid) {
- bool hit = raycast(
- Vec2(pos.x + 0.5, pos.y + 0.5),
- Vec2(lightpos.x + 0.5, lightpos.y + 0.5));
- if (!hit) {
- acc += light;
- }
-
- continue;
- }
-
- float frac =
- diffusedRaycast(
- Vec2(pos.x + 0.5, pos.y - 0.1),
- Vec2(lightpos.x + 0.5, lightpos.y + 0.5),
- Vec2(0, -1)) +
- diffusedRaycast(
- Vec2(pos.x + 0.5, pos.y + 1.1),
- Vec2(lightpos.x + 0.5, lightpos.y + 0.5),
- Vec2(0, 1)) +
- diffusedRaycast(
- Vec2(pos.x - 0.1, pos.y + 0.5),
- Vec2(lightpos.x + 0.5, lightpos.y + 0.5),
- Vec2(-1, 0)) +
- diffusedRaycast(
- Vec2(pos.x + 1.1, pos.y + 0.5),
- Vec2(lightpos.x + 0.5, lightpos.y + 0.5),
- Vec2(1, 0));
-
- acc += light * frac;
- }
-
- return acc;
- }
-
- void LightServer::processChunkLights(LightChunk &chunk, ChunkPos cpos) {
- TilePos base = cpos * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT);
- std::vector<std::pair<TilePos, float>> lights;
-
- for (auto &[pos, level]: chunk.light_sources) {
- lights.emplace_back(Vec2i(pos) + base, level);
- }
-
- auto addLightFromChunk = [&](LightChunk *chunk, int dx, int dy) {
- if (chunk == nullptr) {
- return;
- }
-
- TilePos b = base + Vec2i(dx * CHUNK_WIDTH, dy * CHUNK_HEIGHT);
- for (auto &[pos, level]: chunk->light_sources) {
- lights.emplace_back(TilePos(pos) + b, level);
- }
- };
-
- for (int y = -1; y <= 1; ++y) {
- for (int x = -1; x <= 1; ++x) {
- if (y == 0 && x == 0) continue;
- addLightFromChunk(getChunk(cpos + Vec2i(x, y)), x, y);
- }
- }
-
- chunk.bounces.clear();
- for (int y = 0; y < CHUNK_HEIGHT; ++y) {
- for (int x = 0; x < CHUNK_WIDTH; ++x) {
- float light = recalcTile(chunk, cpos, Vec2i(x, y), base, lights);
- chunk.light_levels[y * CHUNK_WIDTH + x] =
- std::min((int)round(light), 255);
-
- if (light > 0 && chunk.blocks[y * CHUNK_WIDTH + x]) {
- chunk.bounces.emplace_back(base + Vec2i(x, y), light);
- }
- }
- }
- }
-
- void LightServer::processChunkBounces(LightChunk &chunk, ChunkPos cpos) {
- TilePos base = cpos * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT);
- std::vector<std::pair<TilePos, float>> lights;
-
- for (auto &light: chunk.bounces) {
- lights.emplace_back(light);
- }
-
- auto addLightFromChunk = [&](LightChunk *chunk) {
- if (chunk == nullptr) {
- return;
- }
-
- for (auto &light: chunk->bounces) {
- lights.emplace_back(light);
- }
- };
-
- for (int y = -1; y <= 1; ++y) {
- for (int x = -1; x <= 1; ++x) {
- if (y == 0 && x == 0) continue;
- addLightFromChunk(getChunk(cpos + Vec2i(x, y)));
- }
- }
-
- for (int y = 0; y < CHUNK_HEIGHT; ++y) {
- for (int x = 0; x < CHUNK_WIDTH; ++x) {
- float light = recalcTile(chunk, cpos, Vec2i(x, y), base, lights) * 0.1;
- float sum = chunk.light_levels[y * CHUNK_WIDTH + x] + light;
- chunk.light_levels[y * CHUNK_WIDTH + x] =
- std::min((int)round(sum), 255);
- }
- }
- }
-
- void LightServer::processChunkSmoothing(LightChunk &chunk, ChunkPos cpos) {
- for (int y = 1; y < CHUNK_HEIGHT - 1; ++y) {
- for (int x = 1; x < CHUNK_WIDTH - 1; ++x) {
- }
- }
- }
-
- void LightServer::run() {
- std::unique_lock<std::mutex> lock(mut_, std::defer_lock);
- while (running_) {
- lock.lock();
- cond_.wait(lock, [&] { return buffers_[buffer_].size() > 0 || !running_; });
-
- std::vector<Event> &buf = buffers_[buffer_];
- std::vector<NewLightChunk> &newChunks = new_chunk_buffers_[buffer_];
- buffer_ = (buffer_ + 1) % 2;
- lock.unlock();
-
- updated_chunks_.clear();
- for (auto &evt: buf) {
- processEvent(evt, newChunks);
- }
-
- buf.clear();
- newChunks.clear();
-
- auto start = std::chrono::steady_clock::now();
-
- for (auto &pos: updated_chunks_) {
- auto ch = chunks_.find(pos);
- if (ch != chunks_.end()) {
- processChunkLights(ch->second, ChunkPos(pos.first, pos.second));
- }
- }
-
- for (auto &pos: updated_chunks_) {
- auto ch = chunks_.find(pos);
- if (ch != chunks_.end()) {
- processChunkBounces(ch->second, ChunkPos(pos.first, pos.second));
- }
- }
-
- auto end = std::chrono::steady_clock::now();
- auto dur = std::chrono::duration<double, std::milli>(end - start);
- info << "Generating light for " << updated_chunks_.size()
- << " chunks took " << dur.count() << "ms";
-
- for (auto &pos: updated_chunks_) {
- auto ch = chunks_.find(pos);
- if (ch != chunks_.end()) {
- cb_.onLightChunkUpdated(ch->second, pos);
- }
- }
- }
- }
-
- }
|