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