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