]> git.localhorst.tv Git - blank.git/blob - src/chunk.cpp
a0415fd4f8f735072cf92174eb530d6a7c2423eb
[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         } else {
337                 return light;
338         }
339
340         if (Type(BlockAt(index)).luminosity > 0 || direct.GetType().block_light) {
341                 return light;
342         }
343
344         Block::Face edge[2];
345         switch (Block::Axis(direct_face)) {
346                 case 0: // X
347                         edge[0] = (vtx.y - pos.y) > 0.5f ? Block::FACE_UP : Block::FACE_DOWN;
348                         edge[1] = (vtx.z - pos.z) > 0.5f ? Block::FACE_FRONT : Block::FACE_BACK;
349                         break;
350                 case 1: // Y
351                         edge[0] = (vtx.z - pos.z) > 0.5f ? Block::FACE_FRONT : Block::FACE_BACK;
352                         edge[1] = (vtx.x - pos.x) > 0.5f ? Block::FACE_RIGHT : Block::FACE_LEFT;
353                         break;
354                 case 2: // Z
355                         edge[0] = (vtx.x - pos.x) > 0.5f ? Block::FACE_RIGHT : Block::FACE_LEFT;
356                         edge[1] = (vtx.y - pos.y) > 0.5f ? Block::FACE_UP : Block::FACE_DOWN;
357                         break;
358         }
359
360         int num = 1;
361         int occlusion = 0;
362
363         BlockLookup next[2] = {
364                 direct.Next(edge[0]),
365                 direct.Next(edge[1]),
366         };
367
368         if (next[0]) {
369                 if (next[0].GetType().block_light) {
370                         ++occlusion;
371                 } else {
372                         light += next[0].GetLight();
373                         ++num;
374                 }
375         }
376         if (next[1]) {
377                 if (next[1].GetType().block_light) {
378                         ++occlusion;
379                 } else {
380                         light += next[1].GetLight();
381                         ++num;
382                 }
383         }
384         if (occlusion < 2) {
385                 if (next[0]) {
386                         BlockLookup corner = next[0].Next(edge[1]);
387                         if (corner) {
388                                 if (corner.GetType().block_light) {
389                                         ++occlusion;
390                                 } else {
391                                         light += corner.GetLight();
392                                         ++num;
393                                 }
394                         }
395                 } else if (next[1]) {
396                         BlockLookup corner = next[1].Next(edge[0]);
397                         if (corner) {
398                                 if (corner.GetType().block_light) {
399                                         ++occlusion;
400                                 } else {
401                                         light += corner.GetLight();
402                                         ++num;
403                                 }
404                         }
405                 }
406         } else {
407                 ++occlusion;
408         }
409
410         return (light / num) - (occlusion * 0.8f);
411 }
412
413
414 bool Chunk::IsSurface(const Pos &pos) const noexcept {
415         const Block &block = BlockAt(pos);
416         if (!Type(block).visible) {
417                 return false;
418         }
419         for (int face = 0; face < Block::FACE_COUNT; ++face) {
420                 BlockLookup next = BlockLookup(const_cast<Chunk *>(this), pos, Block::Face(face));
421                 if (!next || !next.GetType().visible) {
422                         return true;
423                 }
424         }
425         return false;
426 }
427
428
429 void Chunk::Draw() noexcept {
430         if (dirty) {
431                 Update();
432         }
433         model.Draw();
434 }
435
436
437 bool Chunk::Intersection(
438         const Ray &ray,
439         const glm::mat4 &M,
440         int &blkid,
441         float &dist,
442         glm::vec3 &normal
443 ) const noexcept {
444         // TODO: should be possible to heavily optimize this
445         int id = 0;
446         blkid = -1;
447         dist = std::numeric_limits<float>::infinity();
448         for (int z = 0; z < Depth(); ++z) {
449                 for (int y = 0; y < Height(); ++y) {
450                         for (int x = 0; x < Width(); ++x, ++id) {
451                                 if (!Type(blocks[id]).visible) {
452                                         continue;
453                                 }
454                                 float cur_dist;
455                                 glm::vec3 cur_norm;
456                                 if (Type(blocks[id]).shape->Intersects(ray, M * ToTransform(id), cur_dist, cur_norm)) {
457                                         if (cur_dist < dist) {
458                                                 blkid = id;
459                                                 dist = cur_dist;
460                                                 normal = cur_norm;
461                                         }
462                                 }
463                         }
464                 }
465         }
466
467         if (blkid < 0) {
468                 return false;
469         } else {
470                 normal = glm::vec3(BlockAt(blkid).Transform() * glm::vec4(normal, 0.0f));
471                 return true;
472         }
473 }
474
475
476 namespace {
477
478 BlockModel::Buffer buf;
479
480 }
481
482 void Chunk::CheckUpdate() noexcept {
483         if (dirty) {
484                 Update();
485         }
486 }
487
488 void Chunk::Update() noexcept {
489         int vtx_count = 0, idx_count = 0;
490         for (const auto &block : blocks) {
491                 const Shape *shape = Type(block).shape;
492                 vtx_count += shape->VertexCount();
493                 idx_count += shape->VertexIndexCount();
494         }
495         buf.Clear();
496         buf.Reserve(vtx_count, idx_count);
497
498         BlockModel::Index vtx_counter = 0;
499         for (size_t i = 0; i < Size(); ++i) {
500                 const BlockType &type = Type(blocks[i]);
501
502                 if (!type.visible || Obstructed(i).All()) continue;
503
504                 type.FillBlockModel(buf, ToTransform(i), vtx_counter);
505                 size_t vtx_begin = vtx_counter;
506                 vtx_counter += type.shape->VertexCount();
507
508                 for (size_t vtx = vtx_begin; vtx < vtx_counter; ++vtx) {
509                         buf.lights.emplace_back(GetVertexLight(
510                                 i,
511                                 buf.vertices[vtx],
512                                 type.shape->VertexNormal(vtx - vtx_begin, blocks[i].Transform())
513                         ));
514                 }
515         }
516
517         model.Update(buf);
518         dirty = false;
519 }
520
521 Block::FaceSet Chunk::Obstructed(int idx) const noexcept {
522         Chunk::Pos pos(ToPos(idx));
523         Block::FaceSet result;
524
525         for (int f = 0; f < Block::FACE_COUNT; ++f) {
526                 Block::Face face = Block::Face(f);
527                 BlockLookup next(const_cast<Chunk *>(this), pos, face);
528                 if (next && next.GetType().FaceFilled(next.GetBlock(), Block::Opposite(face))) {
529                         result.Set(face);
530                 }
531         }
532
533         return result;
534 }
535
536 glm::mat4 Chunk::ToTransform(int idx) const noexcept {
537         return glm::translate(ToCoords(idx)) * blocks[idx].Transform();
538 }
539
540
541 BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p) noexcept
542 : chunk(c), pos(p) {
543         while (pos.x >= Chunk::Width()) {
544                 if (chunk->HasNeighbor(Block::FACE_RIGHT)) {
545                         chunk = &chunk->GetNeighbor(Block::FACE_RIGHT);
546                         pos.x -= Chunk::Width();
547                 } else {
548                         chunk = nullptr;
549                         return;
550                 }
551         }
552         while (pos.x < 0) {
553                 if (chunk->HasNeighbor(Block::FACE_LEFT)) {
554                         chunk = &chunk->GetNeighbor(Block::FACE_LEFT);
555                         pos.x += Chunk::Width();
556                 } else {
557                         chunk = nullptr;
558                         return;
559                 }
560         }
561         while (pos.y >= Chunk::Height()) {
562                 if (chunk->HasNeighbor(Block::FACE_UP)) {
563                         chunk = &chunk->GetNeighbor(Block::FACE_UP);
564                         pos.y -= Chunk::Height();
565                 } else {
566                         chunk = nullptr;
567                         return;
568                 }
569         }
570         while (pos.y < 0) {
571                 if (chunk->HasNeighbor(Block::FACE_DOWN)) {
572                         chunk = &chunk->GetNeighbor(Block::FACE_DOWN);
573                         pos.y += Chunk::Height();
574                 } else {
575                         chunk = nullptr;
576                         return;
577                 }
578         }
579         while (pos.z >= Chunk::Depth()) {
580                 if (chunk->HasNeighbor(Block::FACE_FRONT)) {
581                         chunk = &chunk->GetNeighbor(Block::FACE_FRONT);
582                         pos.z -= Chunk::Depth();
583                 } else {
584                         chunk = nullptr;
585                         return;
586                 }
587         }
588         while (pos.z < 0) {
589                 if (chunk->HasNeighbor(Block::FACE_BACK)) {
590                         chunk = &chunk->GetNeighbor(Block::FACE_BACK);
591                         pos.z += Chunk::Depth();
592                 } else {
593                         chunk = nullptr;
594                         return;
595                 }
596         }
597 }
598
599 BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p, Block::Face face) noexcept
600 : chunk(c), pos(p) {
601         pos += Block::FaceNormal(face);
602         if (!Chunk::InBounds(pos)) {
603                 pos -= Block::FaceNormal(face) * Chunk::Extent();
604                 chunk = &chunk->GetNeighbor(face);
605         }
606 }
607
608
609 ChunkLoader::ChunkLoader(const Config &config, const BlockTypeRegistry &reg, const Generator &gen) noexcept
610 : base(0, 0, 0)
611 , reg(reg)
612 , gen(gen)
613 , loaded()
614 , to_generate()
615 , to_free()
616 , load_dist(config.load_dist)
617 , unload_dist(config.unload_dist) {
618
619 }
620
621 namespace {
622
623 struct ChunkLess {
624
625         explicit ChunkLess(const Chunk::Pos &base) noexcept
626         : base(base) { }
627
628         bool operator ()(const Chunk::Pos &a, const Chunk::Pos &b) const noexcept {
629                 Chunk::Pos da(base - a);
630                 Chunk::Pos db(base - b);
631                 return
632                         da.x * da.x + da.y * da.y + da.z * da.z <
633                         db.x * db.x + db.y * db.y + db.z * db.z;
634         }
635
636         Chunk::Pos base;
637
638 };
639
640 }
641
642 void ChunkLoader::Generate(const Chunk::Pos &from, const Chunk::Pos &to) {
643         for (int z = from.z; z < to.z; ++z) {
644                 for (int y = from.y; y < to.y; ++y) {
645                         for (int x = from.x; x < to.x; ++x) {
646                                 Chunk::Pos pos(x, y, z);
647                                 if (Known(pos)) {
648                                         continue;
649                                 } else if (pos == base) {
650                                         Generate(pos);
651
652                                 //      light testing
653                                 //      for (int i = 0; i < 16; ++i) {
654                                 //              for (int j = 0; j < 16; ++j) {
655                                 //                      loaded.back().SetBlock(Chunk::Pos{  i, j,  0 }, Block(1));
656                                 //                      loaded.back().SetBlock(Chunk::Pos{  i, j, 15 }, Block(1));
657                                 //                      loaded.back().SetBlock(Chunk::Pos{  0, j,  i }, Block(1));
658                                 //                      loaded.back().SetBlock(Chunk::Pos{ 15, j,  i }, Block(1));
659                                 //              }
660                                 //      }
661                                 //      loaded.back().SetBlock(Chunk::Pos{  1,  0,  1 }, Block(13));
662                                 //      loaded.back().SetBlock(Chunk::Pos{ 14,  0,  1 }, Block(13));
663                                 //      loaded.back().SetBlock(Chunk::Pos{  1,  0, 14 }, Block(13));
664                                 //      loaded.back().SetBlock(Chunk::Pos{ 14,  0, 14 }, Block(13));
665                                 //      loaded.back().SetBlock(Chunk::Pos{  1, 15,  1 }, Block(13));
666                                 //      loaded.back().SetBlock(Chunk::Pos{ 14, 15,  1 }, Block(13));
667                                 //      loaded.back().SetBlock(Chunk::Pos{  1, 15, 14 }, Block(13));
668                                 //      loaded.back().SetBlock(Chunk::Pos{ 14, 15, 14 }, Block(13));
669                                 //      loaded.back().SetBlock(Chunk::Pos{  7,  7,  0 }, Block(13));
670                                 //      loaded.back().SetBlock(Chunk::Pos{  8,  7,  0 }, Block(13));
671                                 //      loaded.back().SetBlock(Chunk::Pos{  7,  8,  0 }, Block(13));
672                                 //      loaded.back().SetBlock(Chunk::Pos{  8,  8,  0 }, Block(13));
673                                 //      loaded.back().SetBlock(Chunk::Pos{  7,  7, 15 }, Block(13));
674                                 //      loaded.back().SetBlock(Chunk::Pos{  8,  7, 15 }, Block(13));
675                                 //      loaded.back().SetBlock(Chunk::Pos{  7,  8, 15 }, Block(13));
676                                 //      loaded.back().SetBlock(Chunk::Pos{  8,  8, 15 }, Block(13));
677                                 //      loaded.back().SetBlock(Chunk::Pos{  0,  7,  7 }, Block(13));
678                                 //      loaded.back().SetBlock(Chunk::Pos{  0,  7,  8 }, Block(13));
679                                 //      loaded.back().SetBlock(Chunk::Pos{  0,  8,  7 }, Block(13));
680                                 //      loaded.back().SetBlock(Chunk::Pos{  0,  8,  8 }, Block(13));
681                                 //      loaded.back().SetBlock(Chunk::Pos{ 15,  7,  7 }, Block(13));
682                                 //      loaded.back().SetBlock(Chunk::Pos{ 15,  7,  8 }, Block(13));
683                                 //      loaded.back().SetBlock(Chunk::Pos{ 15,  8,  7 }, Block(13));
684                                 //      loaded.back().SetBlock(Chunk::Pos{ 15,  8,  8 }, Block(13));
685                                 //      loaded.back().Invalidate();
686                                 //      loaded.back().CheckUpdate();
687
688                                 //      orientation testing
689                                 //      for (int i = 0; i < Block::FACE_COUNT; ++i) {
690                                 //              for (int j = 0; j < Block::TURN_COUNT; ++j) {
691                                 //                      loaded.back().BlockAt(512 * j + 2 * i) = Block(3 * (j + 1), Block::Face(i), Block::Turn(j));
692                                 //              }
693                                 //      }
694                                 //      loaded.back().Invalidate();
695                                 //      loaded.back().CheckUpdate();
696                                 } else {
697                                         to_generate.emplace_back(pos);
698                                 }
699                         }
700                 }
701         }
702         to_generate.sort(ChunkLess(base));
703 }
704
705 Chunk &ChunkLoader::Generate(const Chunk::Pos &pos) {
706         loaded.emplace_back(reg);
707         Chunk &chunk = loaded.back();
708         chunk.Position(pos);
709         gen(chunk);
710         Insert(chunk);
711         return chunk;
712 }
713
714 void ChunkLoader::Insert(Chunk &chunk) noexcept {
715         for (Chunk &other : loaded) {
716                 chunk.SetNeighbor(other);
717         }
718 }
719
720 void ChunkLoader::Remove(Chunk &chunk) noexcept {
721         chunk.Unlink();
722 }
723
724 Chunk *ChunkLoader::Loaded(const Chunk::Pos &pos) noexcept {
725         for (Chunk &chunk : loaded) {
726                 if (chunk.Position() == pos) {
727                         return &chunk;
728                 }
729         }
730         return nullptr;
731 }
732
733 bool ChunkLoader::Queued(const Chunk::Pos &pos) noexcept {
734         for (const Chunk::Pos &chunk : to_generate) {
735                 if (chunk == pos) {
736                         return true;
737                 }
738         }
739         return nullptr;
740 }
741
742 bool ChunkLoader::Known(const Chunk::Pos &pos) noexcept {
743         if (Loaded(pos)) return true;
744         return Queued(pos);
745 }
746
747 Chunk &ChunkLoader::ForceLoad(const Chunk::Pos &pos) {
748         Chunk *chunk = Loaded(pos);
749         if (chunk) {
750                 return *chunk;
751         }
752
753         for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end; ++iter) {
754                 if (*iter == pos) {
755                         to_generate.erase(iter);
756                         break;
757                 }
758         }
759
760         return Generate(pos);
761 }
762
763 void ChunkLoader::Rebase(const Chunk::Pos &new_base) {
764         if (new_base == base) {
765                 return;
766         }
767         base = new_base;
768
769         // unload far away chunks
770         for (auto iter(loaded.begin()), end(loaded.end()); iter != end;) {
771                 if (std::abs(base.x - iter->Position().x) > unload_dist
772                                 || std::abs(base.y - iter->Position().y) > unload_dist
773                                 || std::abs(base.z - iter->Position().z) > unload_dist) {
774                         auto saved = iter;
775                         Remove(*saved);
776                         ++iter;
777                         to_free.splice(to_free.end(), loaded, saved);
778                 } else {
779                         ++iter;
780                 }
781         }
782         // abort far away queued chunks
783         for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end;) {
784                 if (std::abs(base.x - iter->x) > unload_dist
785                                 || std::abs(base.y - iter->y) > unload_dist
786                                 || std::abs(base.z - iter->z) > unload_dist) {
787                         iter = to_generate.erase(iter);
788                 } else {
789                         ++iter;
790                 }
791         }
792         // add missing new chunks
793         GenerateSurrounding(base);
794 }
795
796 void ChunkLoader::GenerateSurrounding(const Chunk::Pos &pos) {
797         const Chunk::Pos offset(load_dist, load_dist, load_dist);
798         Generate(pos - offset, pos + offset);
799 }
800
801 void ChunkLoader::Update() {
802         if (to_generate.empty()) {
803                 return;
804         }
805
806         Chunk::Pos pos(to_generate.front());
807         to_generate.pop_front();
808
809         for (auto iter(to_free.begin()), end(to_free.end()); iter != end; ++iter) {
810                 if (iter->Position() == pos) {
811                         iter->Relink();
812                         loaded.splice(loaded.end(), to_free, iter);
813                         return;
814                 }
815         }
816
817         if (to_free.empty()) {
818                 loaded.emplace_back(reg);
819         } else {
820                 to_free.front().ClearNeighbors();
821                 loaded.splice(loaded.end(), to_free, to_free.begin());
822         }
823         Chunk &chunk = loaded.back();
824         chunk.Position(pos);
825         gen(chunk);
826         Insert(chunk);
827 }
828
829 }