X-Git-Url: http://git.localhorst.tv/?a=blobdiff_plain;f=src%2Fworld%2Fchunk.cpp;h=452e7aa3b3cf23345c7cb1e7f085e3c5d16572c5;hb=9da6ac5e93d79e79658a95d5f6efe42146873583;hp=569bb11c9f8e240ff25864cf7323ce8aed579504;hpb=f2f254f8d576b48fde9305c3d2927552d1d4c20d;p=blank.git diff --git a/src/world/chunk.cpp b/src/world/chunk.cpp index 569bb11..452e7aa 100644 --- a/src/world/chunk.cpp +++ b/src/world/chunk.cpp @@ -1,11 +1,21 @@ #include "BlockLookup.hpp" #include "Chunk.hpp" +#include "ChunkIndex.hpp" #include "ChunkLoader.hpp" +#include "ChunkRenderer.hpp" +#include "ChunkStore.hpp" #include "Generator.hpp" +#include "WorldCollision.hpp" +#include "../app/Assets.hpp" +#include "../graphics/BlockLighting.hpp" +#include "../graphics/Viewport.hpp" +#include "../io/WorldSave.hpp" +#include "../model/BlockModel.hpp" #include #include +#include #include @@ -22,17 +32,20 @@ Chunk::Chunk(const BlockTypeRegistry &types) noexcept , neighbor{0} , blocks{} , light{0} -, model() +, generated(false) +, lighted(false) , position(0, 0, 0) -, dirty(false) { +, ref_count(0) +, dirty_model(false) +, dirty_save(false) { } Chunk::Chunk(Chunk &&other) noexcept : types(other.types) -, model(std::move(other.model)) , position(other.position) -, dirty(other.dirty) { +, dirty_model(other.dirty_model) +, dirty_save(other.dirty_save) { std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor); std::copy(other.blocks, other.blocks + sizeof(blocks), blocks); std::copy(other.light, other.light + sizeof(light), light); @@ -43,9 +56,9 @@ Chunk &Chunk::operator =(Chunk &&other) noexcept { std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor); std::copy(other.blocks, other.blocks + sizeof(blocks), blocks); std::copy(other.light, other.light + sizeof(light), light); - model = std::move(other.model); position = other.position; - dirty = other.dirty; + dirty_model = other.dirty_save; + dirty_save = other.dirty_save; return *this; } @@ -63,9 +76,13 @@ struct SetNode { int Get() const noexcept { return chunk->GetLight(pos); } void Set(int level) noexcept { chunk->SetLight(pos, level); } + const BlockType &GetType() const noexcept { return chunk->Type(Chunk::ToIndex(pos)); } + bool HasNext(Block::Face face) noexcept { + const BlockType &type = GetType(); + if (type.block_light && !type.luminosity) return false; const BlockLookup next(chunk, pos, face); - return next && !next.GetType().block_light; + return next; } SetNode GetNext(Block::Face face) noexcept { const BlockLookup next(chunk, pos, face); @@ -142,8 +159,9 @@ void Chunk::SetBlock(int index, const Block &block) noexcept { const BlockType &new_type = Type(block); blocks[index] = block; + Invalidate(); - if (&old_type == &new_type) return; + if (!lighted || &old_type == &new_type) return; if (new_type.luminosity > old_type.luminosity) { // light added @@ -184,136 +202,34 @@ void Chunk::SetBlock(int index, const Block &block) noexcept { } } -void Chunk::SetNeighbor(Chunk &other) noexcept { - if (other.position == position + Pos(-1, 0, 0)) { - if (neighbor[Block::FACE_LEFT] != &other) { - neighbor[Block::FACE_LEFT] = &other; - other.neighbor[Block::FACE_RIGHT] = this; - for (int z = 0; z < depth; ++z) { - for (int y = 0; y < height; ++y) { - Pos my_pos(0, y, z); - Pos other_pos(width - 1, y, z); - if (GetLight(my_pos) > 0) { - light_queue.emplace(this, my_pos); - } - if (other.GetLight(other_pos) > 0) { - light_queue.emplace(&other, other_pos); - } - } - } - work_light(); - } - } else if (other.position == position + Pos(1, 0, 0)) { - if (neighbor[Block::FACE_RIGHT] != &other) { - neighbor[Block::FACE_RIGHT] = &other; - other.neighbor[Block::FACE_LEFT] = this; - for (int z = 0; z < depth; ++z) { - for (int y = 0; y < height; ++y) { - Pos my_pos(width - 1, y, z); - Pos other_pos(0, y, z); - if (GetLight(my_pos) > 0) { - light_queue.emplace(this, my_pos); - } - if (other.GetLight(other_pos) > 0) { - light_queue.emplace(&other, other_pos); - } - } - } - work_light(); - } - } else if (other.position == position + Pos(0, -1, 0)) { - if (neighbor[Block::FACE_DOWN] != &other) { - neighbor[Block::FACE_DOWN] = &other; - other.neighbor[Block::FACE_UP] = this; - for (int z = 0; z < depth; ++z) { - for (int x = 0; x < width; ++x) { - Pos my_pos(x, 0, z); - Pos other_pos(x, height - 1, z); - if (GetLight(my_pos) > 0) { - light_queue.emplace(this, my_pos); - } - if (other.GetLight(other_pos) > 0) { - light_queue.emplace(&other, other_pos); - } - } - } - work_light(); - } - } else if (other.position == position + Pos(0, 1, 0)) { - if (neighbor[Block::FACE_UP] != &other) { - neighbor[Block::FACE_UP] = &other; - other.neighbor[Block::FACE_DOWN] = this; - for (int z = 0; z < depth; ++z) { - for (int x = 0; x < width; ++x) { - Pos my_pos(x, height - 1, z); - Pos other_pos(x, 0, z); - if (GetLight(my_pos) > 0) { - light_queue.emplace(this, my_pos); - } - if (other.GetLight(other_pos) > 0) { - light_queue.emplace(&other, other_pos); - } - } - } - work_light(); - } - } else if (other.position == position + Pos(0, 0, -1)) { - if (neighbor[Block::FACE_BACK] != &other) { - neighbor[Block::FACE_BACK] = &other; - other.neighbor[Block::FACE_FRONT] = this; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - Pos my_pos(x, y, 0); - Pos other_pos(x, y, depth - 1); - if (GetLight(my_pos) > 0) { - light_queue.emplace(this, my_pos); - } - if (other.GetLight(other_pos) > 0) { - light_queue.emplace(&other, other_pos); - } - } - } - work_light(); - } - } else if (other.position == position + Pos(0, 0, 1)) { - if (neighbor[Block::FACE_FRONT] != &other) { - neighbor[Block::FACE_FRONT] = &other; - other.neighbor[Block::FACE_BACK] = this; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - Pos my_pos(x, y, depth - 1); - Pos other_pos(x, y, 0); - if (GetLight(my_pos) > 0) { - light_queue.emplace(this, my_pos); - } - if (other.GetLight(other_pos) > 0) { - light_queue.emplace(&other, other_pos); - } +void Chunk::ScanLights() { + int idx = 0; + Pos pos(0, 0, 0); + for (; pos.z < depth; ++pos.z) { + for (pos.y = 0; pos.y < height; ++pos.y) { + for (pos.x = 0; pos.x < width; ++pos.x, ++idx) { + const BlockType &type = Type(blocks[idx]); + if (type.luminosity) { + SetLight(idx, type.luminosity); + light_queue.emplace(this, pos); } } - work_light(); } } + work_light(); + lighted = true; } -void Chunk::ClearNeighbors() noexcept { - for (int i = 0; i < Block::FACE_COUNT; ++i) { - neighbor[i] = nullptr; - } +void Chunk::SetNeighbor(Block::Face face, Chunk &other) noexcept { + neighbor[face] = &other; + other.neighbor[Block::Opposite(face)] = this; } void Chunk::Unlink() noexcept { for (int face = 0; face < Block::FACE_COUNT; ++face) { if (neighbor[face]) { neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = nullptr; - } - } -} - -void Chunk::Relink() noexcept { - for (int face = 0; face < Block::FACE_COUNT; ++face) { - if (neighbor[face]) { - neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = this; + neighbor[face] = nullptr; } } } @@ -330,7 +246,7 @@ int Chunk::GetLight(int index) const noexcept { return light[index]; } -float Chunk::GetVertexLight(const Pos &pos, const BlockModel::Position &vtx, const Model::Normal &norm) const noexcept { +float Chunk::GetVertexLight(const Pos &pos, const BlockModel::Position &vtx, const EntityModel::Normal &norm) const noexcept { int index = ToIndex(pos); float light = GetLight(index); @@ -435,24 +351,15 @@ bool Chunk::IsSurface(const Pos &pos) const noexcept { } -void Chunk::Draw() noexcept { - if (dirty) { - Update(); - } - model.Draw(); -} - - bool Chunk::Intersection( const Ray &ray, const glm::mat4 &M, - int &blkid, - float &dist, - glm::vec3 &normal -) const noexcept { + WorldCollision &coll +) noexcept { int idx = 0; - blkid = -1; - dist = std::numeric_limits::infinity(); + coll.chunk = this; + coll.block = -1; + coll.depth = std::numeric_limits::infinity(); for (int z = 0; z < depth; ++z) { for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x, ++idx) { @@ -463,24 +370,54 @@ bool Chunk::Intersection( float cur_dist; glm::vec3 cur_norm; if (type.shape->Intersects(ray, M * ToTransform(Pos(x, y, z), idx), cur_dist, cur_norm)) { - if (cur_dist < dist) { - blkid = idx; - dist = cur_dist; - normal = cur_norm; + if (cur_dist < coll.depth) { + coll.block = idx; + coll.depth = cur_dist; + coll.normal = cur_norm; } } } } } - if (blkid < 0) { + if (coll.block < 0) { return false; } else { - normal = glm::vec3(BlockAt(blkid).Transform() * glm::vec4(normal, 0.0f)); + coll.normal = glm::vec3(BlockAt(coll.block).Transform() * glm::vec4(coll.normal, 0.0f)); return true; } } +bool Chunk::Intersection( + const AABB &box, + const glm::mat4 &Mbox, + const glm::mat4 &Mchunk, + std::vector &col +) noexcept { + bool any = false; + float penetration; + glm::vec3 normal; + + if (!blank::Intersection(box, Mbox, Bounds(), Mchunk, penetration, normal)) { + return false; + } + for (int idx = 0, z = 0; z < depth; ++z) { + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x, ++idx) { + const BlockType &type = Type(idx); + if (!type.collision) { + continue; + } + if (type.shape->Intersects(Mchunk * ToTransform(Pos(x, y, z), idx), box, Mbox, penetration, normal)) { + col.emplace_back(this, idx, penetration, normal); + any = true; + } + } + } + } + return any; +} + namespace { @@ -488,13 +425,7 @@ BlockModel::Buffer buf; } -void Chunk::CheckUpdate() noexcept { - if (dirty) { - Update(); - } -} - -void Chunk::Update() noexcept { +void Chunk::Update(BlockModel &model) noexcept { int vtx_count = 0, idx_count = 0; for (const auto &block : blocks) { const Shape *shape = Type(block).shape; @@ -530,7 +461,7 @@ void Chunk::Update() noexcept { } model.Update(buf); - dirty = false; + ClearModel(); } Block::FaceSet Chunk::Obstructed(const Pos &pos) const noexcept { @@ -620,224 +551,477 @@ BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p, Block::Face face) noexce } -ChunkLoader::ChunkLoader(const Config &config, const BlockTypeRegistry ®, const Generator &gen) noexcept -: base(0, 0, 0) -, reg(reg) +ChunkLoader::ChunkLoader( + ChunkStore &store, + const Generator &gen, + const WorldSave &save +) noexcept +: store(store) , gen(gen) -, loaded() -, to_generate() -, to_free() -, load_dist(config.load_dist) -, unload_dist(config.unload_dist) { +, save(save) { } -namespace { +void ChunkLoader::Update(int dt) { + // check if there's chunks waiting to be loaded + // load until one of load or generation limits was hit + constexpr int max_load = 10; + constexpr int max_gen = 1; + int loaded = 0; + int generated = 0; + while (loaded < max_load && generated < max_gen && store.HasMissing()) { + if (LoadOne()) { + ++generated; + } else { + ++loaded; + } + } -struct ChunkLess { + // store a few chunks as well + constexpr int max_save = 10; + int saved = 0; + for (Chunk &chunk : store) { + if (chunk.ShouldUpdateSave()) { + save.Write(chunk); + ++saved; + if (saved >= max_save) { + break; + } + } + } +} - explicit ChunkLess(const Chunk::Pos &base) noexcept - : base(base) { } +int ChunkLoader::ToLoad() const noexcept { + return store.EstimateMissing(); +} - bool operator ()(const Chunk::Pos &a, const Chunk::Pos &b) const noexcept { - Chunk::Pos da(base - a); - Chunk::Pos db(base - b); - return - da.x * da.x + da.y * da.y + da.z * da.z < - db.x * db.x + db.y * db.y + db.z * db.z; - } +bool ChunkLoader::LoadOne() { + if (!store.HasMissing()) return false; - Chunk::Pos base; + Chunk::Pos pos = store.NextMissing(); + Chunk *chunk = store.Allocate(pos); + if (!chunk) { + // chunk store corrupted? + return false; + } -}; + bool generated = false; + if (save.Exists(pos)) { + save.Read(*chunk); + } else { + gen(*chunk); + generated = true; + } -} + ChunkIndex *index = store.ClosestIndex(pos); + if (!index) { + return generated; + } -void ChunkLoader::Generate(const Chunk::Pos &from, const Chunk::Pos &to) { - for (int z = from.z; z < to.z; ++z) { - for (int y = from.y; y < to.y; ++y) { - for (int x = from.x; x < to.x; ++x) { - Chunk::Pos pos(x, y, z); - if (Known(pos)) { - continue; - } else if (pos == base) { - Generate(pos); - - // light testing - // for (int i = 0; i < 16; ++i) { - // for (int j = 0; j < 16; ++j) { - // loaded.back().SetBlock(Chunk::Pos{ i, j, 0 }, Block(1)); - // loaded.back().SetBlock(Chunk::Pos{ i, j, 15 }, Block(1)); - // loaded.back().SetBlock(Chunk::Pos{ 0, j, i }, Block(1)); - // loaded.back().SetBlock(Chunk::Pos{ 15, j, i }, Block(1)); - // } - // } - // loaded.back().SetBlock(Chunk::Pos{ 1, 0, 1 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 14, 0, 1 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 1, 0, 14 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 14, 0, 14 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 1, 15, 1 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 14, 15, 1 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 1, 15, 14 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 14, 15, 14 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 7, 7, 0 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 8, 7, 0 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 7, 8, 0 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 8, 8, 0 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 7, 7, 15 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 8, 7, 15 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 7, 8, 15 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 8, 8, 15 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 0, 7, 7 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 0, 7, 8 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 0, 8, 7 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 0, 8, 8 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 15, 7, 7 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 15, 7, 8 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 15, 8, 7 }, Block(13)); - // loaded.back().SetBlock(Chunk::Pos{ 15, 8, 8 }, Block(13)); - // loaded.back().Invalidate(); - // loaded.back().CheckUpdate(); - - // orientation testing - // for (int i = 0; i < Block::FACE_COUNT; ++i) { - // for (int j = 0; j < Block::TURN_COUNT; ++j) { - // loaded.back().BlockAt(512 * j + 2 * i) = Block(3 * (j + 1), Block::Face(i), Block::Turn(j)); - // } - // } - // loaded.back().Invalidate(); - // loaded.back().CheckUpdate(); - } else { - to_generate.emplace_back(pos); + Chunk::Pos begin(pos - Chunk::Pos(1)); + Chunk::Pos end(pos + Chunk::Pos(2)); + for (Chunk::Pos iter(begin); iter.z < end.z; ++iter.z) { + for (iter.y = begin.y; iter.y < end.y; ++iter.y) { + for (iter.x = begin.x; iter.x < end.x; ++iter.x) { + if (index->IsBorder(iter)) continue; + Chunk *light_chunk = index->Get(iter); + if (!light_chunk || light_chunk->Lighted()) continue; + if (index->HasAllSurrounding(iter)) { + light_chunk->ScanLights(); } } } } - to_generate.sort(ChunkLess(base)); -} -Chunk &ChunkLoader::Generate(const Chunk::Pos &pos) { - loaded.emplace_back(reg); - Chunk &chunk = loaded.back(); - chunk.Position(pos); - gen(chunk); - Insert(chunk); - return chunk; + return generated; } -void ChunkLoader::Insert(Chunk &chunk) noexcept { - for (Chunk &other : loaded) { - chunk.SetNeighbor(other); +void ChunkLoader::LoadN(std::size_t n) { + std::size_t end = std::min(n, std::size_t(ToLoad())); + for (std::size_t i = 0; i < end && store.HasMissing(); ++i) { + LoadOne(); } } -void ChunkLoader::Remove(Chunk &chunk) noexcept { - chunk.Unlink(); + +ChunkRenderer::ChunkRenderer(ChunkIndex &index) +: index(index) +, models(index.TotalChunks()) +, block_tex() +, fog_density(0.0f) { + } -Chunk *ChunkLoader::Loaded(const Chunk::Pos &pos) noexcept { - for (Chunk &chunk : loaded) { - if (chunk.Position() == pos) { - return &chunk; +ChunkRenderer::~ChunkRenderer() { + +} + +int ChunkRenderer::MissingChunks() const noexcept { + return index.MissingChunks(); +} + +void ChunkRenderer::LoadTextures(const AssetLoader &loader, const TextureIndex &tex_index) { + block_tex.Bind(); + loader.LoadTextures(tex_index, block_tex); + block_tex.FilterNearest(); +} + +void ChunkRenderer::Update(int dt) { + for (int i = 0, updates = 0; updates < dt && i < index.TotalChunks(); ++i) { + if (index[i] && index[i]->ShouldUpdateModel()) { + index[i]->Update(models[i]); + ++updates; } } - return nullptr; } -bool ChunkLoader::Queued(const Chunk::Pos &pos) noexcept { - for (const Chunk::Pos &chunk : to_generate) { - if (chunk == pos) { - return true; +void ChunkRenderer::Render(Viewport &viewport) { + BlockLighting &chunk_prog = viewport.ChunkProgram(); + chunk_prog.SetTexture(block_tex); + chunk_prog.SetFogDensity(fog_density); + + for (int i = 0; i < index.TotalChunks(); ++i) { + if (!index[i]) continue; + glm::mat4 m(index[i]->Transform(index.Base())); + glm::mat4 mvp(chunk_prog.GetVP() * m); + if (!CullTest(Chunk::Bounds(), mvp)) { + if (index[i]->ShouldUpdateModel()) { + index[i]->Update(models[i]); + } + chunk_prog.SetM(m); + models[i].Draw(); } } - return false; } -bool ChunkLoader::Known(const Chunk::Pos &pos) noexcept { - if (Loaded(pos)) return true; - return Queued(pos); + +ChunkIndex::ChunkIndex(ChunkStore &store, const Chunk::Pos &base, int extent) +: store(store) +, base(base) +, extent(extent) +, side_length(2 * extent + 1) +, total_length(side_length * side_length * side_length) +, total_indexed(0) +, last_missing(0) +, stride(1, side_length, side_length * side_length) +, chunks(total_length, nullptr) { + Scan(); } -Chunk &ChunkLoader::ForceLoad(const Chunk::Pos &pos) { - Chunk *chunk = Loaded(pos); - if (chunk) { - return *chunk; - } +ChunkIndex::~ChunkIndex() { + Clear(); +} - for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end; ++iter) { - if (*iter == pos) { - to_generate.erase(iter); - break; +bool ChunkIndex::InRange(const Chunk::Pos &pos) const noexcept { + return Distance(pos) <= extent; +} + +bool ChunkIndex::IsBorder(const Chunk::Pos &pos) const noexcept { + return Distance(pos) == extent; +} + +int ChunkIndex::Distance(const Chunk::Pos &pos) const noexcept { + return manhattan_radius(pos - base); +} + +bool ChunkIndex::HasAllSurrounding(const Chunk::Pos &pos) const noexcept { + Chunk::Pos begin(pos - Chunk::Pos(1)); + Chunk::Pos end(pos + Chunk::Pos(2)); + for (Chunk::Pos iter(begin); iter.z < end.z; ++iter.z) { + for (iter.y = begin.y; iter.y < end.y; ++iter.y) { + for (iter.x = begin.x; iter.x < end.x; ++iter.x) { + if (!Get(iter)) return false; + } } } + return true; +} + +int ChunkIndex::IndexOf(const Chunk::Pos &pos) const noexcept { + Chunk::Pos mod_pos( + GetCol(pos.x), + GetCol(pos.y), + GetCol(pos.z) + ); + return mod_pos.x * stride.x + + mod_pos.y * stride.y + + mod_pos.z * stride.z; +} + +Chunk::Pos ChunkIndex::PositionOf(int i) const noexcept { + Chunk::Pos zero_pos( + (i / stride.x) % side_length, + (i / stride.y) % side_length, + (i / stride.z) % side_length + ); + Chunk::Pos zero_base( + GetCol(base.x), + GetCol(base.y), + GetCol(base.z) + ); + Chunk::Pos base_relative(zero_pos - zero_base); + if (base_relative.x > extent) base_relative.x -= side_length; + else if (base_relative.x < -extent) base_relative.x += side_length; + if (base_relative.y > extent) base_relative.y -= side_length; + else if (base_relative.y < -extent) base_relative.y += side_length; + if (base_relative.z > extent) base_relative.z -= side_length; + else if (base_relative.z < -extent) base_relative.z += side_length; + return base + base_relative; +} + +Chunk *ChunkIndex::Get(const Chunk::Pos &pos) noexcept { + if (InRange(pos)) { + return chunks[IndexOf(pos)]; + } else { + return nullptr; + } +} - return Generate(pos); +const Chunk *ChunkIndex::Get(const Chunk::Pos &pos) const noexcept { + if (InRange(pos)) { + return chunks[IndexOf(pos)]; + } else { + return nullptr; + } } -void ChunkLoader::Rebase(const Chunk::Pos &new_base) { - if (new_base == base) { +void ChunkIndex::Rebase(const Chunk::Pos &new_base) { + if (new_base == base) return; + + Chunk::Pos diff(new_base - base); + + if (manhattan_radius(diff) > extent) { + // that's more than half, so probably not worth shifting + base = new_base; + Clear(); + Scan(); + store.Clean(); return; } - base = new_base; - - // unload far away chunks - for (auto iter(loaded.begin()), end(loaded.end()); iter != end;) { - if (std::abs(base.x - iter->Position().x) > unload_dist - || std::abs(base.y - iter->Position().y) > unload_dist - || std::abs(base.z - iter->Position().z) > unload_dist) { - auto saved = iter; - Remove(*saved); - ++iter; - to_free.splice(to_free.end(), loaded, saved); - } else { - ++iter; - } + + while (diff.x > 0) { + Shift(Block::FACE_RIGHT); + --diff.x; } - // abort far away queued chunks - for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end;) { - if (std::abs(base.x - iter->x) > unload_dist - || std::abs(base.y - iter->y) > unload_dist - || std::abs(base.z - iter->z) > unload_dist) { - iter = to_generate.erase(iter); - } else { - ++iter; + while (diff.x < 0) { + Shift(Block::FACE_LEFT); + ++diff.x; + } + while (diff.y > 0) { + Shift(Block::FACE_UP); + --diff.y; + } + while (diff.y < 0) { + Shift(Block::FACE_DOWN); + ++diff.y; + } + while (diff.z > 0) { + Shift(Block::FACE_FRONT); + --diff.z; + } + while (diff.z < 0) { + Shift(Block::FACE_BACK); + ++diff.z; + } + store.Clean(); +} + +int ChunkIndex::GetCol(int c) const noexcept { + c %= side_length; + if (c < 0) c += side_length; + return c; +} + +void ChunkIndex::Shift(Block::Face f) { + int a_axis = Block::Axis(f); + int b_axis = (a_axis + 1) % 3; + int c_axis = (a_axis + 2) % 3; + int dir = Block::Direction(f); + base[a_axis] += dir; + int a = GetCol(base[a_axis] + (extent * dir)); + int a_stride = a * stride[a_axis]; + for (int b = 0; b < side_length; ++b) { + int b_stride = b * stride[b_axis]; + for (int c = 0; c < side_length; ++c) { + int bc_stride = b_stride + c * stride[c_axis]; + int index = a_stride + bc_stride; + Unset(index); + int neighbor = ((a - dir + side_length) % side_length) * stride[a_axis] + bc_stride; + if (chunks[neighbor] && chunks[neighbor]->HasNeighbor(f)) { + Set(index, chunks[neighbor]->GetNeighbor(f)); + } } } - // add missing new chunks - GenerateSurrounding(base); } -void ChunkLoader::GenerateSurrounding(const Chunk::Pos &pos) { - const Chunk::Pos offset(load_dist, load_dist, load_dist); - Generate(pos - offset, pos + offset); +void ChunkIndex::Clear() noexcept { + for (int i = 0; i < total_length && total_indexed > 0; ++i) { + Unset(i); + } } -void ChunkLoader::Update() { - if (to_generate.empty()) { - return; +void ChunkIndex::Scan() noexcept { + for (Chunk &chunk : store) { + Register(chunk); } +} + +void ChunkIndex::Register(Chunk &chunk) noexcept { + if (InRange(chunk.Position())) { + Set(IndexOf(chunk.Position()), chunk); + } +} + +void ChunkIndex::Set(int index, Chunk &chunk) noexcept { + Unset(index); + chunks[index] = &chunk; + chunk.Ref(); + ++total_indexed; +} - Chunk::Pos pos(to_generate.front()); - to_generate.pop_front(); +void ChunkIndex::Unset(int index) noexcept { + if (chunks[index]) { + chunks[index]->UnRef(); + chunks[index] = nullptr; + --total_indexed; + } +} - for (auto iter(to_free.begin()), end(to_free.end()); iter != end; ++iter) { - if (iter->Position() == pos) { - iter->Relink(); - loaded.splice(loaded.end(), to_free, iter); +Chunk::Pos ChunkIndex::NextMissing() noexcept { + if (MissingChunks() > 0) { + int roundtrip = last_missing; + last_missing = (last_missing + 1) % total_length; + while (chunks[last_missing]) { + last_missing = (last_missing + 1) % total_length; + if (last_missing == roundtrip) { + break; + } + } + } + return PositionOf(last_missing); +} + + +ChunkStore::ChunkStore(const BlockTypeRegistry &types) +: types(types) +, loaded() +, free() +, indices() { + +} + +ChunkStore::~ChunkStore() { + +} + +ChunkIndex &ChunkStore::MakeIndex(const Chunk::Pos &pos, int extent) { + indices.emplace_back(*this, pos, extent); + return indices.back(); +} + +void ChunkStore::UnregisterIndex(ChunkIndex &index) { + for (auto i = indices.begin(), end = indices.end(); i != end; ++i) { + if (&*i == &index) { + indices.erase(i); return; + } else { + ++i; } } +} - if (to_free.empty()) { - loaded.emplace_back(reg); +ChunkIndex *ChunkStore::ClosestIndex(const Chunk::Pos &pos) { + ChunkIndex *closest_index = nullptr; + int closest_distance = std::numeric_limits::max(); + + for (ChunkIndex &index : indices) { + int distance = index.Distance(pos); + if (distance < closest_distance) { + closest_index = &index; + closest_distance = distance; + } + } + + return closest_index; +} + +Chunk *ChunkStore::Get(const Chunk::Pos &pos) { + for (ChunkIndex &index : indices) { + Chunk *chunk = index.Get(pos); + if (chunk) { + return chunk; + } + } + return nullptr; +} + +Chunk *ChunkStore::Allocate(const Chunk::Pos &pos) { + Chunk *chunk = Get(pos); + if (chunk) { + return chunk; + } + if (free.empty()) { + loaded.emplace(loaded.begin(), types); } else { - to_free.front().ClearNeighbors(); - loaded.splice(loaded.end(), to_free, to_free.begin()); + loaded.splice(loaded.begin(), free, free.begin()); + loaded.front().Unlink(); + } + chunk = &loaded.front(); + chunk->Position(pos); + for (ChunkIndex &index : indices) { + if (index.InRange(pos)) { + index.Register(*chunk); + } + } + for (int i = 0; i < Block::FACE_COUNT; ++i) { + Block::Face face = Block::Face(i); + Chunk::Pos neighbor_pos(pos + Block::FaceNormal(face)); + Chunk *neighbor = Get(neighbor_pos); + if (neighbor) { + chunk->SetNeighbor(face, *neighbor); + } + } + return chunk; +} + +bool ChunkStore::HasMissing() const noexcept { + for (const ChunkIndex &index : indices) { + if (index.MissingChunks() > 0) { + return true; + } + } + return false; +} + +int ChunkStore::EstimateMissing() const noexcept { + int missing = 0; + for (const ChunkIndex &index : indices) { + missing += index.MissingChunks(); + } + return missing; +} + +Chunk::Pos ChunkStore::NextMissing() noexcept { + for (ChunkIndex &index : indices) { + if (index.MissingChunks()) { + return index.NextMissing(); + } + } + return Chunk::Pos(0, 0, 0); +} + +void ChunkStore::Clean() { + for (auto i = loaded.begin(), end = loaded.end(); i != end;) { + if (i->Referenced() || i->ShouldUpdateSave()) { + ++i; + } else { + auto chunk = i; + ++i; + free.splice(free.end(), loaded, chunk); + chunk->Unlink(); + chunk->InvalidateModel(); + } } - Chunk &chunk = loaded.back(); - chunk.Position(pos); - gen(chunk); - Insert(chunk); } }