]> git.localhorst.tv Git - blank.git/blob - src/chunk.cpp
c31926bc4acd9354aa44201657010797889b0224
[blank.git] / src / chunk.cpp
1 #include "chunk.hpp"
2
3 #include "generator.hpp"
4
5 #include <algorithm>
6 #include <limits>
7 #include <queue>
8
9
10 namespace blank {
11
12 Chunk::Chunk(const BlockTypeRegistry &types) noexcept
13 : types(&types)
14 , neighbor{0}
15 , blocks{}
16 , light{0}
17 , model()
18 , position(0, 0, 0)
19 , dirty(false) {
20
21 }
22
23 Chunk::Chunk(Chunk &&other) noexcept
24 : types(other.types)
25 , model(std::move(other.model))
26 , position(other.position)
27 , dirty(other.dirty) {
28         std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor);
29         std::copy(other.blocks, other.blocks + sizeof(blocks), blocks);
30         std::copy(other.light, other.light + sizeof(light), light);
31 }
32
33 Chunk &Chunk::operator =(Chunk &&other) noexcept {
34         types = other.types;
35         std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor);
36         std::copy(other.blocks, other.blocks + sizeof(blocks), blocks);
37         std::copy(other.light, other.light + sizeof(light), light);
38         model = std::move(other.model);
39         position = other.position;
40         dirty = other.dirty;
41         return *this;
42 }
43
44
45 namespace {
46
47 struct SetNode {
48
49         Chunk *chunk;
50         Chunk::Pos pos;
51
52         SetNode(Chunk *chunk, Chunk::Pos pos)
53         : chunk(chunk), pos(pos) { }
54
55         int Get() const noexcept { return chunk->GetLight(pos); }
56         void Set(int level) noexcept { chunk->SetLight(pos, level); }
57
58         bool HasNext(Block::Face face) noexcept {
59                 const BlockLookup next(chunk, pos, face);
60                 return next && !next.GetType().block_light;
61         }
62         SetNode GetNext(Block::Face face) noexcept {
63                 const BlockLookup next(chunk, pos, face);
64                 return SetNode(&next.GetChunk(), next.GetBlockPos());
65         }
66
67 };
68
69 struct UnsetNode
70 : public SetNode {
71
72         int level;
73
74         UnsetNode(Chunk *chunk, Chunk::Pos pos)
75         : SetNode(chunk, pos), level(Get()) { }
76
77         UnsetNode(const SetNode &set)
78         : SetNode(set), level(Get()) { }
79
80
81         bool HasNext(Block::Face face) noexcept {
82                 const BlockLookup next(chunk, pos, face);
83                 return next;
84         }
85         UnsetNode GetNext(Block::Face face) noexcept { return UnsetNode(SetNode::GetNext(face)); }
86
87 };
88
89 std::queue<SetNode> light_queue;
90 std::queue<UnsetNode> dark_queue;
91
92 void work_light() noexcept {
93         while (!light_queue.empty()) {
94                 SetNode node = light_queue.front();
95                 light_queue.pop();
96
97                 int level = node.Get() - 1;
98                 for (int face = 0; face < Block::FACE_COUNT; ++face) {
99                         if (node.HasNext(Block::Face(face))) {
100                                 SetNode other = node.GetNext(Block::Face(face));
101                                 if (other.Get() < level) {
102                                         other.Set(level);
103                                         light_queue.emplace(other);
104                                 }
105                         }
106                 }
107         }
108 }
109
110 void work_dark() noexcept {
111         while (!dark_queue.empty()) {
112                 UnsetNode node = dark_queue.front();
113                 dark_queue.pop();
114
115                 for (int face = 0; face < Block::FACE_COUNT; ++face) {
116                         if (node.HasNext(Block::Face(face))) {
117                                 UnsetNode other = node.GetNext(Block::Face(face));
118                                 // TODO: if there a light source here with the same level this will err
119                                 if (other.Get() != 0 && other.Get() < node.level) {
120                                         other.Set(0);
121                                         dark_queue.emplace(other);
122                                 } else {
123                                         light_queue.emplace(other);
124                                 }
125                         }
126                 }
127         }
128 }
129
130 }
131
132 void Chunk::SetBlock(int index, const Block &block) noexcept {
133         const BlockType &old_type = Type(blocks[index]);
134         const BlockType &new_type = Type(block);
135
136         blocks[index] = block;
137
138         if (&old_type == &new_type) return;
139
140         if (new_type.luminosity > old_type.luminosity) {
141                 // light added
142                 SetLight(index, new_type.luminosity);
143                 light_queue.emplace(this, ToPos(index));
144                 work_light();
145         } else if (new_type.luminosity < old_type.luminosity) {
146                 // light removed
147                 dark_queue.emplace(this, ToPos(index));
148                 SetLight(index, 0);
149                 work_dark();
150                 SetLight(index, new_type.luminosity);
151                 light_queue.emplace(this, ToPos(index));
152                 work_light();
153         } else if (new_type.block_light && !old_type.block_light) {
154                 // obstacle added
155                 if (GetLight(index) > 0) {
156                         dark_queue.emplace(this, ToPos(index));
157                         SetLight(index, 0);
158                         work_dark();
159                         work_light();
160                 }
161         } else if (!new_type.block_light && old_type.block_light) {
162                 // obstacle removed
163                 int level = 0;
164                 for (int face = 0; face < Block::FACE_COUNT; ++face) {
165                         BlockLookup next_block(this, ToPos(index), Block::Face(face));
166                         if (next_block) {
167                                 level = std::min(level, next_block.GetLight());
168                         }
169                 }
170                 if (level > 1) {
171                         SetLight(index, level - 1);
172                         light_queue.emplace(this, ToPos(index));
173                         work_light();
174                 }
175         }
176 }
177
178 void Chunk::SetNeighbor(Chunk &other) noexcept {
179         if (other.position == position + Pos(-1, 0, 0)) {
180                 if (neighbor[Block::FACE_LEFT] != &other) {
181                         neighbor[Block::FACE_LEFT] = &other;
182                         other.neighbor[Block::FACE_RIGHT] = this;
183                         for (int z = 0; z < Depth(); ++z) {
184                                 for (int y = 0; y < Height(); ++y) {
185                                         Pos my_pos(0, y, z);
186                                         Pos other_pos(Width() - 1, y, z);
187                                         if (GetLight(my_pos) > 0) {
188                                                 light_queue.emplace(this, my_pos);
189                                         }
190                                         if (other.GetLight(other_pos) > 0) {
191                                                 light_queue.emplace(&other, other_pos);
192                                         }
193                                 }
194                         }
195                         work_light();
196                 }
197         } else if (other.position == position + Pos(1, 0, 0)) {
198                 if (neighbor[Block::FACE_RIGHT] != &other) {
199                         neighbor[Block::FACE_RIGHT] = &other;
200                         other.neighbor[Block::FACE_LEFT] = this;
201                         for (int z = 0; z < Depth(); ++z) {
202                                 for (int y = 0; y < Height(); ++y) {
203                                         Pos my_pos(Width() - 1, y, z);
204                                         Pos other_pos(0, y, z);
205                                         if (GetLight(my_pos) > 0) {
206                                                 light_queue.emplace(this, my_pos);
207                                         }
208                                         if (other.GetLight(other_pos) > 0) {
209                                                 light_queue.emplace(&other, other_pos);
210                                         }
211                                 }
212                         }
213                         work_light();
214                 }
215         } else if (other.position == position + Pos(0, -1, 0)) {
216                 if (neighbor[Block::FACE_DOWN] != &other) {
217                         neighbor[Block::FACE_DOWN] = &other;
218                         other.neighbor[Block::FACE_UP] = this;
219                         for (int z = 0; z < Depth(); ++z) {
220                                 for (int x = 0; x < Width(); ++x) {
221                                         Pos my_pos(x, 0, z);
222                                         Pos other_pos(x, Height() - 1, z);
223                                         if (GetLight(my_pos) > 0) {
224                                                 light_queue.emplace(this, my_pos);
225                                         }
226                                         if (other.GetLight(other_pos) > 0) {
227                                                 light_queue.emplace(&other, other_pos);
228                                         }
229                                 }
230                         }
231                         work_light();
232                 }
233         } else if (other.position == position + Pos(0, 1, 0)) {
234                 if (neighbor[Block::FACE_UP] != &other) {
235                         neighbor[Block::FACE_UP] = &other;
236                         other.neighbor[Block::FACE_DOWN] = this;
237                         for (int z = 0; z < Depth(); ++z) {
238                                 for (int x = 0; x < Width(); ++x) {
239                                         Pos my_pos(x, Height() - 1, z);
240                                         Pos other_pos(x, 0, z);
241                                         if (GetLight(my_pos) > 0) {
242                                                 light_queue.emplace(this, my_pos);
243                                         }
244                                         if (other.GetLight(other_pos) > 0) {
245                                                 light_queue.emplace(&other, other_pos);
246                                         }
247                                 }
248                         }
249                         work_light();
250                 }
251         } else if (other.position == position + Pos(0, 0, -1)) {
252                 if (neighbor[Block::FACE_BACK] != &other) {
253                         neighbor[Block::FACE_BACK] = &other;
254                         other.neighbor[Block::FACE_FRONT] = this;
255                         for (int y = 0; y < Height(); ++y) {
256                                 for (int x = 0; x < Width(); ++x) {
257                                         Pos my_pos(x, y, 0);
258                                         Pos other_pos(x, y, Depth() - 1);
259                                         if (GetLight(my_pos) > 0) {
260                                                 light_queue.emplace(this, my_pos);
261                                         }
262                                         if (other.GetLight(other_pos) > 0) {
263                                                 light_queue.emplace(&other, other_pos);
264                                         }
265                                 }
266                         }
267                         work_light();
268                 }
269         } else if (other.position == position + Pos(0, 0, 1)) {
270                 if (neighbor[Block::FACE_FRONT] != &other) {
271                         neighbor[Block::FACE_FRONT] = &other;
272                         other.neighbor[Block::FACE_BACK] = this;
273                         for (int y = 0; y < Height(); ++y) {
274                                 for (int x = 0; x < Width(); ++x) {
275                                         Pos my_pos(x, y, Depth() - 1);
276                                         Pos other_pos(x, y, 0);
277                                         if (GetLight(my_pos) > 0) {
278                                                 light_queue.emplace(this, my_pos);
279                                         }
280                                         if (other.GetLight(other_pos) > 0) {
281                                                 light_queue.emplace(&other, other_pos);
282                                         }
283                                 }
284                         }
285                         work_light();
286                 }
287         }
288 }
289
290 void Chunk::ClearNeighbors() noexcept {
291         for (int i = 0; i < Block::FACE_COUNT; ++i) {
292                 neighbor[i] = nullptr;
293         }
294 }
295
296 void Chunk::Unlink() noexcept {
297         for (int face = 0; face < Block::FACE_COUNT; ++face) {
298                 if (neighbor[face]) {
299                         neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = nullptr;
300                 }
301         }
302 }
303
304 void Chunk::Relink() noexcept {
305         for (int face = 0; face < Block::FACE_COUNT; ++face) {
306                 if (neighbor[face]) {
307                         neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = this;
308                 }
309         }
310 }
311
312
313 void Chunk::SetLight(int index, int level) noexcept {
314         if (light[index] != level) {
315                 light[index] = level;
316                 Invalidate();
317         }
318 }
319
320 int Chunk::GetLight(int index) const noexcept {
321         return light[index];
322 }
323
324 float Chunk::GetVertexLight(int index, const BlockModel::Position &vtx, const Model::Normal &norm) const noexcept {
325         float light = GetLight(index);
326         Chunk::Pos pos(ToPos(index));
327
328         Block::Face direct_face(Block::NormalFace(norm));
329         // tis okay
330         BlockLookup direct(const_cast<Chunk *>(this), pos, Block::NormalFace(norm));
331         if (direct) {
332                 float direct_light = direct.GetLight();
333                 if (direct_light > light) {
334                         light = direct_light;
335                 }
336         }
337
338         // cheap alternative until AO etc are implemented
339         // to tell the faces apart
340
341         if (direct_face == Block::FACE_LEFT || direct_face == Block::FACE_RIGHT) {
342                 light -= 0.2;
343         } else if (direct_face == Block::FACE_FRONT || direct_face == Block::FACE_BACK) {
344                 light -= 0.4;
345         }
346
347         return light;
348 }
349
350
351 bool Chunk::IsSurface(const Pos &pos) const noexcept {
352         const Block &block = BlockAt(pos);
353         if (!Type(block).visible) {
354                 return false;
355         }
356         for (int face = 0; face < Block::FACE_COUNT; ++face) {
357                 BlockLookup next = BlockLookup(const_cast<Chunk *>(this), pos, Block::Face(face));
358                 if (!next || !next.GetType().visible) {
359                         return true;
360                 }
361         }
362         return false;
363 }
364
365
366 void Chunk::Draw() noexcept {
367         if (dirty) {
368                 Update();
369         }
370         model.Draw();
371 }
372
373
374 bool Chunk::Intersection(
375         const Ray &ray,
376         const glm::mat4 &M,
377         int &blkid,
378         float &dist,
379         glm::vec3 &normal
380 ) const noexcept {
381         // TODO: should be possible to heavily optimize this
382         int id = 0;
383         blkid = -1;
384         dist = std::numeric_limits<float>::infinity();
385         for (int z = 0; z < Depth(); ++z) {
386                 for (int y = 0; y < Height(); ++y) {
387                         for (int x = 0; x < Width(); ++x, ++id) {
388                                 if (!Type(blocks[id]).visible) {
389                                         continue;
390                                 }
391                                 float cur_dist;
392                                 glm::vec3 cur_norm;
393                                 if (Type(blocks[id]).shape->Intersects(ray, M * ToTransform(id), cur_dist, cur_norm)) {
394                                         if (cur_dist < dist) {
395                                                 blkid = id;
396                                                 dist = cur_dist;
397                                                 normal = cur_norm;
398                                         }
399                                 }
400                         }
401                 }
402         }
403
404         if (blkid < 0) {
405                 return false;
406         } else {
407                 normal = glm::vec3(BlockAt(blkid).Transform() * glm::vec4(normal, 0.0f));
408                 return true;
409         }
410 }
411
412
413 namespace {
414
415 BlockModel::Buffer buf;
416
417 }
418
419 void Chunk::CheckUpdate() noexcept {
420         if (dirty) {
421                 Update();
422         }
423 }
424
425 void Chunk::Update() noexcept {
426         int vtx_count = 0, idx_count = 0;
427         for (const auto &block : blocks) {
428                 const Shape *shape = Type(block).shape;
429                 vtx_count += shape->VertexCount();
430                 idx_count += shape->VertexIndexCount();
431         }
432         buf.Clear();
433         buf.Reserve(vtx_count, idx_count);
434
435         BlockModel::Index vtx_counter = 0;
436         for (size_t i = 0; i < Size(); ++i) {
437                 const BlockType &type = Type(blocks[i]);
438
439                 if (!type.visible || Obstructed(i).All()) continue;
440
441                 type.FillBlockModel(buf, ToTransform(i), vtx_counter);
442                 size_t vtx_begin = vtx_counter;
443                 vtx_counter += type.shape->VertexCount();
444
445                 for (size_t vtx = vtx_begin; vtx < vtx_counter; ++vtx) {
446                         buf.lights.emplace_back(GetVertexLight(
447                                 i,
448                                 buf.vertices[vtx],
449                                 type.shape->VertexNormal(vtx - vtx_begin, blocks[i].Transform())
450                         ));
451                 }
452         }
453
454         model.Update(buf);
455         dirty = false;
456 }
457
458 Block::FaceSet Chunk::Obstructed(int idx) const noexcept {
459         Chunk::Pos pos(ToPos(idx));
460         Block::FaceSet result;
461
462         for (int f = 0; f < Block::FACE_COUNT; ++f) {
463                 Block::Face face = Block::Face(f);
464                 BlockLookup next(const_cast<Chunk *>(this), pos, face);
465                 if (next && next.GetType().FaceFilled(next.GetBlock(), Block::Opposite(face))) {
466                         result.Set(face);
467                 }
468         }
469
470         return result;
471 }
472
473 glm::mat4 Chunk::ToTransform(int idx) const noexcept {
474         return glm::translate(glm::mat4(1.0f), ToCoords(idx)) * blocks[idx].Transform();
475 }
476
477
478 BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p) noexcept
479 : chunk(c), pos(p) {
480         while (pos.x >= Chunk::Width()) {
481                 if (chunk->HasNeighbor(Block::FACE_RIGHT)) {
482                         chunk = &chunk->GetNeighbor(Block::FACE_RIGHT);
483                         pos.x -= Chunk::Width();
484                 } else {
485                         chunk = nullptr;
486                         return;
487                 }
488         }
489         while (pos.x < 0) {
490                 if (chunk->HasNeighbor(Block::FACE_LEFT)) {
491                         chunk = &chunk->GetNeighbor(Block::FACE_LEFT);
492                         pos.x += Chunk::Width();
493                 } else {
494                         chunk = nullptr;
495                         return;
496                 }
497         }
498         while (pos.y >= Chunk::Height()) {
499                 if (chunk->HasNeighbor(Block::FACE_UP)) {
500                         chunk = &chunk->GetNeighbor(Block::FACE_UP);
501                         pos.y -= Chunk::Height();
502                 } else {
503                         chunk = nullptr;
504                         return;
505                 }
506         }
507         while (pos.y < 0) {
508                 if (chunk->HasNeighbor(Block::FACE_DOWN)) {
509                         chunk = &chunk->GetNeighbor(Block::FACE_DOWN);
510                         pos.y += Chunk::Height();
511                 } else {
512                         chunk = nullptr;
513                         return;
514                 }
515         }
516         while (pos.z >= Chunk::Depth()) {
517                 if (chunk->HasNeighbor(Block::FACE_FRONT)) {
518                         chunk = &chunk->GetNeighbor(Block::FACE_FRONT);
519                         pos.z -= Chunk::Depth();
520                 } else {
521                         chunk = nullptr;
522                         return;
523                 }
524         }
525         while (pos.z < 0) {
526                 if (chunk->HasNeighbor(Block::FACE_BACK)) {
527                         chunk = &chunk->GetNeighbor(Block::FACE_BACK);
528                         pos.z += Chunk::Depth();
529                 } else {
530                         chunk = nullptr;
531                         return;
532                 }
533         }
534 }
535
536 BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p, Block::Face face) noexcept
537 : chunk(c), pos(p) {
538         pos += Block::FaceNormal(face);
539         if (!Chunk::InBounds(pos)) {
540                 pos -= Block::FaceNormal(face) * Chunk::Extent();
541                 chunk = &chunk->GetNeighbor(face);
542         }
543 }
544
545
546 ChunkLoader::ChunkLoader(const Config &config, const BlockTypeRegistry &reg, const Generator &gen) noexcept
547 : base(0, 0, 0)
548 , reg(reg)
549 , gen(gen)
550 , loaded()
551 , to_generate()
552 , to_free()
553 , load_dist(config.load_dist)
554 , unload_dist(config.unload_dist) {
555
556 }
557
558 namespace {
559
560 struct ChunkLess {
561
562         explicit ChunkLess(const Chunk::Pos &base) noexcept
563         : base(base) { }
564
565         bool operator ()(const Chunk::Pos &a, const Chunk::Pos &b) const noexcept {
566                 Chunk::Pos da(base - a);
567                 Chunk::Pos db(base - b);
568                 return
569                         da.x * da.x + da.y * da.y + da.z * da.z <
570                         db.x * db.x + db.y * db.y + db.z * db.z;
571         }
572
573         Chunk::Pos base;
574
575 };
576
577 }
578
579 void ChunkLoader::Generate(const Chunk::Pos &from, const Chunk::Pos &to) {
580         for (int z = from.z; z < to.z; ++z) {
581                 for (int y = from.y; y < to.y; ++y) {
582                         for (int x = from.x; x < to.x; ++x) {
583                                 Chunk::Pos pos(x, y, z);
584                                 if (Known(pos)) {
585                                         continue;
586                                 } else if (pos == base) {
587                                         Generate(pos);
588
589                                 //      light testing
590                                 //      for (int i = 0; i < 16; ++i) {
591                                 //              for (int j = 0; j < 16; ++j) {
592                                 //                      loaded.back().SetBlock(Chunk::Pos{  i, j,  0 }, Block(1));
593                                 //                      loaded.back().SetBlock(Chunk::Pos{  i, j, 15 }, Block(1));
594                                 //                      loaded.back().SetBlock(Chunk::Pos{  0, j,  i }, Block(1));
595                                 //                      loaded.back().SetBlock(Chunk::Pos{ 15, j,  i }, Block(1));
596                                 //              }
597                                 //      }
598                                 //      loaded.back().SetBlock(Chunk::Pos{  1,  0,  1 }, Block(13));
599                                 //      loaded.back().SetBlock(Chunk::Pos{ 14,  0,  1 }, Block(13));
600                                 //      loaded.back().SetBlock(Chunk::Pos{  1,  0, 14 }, Block(13));
601                                 //      loaded.back().SetBlock(Chunk::Pos{ 14,  0, 14 }, Block(13));
602                                 //      loaded.back().SetBlock(Chunk::Pos{  1, 15,  1 }, Block(13));
603                                 //      loaded.back().SetBlock(Chunk::Pos{ 14, 15,  1 }, Block(13));
604                                 //      loaded.back().SetBlock(Chunk::Pos{  1, 15, 14 }, Block(13));
605                                 //      loaded.back().SetBlock(Chunk::Pos{ 14, 15, 14 }, Block(13));
606                                 //      loaded.back().SetBlock(Chunk::Pos{  7,  7,  0 }, Block(13));
607                                 //      loaded.back().SetBlock(Chunk::Pos{  8,  7,  0 }, Block(13));
608                                 //      loaded.back().SetBlock(Chunk::Pos{  7,  8,  0 }, Block(13));
609                                 //      loaded.back().SetBlock(Chunk::Pos{  8,  8,  0 }, Block(13));
610                                 //      loaded.back().SetBlock(Chunk::Pos{  7,  7, 15 }, Block(13));
611                                 //      loaded.back().SetBlock(Chunk::Pos{  8,  7, 15 }, Block(13));
612                                 //      loaded.back().SetBlock(Chunk::Pos{  7,  8, 15 }, Block(13));
613                                 //      loaded.back().SetBlock(Chunk::Pos{  8,  8, 15 }, Block(13));
614                                 //      loaded.back().SetBlock(Chunk::Pos{  0,  7,  7 }, Block(13));
615                                 //      loaded.back().SetBlock(Chunk::Pos{  0,  7,  8 }, Block(13));
616                                 //      loaded.back().SetBlock(Chunk::Pos{  0,  8,  7 }, Block(13));
617                                 //      loaded.back().SetBlock(Chunk::Pos{  0,  8,  8 }, Block(13));
618                                 //      loaded.back().SetBlock(Chunk::Pos{ 15,  7,  7 }, Block(13));
619                                 //      loaded.back().SetBlock(Chunk::Pos{ 15,  7,  8 }, Block(13));
620                                 //      loaded.back().SetBlock(Chunk::Pos{ 15,  8,  7 }, Block(13));
621                                 //      loaded.back().SetBlock(Chunk::Pos{ 15,  8,  8 }, Block(13));
622                                 //      loaded.back().Invalidate();
623                                 //      loaded.back().CheckUpdate();
624
625                                 //      orientation testing
626                                 //      for (int i = 0; i < Block::FACE_COUNT; ++i) {
627                                 //              for (int j = 0; j < Block::TURN_COUNT; ++j) {
628                                 //                      loaded.back().BlockAt(512 * j + 2 * i) = Block(3 * (j + 1), Block::Face(i), Block::Turn(j));
629                                 //              }
630                                 //      }
631                                 //      loaded.back().Invalidate();
632                                 //      loaded.back().CheckUpdate();
633                                 } else {
634                                         to_generate.emplace_back(pos);
635                                 }
636                         }
637                 }
638         }
639         to_generate.sort(ChunkLess(base));
640 }
641
642 Chunk &ChunkLoader::Generate(const Chunk::Pos &pos) {
643         loaded.emplace_back(reg);
644         Chunk &chunk = loaded.back();
645         chunk.Position(pos);
646         gen(chunk);
647         Insert(chunk);
648         return chunk;
649 }
650
651 void ChunkLoader::Insert(Chunk &chunk) noexcept {
652         for (Chunk &other : loaded) {
653                 chunk.SetNeighbor(other);
654         }
655 }
656
657 void ChunkLoader::Remove(Chunk &chunk) noexcept {
658         chunk.Unlink();
659 }
660
661 Chunk *ChunkLoader::Loaded(const Chunk::Pos &pos) noexcept {
662         for (Chunk &chunk : loaded) {
663                 if (chunk.Position() == pos) {
664                         return &chunk;
665                 }
666         }
667         return nullptr;
668 }
669
670 bool ChunkLoader::Queued(const Chunk::Pos &pos) noexcept {
671         for (const Chunk::Pos &chunk : to_generate) {
672                 if (chunk == pos) {
673                         return true;
674                 }
675         }
676         return nullptr;
677 }
678
679 bool ChunkLoader::Known(const Chunk::Pos &pos) noexcept {
680         if (Loaded(pos)) return true;
681         return Queued(pos);
682 }
683
684 Chunk &ChunkLoader::ForceLoad(const Chunk::Pos &pos) {
685         Chunk *chunk = Loaded(pos);
686         if (chunk) {
687                 return *chunk;
688         }
689
690         for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end; ++iter) {
691                 if (*iter == pos) {
692                         to_generate.erase(iter);
693                         break;
694                 }
695         }
696
697         return Generate(pos);
698 }
699
700 void ChunkLoader::Rebase(const Chunk::Pos &new_base) {
701         if (new_base == base) {
702                 return;
703         }
704         base = new_base;
705
706         // unload far away chunks
707         for (auto iter(loaded.begin()), end(loaded.end()); iter != end;) {
708                 if (std::abs(base.x - iter->Position().x) > unload_dist
709                                 || std::abs(base.y - iter->Position().y) > unload_dist
710                                 || std::abs(base.z - iter->Position().z) > unload_dist) {
711                         auto saved = iter;
712                         Remove(*saved);
713                         ++iter;
714                         to_free.splice(to_free.end(), loaded, saved);
715                 } else {
716                         ++iter;
717                 }
718         }
719         // abort far away queued chunks
720         for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end;) {
721                 if (std::abs(base.x - iter->x) > unload_dist
722                                 || std::abs(base.y - iter->y) > unload_dist
723                                 || std::abs(base.z - iter->z) > unload_dist) {
724                         iter = to_generate.erase(iter);
725                 } else {
726                         ++iter;
727                 }
728         }
729         // add missing new chunks
730         GenerateSurrounding(base);
731 }
732
733 void ChunkLoader::GenerateSurrounding(const Chunk::Pos &pos) {
734         const Chunk::Pos offset(load_dist, load_dist, load_dist);
735         Generate(pos - offset, pos + offset);
736 }
737
738 void ChunkLoader::Update() {
739         if (to_generate.empty()) {
740                 return;
741         }
742
743         Chunk::Pos pos(to_generate.front());
744         to_generate.pop_front();
745
746         for (auto iter(to_free.begin()), end(to_free.end()); iter != end; ++iter) {
747                 if (iter->Position() == pos) {
748                         iter->Relink();
749                         loaded.splice(loaded.end(), to_free, iter);
750                         return;
751                 }
752         }
753
754         if (to_free.empty()) {
755                 loaded.emplace_back(reg);
756         } else {
757                 to_free.front().ClearNeighbors();
758                 loaded.splice(loaded.end(), to_free, to_free.begin());
759         }
760         Chunk &chunk = loaded.back();
761         chunk.Position(pos);
762         gen(chunk);
763         Insert(chunk);
764 }
765
766 }