A 2D tile-based sandbox game.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

LightServer.cc 10KB

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