]> git.localhorst.tv Git - blank.git/blob - src/world/chunk.cpp
6eb027a58f233173dc1026de7da64e4931ad7f59
[blank.git] / src / world / chunk.cpp
1 #include "BlockLookup.hpp"
2 #include "Chunk.hpp"
3 #include "ChunkIndex.hpp"
4 #include "ChunkLoader.hpp"
5 #include "ChunkRenderer.hpp"
6 #include "ChunkStore.hpp"
7
8 #include "Generator.hpp"
9 #include "WorldCollision.hpp"
10 #include "../app/Assets.hpp"
11 #include "../geometry/distance.hpp"
12 #include "../graphics/BlockLighting.hpp"
13 #include "../graphics/BlockMesh.hpp"
14 #include "../graphics/Viewport.hpp"
15 #include "../io/WorldSave.hpp"
16
17 #include <algorithm>
18 #include <limits>
19 #include <ostream>
20 #include <queue>
21
22 #include <iostream>
23 #include <glm/gtx/io.hpp>
24
25
26 namespace blank {
27
28 constexpr int Chunk::side;
29 constexpr int Chunk::size;
30
31
32 Chunk::Chunk(const BlockTypeRegistry &types) noexcept
33 : types(&types)
34 , neighbor{0}
35 , gravity()
36 , blocks{}
37 , light{0}
38 , generated(false)
39 , lighted(false)
40 , position(0, 0, 0)
41 , ref_count(0)
42 , dirty_mesh(false)
43 , dirty_save(false) {
44
45 }
46
47 Chunk::Chunk(Chunk &&other) noexcept
48 : types(other.types)
49 , gravity(std::move(other.gravity))
50 , generated(other.generated)
51 , lighted(other.lighted)
52 , position(other.position)
53 , ref_count(other.ref_count)
54 , dirty_mesh(other.dirty_mesh)
55 , dirty_save(other.dirty_save) {
56         std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor);
57         std::copy(other.blocks, other.blocks + sizeof(blocks), blocks);
58         std::copy(other.light, other.light + sizeof(light), light);
59         other.ref_count = 0;
60 }
61
62 Chunk &Chunk::operator =(Chunk &&other) noexcept {
63         types = other.types;
64         std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor);
65         gravity = std::move(other.gravity);
66         std::copy(other.blocks, other.blocks + sizeof(blocks), blocks);
67         std::copy(other.light, other.light + sizeof(light), light);
68         generated = other.generated;
69         lighted = other.lighted;
70         position = other.position;
71         std::swap(ref_count, other.ref_count);
72         dirty_mesh = other.dirty_save;
73         dirty_save = other.dirty_save;
74         return *this;
75 }
76
77
78 namespace {
79
80 struct SetNode {
81
82         Chunk *chunk;
83         RoughLocation::Fine pos;
84
85         SetNode(Chunk *chunk, RoughLocation::Fine pos)
86         : chunk(chunk), pos(pos) { }
87
88         int Get() const noexcept { return chunk->GetLight(pos); }
89         void Set(int level) noexcept { chunk->SetLight(pos, level); }
90
91         const BlockType &GetType() const noexcept { return chunk->Type(Chunk::ToIndex(pos)); }
92
93         int EmitLevel() const noexcept { return GetType().luminosity; }
94         bool EmitsLight() const noexcept { return EmitLevel() > 0; }
95
96         bool HasNext(Block::Face face) noexcept {
97                 const BlockType &type = GetType();
98                 if (type.block_light && !type.luminosity) return false;
99                 const BlockLookup next(chunk, pos, face);
100                 return next;
101         }
102         SetNode GetNext(Block::Face face) noexcept {
103                 const BlockLookup next(chunk, pos, face);
104                 return SetNode(&next.GetChunk(), next.GetBlockPos());
105         }
106
107 };
108
109 struct UnsetNode
110 : public SetNode {
111
112         int level;
113
114         UnsetNode(Chunk *chunk, RoughLocation::Fine pos)
115         : SetNode(chunk, pos), level(Get()) { }
116
117         UnsetNode(const SetNode &set)
118         : SetNode(set), level(Get()) { }
119
120
121         bool HasNext(Block::Face face) noexcept {
122                 const BlockLookup next(chunk, pos, face);
123                 return next;
124         }
125         UnsetNode GetNext(Block::Face face) noexcept { return UnsetNode(SetNode::GetNext(face)); }
126
127 };
128
129 std::queue<SetNode> light_queue;
130 std::queue<UnsetNode> dark_queue;
131
132 void work_light() noexcept {
133         while (!light_queue.empty()) {
134                 SetNode node = light_queue.front();
135                 light_queue.pop();
136
137                 int level = node.Get() - 1;
138                 for (int face = 0; face < Block::FACE_COUNT; ++face) {
139                         if (node.HasNext(Block::Face(face))) {
140                                 SetNode other = node.GetNext(Block::Face(face));
141                                 if (other.Get() < level) {
142                                         other.Set(level);
143                                         light_queue.emplace(other);
144                                 }
145                         }
146                 }
147         }
148 }
149
150 void work_dark() noexcept {
151         while (!dark_queue.empty()) {
152                 UnsetNode node = dark_queue.front();
153                 dark_queue.pop();
154
155                 for (int face = 0; face < Block::FACE_COUNT; ++face) {
156                         if (node.HasNext(Block::Face(face))) {
157                                 UnsetNode other = node.GetNext(Block::Face(face));
158                                 if (other.Get() != 0 && other.Get() < node.level) {
159                                         if (other.EmitsLight()) {
160                                                 other.Set(other.EmitLevel());
161                                                 light_queue.emplace(other);
162                                         } else {
163                                                 other.Set(0);
164                                         }
165                                         dark_queue.emplace(other);
166                                 } else {
167                                         light_queue.emplace(other);
168                                 }
169                         }
170                 }
171         }
172 }
173
174 }
175
176 void Chunk::SetBlock(int index, const Block &block) noexcept {
177         const BlockType &old_type = Type(blocks[index]);
178         const BlockType &new_type = Type(block);
179
180         blocks[index] = block;
181         Invalidate();
182
183         if (old_type.gravity && !new_type.gravity) {
184                 gravity.erase(index);
185         } else if (new_type.gravity && !old_type.gravity) {
186                 gravity.insert(index);
187         }
188
189         if (!lighted || &old_type == &new_type) return;
190
191         if (new_type.luminosity > old_type.luminosity) {
192                 // light added
193                 SetLight(index, new_type.luminosity);
194                 light_queue.emplace(this, ToPos(index));
195                 work_light();
196         } else if (new_type.luminosity < old_type.luminosity) {
197                 // light removed
198                 dark_queue.emplace(this, ToPos(index));
199                 SetLight(index, 0);
200                 work_dark();
201                 SetLight(index, new_type.luminosity);
202                 light_queue.emplace(this, ToPos(index));
203                 work_light();
204         } else if (new_type.block_light && !old_type.block_light) {
205                 // obstacle added
206                 if (GetLight(index) > 0) {
207                         dark_queue.emplace(this, ToPos(index));
208                         SetLight(index, 0);
209                         work_dark();
210                         work_light();
211                 }
212         } else if (!new_type.block_light && old_type.block_light) {
213                 // obstacle removed
214                 int level = 0;
215                 RoughLocation::Fine pos(ToPos(index));
216                 for (int face = 0; face < Block::FACE_COUNT; ++face) {
217                         BlockLookup next_block(this, pos, Block::Face(face));
218                         if (next_block) {
219                                 level = std::max(level, next_block.GetLight());
220                         }
221                 }
222                 if (level > 1) {
223                         SetLight(index, level - 1);
224                         light_queue.emplace(this, pos);
225                         work_light();
226                 }
227         }
228 }
229
230 void Chunk::ScanLights() {
231         int idx = 0;
232         RoughLocation::Fine pos(0, 0, 0);
233         for (; pos.z < side; ++pos.z) {
234                 for (pos.y = 0; pos.y < side; ++pos.y) {
235                         for (pos.x = 0; pos.x < side; ++pos.x, ++idx) {
236                                 const BlockType &type = Type(blocks[idx]);
237                                 if (type.luminosity) {
238                                         SetLight(idx, type.luminosity);
239                                         light_queue.emplace(this, pos);
240                                 }
241                         }
242                 }
243         }
244         work_light();
245         lighted = true;
246 }
247
248 void Chunk::ScanActive() {
249         gravity.clear();
250         for (int index = 0; index < size; ++index) {
251                 if (Type(index).gravity) {
252                         gravity.insert(gravity.end(), index);
253                 }
254         }
255 }
256
257 void Chunk::SetNeighbor(Block::Face face, Chunk &other) noexcept {
258         neighbor[face] = &other;
259         other.neighbor[Block::Opposite(face)] = this;
260 }
261
262 void Chunk::Unlink() noexcept {
263         for (int face = 0; face < Block::FACE_COUNT; ++face) {
264                 if (neighbor[face]) {
265                         neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = nullptr;
266                         neighbor[face] = nullptr;
267                 }
268         }
269 }
270
271
272 void Chunk::SetLight(int index, int level) noexcept {
273         if (light[index] != level) {
274                 light[index] = level;
275                 Invalidate();
276         }
277 }
278
279 int Chunk::GetLight(int index) const noexcept {
280         return light[index];
281 }
282
283 float Chunk::GetVertexLight(const RoughLocation::Fine &pos, const BlockMesh::Position &vtx, const EntityMesh::Normal &norm) const noexcept {
284         int index = ToIndex(pos);
285         float light = GetLight(index);
286
287         Block::Face direct_face(Block::NormalFace(norm));
288         // tis okay
289         BlockLookup direct(const_cast<Chunk *>(this), pos, Block::NormalFace(norm));
290         if (direct) {
291                 float direct_light = direct.GetLight();
292                 if (direct_light > light) {
293                         light = direct_light;
294                 }
295         } else {
296                 return light;
297         }
298
299         if (Type(BlockAt(index)).luminosity > 0 || direct.GetType().block_light) {
300                 return light;
301         }
302
303         Block::Face edge[2];
304         switch (Block::Axis(direct_face)) {
305                 case 0: // X
306                         edge[0] = (vtx.y - pos.y) > 0.5f ? Block::FACE_UP : Block::FACE_DOWN;
307                         edge[1] = (vtx.z - pos.z) > 0.5f ? Block::FACE_FRONT : Block::FACE_BACK;
308                         break;
309                 case 1: // Y
310                         edge[0] = (vtx.z - pos.z) > 0.5f ? Block::FACE_FRONT : Block::FACE_BACK;
311                         edge[1] = (vtx.x - pos.x) > 0.5f ? Block::FACE_RIGHT : Block::FACE_LEFT;
312                         break;
313                 case 2: // Z
314                         edge[0] = (vtx.x - pos.x) > 0.5f ? Block::FACE_RIGHT : Block::FACE_LEFT;
315                         edge[1] = (vtx.y - pos.y) > 0.5f ? Block::FACE_UP : Block::FACE_DOWN;
316                         break;
317         }
318
319         int num = 1;
320         int occlusion = 0;
321
322         BlockLookup next[2] = {
323                 direct.Next(edge[0]),
324                 direct.Next(edge[1]),
325         };
326
327         if (next[0]) {
328                 if (next[0].GetType().block_light) {
329                         ++occlusion;
330                 } else {
331                         light += next[0].GetLight();
332                         ++num;
333                 }
334         }
335         if (next[1]) {
336                 if (next[1].GetType().block_light) {
337                         ++occlusion;
338                 } else {
339                         light += next[1].GetLight();
340                         ++num;
341                 }
342         }
343         if (occlusion < 2) {
344                 if (next[0]) {
345                         BlockLookup corner = next[0].Next(edge[1]);
346                         if (corner) {
347                                 if (corner.GetType().block_light) {
348                                         ++occlusion;
349                                 } else {
350                                         light += corner.GetLight();
351                                         ++num;
352                                 }
353                         }
354                 } else if (next[1]) {
355                         BlockLookup corner = next[1].Next(edge[0]);
356                         if (corner) {
357                                 if (corner.GetType().block_light) {
358                                         ++occlusion;
359                                 } else {
360                                         light += corner.GetLight();
361                                         ++num;
362                                 }
363                         }
364                 }
365         } else {
366                 ++occlusion;
367         }
368
369         return (light / num) - (occlusion * 0.8f);
370 }
371
372
373 glm::vec3 Chunk::GravityAt(const ExactLocation &coords) const noexcept {
374         glm::vec3 grav(0.0f);
375         for (int index : gravity) {
376                 RoughLocation::Fine block_pos(ToPos(index));
377                 ExactLocation block_coords(position, ToCoords(block_pos));
378                 // trust that block type hasn't changed
379                 grav += Type(index).gravity->GetGravity(
380                         coords.Difference(block_coords).Absolute(),
381                         ToTransform(block_pos, index));
382         }
383         return grav;
384 }
385
386
387 bool Chunk::IsSurface(const RoughLocation::Fine &pos) const noexcept {
388         const Block &block = BlockAt(pos);
389         if (!Type(block).visible) {
390                 return false;
391         }
392         for (int face = 0; face < Block::FACE_COUNT; ++face) {
393                 BlockLookup next = BlockLookup(const_cast<Chunk *>(this), pos, Block::Face(face));
394                 if (!next || !next.GetType().visible) {
395                         return true;
396                 }
397         }
398         return false;
399 }
400
401
402 bool Chunk::Intersection(
403         const Ray &ray,
404         const ExactLocation::Coarse &reference,
405         WorldCollision &coll
406 ) noexcept {
407         int idx = 0;
408         coll.chunk = this;
409         coll.block = -1;
410         coll.depth = std::numeric_limits<float>::infinity();
411         for (int z = 0; z < side; ++z) {
412                 for (int y = 0; y < side; ++y) {
413                         for (int x = 0; x < side; ++x, ++idx) {
414                                 const BlockType &type = Type(idx);
415                                 if (!type.collision || !type.shape) {
416                                         continue;
417                                 }
418                                 float cur_dist;
419                                 glm::vec3 cur_norm;
420                                 if (type.shape->Intersects(ray, ToTransform(reference, RoughLocation::Fine(x, y, z), idx), cur_dist, cur_norm)) {
421                                         if (cur_dist < coll.depth) {
422                                                 coll.block = idx;
423                                                 coll.depth = cur_dist;
424                                                 coll.normal = cur_norm;
425                                         }
426                                 }
427                         }
428                 }
429         }
430
431         if (coll.block < 0) {
432                 return false;
433         } else {
434                 coll.normal = glm::vec3(BlockAt(coll.block).Transform() * glm::vec4(coll.normal, 0.0f));
435                 return true;
436         }
437 }
438
439 bool Chunk::Intersection(
440         const AABB &box,
441         const glm::mat4 &Mbox,
442         const glm::mat4 &Mchunk,
443         std::vector<WorldCollision> &col
444 ) noexcept {
445         bool any = false;
446         float penetration;
447         glm::vec3 normal;
448
449         if (!blank::Intersection(box, Mbox, Bounds(), Mchunk, penetration, normal)) {
450                 return false;
451         }
452
453         // box's origin relative to the chunk
454         const glm::vec3 box_coords(Mbox[3] - Mchunk[3]);
455         const float box_rad = box.OriginRadius();
456
457         // assume a bounding radius of 2 for blocks
458         constexpr float block_rad = 2.0f;
459         const float bb_radius = box_rad + block_rad;
460
461         const RoughLocation::Fine begin(max(
462                 RoughLocation::Fine(0),
463                 RoughLocation::Fine(floor(box_coords - bb_radius))
464         ));
465         const RoughLocation::Fine end(min(
466                 RoughLocation::Fine(side - 1),
467                 RoughLocation::Fine(ceil(box_coords + bb_radius))
468         ) - 1);
469
470         for (RoughLocation::Fine pos(begin); pos.z < end.y; ++pos.z) {
471                 for (pos.y = begin.y; pos.y < end.y; ++pos.y) {
472                         for (pos.x = begin.x; pos.x < end.x; ++pos.x) {
473                                 int idx = ToIndex(pos);
474                                 const BlockType &type = Type(idx);
475                                 if (!type.collision || !type.shape) {
476                                         continue;
477                                 }
478                                 if (type.shape->Intersects(Mchunk * ToTransform(pos, idx), box, Mbox, penetration, normal)) {
479                                         col.emplace_back(this, idx, penetration, normal);
480                                         any = true;
481                                 }
482                         }
483                 }
484         }
485         return any;
486 }
487
488 bool Chunk::Intersection(
489         const Entity &entity,
490         const glm::mat4 &Mentity,
491         const glm::mat4 &Mchunk,
492         std::vector<WorldCollision> &col
493 ) noexcept {
494         // entity's origin relative to the chunk
495         const glm::vec3 entity_coords(Mentity[3] - Mchunk[3]);
496         const float ec_radius = entity.Radius() + Radius();
497
498         if (distance2(entity_coords, Center()) > ec_radius * ec_radius) {
499                 return false;
500         }
501
502         bool any = false;
503         float penetration;
504         glm::vec3 normal;
505
506         // assume a bounding radius of 2 for blocks
507         constexpr float block_rad = 2.0f;
508         const float eb_radius = entity.Radius() + block_rad;
509
510         const RoughLocation::Fine begin(max(
511                 RoughLocation::Fine(0),
512                 RoughLocation::Fine(floor(entity_coords - eb_radius))
513         ));
514         const RoughLocation::Fine end(min(
515                 RoughLocation::Fine(side),
516                 RoughLocation::Fine(ceil(entity_coords + eb_radius))
517         ));
518
519         for (RoughLocation::Fine pos(begin); pos.z < end.z; ++pos.z) {
520                 for (pos.y = begin.y; pos.y < end.y; ++pos.y) {
521                         for (pos.x = begin.x; pos.x < end.x; ++pos.x) {
522                                 int idx = ToIndex(pos);
523                                 const BlockType &type = Type(idx);
524                                 if (!type.collision || !type.shape) {
525                                         continue;
526                                 }
527                                 if (type.shape->Intersects(Mchunk * ToTransform(pos, idx), entity.Bounds(), Mentity, penetration, normal)) {
528                                         col.emplace_back(this, idx, penetration, normal);
529                                         any = true;
530                                 }
531                         }
532                 }
533         }
534         return any;
535 }
536
537
538 namespace {
539
540 BlockMesh::Buffer buf;
541
542 }
543
544 void Chunk::Update(BlockMesh &model) noexcept {
545         int vtx_count = 0, idx_count = 0;
546         for (const auto &block : blocks) {
547                 const BlockType &type = Type(block);
548                 if (type.visible && type.shape) {
549                         vtx_count += type.shape->VertexCount();
550                         idx_count += type.shape->IndexCount();
551                 }
552         }
553         buf.Clear();
554         buf.Reserve(vtx_count, idx_count);
555
556         if (idx_count > 0) {
557                 int idx = 0;
558                 BlockMesh::Index vtx_counter = 0;
559                 for (size_t z = 0; z < side; ++z) {
560                         for (size_t y = 0; y < side; ++y) {
561                                 for (size_t x = 0; x < side; ++x, ++idx) {
562                                         const BlockType &type = Type(BlockAt(idx));
563                                         const RoughLocation::Fine pos(x, y, z);
564
565                                         if (!type.visible || !type.shape || Obstructed(pos).All()) continue;
566
567                                         type.FillBlockMesh(buf, ToTransform(pos, idx), vtx_counter);
568                                         size_t vtx_begin = vtx_counter;
569                                         vtx_counter += type.shape->VertexCount();
570
571                                         for (size_t vtx = vtx_begin; vtx < vtx_counter; ++vtx) {
572                                                 buf.lights.emplace_back(GetVertexLight(
573                                                         pos,
574                                                         buf.vertices[vtx],
575                                                         type.shape->VertexNormal(vtx - vtx_begin, BlockAt(idx).Transform())
576                                                 ));
577                                         }
578                                 }
579                         }
580                 }
581         }
582
583         model.Update(buf);
584         ClearMesh();
585 }
586
587 Block::FaceSet Chunk::Obstructed(const RoughLocation::Fine &pos) const noexcept {
588         Block::FaceSet result;
589
590         for (int f = 0; f < Block::FACE_COUNT; ++f) {
591                 Block::Face face = Block::Face(f);
592                 BlockLookup next(const_cast<Chunk *>(this), pos, face);
593                 if (next && next.GetType().FaceFilled(next.GetBlock(), Block::Opposite(face))) {
594                         result.Set(face);
595                 }
596         }
597
598         return result;
599 }
600
601 glm::mat4 Chunk::ToTransform(const RoughLocation::Fine &pos, int idx) const noexcept {
602         return glm::translate(ToCoords(pos)) * BlockAt(idx).Transform();
603 }
604
605 glm::mat4 Chunk::ToTransform(const ExactLocation::Coarse &ref, const RoughLocation::Fine &pos, int idx) const noexcept {
606         return glm::translate(ExactLocation::Fine((position - ref) * ExactLocation::Extent()) + ToCoords(pos)) * BlockAt(idx).Transform();
607 }
608
609
610 BlockLookup::BlockLookup(Chunk *c, const RoughLocation::Fine &p) noexcept
611 : chunk(c), pos(p) {
612         while (pos.x >= Chunk::side) {
613                 if (chunk->HasNeighbor(Block::FACE_RIGHT)) {
614                         chunk = &chunk->GetNeighbor(Block::FACE_RIGHT);
615                         pos.x -= Chunk::side;
616                 } else {
617                         chunk = nullptr;
618                         return;
619                 }
620         }
621         while (pos.x < 0) {
622                 if (chunk->HasNeighbor(Block::FACE_LEFT)) {
623                         chunk = &chunk->GetNeighbor(Block::FACE_LEFT);
624                         pos.x += Chunk::side;
625                 } else {
626                         chunk = nullptr;
627                         return;
628                 }
629         }
630         while (pos.y >= Chunk::side) {
631                 if (chunk->HasNeighbor(Block::FACE_UP)) {
632                         chunk = &chunk->GetNeighbor(Block::FACE_UP);
633                         pos.y -= Chunk::side;
634                 } else {
635                         chunk = nullptr;
636                         return;
637                 }
638         }
639         while (pos.y < 0) {
640                 if (chunk->HasNeighbor(Block::FACE_DOWN)) {
641                         chunk = &chunk->GetNeighbor(Block::FACE_DOWN);
642                         pos.y += Chunk::side;
643                 } else {
644                         chunk = nullptr;
645                         return;
646                 }
647         }
648         while (pos.z >= Chunk::side) {
649                 if (chunk->HasNeighbor(Block::FACE_FRONT)) {
650                         chunk = &chunk->GetNeighbor(Block::FACE_FRONT);
651                         pos.z -= Chunk::side;
652                 } else {
653                         chunk = nullptr;
654                         return;
655                 }
656         }
657         while (pos.z < 0) {
658                 if (chunk->HasNeighbor(Block::FACE_BACK)) {
659                         chunk = &chunk->GetNeighbor(Block::FACE_BACK);
660                         pos.z += Chunk::side;
661                 } else {
662                         chunk = nullptr;
663                         return;
664                 }
665         }
666 }
667
668 BlockLookup::BlockLookup(Chunk *c, const RoughLocation::Fine &p, Block::Face face) noexcept
669 : chunk(c), pos(p) {
670         pos += Block::FaceNormal(face);
671         if (!Chunk::InBounds(pos)) {
672                 pos -= Block::FaceNormal(face) * ExactLocation::Extent();
673                 chunk = &chunk->GetNeighbor(face);
674         }
675 }
676
677
678 ChunkLoader::ChunkLoader(
679         ChunkStore &store,
680         const Generator &gen,
681         const WorldSave &save
682 ) noexcept
683 : store(store)
684 , gen(gen)
685 , save(save) {
686
687 }
688
689 void ChunkLoader::Update(int dt) {
690         // check if there's chunks waiting to be loaded
691         // load until one of load or generation limits was hit
692         constexpr int max_load = 10;
693         constexpr int max_gen = 1;
694         int loaded = 0;
695         int generated = 0;
696         while (loaded < max_load && generated < max_gen && store.HasMissing()) {
697                 if (LoadOne()) {
698                         ++generated;
699                 } else {
700                         ++loaded;
701                 }
702         }
703
704         // store a few chunks as well
705         constexpr int max_save = 10;
706         int saved = 0;
707         for (Chunk &chunk : store) {
708                 if (chunk.ShouldUpdateSave()) {
709                         save.Write(chunk);
710                         ++saved;
711                         if (saved >= max_save) {
712                                 break;
713                         }
714                 }
715         }
716 }
717
718 int ChunkLoader::ToLoad() const noexcept {
719         return store.EstimateMissing();
720 }
721
722 bool ChunkLoader::LoadOne() {
723         if (!store.HasMissing()) return false;
724
725         ExactLocation::Coarse pos = store.NextMissing();
726         Chunk *chunk = store.Allocate(pos);
727         if (!chunk) {
728                 // chunk store corrupted?
729                 return false;
730         }
731
732         bool generated = false;
733         if (save.Exists(pos)) {
734                 save.Read(*chunk);
735         } else {
736                 gen(*chunk);
737                 generated = true;
738         }
739
740         ChunkIndex *index = store.ClosestIndex(pos);
741         if (!index) {
742                 return generated;
743         }
744
745         ExactLocation::Coarse begin(pos - ExactLocation::Coarse(1));
746         ExactLocation::Coarse end(pos + ExactLocation::Coarse(2));
747         for (ExactLocation::Coarse iter(begin); iter.z < end.z; ++iter.z) {
748                 for (iter.y = begin.y; iter.y < end.y; ++iter.y) {
749                         for (iter.x = begin.x; iter.x < end.x; ++iter.x) {
750                                 if (index->IsBorder(iter)) continue;
751                                 Chunk *light_chunk = index->Get(iter);
752                                 if (!light_chunk) continue;
753                                 if (index->HasAllSurrounding(iter)) {
754                                         if (!light_chunk->Lighted()) {
755                                                 light_chunk->ScanLights();
756                                         } else {
757                                                 light_chunk->InvalidateMesh();
758                                         }
759                                 }
760                         }
761                 }
762         }
763
764         return generated;
765 }
766
767 void ChunkLoader::LoadN(std::size_t n) {
768         std::size_t end = std::min(n, std::size_t(ToLoad()));
769         for (std::size_t i = 0; i < end && store.HasMissing(); ++i) {
770                 LoadOne();
771         }
772 }
773
774
775 ChunkRenderer::ChunkRenderer(ChunkIndex &index)
776 : index(index)
777 , models(index.TotalChunks())
778 , block_tex()
779 , fog_density(0.0f) {
780
781 }
782
783 ChunkRenderer::~ChunkRenderer() {
784
785 }
786
787 int ChunkRenderer::MissingChunks() const noexcept {
788         return index.MissingChunks();
789 }
790
791 void ChunkRenderer::LoadTextures(const AssetLoader &loader, const ResourceIndex &tex_index) {
792         block_tex.Bind();
793         loader.LoadTextures(tex_index, block_tex);
794         block_tex.FilterNearest();
795 }
796
797 void ChunkRenderer::Update(int dt) {
798         for (int i = 0, updates = 0; updates < dt && i < index.TotalChunks(); ++i) {
799                 if (!index[i]) continue;
800                 if (!index[i]->Lighted() && index.HasAllSurrounding(index[i]->Position())) {
801                         index[i]->ScanLights();
802                 }
803                 if (index[i]->ShouldUpdateMesh()) {
804                         index[i]->Update(models[i]);
805                         ++updates;
806                 }
807         }
808 }
809
810 void ChunkRenderer::Render(Viewport &viewport) {
811         BlockLighting &chunk_prog = viewport.ChunkProgram();
812         chunk_prog.SetTexture(block_tex);
813         chunk_prog.SetFogDensity(fog_density);
814
815         Frustum frustum(transpose(chunk_prog.GetVP()));
816         AABB box;
817
818         //std::cout << "V = " << chunk_prog.View() << std::endl;
819         //std::cout << "P = " << chunk_prog.Projection() << std::endl;
820         //std::cout << "VP = " << chunk_prog.GetVP() << std::endl;
821         //std::cout << "frustum = " << frustum << std::endl;
822
823         for (int i = 0; i < index.TotalChunks(); ++i) {
824                 if (!index[i]) continue;
825                 box.min = (index[i]->Position() - index.Base()) * ExactLocation::Extent();
826                 box.max = box.min + ExactLocation::FExtent();
827
828                 if (!CullTest(box, frustum)) {
829
830                         //glm::mat4 m(index[i]->Transform(index.Base()));
831                         //if (CullTest(Chunk::Bounds(), chunk_prog.GetVP() * m)) {
832                         //      std::cout << "M = " << m << std::endl;
833                         //      std::cout << "box = " << box.min << ", " << box.max << std::endl;
834                         //      std::cout << "should've been culled" << std::endl;
835                         //}
836
837                         if (index[i]->ShouldUpdateMesh()) {
838                                 index[i]->Update(models[i]);
839                         }
840                         chunk_prog.SetM(index[i]->Transform(index.Base()));
841                         models[i].Draw();
842                 }
843         }
844         //std::cout << std::endl;
845 }
846
847
848 ChunkIndex::ChunkIndex(ChunkStore &store, const ExactLocation::Coarse &base, int extent)
849 : store(store)
850 , base(base)
851 , extent(extent)
852 , side_length(2 * extent + 1)
853 , total_length(side_length * side_length * side_length)
854 , total_indexed(0)
855 , last_missing(0)
856 , stride(1, side_length, side_length * side_length)
857 , chunks(total_length, nullptr) {
858         Scan();
859 }
860
861 ChunkIndex::~ChunkIndex() {
862         Clear();
863 }
864
865 bool ChunkIndex::InRange(const ExactLocation::Coarse &pos) const noexcept {
866         return Distance(pos) <= extent;
867 }
868
869 bool ChunkIndex::IsBorder(const ExactLocation::Coarse &pos) const noexcept {
870         return Distance(pos) == extent;
871 }
872
873 int ChunkIndex::Distance(const ExactLocation::Coarse &pos) const noexcept {
874         return manhattan_radius(pos - base);
875 }
876
877 bool ChunkIndex::HasAllSurrounding(const ExactLocation::Coarse &pos) const noexcept {
878         ExactLocation::Coarse begin(pos - ExactLocation::Coarse(1));
879         ExactLocation::Coarse end(pos + ExactLocation::Coarse(2));
880         for (ExactLocation::Coarse iter(begin); iter.z < end.z; ++iter.z) {
881                 for (iter.y = begin.y; iter.y < end.y; ++iter.y) {
882                         for (iter.x = begin.x; iter.x < end.x; ++iter.x) {
883                                 if (!Get(iter)) return false;
884                         }
885                 }
886         }
887         return true;
888 }
889
890 int ChunkIndex::IndexOf(const ExactLocation::Coarse &pos) const noexcept {
891         ExactLocation::Coarse mod_pos(
892                 GetCol(pos.x),
893                 GetCol(pos.y),
894                 GetCol(pos.z)
895         );
896         return mod_pos.x * stride.x
897                 +  mod_pos.y * stride.y
898                 +  mod_pos.z * stride.z;
899 }
900
901 ExactLocation::Coarse ChunkIndex::PositionOf(int i) const noexcept {
902         ExactLocation::Coarse zero_pos(
903                 (i / stride.x) % side_length,
904                 (i / stride.y) % side_length,
905                 (i / stride.z) % side_length
906         );
907         ExactLocation::Coarse zero_base(
908                 GetCol(base.x),
909                 GetCol(base.y),
910                 GetCol(base.z)
911         );
912         ExactLocation::Coarse base_relative(zero_pos - zero_base);
913         if (base_relative.x > extent) base_relative.x -= side_length;
914         else if (base_relative.x < -extent) base_relative.x += side_length;
915         if (base_relative.y > extent) base_relative.y -= side_length;
916         else if (base_relative.y < -extent) base_relative.y += side_length;
917         if (base_relative.z > extent) base_relative.z -= side_length;
918         else if (base_relative.z < -extent) base_relative.z += side_length;
919         return base + base_relative;
920 }
921
922 Chunk *ChunkIndex::Get(const ExactLocation::Coarse &pos) noexcept {
923         if (InRange(pos)) {
924                 return chunks[IndexOf(pos)];
925         } else {
926                 return nullptr;
927         }
928 }
929
930 const Chunk *ChunkIndex::Get(const ExactLocation::Coarse &pos) const noexcept {
931         if (InRange(pos)) {
932                 return chunks[IndexOf(pos)];
933         } else {
934                 return nullptr;
935         }
936 }
937
938 void ChunkIndex::Rebase(const ExactLocation::Coarse &new_base) {
939         if (new_base == base) return;
940
941         ExactLocation::Coarse diff(new_base - base);
942
943         if (manhattan_radius(diff) > extent) {
944                 // that's more than half, so probably not worth shifting
945                 base = new_base;
946                 Clear();
947                 Scan();
948                 store.Clean();
949                 return;
950         }
951
952         while (diff.x > 0) {
953                 Shift(Block::FACE_RIGHT);
954                 --diff.x;
955         }
956         while (diff.x < 0) {
957                 Shift(Block::FACE_LEFT);
958                 ++diff.x;
959         }
960         while (diff.y > 0) {
961                 Shift(Block::FACE_UP);
962                 --diff.y;
963         }
964         while (diff.y < 0) {
965                 Shift(Block::FACE_DOWN);
966                 ++diff.y;
967         }
968         while (diff.z > 0) {
969                 Shift(Block::FACE_FRONT);
970                 --diff.z;
971         }
972         while (diff.z < 0) {
973                 Shift(Block::FACE_BACK);
974                 ++diff.z;
975         }
976         store.Clean();
977 }
978
979 int ChunkIndex::GetCol(int c) const noexcept {
980         c %= side_length;
981         if (c < 0) c += side_length;
982         return c;
983 }
984
985 void ChunkIndex::Shift(Block::Face f) {
986         int a_axis = Block::Axis(f);
987         int b_axis = (a_axis + 1) % 3;
988         int c_axis = (a_axis + 2) % 3;
989         int dir = Block::Direction(f);
990         base[a_axis] += dir;
991         int a = GetCol(base[a_axis] + (extent * dir));
992         int a_stride = a * stride[a_axis];
993         for (int b = 0; b < side_length; ++b) {
994                 int b_stride = b * stride[b_axis];
995                 for (int c = 0; c < side_length; ++c) {
996                         int bc_stride = b_stride + c * stride[c_axis];
997                         int index = a_stride + bc_stride;
998                         Unset(index);
999                         int neighbor = ((a - dir + side_length) % side_length) * stride[a_axis] + bc_stride;
1000                         if (chunks[neighbor] && chunks[neighbor]->HasNeighbor(f)) {
1001                                 Set(index, chunks[neighbor]->GetNeighbor(f));
1002                         }
1003                 }
1004         }
1005 }
1006
1007 void ChunkIndex::Clear() noexcept {
1008         for (int i = 0; i < total_length && total_indexed > 0; ++i) {
1009                 Unset(i);
1010         }
1011 }
1012
1013 void ChunkIndex::Scan() noexcept {
1014         for (Chunk &chunk : store) {
1015                 Register(chunk);
1016         }
1017 }
1018
1019 void ChunkIndex::Register(Chunk &chunk) noexcept {
1020         if (InRange(chunk.Position())) {
1021                 Set(IndexOf(chunk.Position()), chunk);
1022         }
1023 }
1024
1025 void ChunkIndex::Set(int index, Chunk &chunk) noexcept {
1026         Unset(index);
1027         chunks[index] = &chunk;
1028         chunk.Ref();
1029         ++total_indexed;
1030 }
1031
1032 void ChunkIndex::Unset(int index) noexcept {
1033         if (chunks[index]) {
1034                 chunks[index]->UnRef();
1035                 chunks[index] = nullptr;
1036                 --total_indexed;
1037         }
1038 }
1039
1040 ExactLocation::Coarse ChunkIndex::NextMissing() noexcept {
1041         if (MissingChunks() > 0) {
1042                 int roundtrip = last_missing;
1043                 last_missing = (last_missing + 1) % total_length;
1044                 while (chunks[last_missing]) {
1045                         last_missing = (last_missing + 1) % total_length;
1046                         if (last_missing == roundtrip) {
1047                                 break;
1048                         }
1049                 }
1050         }
1051         return PositionOf(last_missing);
1052 }
1053
1054
1055 ChunkStore::ChunkStore(const BlockTypeRegistry &types)
1056 : types(types)
1057 , loaded()
1058 , free()
1059 , indices() {
1060
1061 }
1062
1063 ChunkStore::~ChunkStore() {
1064
1065 }
1066
1067 ChunkIndex &ChunkStore::MakeIndex(const ExactLocation::Coarse &pos, int extent) {
1068         indices.emplace_back(*this, pos, extent);
1069         return indices.back();
1070 }
1071
1072 void ChunkStore::UnregisterIndex(ChunkIndex &index) {
1073         for (auto i = indices.begin(), end = indices.end(); i != end; ++i) {
1074                 if (&*i == &index) {
1075                         indices.erase(i);
1076                         return;
1077                 } else {
1078                         ++i;
1079                 }
1080         }
1081 }
1082
1083 ChunkIndex *ChunkStore::ClosestIndex(const ExactLocation::Coarse &pos) {
1084         ChunkIndex *closest_index = nullptr;
1085         int closest_distance = std::numeric_limits<int>::max();
1086
1087         for (ChunkIndex &index : indices) {
1088                 int distance = index.Distance(pos);
1089                 if (distance < closest_distance) {
1090                         closest_index = &index;
1091                         closest_distance = distance;
1092                 }
1093         }
1094
1095         return closest_index;
1096 }
1097
1098 Chunk *ChunkStore::Get(const ExactLocation::Coarse &pos) noexcept {
1099         for (ChunkIndex &index : indices) {
1100                 Chunk *chunk = index.Get(pos);
1101                 if (chunk) {
1102                         return chunk;
1103                 }
1104         }
1105         return nullptr;
1106 }
1107
1108 const Chunk *ChunkStore::Get(const ExactLocation::Coarse &pos) const noexcept {
1109         for (const ChunkIndex &index : indices) {
1110                 const Chunk *chunk = index.Get(pos);
1111                 if (chunk) {
1112                         return chunk;
1113                 }
1114         }
1115         return nullptr;
1116 }
1117
1118 Chunk *ChunkStore::Allocate(const ExactLocation::Coarse &pos) {
1119         Chunk *chunk = Get(pos);
1120         if (chunk) {
1121                 return chunk;
1122         }
1123         if (free.empty()) {
1124                 loaded.emplace(loaded.begin(), types);
1125         } else {
1126                 loaded.splice(loaded.begin(), free, free.begin());
1127                 loaded.front().Unlink();
1128         }
1129         chunk = &loaded.front();
1130         chunk->Position(pos);
1131         for (ChunkIndex &index : indices) {
1132                 if (index.InRange(pos)) {
1133                         index.Register(*chunk);
1134                 }
1135         }
1136         for (int i = 0; i < Block::FACE_COUNT; ++i) {
1137                 Block::Face face = Block::Face(i);
1138                 ExactLocation::Coarse neighbor_pos(pos + Block::FaceNormal(face));
1139                 Chunk *neighbor = Get(neighbor_pos);
1140                 if (neighbor) {
1141                         chunk->SetNeighbor(face, *neighbor);
1142                 }
1143         }
1144         return chunk;
1145 }
1146
1147 bool ChunkStore::HasMissing() const noexcept {
1148         for (const ChunkIndex &index : indices) {
1149                 if (index.MissingChunks() > 0) {
1150                         return true;
1151                 }
1152         }
1153         return false;
1154 }
1155
1156 int ChunkStore::EstimateMissing() const noexcept {
1157         int missing = 0;
1158         for (const ChunkIndex &index : indices) {
1159                 missing += index.MissingChunks();
1160         }
1161         return missing;
1162 }
1163
1164 ExactLocation::Coarse ChunkStore::NextMissing() noexcept {
1165         for (ChunkIndex &index : indices) {
1166                 if (index.MissingChunks()) {
1167                         return index.NextMissing();
1168                 }
1169         }
1170         return ExactLocation::Coarse(0, 0, 0);
1171 }
1172
1173 void ChunkStore::Clean() {
1174         for (auto i = loaded.begin(), end = loaded.end(); i != end;) {
1175                 if (i->Referenced() || i->ShouldUpdateSave()) {
1176                         ++i;
1177                 } else {
1178                         auto chunk = i;
1179                         ++i;
1180                         free.splice(free.end(), loaded, chunk);
1181                         chunk->Unlink();
1182                         chunk->InvalidateMesh();
1183                 }
1184         }
1185 }
1186
1187 }