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