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