A 2D tile-based sandbox game.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LightServer.cc 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. #include "LightServer.h"
  2. #include "log.h"
  3. namespace Swan {
  4. static ChunkPos lightChunkPos(TilePos pos) {
  5. // Same logic as in WorldPlane.cc
  6. return Vec2i(
  7. ((size_t)pos.x + (LLONG_MAX / 2) + 1) / CHUNK_WIDTH -
  8. ((LLONG_MAX / 2) / CHUNK_WIDTH) - 1,
  9. ((size_t)pos.y + (LLONG_MAX / 2) + 1) / CHUNK_HEIGHT -
  10. ((LLONG_MAX / 2) / CHUNK_HEIGHT) - 1);
  11. }
  12. static Vec2i lightRelPos(TilePos pos) {
  13. // Same logic as in WorldPlane.cc
  14. return Vec2i(
  15. (pos.x + (size_t)CHUNK_WIDTH * ((LLONG_MAX / 2) /
  16. CHUNK_WIDTH)) % CHUNK_WIDTH,
  17. (pos.y + (size_t)CHUNK_HEIGHT * ((LLONG_MAX / 2) /
  18. CHUNK_HEIGHT)) % CHUNK_HEIGHT);
  19. }
  20. LightServer::LightServer(LightCallback &cb):
  21. cb_(cb), thread_(&LightServer::run, this) {}
  22. LightServer::~LightServer() {
  23. running_ = false;
  24. cond_.notify_one();
  25. thread_.join();
  26. }
  27. bool LightServer::tileIsSolid(TilePos pos) {
  28. ChunkPos cpos = lightChunkPos(pos);
  29. LightChunk *chunk = getChunk(cpos);
  30. if (chunk == nullptr) {
  31. return true;
  32. }
  33. Vec2i rpos = lightRelPos(pos);
  34. return chunk->blocks[rpos.y * CHUNK_WIDTH + rpos.x];
  35. }
  36. LightChunk *LightServer::getChunk(ChunkPos cpos) {
  37. if (cached_chunk_ && cached_chunk_pos_ == cpos) {
  38. return cached_chunk_;
  39. }
  40. auto it = chunks_.find(cpos);
  41. if (it != chunks_.end()) {
  42. cached_chunk_ = &it->second;
  43. cached_chunk_pos_ = cpos;
  44. return &it->second;
  45. }
  46. return nullptr;
  47. }
  48. void LightServer::processEvent(const Event &evt, std::vector<NewLightChunk> &newChunks) {
  49. auto markAdjacentChunksModified = [&](ChunkPos cpos) {
  50. for (int y = -1; y <= 1; ++y) {
  51. for (int x = -1; x <= 1; ++x) {
  52. updated_chunks_.insert(cpos + Vec2i(x, y));
  53. }
  54. }
  55. };
  56. auto markChunksModified = [&](ChunkPos cpos, Vec2i rpos, int range) {
  57. bool l = rpos.x - range <= 0;
  58. bool r = rpos.x + range >= CHUNK_WIDTH - 1;
  59. bool t = rpos.y - range <= 0;
  60. bool b = rpos.y + range >= CHUNK_HEIGHT - 1;
  61. updated_chunks_.insert(cpos);
  62. if (l) updated_chunks_.insert(cpos + Vec2i(-1, 0));
  63. if (r) updated_chunks_.insert(cpos + Vec2i(1, 0));
  64. if (t) updated_chunks_.insert(cpos + Vec2i(0, -1));
  65. if (b) updated_chunks_.insert(cpos + Vec2i(0, 1));
  66. if (l && t) updated_chunks_.insert(cpos + Vec2i(-1, -1));
  67. if (r && t) updated_chunks_.insert(cpos + Vec2i(1, -1));
  68. if (l && b) updated_chunks_.insert(cpos + Vec2i(-1, 1));
  69. if (r && b) updated_chunks_.insert(cpos + Vec2i(1, 1));
  70. };
  71. if (evt.tag == Event::Tag::CHUNK_ADDED) {
  72. chunks_.emplace(std::piecewise_construct,
  73. std::forward_as_tuple(evt.pos),
  74. std::forward_as_tuple(std::move(newChunks[evt.num])));
  75. markAdjacentChunksModified(evt.pos);
  76. return;
  77. } else if (evt.tag == Event::Tag::CHUNK_REMOVED) {
  78. chunks_.erase(evt.pos);
  79. markAdjacentChunksModified(evt.pos);
  80. return;
  81. }
  82. ChunkPos cpos = lightChunkPos(evt.pos);
  83. LightChunk *ch = getChunk(cpos);
  84. if (!ch) return;
  85. Vec2i rpos = lightRelPos(evt.pos);
  86. switch (evt.tag) {
  87. case Event::Tag::BLOCK_ADDED:
  88. ch->blocks.set(rpos.y * CHUNK_WIDTH + rpos.x, true);
  89. ch->blocks_line[rpos.x] += 1;
  90. markChunksModified(cpos, rpos, LIGHT_CUTOFF);
  91. break;
  92. case Event::Tag::BLOCK_REMOVED:
  93. ch->blocks.set(rpos.y * CHUNK_WIDTH + rpos.x, false);
  94. ch->blocks_line[rpos.x] -= 1;
  95. markChunksModified(cpos, rpos, LIGHT_CUTOFF);
  96. break;
  97. case Event::Tag::LIGHT_ADDED:
  98. info << cpos << ": Add " << evt.num << " light to " << rpos;
  99. ch->light_sources[rpos] += evt.num;
  100. markChunksModified(cpos, rpos, ch->light_sources[rpos]);
  101. break;
  102. case Event::Tag::LIGHT_REMOVED:
  103. info << cpos << ": Remove " << evt.num << " light to " << rpos;
  104. markChunksModified(cpos, rpos, ch->light_sources[rpos]);
  105. ch->light_sources[rpos] -= evt.num;
  106. if (ch->light_sources[rpos] == 0) {
  107. ch->light_sources.erase(rpos);
  108. }
  109. break;
  110. // These were handled earlier
  111. case Event::Tag::CHUNK_ADDED:
  112. case Event::Tag::CHUNK_REMOVED:
  113. break;
  114. }
  115. }
  116. float LightServer::recalcTile(
  117. LightChunk &chunk, ChunkPos cpos, Vec2i rpos, TilePos base,
  118. std::vector<std::pair<TilePos, float>> &lights) {
  119. TilePos pos = rpos + base;
  120. constexpr int accuracy = 4;
  121. auto raycast = [&](Vec2 from, Vec2 to) {
  122. auto diff = to - from;
  123. float dist = ((Vec2)diff).length();
  124. Vec2 step = (Vec2)diff / (dist * accuracy);
  125. Vec2 currpos = from;
  126. TilePos currtile = TilePos(floor(currpos.x), floor(currpos.y));
  127. auto proceed = [&]() {
  128. TilePos t;
  129. while ((t = TilePos(floor(currpos.x), floor(currpos.y))) == currtile) {
  130. currpos += step;
  131. }
  132. currtile = t;
  133. };
  134. bool hit = false;
  135. Vec2i target = TilePos(floor(to.x), floor(to.y));
  136. while (currtile != target && (currpos - from).squareLength() <= diff.squareLength()) {
  137. if (tileIsSolid(currtile)) {
  138. hit = true;
  139. break;
  140. }
  141. proceed();
  142. }
  143. return hit;
  144. };
  145. auto diffusedRaycast = [&](Vec2 from, Vec2 to, Vec2 norm) {
  146. if (raycast(from, to)) {
  147. return 0.0f;
  148. }
  149. float dot = (to - from).norm().dot(norm);
  150. if (dot > 1) dot = 1;
  151. else if (dot < 0) dot = 0;
  152. return dot;
  153. };
  154. float acc = 0;
  155. for (auto &[lightpos, level]: lights) {
  156. if (lightpos == pos) {
  157. acc += level;
  158. continue;
  159. }
  160. if ((lightpos - pos).squareLength() > level * level) {
  161. continue;
  162. }
  163. float dist = ((Vec2)(lightpos - pos)).length();
  164. float light = level - dist;
  165. if (!tileIsSolid(pos)) {
  166. bool hit = raycast(
  167. Vec2(pos.x + 0.5, pos.y + 0.5),
  168. Vec2(lightpos.x + 0.5, lightpos.y + 0.5));
  169. if (!hit) {
  170. acc += light;
  171. }
  172. continue;
  173. }
  174. float frac =
  175. diffusedRaycast(
  176. Vec2(pos.x + 0.5, pos.y - 0.1),
  177. Vec2(lightpos.x + 0.5, lightpos.y + 0.5),
  178. Vec2(0, -1)) +
  179. diffusedRaycast(
  180. Vec2(pos.x + 0.5, pos.y + 1.1),
  181. Vec2(lightpos.x + 0.5, lightpos.y + 0.5),
  182. Vec2(0, 1)) +
  183. diffusedRaycast(
  184. Vec2(pos.x - 0.1, pos.y + 0.5),
  185. Vec2(lightpos.x + 0.5, lightpos.y + 0.5),
  186. Vec2(-1, 0)) +
  187. diffusedRaycast(
  188. Vec2(pos.x + 1.1, pos.y + 0.5),
  189. Vec2(lightpos.x + 0.5, lightpos.y + 0.5),
  190. Vec2(1, 0));
  191. acc += light * frac;
  192. }
  193. return acc;
  194. }
  195. void LightServer::processChunkLights(LightChunk &chunk, ChunkPos cpos) {
  196. TilePos base = cpos * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT);
  197. std::vector<std::pair<TilePos, float>> lights;
  198. for (auto &[pos, level]: chunk.light_sources) {
  199. lights.emplace_back(Vec2i(pos) + base, level);
  200. }
  201. auto addLightFromChunk = [&](LightChunk *chunk, int dx, int dy) {
  202. if (chunk == nullptr) {
  203. return;
  204. }
  205. TilePos b = base + Vec2i(dx * CHUNK_WIDTH, dy * CHUNK_HEIGHT);
  206. for (auto &[pos, level]: chunk->light_sources) {
  207. lights.emplace_back(TilePos(pos) + b, level);
  208. }
  209. };
  210. for (int y = -1; y <= 1; ++y) {
  211. for (int x = -1; x <= 1; ++x) {
  212. if (y == 0 && x == 0) continue;
  213. addLightFromChunk(getChunk(cpos + Vec2i(x, y)), x, y);
  214. }
  215. }
  216. chunk.bounces.clear();
  217. for (int y = 0; y < CHUNK_HEIGHT; ++y) {
  218. for (int x = 0; x < CHUNK_WIDTH; ++x) {
  219. float light = recalcTile(chunk, cpos, Vec2i(x, y), base, lights);
  220. chunk.light_levels[y * CHUNK_WIDTH + x] = std::min((int)light, 255);
  221. if (light > 0 && chunk.blocks[y * CHUNK_WIDTH + x]) {
  222. chunk.bounces.emplace_back(base + Vec2i(x, y), light);
  223. }
  224. }
  225. }
  226. }
  227. void LightServer::processChunkBounces(LightChunk &chunk, ChunkPos cpos) {
  228. TilePos base = cpos * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT);
  229. std::vector<std::pair<TilePos, float>> lights;
  230. for (auto &light: chunk.bounces) {
  231. lights.emplace_back(light);
  232. }
  233. auto addLightFromChunk = [&](LightChunk *chunk) {
  234. if (chunk == nullptr) {
  235. return;
  236. }
  237. for (auto &light: chunk->bounces) {
  238. lights.emplace_back(light);
  239. }
  240. };
  241. for (int y = -1; y <= 1; ++y) {
  242. for (int x = -1; x <= 1; ++x) {
  243. if (y == 0 && x == 0) continue;
  244. addLightFromChunk(getChunk(cpos + Vec2i(x, y)));
  245. }
  246. }
  247. for (int y = 0; y < CHUNK_HEIGHT; ++y) {
  248. for (int x = 0; x < CHUNK_WIDTH; ++x) {
  249. float light = recalcTile(chunk, cpos, Vec2i(x, y), base, lights) * 0.1;
  250. float sum = chunk.light_levels[y * CHUNK_WIDTH + x] + light;
  251. chunk.light_levels[y * CHUNK_WIDTH + x] = std::min((int)sum, 255);
  252. }
  253. }
  254. }
  255. void LightServer::processChunkSmoothing(LightChunk &chunk, ChunkPos cpos) {
  256. for (int y = 1; y < CHUNK_HEIGHT - 1; ++y) {
  257. for (int x = 1; x < CHUNK_WIDTH - 1; ++x) {
  258. }
  259. }
  260. }
  261. void LightServer::run() {
  262. std::unique_lock<std::mutex> lock(mut_, std::defer_lock);
  263. while (running_) {
  264. lock.lock();
  265. cond_.wait(lock, [&] { return buffers_[buffer_].size() > 0 || !running_; });
  266. std::vector<Event> &buf = buffers_[buffer_];
  267. std::vector<NewLightChunk> &newChunks = new_chunk_buffers_[buffer_];
  268. buffer_ = (buffer_ + 1) % 2;
  269. lock.unlock();
  270. updated_chunks_.clear();
  271. for (auto &evt: buf) {
  272. processEvent(evt, newChunks);
  273. }
  274. buf.clear();
  275. newChunks.clear();
  276. auto start = std::chrono::steady_clock::now();
  277. for (auto &pos: updated_chunks_) {
  278. auto ch = chunks_.find(pos);
  279. if (ch != chunks_.end()) {
  280. processChunkLights(ch->second, ChunkPos(pos.first, pos.second));
  281. }
  282. }
  283. for (auto &pos: updated_chunks_) {
  284. auto ch = chunks_.find(pos);
  285. if (ch != chunks_.end()) {
  286. processChunkBounces(ch->second, ChunkPos(pos.first, pos.second));
  287. }
  288. }
  289. auto end = std::chrono::steady_clock::now();
  290. auto dur = std::chrono::duration<double, std::milli>(end - start);
  291. info << "Generating light for " << updated_chunks_.size()
  292. << " chunks took " << dur.count() << "ms";
  293. for (auto &pos: updated_chunks_) {
  294. auto ch = chunks_.find(pos);
  295. if (ch != chunks_.end()) {
  296. cb_.onLightChunkUpdated(ch->second, pos);
  297. }
  298. }
  299. }
  300. }
  301. }