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 "../geometry/distance.hpp"
12 #include "../graphics/BlockLighting.hpp"
13 #include "../graphics/BlockMesh.hpp"
14 #include "../graphics/Viewport.hpp"
15 #include "../io/WorldSave.hpp"
25 constexpr int Chunk::side;
26 constexpr int Chunk::size;
29 Chunk::Chunk(const BlockTypeRegistry &types) noexcept
43 Chunk::Chunk(Chunk &&other) noexcept
45 , generated(other.generated)
46 , lighted(other.lighted)
47 , position(other.position)
48 , ref_count(other.ref_count)
49 , dirty_mesh(other.dirty_mesh)
50 , dirty_save(other.dirty_save) {
51 std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor);
52 std::copy(other.blocks, other.blocks + sizeof(blocks), blocks);
53 std::copy(other.light, other.light + sizeof(light), light);
57 Chunk &Chunk::operator =(Chunk &&other) noexcept {
59 std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor);
60 std::copy(other.blocks, other.blocks + sizeof(blocks), blocks);
61 std::copy(other.light, other.light + sizeof(light), light);
62 generated = other.generated;
63 lighted = other.lighted;
64 position = other.position;
65 std::swap(ref_count, other.ref_count);
66 dirty_mesh = other.dirty_save;
67 dirty_save = other.dirty_save;
77 RoughLocation::Fine pos;
79 SetNode(Chunk *chunk, RoughLocation::Fine pos)
80 : chunk(chunk), pos(pos) { }
82 int Get() const noexcept { return chunk->GetLight(pos); }
83 void Set(int level) noexcept { chunk->SetLight(pos, level); }
85 const BlockType &GetType() const noexcept { return chunk->Type(Chunk::ToIndex(pos)); }
87 bool HasNext(Block::Face face) noexcept {
88 const BlockType &type = GetType();
89 if (type.block_light && !type.luminosity) return false;
90 const BlockLookup next(chunk, pos, face);
93 SetNode GetNext(Block::Face face) noexcept {
94 const BlockLookup next(chunk, pos, face);
95 return SetNode(&next.GetChunk(), next.GetBlockPos());
105 UnsetNode(Chunk *chunk, RoughLocation::Fine pos)
106 : SetNode(chunk, pos), level(Get()) { }
108 UnsetNode(const SetNode &set)
109 : SetNode(set), level(Get()) { }
112 bool HasNext(Block::Face face) noexcept {
113 const BlockLookup next(chunk, pos, face);
116 UnsetNode GetNext(Block::Face face) noexcept { return UnsetNode(SetNode::GetNext(face)); }
120 std::queue<SetNode> light_queue;
121 std::queue<UnsetNode> dark_queue;
123 void work_light() noexcept {
124 while (!light_queue.empty()) {
125 SetNode node = light_queue.front();
128 int level = node.Get() - 1;
129 for (int face = 0; face < Block::FACE_COUNT; ++face) {
130 if (node.HasNext(Block::Face(face))) {
131 SetNode other = node.GetNext(Block::Face(face));
132 if (other.Get() < level) {
134 light_queue.emplace(other);
141 void work_dark() noexcept {
142 while (!dark_queue.empty()) {
143 UnsetNode node = dark_queue.front();
146 for (int face = 0; face < Block::FACE_COUNT; ++face) {
147 if (node.HasNext(Block::Face(face))) {
148 UnsetNode other = node.GetNext(Block::Face(face));
149 // TODO: if there a light source here with the same level this will err
150 if (other.Get() != 0 && other.Get() < node.level) {
152 dark_queue.emplace(other);
154 light_queue.emplace(other);
163 void Chunk::SetBlock(int index, const Block &block) noexcept {
164 const BlockType &old_type = Type(blocks[index]);
165 const BlockType &new_type = Type(block);
167 blocks[index] = block;
170 if (!lighted || &old_type == &new_type) return;
172 if (new_type.luminosity > old_type.luminosity) {
174 SetLight(index, new_type.luminosity);
175 light_queue.emplace(this, ToPos(index));
177 } else if (new_type.luminosity < old_type.luminosity) {
179 dark_queue.emplace(this, ToPos(index));
182 SetLight(index, new_type.luminosity);
183 light_queue.emplace(this, ToPos(index));
185 } else if (new_type.block_light && !old_type.block_light) {
187 if (GetLight(index) > 0) {
188 dark_queue.emplace(this, ToPos(index));
193 } else if (!new_type.block_light && old_type.block_light) {
196 RoughLocation::Fine pos(ToPos(index));
197 for (int face = 0; face < Block::FACE_COUNT; ++face) {
198 BlockLookup next_block(this, pos, Block::Face(face));
200 level = std::max(level, next_block.GetLight());
204 SetLight(index, level - 1);
205 light_queue.emplace(this, pos);
211 void Chunk::ScanLights() {
213 RoughLocation::Fine pos(0, 0, 0);
214 for (; pos.z < side; ++pos.z) {
215 for (pos.y = 0; pos.y < side; ++pos.y) {
216 for (pos.x = 0; pos.x < side; ++pos.x, ++idx) {
217 const BlockType &type = Type(blocks[idx]);
218 if (type.luminosity) {
219 SetLight(idx, type.luminosity);
220 light_queue.emplace(this, pos);
229 void Chunk::SetNeighbor(Block::Face face, Chunk &other) noexcept {
230 neighbor[face] = &other;
231 other.neighbor[Block::Opposite(face)] = this;
234 void Chunk::Unlink() noexcept {
235 for (int face = 0; face < Block::FACE_COUNT; ++face) {
236 if (neighbor[face]) {
237 neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = nullptr;
238 neighbor[face] = nullptr;
244 void Chunk::SetLight(int index, int level) noexcept {
245 if (light[index] != level) {
246 light[index] = level;
251 int Chunk::GetLight(int index) const noexcept {
255 float Chunk::GetVertexLight(const RoughLocation::Fine &pos, const BlockMesh::Position &vtx, const EntityMesh::Normal &norm) const noexcept {
256 int index = ToIndex(pos);
257 float light = GetLight(index);
259 Block::Face direct_face(Block::NormalFace(norm));
261 BlockLookup direct(const_cast<Chunk *>(this), pos, Block::NormalFace(norm));
263 float direct_light = direct.GetLight();
264 if (direct_light > light) {
265 light = direct_light;
271 if (Type(BlockAt(index)).luminosity > 0 || direct.GetType().block_light) {
276 switch (Block::Axis(direct_face)) {
278 edge[0] = (vtx.y - pos.y) > 0.5f ? Block::FACE_UP : Block::FACE_DOWN;
279 edge[1] = (vtx.z - pos.z) > 0.5f ? Block::FACE_FRONT : Block::FACE_BACK;
282 edge[0] = (vtx.z - pos.z) > 0.5f ? Block::FACE_FRONT : Block::FACE_BACK;
283 edge[1] = (vtx.x - pos.x) > 0.5f ? Block::FACE_RIGHT : Block::FACE_LEFT;
286 edge[0] = (vtx.x - pos.x) > 0.5f ? Block::FACE_RIGHT : Block::FACE_LEFT;
287 edge[1] = (vtx.y - pos.y) > 0.5f ? Block::FACE_UP : Block::FACE_DOWN;
294 BlockLookup next[2] = {
295 direct.Next(edge[0]),
296 direct.Next(edge[1]),
300 if (next[0].GetType().block_light) {
303 light += next[0].GetLight();
308 if (next[1].GetType().block_light) {
311 light += next[1].GetLight();
317 BlockLookup corner = next[0].Next(edge[1]);
319 if (corner.GetType().block_light) {
322 light += corner.GetLight();
326 } else if (next[1]) {
327 BlockLookup corner = next[1].Next(edge[0]);
329 if (corner.GetType().block_light) {
332 light += corner.GetLight();
341 return (light / num) - (occlusion * 0.8f);
345 bool Chunk::IsSurface(const RoughLocation::Fine &pos) const noexcept {
346 const Block &block = BlockAt(pos);
347 if (!Type(block).visible) {
350 for (int face = 0; face < Block::FACE_COUNT; ++face) {
351 BlockLookup next = BlockLookup(const_cast<Chunk *>(this), pos, Block::Face(face));
352 if (!next || !next.GetType().visible) {
360 bool Chunk::Intersection(
368 coll.depth = std::numeric_limits<float>::infinity();
369 for (int z = 0; z < side; ++z) {
370 for (int y = 0; y < side; ++y) {
371 for (int x = 0; x < side; ++x, ++idx) {
372 const BlockType &type = Type(idx);
373 if (!type.collision || !type.shape) {
378 if (type.shape->Intersects(ray, M * ToTransform(RoughLocation::Fine(x, y, z), idx), cur_dist, cur_norm)) {
379 if (cur_dist < coll.depth) {
381 coll.depth = cur_dist;
382 coll.normal = cur_norm;
389 if (coll.block < 0) {
392 coll.normal = glm::vec3(BlockAt(coll.block).Transform() * glm::vec4(coll.normal, 0.0f));
397 bool Chunk::Intersection(
399 const glm::mat4 &Mbox,
400 const glm::mat4 &Mchunk,
401 std::vector<WorldCollision> &col
403 // box's origin relative to the chunk
404 const glm::vec3 box_coords(Mbox[3] - Mchunk[3]);
405 const float box_rad = box.OriginRadius();
407 if (distance_squared(box_coords, Center()) > (box_rad + Radius()) * (box_rad + Radius())) {
415 // assume a bounding radius of 2 for blocks
416 constexpr float block_rad = 2.0f;
417 for (int idx = 0, z = 0; z < side; ++z) {
418 for (int y = 0; y < side; ++y) {
419 for (int x = 0; x < side; ++x, ++idx) {
420 const BlockType &type = Type(idx);
421 if (!type.collision || !type.shape) {
424 const RoughLocation::Fine block_pos(x, y, z);
425 const ExactLocation::Fine block_coords(ToCoords(block_pos));
426 if (distance_squared(box_coords, block_coords) <= (box_rad + block_rad) * (box_rad + block_rad)
427 && type.shape->Intersects(Mchunk * ToTransform(block_pos, idx), box, Mbox, penetration, normal)
429 col.emplace_back(this, idx, penetration, normal);
441 BlockMesh::Buffer buf;
445 void Chunk::Update(BlockMesh &model) noexcept {
446 int vtx_count = 0, idx_count = 0;
447 for (const auto &block : blocks) {
448 const BlockType &type = Type(block);
449 if (type.visible && type.shape) {
450 vtx_count += type.shape->VertexCount();
451 idx_count += type.shape->IndexCount();
455 buf.Reserve(vtx_count, idx_count);
459 BlockMesh::Index vtx_counter = 0;
460 for (size_t z = 0; z < side; ++z) {
461 for (size_t y = 0; y < side; ++y) {
462 for (size_t x = 0; x < side; ++x, ++idx) {
463 const BlockType &type = Type(BlockAt(idx));
464 const RoughLocation::Fine pos(x, y, z);
466 if (!type.visible || !type.shape || Obstructed(pos).All()) continue;
468 type.FillBlockMesh(buf, ToTransform(pos, idx), vtx_counter);
469 size_t vtx_begin = vtx_counter;
470 vtx_counter += type.shape->VertexCount();
472 for (size_t vtx = vtx_begin; vtx < vtx_counter; ++vtx) {
473 buf.lights.emplace_back(GetVertexLight(
476 type.shape->VertexNormal(vtx - vtx_begin, BlockAt(idx).Transform())
488 Block::FaceSet Chunk::Obstructed(const RoughLocation::Fine &pos) const noexcept {
489 Block::FaceSet result;
491 for (int f = 0; f < Block::FACE_COUNT; ++f) {
492 Block::Face face = Block::Face(f);
493 BlockLookup next(const_cast<Chunk *>(this), pos, face);
494 if (next && next.GetType().FaceFilled(next.GetBlock(), Block::Opposite(face))) {
502 glm::mat4 Chunk::ToTransform(const RoughLocation::Fine &pos, int idx) const noexcept {
503 return glm::translate(ToCoords(pos)) * BlockAt(idx).Transform();
507 BlockLookup::BlockLookup(Chunk *c, const RoughLocation::Fine &p) noexcept
509 while (pos.x >= Chunk::side) {
510 if (chunk->HasNeighbor(Block::FACE_RIGHT)) {
511 chunk = &chunk->GetNeighbor(Block::FACE_RIGHT);
512 pos.x -= Chunk::side;
519 if (chunk->HasNeighbor(Block::FACE_LEFT)) {
520 chunk = &chunk->GetNeighbor(Block::FACE_LEFT);
521 pos.x += Chunk::side;
527 while (pos.y >= Chunk::side) {
528 if (chunk->HasNeighbor(Block::FACE_UP)) {
529 chunk = &chunk->GetNeighbor(Block::FACE_UP);
530 pos.y -= Chunk::side;
537 if (chunk->HasNeighbor(Block::FACE_DOWN)) {
538 chunk = &chunk->GetNeighbor(Block::FACE_DOWN);
539 pos.y += Chunk::side;
545 while (pos.z >= Chunk::side) {
546 if (chunk->HasNeighbor(Block::FACE_FRONT)) {
547 chunk = &chunk->GetNeighbor(Block::FACE_FRONT);
548 pos.z -= Chunk::side;
555 if (chunk->HasNeighbor(Block::FACE_BACK)) {
556 chunk = &chunk->GetNeighbor(Block::FACE_BACK);
557 pos.z += Chunk::side;
565 BlockLookup::BlockLookup(Chunk *c, const RoughLocation::Fine &p, Block::Face face) noexcept
567 pos += Block::FaceNormal(face);
568 if (!Chunk::InBounds(pos)) {
569 pos -= Block::FaceNormal(face) * ExactLocation::Extent();
570 chunk = &chunk->GetNeighbor(face);
575 ChunkLoader::ChunkLoader(
577 const Generator &gen,
578 const WorldSave &save
586 void ChunkLoader::Update(int dt) {
587 // check if there's chunks waiting to be loaded
588 // load until one of load or generation limits was hit
589 constexpr int max_load = 10;
590 constexpr int max_gen = 1;
593 while (loaded < max_load && generated < max_gen && store.HasMissing()) {
601 // store a few chunks as well
602 constexpr int max_save = 10;
604 for (Chunk &chunk : store) {
605 if (chunk.ShouldUpdateSave()) {
608 if (saved >= max_save) {
615 int ChunkLoader::ToLoad() const noexcept {
616 return store.EstimateMissing();
619 bool ChunkLoader::LoadOne() {
620 if (!store.HasMissing()) return false;
622 ExactLocation::Coarse pos = store.NextMissing();
623 Chunk *chunk = store.Allocate(pos);
625 // chunk store corrupted?
629 bool generated = false;
630 if (save.Exists(pos)) {
637 ChunkIndex *index = store.ClosestIndex(pos);
642 ExactLocation::Coarse begin(pos - ExactLocation::Coarse(1));
643 ExactLocation::Coarse end(pos + ExactLocation::Coarse(2));
644 for (ExactLocation::Coarse iter(begin); iter.z < end.z; ++iter.z) {
645 for (iter.y = begin.y; iter.y < end.y; ++iter.y) {
646 for (iter.x = begin.x; iter.x < end.x; ++iter.x) {
647 if (index->IsBorder(iter)) continue;
648 Chunk *light_chunk = index->Get(iter);
649 if (!light_chunk) continue;
650 if (index->HasAllSurrounding(iter)) {
651 if (!light_chunk->Lighted()) {
652 light_chunk->ScanLights();
654 light_chunk->InvalidateMesh();
664 void ChunkLoader::LoadN(std::size_t n) {
665 std::size_t end = std::min(n, std::size_t(ToLoad()));
666 for (std::size_t i = 0; i < end && store.HasMissing(); ++i) {
672 ChunkRenderer::ChunkRenderer(ChunkIndex &index)
674 , models(index.TotalChunks())
676 , fog_density(0.0f) {
680 ChunkRenderer::~ChunkRenderer() {
684 int ChunkRenderer::MissingChunks() const noexcept {
685 return index.MissingChunks();
688 void ChunkRenderer::LoadTextures(const AssetLoader &loader, const ResourceIndex &tex_index) {
690 loader.LoadTextures(tex_index, block_tex);
691 block_tex.FilterNearest();
694 void ChunkRenderer::Update(int dt) {
695 for (int i = 0, updates = 0; updates < dt && i < index.TotalChunks(); ++i) {
696 if (!index[i]) continue;
697 if (!index[i]->Lighted() && index.HasAllSurrounding(index[i]->Position())) {
698 index[i]->ScanLights();
700 if (index[i]->ShouldUpdateMesh()) {
701 index[i]->Update(models[i]);
707 void ChunkRenderer::Render(Viewport &viewport) {
708 BlockLighting &chunk_prog = viewport.ChunkProgram();
709 chunk_prog.SetTexture(block_tex);
710 chunk_prog.SetFogDensity(fog_density);
712 for (int i = 0; i < index.TotalChunks(); ++i) {
713 if (!index[i]) continue;
714 glm::mat4 m(index[i]->Transform(index.Base()));
715 glm::mat4 mvp(chunk_prog.GetVP() * m);
716 if (!CullTest(Chunk::Bounds(), mvp)) {
717 if (index[i]->ShouldUpdateMesh()) {
718 index[i]->Update(models[i]);
727 ChunkIndex::ChunkIndex(ChunkStore &store, const ExactLocation::Coarse &base, int extent)
731 , side_length(2 * extent + 1)
732 , total_length(side_length * side_length * side_length)
735 , stride(1, side_length, side_length * side_length)
736 , chunks(total_length, nullptr) {
740 ChunkIndex::~ChunkIndex() {
744 bool ChunkIndex::InRange(const ExactLocation::Coarse &pos) const noexcept {
745 return Distance(pos) <= extent;
748 bool ChunkIndex::IsBorder(const ExactLocation::Coarse &pos) const noexcept {
749 return Distance(pos) == extent;
752 int ChunkIndex::Distance(const ExactLocation::Coarse &pos) const noexcept {
753 return manhattan_radius(pos - base);
756 bool ChunkIndex::HasAllSurrounding(const ExactLocation::Coarse &pos) const noexcept {
757 ExactLocation::Coarse begin(pos - ExactLocation::Coarse(1));
758 ExactLocation::Coarse end(pos + ExactLocation::Coarse(2));
759 for (ExactLocation::Coarse iter(begin); iter.z < end.z; ++iter.z) {
760 for (iter.y = begin.y; iter.y < end.y; ++iter.y) {
761 for (iter.x = begin.x; iter.x < end.x; ++iter.x) {
762 if (!Get(iter)) return false;
769 int ChunkIndex::IndexOf(const ExactLocation::Coarse &pos) const noexcept {
770 ExactLocation::Coarse mod_pos(
775 return mod_pos.x * stride.x
776 + mod_pos.y * stride.y
777 + mod_pos.z * stride.z;
780 ExactLocation::Coarse ChunkIndex::PositionOf(int i) const noexcept {
781 ExactLocation::Coarse zero_pos(
782 (i / stride.x) % side_length,
783 (i / stride.y) % side_length,
784 (i / stride.z) % side_length
786 ExactLocation::Coarse zero_base(
791 ExactLocation::Coarse base_relative(zero_pos - zero_base);
792 if (base_relative.x > extent) base_relative.x -= side_length;
793 else if (base_relative.x < -extent) base_relative.x += side_length;
794 if (base_relative.y > extent) base_relative.y -= side_length;
795 else if (base_relative.y < -extent) base_relative.y += side_length;
796 if (base_relative.z > extent) base_relative.z -= side_length;
797 else if (base_relative.z < -extent) base_relative.z += side_length;
798 return base + base_relative;
801 Chunk *ChunkIndex::Get(const ExactLocation::Coarse &pos) noexcept {
803 return chunks[IndexOf(pos)];
809 const Chunk *ChunkIndex::Get(const ExactLocation::Coarse &pos) const noexcept {
811 return chunks[IndexOf(pos)];
817 void ChunkIndex::Rebase(const ExactLocation::Coarse &new_base) {
818 if (new_base == base) return;
820 ExactLocation::Coarse diff(new_base - base);
822 if (manhattan_radius(diff) > extent) {
823 // that's more than half, so probably not worth shifting
832 Shift(Block::FACE_RIGHT);
836 Shift(Block::FACE_LEFT);
840 Shift(Block::FACE_UP);
844 Shift(Block::FACE_DOWN);
848 Shift(Block::FACE_FRONT);
852 Shift(Block::FACE_BACK);
858 int ChunkIndex::GetCol(int c) const noexcept {
860 if (c < 0) c += side_length;
864 void ChunkIndex::Shift(Block::Face f) {
865 int a_axis = Block::Axis(f);
866 int b_axis = (a_axis + 1) % 3;
867 int c_axis = (a_axis + 2) % 3;
868 int dir = Block::Direction(f);
870 int a = GetCol(base[a_axis] + (extent * dir));
871 int a_stride = a * stride[a_axis];
872 for (int b = 0; b < side_length; ++b) {
873 int b_stride = b * stride[b_axis];
874 for (int c = 0; c < side_length; ++c) {
875 int bc_stride = b_stride + c * stride[c_axis];
876 int index = a_stride + bc_stride;
878 int neighbor = ((a - dir + side_length) % side_length) * stride[a_axis] + bc_stride;
879 if (chunks[neighbor] && chunks[neighbor]->HasNeighbor(f)) {
880 Set(index, chunks[neighbor]->GetNeighbor(f));
886 void ChunkIndex::Clear() noexcept {
887 for (int i = 0; i < total_length && total_indexed > 0; ++i) {
892 void ChunkIndex::Scan() noexcept {
893 for (Chunk &chunk : store) {
898 void ChunkIndex::Register(Chunk &chunk) noexcept {
899 if (InRange(chunk.Position())) {
900 Set(IndexOf(chunk.Position()), chunk);
904 void ChunkIndex::Set(int index, Chunk &chunk) noexcept {
906 chunks[index] = &chunk;
911 void ChunkIndex::Unset(int index) noexcept {
913 chunks[index]->UnRef();
914 chunks[index] = nullptr;
919 ExactLocation::Coarse ChunkIndex::NextMissing() noexcept {
920 if (MissingChunks() > 0) {
921 int roundtrip = last_missing;
922 last_missing = (last_missing + 1) % total_length;
923 while (chunks[last_missing]) {
924 last_missing = (last_missing + 1) % total_length;
925 if (last_missing == roundtrip) {
930 return PositionOf(last_missing);
934 ChunkStore::ChunkStore(const BlockTypeRegistry &types)
942 ChunkStore::~ChunkStore() {
946 ChunkIndex &ChunkStore::MakeIndex(const ExactLocation::Coarse &pos, int extent) {
947 indices.emplace_back(*this, pos, extent);
948 return indices.back();
951 void ChunkStore::UnregisterIndex(ChunkIndex &index) {
952 for (auto i = indices.begin(), end = indices.end(); i != end; ++i) {
962 ChunkIndex *ChunkStore::ClosestIndex(const ExactLocation::Coarse &pos) {
963 ChunkIndex *closest_index = nullptr;
964 int closest_distance = std::numeric_limits<int>::max();
966 for (ChunkIndex &index : indices) {
967 int distance = index.Distance(pos);
968 if (distance < closest_distance) {
969 closest_index = &index;
970 closest_distance = distance;
974 return closest_index;
977 Chunk *ChunkStore::Get(const ExactLocation::Coarse &pos) {
978 for (ChunkIndex &index : indices) {
979 Chunk *chunk = index.Get(pos);
987 Chunk *ChunkStore::Allocate(const ExactLocation::Coarse &pos) {
988 Chunk *chunk = Get(pos);
993 loaded.emplace(loaded.begin(), types);
995 loaded.splice(loaded.begin(), free, free.begin());
996 loaded.front().Unlink();
998 chunk = &loaded.front();
999 chunk->Position(pos);
1000 for (ChunkIndex &index : indices) {
1001 if (index.InRange(pos)) {
1002 index.Register(*chunk);
1005 for (int i = 0; i < Block::FACE_COUNT; ++i) {
1006 Block::Face face = Block::Face(i);
1007 ExactLocation::Coarse neighbor_pos(pos + Block::FaceNormal(face));
1008 Chunk *neighbor = Get(neighbor_pos);
1010 chunk->SetNeighbor(face, *neighbor);
1016 bool ChunkStore::HasMissing() const noexcept {
1017 for (const ChunkIndex &index : indices) {
1018 if (index.MissingChunks() > 0) {
1025 int ChunkStore::EstimateMissing() const noexcept {
1027 for (const ChunkIndex &index : indices) {
1028 missing += index.MissingChunks();
1033 ExactLocation::Coarse ChunkStore::NextMissing() noexcept {
1034 for (ChunkIndex &index : indices) {
1035 if (index.MissingChunks()) {
1036 return index.NextMissing();
1039 return ExactLocation::Coarse(0, 0, 0);
1042 void ChunkStore::Clean() {
1043 for (auto i = loaded.begin(), end = loaded.end(); i != end;) {
1044 if (i->Referenced() || i->ShouldUpdateSave()) {
1049 free.splice(free.end(), loaded, chunk);
1051 chunk->InvalidateMesh();