]> git.localhorst.tv Git - blank.git/blob - src/chunk.cpp
updated description and TODO list
[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         work_light();
198 }
199
200 }
201
202 void Chunk::SetBlock(int index, const Block &block) {
203         const BlockType &old_type = Type(blocks[index]);
204         const BlockType &new_type = Type(block);
205
206         blocks[index] = block;
207
208         if (&old_type == &new_type) return;
209
210         if (new_type.luminosity > 0) {
211                 if (GetLight(index) < new_type.luminosity) {
212                         SetLight(index, new_type.luminosity);
213                         light_queue.emplace(this, ToPos(index));
214                         work_light();
215                 }
216         } else if (new_type.block_light && GetLight(index) != 0) {
217                 SetLight(index, 0);
218                 dark_queue.emplace(this, ToPos(index));
219                 work_dark();
220         } else if (old_type.block_light && !new_type.block_light) {
221                 int level = 0;
222                 for (int face = 0; face < Block::FACE_COUNT; ++face) {
223                         Pos next_pos(ToPos(index) + Block::FaceNormal(Block::Face(face)));
224                         int next_level = 0;
225                         if (InBounds(next_pos)) {
226                                 next_level = GetLight(next_pos);
227                         } else {
228                                 if (HasNeighbor(Block::Face(face))) {
229                                         next_pos -= (Block::FaceNormal(Block::Face(face)) * Chunk::Extent());
230                                         next_level = GetNeighbor(Block::Face(face)).GetLight(next_pos);
231                                 }
232                         }
233                         if (level < next_level) {
234                                 level = next_level;
235                         }
236                 }
237                 if (level > 1) {
238                         SetLight(index, level - 1);
239                         light_queue.emplace(this, ToPos(index));
240                         work_light();
241                 }
242         }
243 }
244
245 const Block *Chunk::FindNext(const Pos &pos, Block::Face face) const {
246         Pos next_pos(pos + Block::FaceNormal(face));
247         if (InBounds(next_pos)) {
248                 return &BlockAt(pos + Block::FaceNormal(face));
249         } else if (HasNeighbor(face)) {
250                 return &GetNeighbor(face).BlockAt(next_pos - (Block::FaceNormal(face) * Extent()));
251         } else {
252                 return nullptr;
253         }
254 }
255
256
257 void Chunk::SetLight(int index, int level) {
258         light[index] = level;
259 }
260
261 int Chunk::GetLight(int index) const {
262         return light[index];
263 }
264
265
266 void Chunk::Allocate() {
267         blocks.resize(Size(), Block(0));
268         light.resize(Size(), 0);
269 }
270
271
272 void Chunk::Draw() {
273         if (dirty) {
274                 Update();
275         }
276         model.Draw();
277 }
278
279
280 bool Chunk::Intersection(
281         const Ray &ray,
282         const glm::mat4 &M,
283         int &blkid,
284         float &dist,
285         glm::vec3 &normal
286 ) const {
287         // TODO: should be possible to heavily optimize this
288         int id = 0;
289         blkid = -1;
290         dist = std::numeric_limits<float>::infinity();
291         for (int z = 0; z < Depth(); ++z) {
292                 for (int y = 0; y < Height(); ++y) {
293                         for (int x = 0; x < Width(); ++x, ++id) {
294                                 if (!Type(blocks[id]).visible) {
295                                         continue;
296                                 }
297                                 float cur_dist;
298                                 glm::vec3 cur_norm;
299                                 if (Type(blocks[id]).shape->Intersects(ray, M * ToTransform(id), cur_dist, cur_norm)) {
300                                         if (cur_dist < dist) {
301                                                 blkid = id;
302                                                 dist = cur_dist;
303                                                 normal = cur_norm;
304                                         }
305                                 }
306                         }
307                 }
308         }
309
310         if (blkid < 0) {
311                 return false;
312         } else {
313                 normal = glm::vec3(BlockAt(blkid).Transform() * glm::vec4(normal, 0.0f));
314                 return true;
315         }
316 }
317
318 void Chunk::Position(const Pos &pos) {
319         position = pos;
320 }
321
322 glm::mat4 Chunk::Transform(const Pos &offset) const {
323         return glm::translate((position - offset) * Extent());
324 }
325
326
327 namespace {
328
329 Model::Buffer buf;
330
331 }
332
333 void Chunk::CheckUpdate() {
334         if (dirty) {
335                 Update();
336         }
337 }
338
339 void Chunk::Update() {
340         int vtx_count = 0, idx_count = 0;
341         for (const auto &block : blocks) {
342                 const Shape *shape = Type(block).shape;
343                 vtx_count += shape->VertexCount();
344                 idx_count += shape->VertexIndexCount();
345         }
346         buf.Clear();
347         buf.Reserve(vtx_count, idx_count);
348
349         Model::Index vtx_counter = 0;
350         for (size_t i = 0; i < Size(); ++i) {
351                 const BlockType &type = Type(blocks[i]);
352
353                 if (!type.visible || Obstructed(i)) continue;
354
355                 type.FillModel(buf, ToTransform(i), vtx_counter);
356                 vtx_counter += type.shape->VertexCount();
357         }
358
359         model.Update(buf);
360         dirty = false;
361 }
362
363 bool Chunk::Obstructed(int idx) const {
364         Chunk::Pos pos(ToPos(idx));
365
366         Chunk::Pos left_pos(pos + Chunk::Pos(-1, 0, 0));
367         const Block *left_block = nullptr;
368         if (InBounds(left_pos)) {
369                 left_block = &BlockAt(left_pos);
370         } else if (HasNeighbor(Block::FACE_LEFT)) {
371                 left_pos += Chunk::Pos(Width(), 0, 0);
372                 left_block = &GetNeighbor(Block::FACE_LEFT).BlockAt(left_pos);
373         } else {
374                 return false;
375         }
376         if (!Type(*left_block).FaceFilled(*left_block, Block::FACE_RIGHT)) {
377                 return false;
378         }
379
380         Chunk::Pos right_pos(pos + Chunk::Pos(1, 0, 0));
381         const Block *right_block = nullptr;
382         if (InBounds(right_pos)) {
383                 right_block = &BlockAt(right_pos);
384         } else if (HasNeighbor(Block::FACE_RIGHT)) {
385                 right_pos += Chunk::Pos(-Width(), 0, 0);
386                 right_block = &GetNeighbor(Block::FACE_RIGHT).BlockAt(right_pos);
387         } else {
388                 return false;
389         }
390         if (!Type(*right_block).FaceFilled(*right_block, Block::FACE_LEFT)) {
391                 return false;
392         }
393
394         Chunk::Pos down_pos(pos + Chunk::Pos(0, -1, 0));
395         const Block *down_block = nullptr;
396         if (InBounds(down_pos)) {
397                 down_block = &BlockAt(down_pos);
398         } else if (HasNeighbor(Block::FACE_DOWN)) {
399                 down_pos += Chunk::Pos(0, Height(), 0);
400                 down_block = &GetNeighbor(Block::FACE_DOWN).BlockAt(down_pos);
401         } else {
402                 return false;
403         }
404         if (!Type(*down_block).FaceFilled(*down_block, Block::FACE_UP)) {
405                 return false;
406         }
407
408         Chunk::Pos up_pos(pos + Chunk::Pos(0, 1, 0));
409         const Block *up_block = nullptr;
410         if (InBounds(up_pos)) {
411                 up_block = &BlockAt(up_pos);
412         } else if (HasNeighbor(Block::FACE_UP)) {
413                 up_pos += Chunk::Pos(0, -Height(), 0);
414                 up_block = &GetNeighbor(Block::FACE_UP).BlockAt(up_pos);
415         } else {
416                 return false;
417         }
418         if (!Type(*up_block).FaceFilled(*up_block, Block::FACE_DOWN)) {
419                 return false;
420         }
421
422         Chunk::Pos back_pos(pos + Chunk::Pos(0, 0, -1));
423         const Block *back_block = nullptr;
424         if (InBounds(back_pos)) {
425                 back_block = &BlockAt(back_pos);
426         } else if (HasNeighbor(Block::FACE_BACK)) {
427                 back_pos += Chunk::Pos(0, 0, Depth());
428                 back_block = &GetNeighbor(Block::FACE_BACK).BlockAt(back_pos);
429         } else {
430                 return false;
431         }
432         if (!Type(*back_block).FaceFilled(*back_block, Block::FACE_FRONT)) {
433                 return false;
434         }
435
436         Chunk::Pos front_pos(pos + Chunk::Pos(0, 0, 1));
437         const Block *front_block = nullptr;
438         if (InBounds(front_pos)) {
439                 front_block = &BlockAt(front_pos);
440         } else if (HasNeighbor(Block::FACE_FRONT)) {
441                 front_pos += Chunk::Pos(0, 0, -Depth());
442                 front_block = &GetNeighbor(Block::FACE_FRONT).BlockAt(front_pos);
443         } else {
444                 return false;
445         }
446         if (!Type(*front_block).FaceFilled(*front_block, Block::FACE_BACK)) {
447                 return false;
448         }
449
450         return true;
451 }
452
453 glm::mat4 Chunk::ToTransform(int idx) const {
454         return glm::translate(glm::mat4(1.0f), ToCoords(idx)) * blocks[idx].Transform();
455 }
456
457
458 ChunkLoader::ChunkLoader(const BlockTypeRegistry &reg, const Generator &gen)
459 : base(0, 0, 0)
460 , reg(reg)
461 , gen(gen)
462 , loaded()
463 , to_generate()
464 , to_free()
465 , load_dist(4)
466 , unload_dist(5) {
467
468 }
469
470 namespace {
471
472 struct ChunkLess {
473
474         explicit ChunkLess(const Chunk::Pos &base)
475         : base(base) { }
476
477         bool operator ()(const Chunk::Pos &a, const Chunk::Pos &b) const {
478                 Chunk::Pos da(base - a);
479                 Chunk::Pos db(base - b);
480                 return
481                         da.x * da.x + da.y * da.y + da.z * da.z <
482                         db.x * db.x + db.y * db.y + db.z * db.z;
483         }
484
485         Chunk::Pos base;
486
487 };
488
489 }
490
491 void ChunkLoader::Generate(const Chunk::Pos &from, const Chunk::Pos &to) {
492         for (int z = from.z; z < to.z; ++z) {
493                 for (int y = from.y; y < to.y; ++y) {
494                         for (int x = from.x; x < to.x; ++x) {
495                                 Chunk::Pos pos(x, y, z);
496                                 if (Known(pos)) {
497                                         continue;
498                                 } else if (pos == base) {
499                                         Generate(pos);
500
501                                 //      orientation testing
502                                 //      for (int i = 0; i < Block::FACE_COUNT; ++i) {
503                                 //              for (int j = 0; j < Block::TURN_COUNT; ++j) {
504                                 //                      loaded.back().BlockAt(512 * j + 2 * i) = Block(3 * (j + 1), Block::Face(i), Block::Turn(j));
505                                 //              }
506                                 //      }
507                                 //      loaded.back().Invalidate();
508                                 //      loaded.back().CheckUpdate();
509                                 } else {
510                                         to_generate.emplace_back(pos);
511                                 }
512                         }
513                 }
514         }
515         to_generate.sort(ChunkLess(base));
516 }
517
518 Chunk &ChunkLoader::Generate(const Chunk::Pos &pos) {
519         loaded.emplace_back(reg);
520         Chunk &chunk = loaded.back();
521         chunk.Position(pos);
522         Insert(chunk);
523         gen(chunk);
524         return chunk;
525 }
526
527 void ChunkLoader::Insert(Chunk &chunk) {
528         for (Chunk &other : loaded) {
529                 chunk.SetNeighbor(other);
530         }
531 }
532
533 void ChunkLoader::Remove(Chunk &chunk) {
534         chunk.Unlink();
535 }
536
537 Chunk *ChunkLoader::Loaded(const Chunk::Pos &pos) {
538         for (Chunk &chunk : loaded) {
539                 if (chunk.Position() == pos) {
540                         return &chunk;
541                 }
542         }
543         return nullptr;
544 }
545
546 bool ChunkLoader::Queued(const Chunk::Pos &pos) {
547         for (const Chunk::Pos &chunk : to_generate) {
548                 if (chunk == pos) {
549                         return true;
550                 }
551         }
552         return nullptr;
553 }
554
555 bool ChunkLoader::Known(const Chunk::Pos &pos) {
556         if (Loaded(pos)) return true;
557         return Queued(pos);
558 }
559
560 Chunk &ChunkLoader::ForceLoad(const Chunk::Pos &pos) {
561         Chunk *chunk = Loaded(pos);
562         if (chunk) {
563                 return *chunk;
564         }
565
566         for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end; ++iter) {
567                 if (*iter == pos) {
568                         to_generate.erase(iter);
569                         break;
570                 }
571         }
572
573         return Generate(pos);
574 }
575
576 void ChunkLoader::Rebase(const Chunk::Pos &new_base) {
577         if (new_base == base) {
578                 return;
579         }
580         base = new_base;
581
582         // unload far away chunks
583         for (auto iter(loaded.begin()), end(loaded.end()); iter != end;) {
584                 if (std::abs(base.x - iter->Position().x) > unload_dist
585                                 || std::abs(base.y - iter->Position().y) > unload_dist
586                                 || std::abs(base.z - iter->Position().z) > unload_dist) {
587                         auto saved = iter;
588                         Remove(*saved);
589                         ++iter;
590                         to_free.splice(to_free.end(), loaded, saved);
591                 } else {
592                         ++iter;
593                 }
594         }
595         // abort far away queued chunks
596         for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end;) {
597                 if (std::abs(base.x - iter->x) > unload_dist
598                                 || std::abs(base.y - iter->y) > unload_dist
599                                 || std::abs(base.z - iter->z) > unload_dist) {
600                         iter = to_generate.erase(iter);
601                 } else {
602                         ++iter;
603                 }
604         }
605         // add missing new chunks
606         const Chunk::Pos offset(load_dist, load_dist, load_dist);
607         Generate(base - offset, base + offset);
608 }
609
610 void ChunkLoader::Update() {
611         bool reused = false;
612         if (!to_generate.empty()) {
613                 Chunk::Pos pos(to_generate.front());
614
615                 for (auto iter(to_free.begin()), end(to_free.end()); iter != end; ++iter) {
616                         if (iter->Position() == pos) {
617                                 iter->Relink();
618                                 loaded.splice(loaded.end(), to_free, iter);
619                                 reused = true;
620                                 break;
621                         }
622                 }
623
624                 if (!reused) {
625                         if (to_free.empty()) {
626                                 loaded.emplace_back(reg);
627                         } else {
628                                 to_free.front().ClearNeighbors();
629                                 loaded.splice(loaded.end(), to_free, to_free.begin());
630                                 reused = true;
631                         }
632                         Chunk &chunk = loaded.back();
633                         chunk.Position(pos);
634                         Insert(chunk);
635                         gen(chunk);
636                 }
637                 to_generate.pop_front();
638         }
639
640         if (!reused && !to_free.empty()) {
641                 to_free.pop_front();
642         }
643 }
644
645 }