1 #include "BlockLookup.hpp"
3 #include "ChunkIndex.hpp"
4 #include "ChunkLoader.hpp"
5 #include "ChunkRenderer.hpp"
6 #include "ChunkStore.hpp"
8 #include "Generator.hpp"
9 #include "WorldCollision.hpp"
10 #include "../app/Assets.hpp"
11 #include "../graphics/BlockLighting.hpp"
12 #include "../graphics/Viewport.hpp"
13 #include "../io/WorldSave.hpp"
14 #include "../model/BlockModel.hpp"
24 constexpr int Chunk::width;
25 constexpr int Chunk::height;
26 constexpr int Chunk::depth;
27 constexpr int Chunk::size;
30 Chunk::Chunk(const BlockTypeRegistry &types) noexcept
44 Chunk::Chunk(Chunk &&other) noexcept
46 , position(other.position)
47 , dirty_model(other.dirty_model)
48 , dirty_save(other.dirty_save) {
49 std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor);
50 std::copy(other.blocks, other.blocks + sizeof(blocks), blocks);
51 std::copy(other.light, other.light + sizeof(light), light);
54 Chunk &Chunk::operator =(Chunk &&other) noexcept {
56 std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor);
57 std::copy(other.blocks, other.blocks + sizeof(blocks), blocks);
58 std::copy(other.light, other.light + sizeof(light), light);
59 position = other.position;
60 dirty_model = other.dirty_save;
61 dirty_save = other.dirty_save;
73 SetNode(Chunk *chunk, Chunk::Pos pos)
74 : chunk(chunk), pos(pos) { }
76 int Get() const noexcept { return chunk->GetLight(pos); }
77 void Set(int level) noexcept { chunk->SetLight(pos, level); }
79 const BlockType &GetType() const noexcept { return chunk->Type(Chunk::ToIndex(pos)); }
81 bool HasNext(Block::Face face) noexcept {
82 const BlockType &type = GetType();
83 if (type.block_light && !type.luminosity) return false;
84 const BlockLookup next(chunk, pos, face);
87 SetNode GetNext(Block::Face face) noexcept {
88 const BlockLookup next(chunk, pos, face);
89 return SetNode(&next.GetChunk(), next.GetBlockPos());
99 UnsetNode(Chunk *chunk, Chunk::Pos pos)
100 : SetNode(chunk, pos), level(Get()) { }
102 UnsetNode(const SetNode &set)
103 : SetNode(set), level(Get()) { }
106 bool HasNext(Block::Face face) noexcept {
107 const BlockLookup next(chunk, pos, face);
110 UnsetNode GetNext(Block::Face face) noexcept { return UnsetNode(SetNode::GetNext(face)); }
114 std::queue<SetNode> light_queue;
115 std::queue<UnsetNode> dark_queue;
117 void work_light() noexcept {
118 while (!light_queue.empty()) {
119 SetNode node = light_queue.front();
122 int level = node.Get() - 1;
123 for (int face = 0; face < Block::FACE_COUNT; ++face) {
124 if (node.HasNext(Block::Face(face))) {
125 SetNode other = node.GetNext(Block::Face(face));
126 if (other.Get() < level) {
128 light_queue.emplace(other);
135 void work_dark() noexcept {
136 while (!dark_queue.empty()) {
137 UnsetNode node = dark_queue.front();
140 for (int face = 0; face < Block::FACE_COUNT; ++face) {
141 if (node.HasNext(Block::Face(face))) {
142 UnsetNode other = node.GetNext(Block::Face(face));
143 // TODO: if there a light source here with the same level this will err
144 if (other.Get() != 0 && other.Get() < node.level) {
146 dark_queue.emplace(other);
148 light_queue.emplace(other);
157 void Chunk::SetBlock(int index, const Block &block) noexcept {
158 const BlockType &old_type = Type(blocks[index]);
159 const BlockType &new_type = Type(block);
161 blocks[index] = block;
164 if (!lighted || &old_type == &new_type) return;
166 if (new_type.luminosity > old_type.luminosity) {
168 SetLight(index, new_type.luminosity);
169 light_queue.emplace(this, ToPos(index));
171 } else if (new_type.luminosity < old_type.luminosity) {
173 dark_queue.emplace(this, ToPos(index));
176 SetLight(index, new_type.luminosity);
177 light_queue.emplace(this, ToPos(index));
179 } else if (new_type.block_light && !old_type.block_light) {
181 if (GetLight(index) > 0) {
182 dark_queue.emplace(this, ToPos(index));
187 } else if (!new_type.block_light && old_type.block_light) {
190 Pos pos(ToPos(index));
191 for (int face = 0; face < Block::FACE_COUNT; ++face) {
192 BlockLookup next_block(this, pos, Block::Face(face));
194 level = std::max(level, next_block.GetLight());
198 SetLight(index, level - 1);
199 light_queue.emplace(this, pos);
205 void Chunk::ScanLights() {
208 for (; pos.z < depth; ++pos.z) {
209 for (pos.y = 0; pos.y < height; ++pos.y) {
210 for (pos.x = 0; pos.x < width; ++pos.x, ++idx) {
211 const BlockType &type = Type(blocks[idx]);
212 if (type.luminosity) {
213 SetLight(idx, type.luminosity);
214 light_queue.emplace(this, pos);
223 void Chunk::SetNeighbor(Block::Face face, Chunk &other) noexcept {
224 neighbor[face] = &other;
225 other.neighbor[Block::Opposite(face)] = this;
228 void Chunk::Unlink() noexcept {
229 for (int face = 0; face < Block::FACE_COUNT; ++face) {
230 if (neighbor[face]) {
231 neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = nullptr;
232 neighbor[face] = nullptr;
238 void Chunk::SetLight(int index, int level) noexcept {
239 if (light[index] != level) {
240 light[index] = level;
245 int Chunk::GetLight(int index) const noexcept {
249 float Chunk::GetVertexLight(const Pos &pos, const BlockModel::Position &vtx, const EntityModel::Normal &norm) const noexcept {
250 int index = ToIndex(pos);
251 float light = GetLight(index);
253 Block::Face direct_face(Block::NormalFace(norm));
255 BlockLookup direct(const_cast<Chunk *>(this), pos, Block::NormalFace(norm));
257 float direct_light = direct.GetLight();
258 if (direct_light > light) {
259 light = direct_light;
265 if (Type(BlockAt(index)).luminosity > 0 || direct.GetType().block_light) {
270 switch (Block::Axis(direct_face)) {
272 edge[0] = (vtx.y - pos.y) > 0.5f ? Block::FACE_UP : Block::FACE_DOWN;
273 edge[1] = (vtx.z - pos.z) > 0.5f ? Block::FACE_FRONT : Block::FACE_BACK;
276 edge[0] = (vtx.z - pos.z) > 0.5f ? Block::FACE_FRONT : Block::FACE_BACK;
277 edge[1] = (vtx.x - pos.x) > 0.5f ? Block::FACE_RIGHT : Block::FACE_LEFT;
280 edge[0] = (vtx.x - pos.x) > 0.5f ? Block::FACE_RIGHT : Block::FACE_LEFT;
281 edge[1] = (vtx.y - pos.y) > 0.5f ? Block::FACE_UP : Block::FACE_DOWN;
288 BlockLookup next[2] = {
289 direct.Next(edge[0]),
290 direct.Next(edge[1]),
294 if (next[0].GetType().block_light) {
297 light += next[0].GetLight();
302 if (next[1].GetType().block_light) {
305 light += next[1].GetLight();
311 BlockLookup corner = next[0].Next(edge[1]);
313 if (corner.GetType().block_light) {
316 light += corner.GetLight();
320 } else if (next[1]) {
321 BlockLookup corner = next[1].Next(edge[0]);
323 if (corner.GetType().block_light) {
326 light += corner.GetLight();
335 return (light / num) - (occlusion * 0.8f);
339 bool Chunk::IsSurface(const Pos &pos) const noexcept {
340 const Block &block = BlockAt(pos);
341 if (!Type(block).visible) {
344 for (int face = 0; face < Block::FACE_COUNT; ++face) {
345 BlockLookup next = BlockLookup(const_cast<Chunk *>(this), pos, Block::Face(face));
346 if (!next || !next.GetType().visible) {
354 bool Chunk::Intersection(
362 coll.depth = std::numeric_limits<float>::infinity();
363 for (int z = 0; z < depth; ++z) {
364 for (int y = 0; y < height; ++y) {
365 for (int x = 0; x < width; ++x, ++idx) {
366 const BlockType &type = Type(idx);
372 if (type.shape->Intersects(ray, M * ToTransform(Pos(x, y, z), idx), cur_dist, cur_norm)) {
373 if (cur_dist < coll.depth) {
375 coll.depth = cur_dist;
376 coll.normal = cur_norm;
383 if (coll.block < 0) {
386 coll.normal = glm::vec3(BlockAt(coll.block).Transform() * glm::vec4(coll.normal, 0.0f));
391 bool Chunk::Intersection(
393 const glm::mat4 &Mbox,
394 const glm::mat4 &Mchunk,
395 std::vector<WorldCollision> &col
401 if (!blank::Intersection(box, Mbox, Bounds(), Mchunk, penetration, normal)) {
404 for (int idx = 0, z = 0; z < depth; ++z) {
405 for (int y = 0; y < height; ++y) {
406 for (int x = 0; x < width; ++x, ++idx) {
407 const BlockType &type = Type(idx);
408 if (!type.collision) {
411 if (type.shape->Intersects(Mchunk * ToTransform(Pos(x, y, z), idx), box, Mbox, penetration, normal)) {
412 col.emplace_back(this, idx, penetration, normal);
424 BlockModel::Buffer buf;
428 void Chunk::Update(BlockModel &model) noexcept {
429 int vtx_count = 0, idx_count = 0;
430 for (const auto &block : blocks) {
431 const Shape *shape = Type(block).shape;
432 vtx_count += shape->VertexCount();
433 idx_count += shape->VertexIndexCount();
436 buf.Reserve(vtx_count, idx_count);
439 BlockModel::Index vtx_counter = 0;
440 for (size_t z = 0; z < depth; ++z) {
441 for (size_t y = 0; y < height; ++y) {
442 for (size_t x = 0; x < width; ++x, ++idx) {
443 const BlockType &type = Type(BlockAt(idx));
444 const Pos pos(x, y, z);
446 if (!type.visible || Obstructed(pos).All()) continue;
448 type.FillBlockModel(buf, ToTransform(pos, idx), vtx_counter);
449 size_t vtx_begin = vtx_counter;
450 vtx_counter += type.shape->VertexCount();
452 for (size_t vtx = vtx_begin; vtx < vtx_counter; ++vtx) {
453 buf.lights.emplace_back(GetVertexLight(
456 type.shape->VertexNormal(vtx - vtx_begin, BlockAt(idx).Transform())
467 Block::FaceSet Chunk::Obstructed(const Pos &pos) const noexcept {
468 Block::FaceSet result;
470 for (int f = 0; f < Block::FACE_COUNT; ++f) {
471 Block::Face face = Block::Face(f);
472 BlockLookup next(const_cast<Chunk *>(this), pos, face);
473 if (next && next.GetType().FaceFilled(next.GetBlock(), Block::Opposite(face))) {
481 glm::mat4 Chunk::ToTransform(const Pos &pos, int idx) const noexcept {
482 return glm::translate(ToCoords(pos)) * BlockAt(idx).Transform();
486 BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p) noexcept
488 while (pos.x >= Chunk::width) {
489 if (chunk->HasNeighbor(Block::FACE_RIGHT)) {
490 chunk = &chunk->GetNeighbor(Block::FACE_RIGHT);
491 pos.x -= Chunk::width;
498 if (chunk->HasNeighbor(Block::FACE_LEFT)) {
499 chunk = &chunk->GetNeighbor(Block::FACE_LEFT);
500 pos.x += Chunk::width;
506 while (pos.y >= Chunk::height) {
507 if (chunk->HasNeighbor(Block::FACE_UP)) {
508 chunk = &chunk->GetNeighbor(Block::FACE_UP);
509 pos.y -= Chunk::height;
516 if (chunk->HasNeighbor(Block::FACE_DOWN)) {
517 chunk = &chunk->GetNeighbor(Block::FACE_DOWN);
518 pos.y += Chunk::height;
524 while (pos.z >= Chunk::depth) {
525 if (chunk->HasNeighbor(Block::FACE_FRONT)) {
526 chunk = &chunk->GetNeighbor(Block::FACE_FRONT);
527 pos.z -= Chunk::depth;
534 if (chunk->HasNeighbor(Block::FACE_BACK)) {
535 chunk = &chunk->GetNeighbor(Block::FACE_BACK);
536 pos.z += Chunk::depth;
544 BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p, Block::Face face) noexcept
546 pos += Block::FaceNormal(face);
547 if (!Chunk::InBounds(pos)) {
548 pos -= Block::FaceNormal(face) * Chunk::Extent();
549 chunk = &chunk->GetNeighbor(face);
554 ChunkLoader::ChunkLoader(
556 const Generator &gen,
557 const WorldSave &save
565 void ChunkLoader::Update(int dt) {
566 // check if there's chunks waiting to be loaded
567 // load until one of load or generation limits was hit
568 constexpr int max_load = 10;
569 constexpr int max_gen = 1;
572 while (loaded < max_load && generated < max_gen && store.HasMissing()) {
580 // store a few chunks as well
581 constexpr int max_save = 10;
583 for (Chunk &chunk : store) {
584 if (chunk.ShouldUpdateSave()) {
587 if (saved >= max_save) {
594 int ChunkLoader::ToLoad() const noexcept {
595 return store.EstimateMissing();
598 bool ChunkLoader::LoadOne() {
599 if (!store.HasMissing()) return false;
601 Chunk::Pos pos = store.NextMissing();
602 Chunk *chunk = store.Allocate(pos);
604 // chunk store corrupted?
608 bool generated = false;
609 if (save.Exists(pos)) {
616 ChunkIndex *index = store.ClosestIndex(pos);
621 Chunk::Pos begin(pos - Chunk::Pos(1));
622 Chunk::Pos end(pos + Chunk::Pos(2));
623 for (Chunk::Pos iter(begin); iter.z < end.z; ++iter.z) {
624 for (iter.y = begin.y; iter.y < end.y; ++iter.y) {
625 for (iter.x = begin.x; iter.x < end.x; ++iter.x) {
626 if (index->IsBorder(iter)) continue;
627 Chunk *light_chunk = index->Get(iter);
628 if (!light_chunk || light_chunk->Lighted()) continue;
629 if (index->HasAllSurrounding(iter)) {
630 light_chunk->ScanLights();
639 void ChunkLoader::LoadN(std::size_t n) {
640 std::size_t end = std::min(n, std::size_t(ToLoad()));
641 for (std::size_t i = 0; i < end && store.HasMissing(); ++i) {
647 ChunkRenderer::ChunkRenderer(ChunkIndex &index)
649 , models(index.TotalChunks())
651 , fog_density(0.0f) {
655 ChunkRenderer::~ChunkRenderer() {
659 int ChunkRenderer::MissingChunks() const noexcept {
660 return index.MissingChunks();
663 void ChunkRenderer::LoadTextures(const AssetLoader &loader, const TextureIndex &tex_index) {
665 loader.LoadTextures(tex_index, block_tex);
666 block_tex.FilterNearest();
669 void ChunkRenderer::Update(int dt) {
670 for (int i = 0, updates = 0; updates < dt && i < index.TotalChunks(); ++i) {
671 if (index[i] && index[i]->ShouldUpdateModel()) {
672 index[i]->Update(models[i]);
678 void ChunkRenderer::Render(Viewport &viewport) {
679 BlockLighting &chunk_prog = viewport.ChunkProgram();
680 chunk_prog.SetTexture(block_tex);
681 chunk_prog.SetFogDensity(fog_density);
683 for (int i = 0; i < index.TotalChunks(); ++i) {
684 if (!index[i]) continue;
685 glm::mat4 m(index[i]->Transform(index.Base()));
686 glm::mat4 mvp(chunk_prog.GetVP() * m);
687 if (!CullTest(Chunk::Bounds(), mvp)) {
688 if (index[i]->ShouldUpdateModel()) {
689 index[i]->Update(models[i]);
698 ChunkIndex::ChunkIndex(ChunkStore &store, const Chunk::Pos &base, int extent)
702 , side_length(2 * extent + 1)
703 , total_length(side_length * side_length * side_length)
706 , stride(1, side_length, side_length * side_length)
707 , chunks(total_length, nullptr) {
711 ChunkIndex::~ChunkIndex() {
715 bool ChunkIndex::InRange(const Chunk::Pos &pos) const noexcept {
716 return Distance(pos) <= extent;
719 bool ChunkIndex::IsBorder(const Chunk::Pos &pos) const noexcept {
720 return Distance(pos) == extent;
723 int ChunkIndex::Distance(const Chunk::Pos &pos) const noexcept {
724 return manhattan_radius(pos - base);
727 bool ChunkIndex::HasAllSurrounding(const Chunk::Pos &pos) const noexcept {
728 Chunk::Pos begin(pos - Chunk::Pos(1));
729 Chunk::Pos end(pos + Chunk::Pos(2));
730 for (Chunk::Pos iter(begin); iter.z < end.z; ++iter.z) {
731 for (iter.y = begin.y; iter.y < end.y; ++iter.y) {
732 for (iter.x = begin.x; iter.x < end.x; ++iter.x) {
733 if (!Get(iter)) return false;
740 int ChunkIndex::IndexOf(const Chunk::Pos &pos) const noexcept {
746 return mod_pos.x * stride.x
747 + mod_pos.y * stride.y
748 + mod_pos.z * stride.z;
751 Chunk::Pos ChunkIndex::PositionOf(int i) const noexcept {
753 (i / stride.x) % side_length,
754 (i / stride.y) % side_length,
755 (i / stride.z) % side_length
757 Chunk::Pos zero_base(
762 Chunk::Pos base_relative(zero_pos - zero_base);
763 if (base_relative.x > extent) base_relative.x -= side_length;
764 else if (base_relative.x < -extent) base_relative.x += side_length;
765 if (base_relative.y > extent) base_relative.y -= side_length;
766 else if (base_relative.y < -extent) base_relative.y += side_length;
767 if (base_relative.z > extent) base_relative.z -= side_length;
768 else if (base_relative.z < -extent) base_relative.z += side_length;
769 return base + base_relative;
772 Chunk *ChunkIndex::Get(const Chunk::Pos &pos) noexcept {
774 return chunks[IndexOf(pos)];
780 const Chunk *ChunkIndex::Get(const Chunk::Pos &pos) const noexcept {
782 return chunks[IndexOf(pos)];
788 void ChunkIndex::Rebase(const Chunk::Pos &new_base) {
789 if (new_base == base) return;
791 Chunk::Pos diff(new_base - base);
793 if (manhattan_radius(diff) > extent) {
794 // that's more than half, so probably not worth shifting
803 Shift(Block::FACE_RIGHT);
807 Shift(Block::FACE_LEFT);
811 Shift(Block::FACE_UP);
815 Shift(Block::FACE_DOWN);
819 Shift(Block::FACE_FRONT);
823 Shift(Block::FACE_BACK);
829 int ChunkIndex::GetCol(int c) const noexcept {
831 if (c < 0) c += side_length;
835 void ChunkIndex::Shift(Block::Face f) {
836 int a_axis = Block::Axis(f);
837 int b_axis = (a_axis + 1) % 3;
838 int c_axis = (a_axis + 2) % 3;
839 int dir = Block::Direction(f);
841 int a = GetCol(base[a_axis] + (extent * dir));
842 int a_stride = a * stride[a_axis];
843 for (int b = 0; b < side_length; ++b) {
844 int b_stride = b * stride[b_axis];
845 for (int c = 0; c < side_length; ++c) {
846 int bc_stride = b_stride + c * stride[c_axis];
847 int index = a_stride + bc_stride;
849 int neighbor = ((a - dir + side_length) % side_length) * stride[a_axis] + bc_stride;
850 if (chunks[neighbor] && chunks[neighbor]->HasNeighbor(f)) {
851 Set(index, chunks[neighbor]->GetNeighbor(f));
857 void ChunkIndex::Clear() noexcept {
858 for (int i = 0; i < total_length && total_indexed > 0; ++i) {
863 void ChunkIndex::Scan() noexcept {
864 for (Chunk &chunk : store) {
869 void ChunkIndex::Register(Chunk &chunk) noexcept {
870 if (InRange(chunk.Position())) {
871 Set(IndexOf(chunk.Position()), chunk);
875 void ChunkIndex::Set(int index, Chunk &chunk) noexcept {
877 chunks[index] = &chunk;
882 void ChunkIndex::Unset(int index) noexcept {
884 chunks[index]->UnRef();
885 chunks[index] = nullptr;
890 Chunk::Pos ChunkIndex::NextMissing() noexcept {
891 if (MissingChunks() > 0) {
892 int roundtrip = last_missing;
893 last_missing = (last_missing + 1) % total_length;
894 while (chunks[last_missing]) {
895 last_missing = (last_missing + 1) % total_length;
896 if (last_missing == roundtrip) {
901 return PositionOf(last_missing);
905 ChunkStore::ChunkStore(const BlockTypeRegistry &types)
913 ChunkStore::~ChunkStore() {
917 ChunkIndex &ChunkStore::MakeIndex(const Chunk::Pos &pos, int extent) {
918 indices.emplace_back(*this, pos, extent);
919 return indices.back();
922 void ChunkStore::UnregisterIndex(ChunkIndex &index) {
923 for (auto i = indices.begin(), end = indices.end(); i != end; ++i) {
933 ChunkIndex *ChunkStore::ClosestIndex(const Chunk::Pos &pos) {
934 ChunkIndex *closest_index = nullptr;
935 int closest_distance = std::numeric_limits<int>::max();
937 for (ChunkIndex &index : indices) {
938 int distance = index.Distance(pos);
939 if (distance < closest_distance) {
940 closest_index = &index;
941 closest_distance = distance;
945 return closest_index;
948 Chunk *ChunkStore::Get(const Chunk::Pos &pos) {
949 for (ChunkIndex &index : indices) {
950 Chunk *chunk = index.Get(pos);
958 Chunk *ChunkStore::Allocate(const Chunk::Pos &pos) {
959 Chunk *chunk = Get(pos);
964 loaded.emplace(loaded.begin(), types);
966 loaded.splice(loaded.begin(), free, free.begin());
967 loaded.front().Unlink();
969 chunk = &loaded.front();
970 chunk->Position(pos);
971 for (ChunkIndex &index : indices) {
972 if (index.InRange(pos)) {
973 index.Register(*chunk);
976 for (int i = 0; i < Block::FACE_COUNT; ++i) {
977 Block::Face face = Block::Face(i);
978 Chunk::Pos neighbor_pos(pos + Block::FaceNormal(face));
979 Chunk *neighbor = Get(neighbor_pos);
981 chunk->SetNeighbor(face, *neighbor);
987 bool ChunkStore::HasMissing() const noexcept {
988 for (const ChunkIndex &index : indices) {
989 if (index.MissingChunks() > 0) {
996 int ChunkStore::EstimateMissing() const noexcept {
998 for (const ChunkIndex &index : indices) {
999 missing += index.MissingChunks();
1004 Chunk::Pos ChunkStore::NextMissing() noexcept {
1005 for (ChunkIndex &index : indices) {
1006 if (index.MissingChunks()) {
1007 return index.NextMissing();
1010 return Chunk::Pos(0, 0, 0);
1013 void ChunkStore::Clean() {
1014 for (auto i = loaded.begin(), end = loaded.end(); i != end;) {
1015 if (i->Referenced() || i->ShouldUpdateSave()) {
1020 free.splice(free.end(), loaded, chunk);
1022 chunk->InvalidateModel();