]> git.localhorst.tv Git - blank.git/blobdiff - src/world/chunk.cpp
use collision structures for ray tests
[blank.git] / src / world / chunk.cpp
index a65f5c31d9b1d0081ab3204903b87cbe10bd562d..83c94e3403a14ddd97283739dfe34e0e50fa9696 100644 (file)
@@ -4,10 +4,11 @@
 
 #include "Generator.hpp"
 #include "WorldCollision.hpp"
+#include "../io/WorldSave.hpp"
 
 #include <algorithm>
-#include <iostream>
 #include <limits>
+#include <ostream>
 #include <queue>
 
 
@@ -26,7 +27,8 @@ Chunk::Chunk(const BlockTypeRegistry &types) noexcept
 , light{0}
 , model()
 , position(0, 0, 0)
-, dirty(false) {
+, dirty_model(false)
+, dirty_save(false) {
 
 }
 
@@ -34,7 +36,8 @@ 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);
@@ -47,7 +50,8 @@ Chunk &Chunk::operator =(Chunk &&other) noexcept {
        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;
 }
 
@@ -65,9 +69,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);
@@ -144,6 +152,7 @@ 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;
 
@@ -186,6 +195,26 @@ void Chunk::SetBlock(int index, const Block &block) noexcept {
        }
 }
 
+namespace {
+
+// propagate light from a's edge to b
+void edge_light(
+       Chunk &a, const Chunk::Pos &a_pos,
+       Chunk &b, const Chunk::Pos &b_pos
+) noexcept {
+       if (a.GetLight(a_pos) > 1) {
+               const BlockType &b_type = b.Type(Chunk::ToIndex(b_pos));
+               if (!b_type.block_light) {
+                       light_queue.emplace(&a, a_pos);
+               }
+               if (b_type.visible) {
+                       b.Invalidate();
+               }
+       }
+}
+
+}
+
 void Chunk::SetNeighbor(Chunk &other) noexcept {
        if (other.position == position + Pos(-1, 0, 0)) {
                if (neighbor[Block::FACE_LEFT] != &other) {
@@ -195,12 +224,8 @@ void Chunk::SetNeighbor(Chunk &other) noexcept {
                                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);
-                                       }
+                                       edge_light(*this, my_pos, other, other_pos);
+                                       edge_light(other, other_pos, *this, my_pos);
                                }
                        }
                        work_light();
@@ -213,12 +238,8 @@ void Chunk::SetNeighbor(Chunk &other) noexcept {
                                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);
-                                       }
+                                       edge_light(*this, my_pos, other, other_pos);
+                                       edge_light(other, other_pos, *this, my_pos);
                                }
                        }
                        work_light();
@@ -231,12 +252,8 @@ void Chunk::SetNeighbor(Chunk &other) noexcept {
                                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);
-                                       }
+                                       edge_light(*this, my_pos, other, other_pos);
+                                       edge_light(other, other_pos, *this, my_pos);
                                }
                        }
                        work_light();
@@ -249,12 +266,8 @@ void Chunk::SetNeighbor(Chunk &other) noexcept {
                                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);
-                                       }
+                                       edge_light(*this, my_pos, other, other_pos);
+                                       edge_light(other, other_pos, *this, my_pos);
                                }
                        }
                        work_light();
@@ -267,12 +280,8 @@ void Chunk::SetNeighbor(Chunk &other) noexcept {
                                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);
-                                       }
+                                       edge_light(*this, my_pos, other, other_pos);
+                                       edge_light(other, other_pos, *this, my_pos);
                                }
                        }
                        work_light();
@@ -285,12 +294,8 @@ void Chunk::SetNeighbor(Chunk &other) noexcept {
                                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);
-                                       }
+                                       edge_light(*this, my_pos, other, other_pos);
+                                       edge_light(other, other_pos, *this, my_pos);
                                }
                        }
                        work_light();
@@ -299,23 +304,10 @@ void Chunk::SetNeighbor(Chunk &other) noexcept {
 }
 
 void Chunk::ClearNeighbors() noexcept {
-       for (int i = 0; i < Block::FACE_COUNT; ++i) {
-               neighbor[i] = nullptr;
-       }
-}
-
-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;
                }
        }
 }
@@ -332,7 +324,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);
 
@@ -438,7 +430,7 @@ bool Chunk::IsSurface(const Pos &pos) const noexcept {
 
 
 void Chunk::Draw() noexcept {
-       if (dirty) {
+       if (ShouldUpdateModel()) {
                Update();
        }
        model.Draw();
@@ -448,13 +440,12 @@ void Chunk::Draw() noexcept {
 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<float>::infinity();
+       coll.chunk = this;
+       coll.block = -1;
+       coll.depth = std::numeric_limits<float>::infinity();
        for (int z = 0; z < depth; ++z) {
                for (int y = 0; y < height; ++y) {
                        for (int x = 0; x < width; ++x, ++idx) {
@@ -465,20 +456,20 @@ 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;
        }
 }
@@ -488,7 +479,7 @@ bool Chunk::Intersection(
        const glm::mat4 &Mbox,
        const glm::mat4 &Mchunk,
        std::vector<WorldCollision> &col
-) const noexcept {
+) noexcept {
        bool any = false;
        float penetration;
        glm::vec3 normal;
@@ -521,7 +512,7 @@ BlockModel::Buffer buf;
 }
 
 void Chunk::CheckUpdate() noexcept {
-       if (dirty) {
+       if (ShouldUpdateModel()) {
                Update();
        }
 }
@@ -562,7 +553,7 @@ void Chunk::Update() noexcept {
        }
 
        model.Update(buf);
-       dirty = false;
+       ClearModel();
 }
 
 Block::FaceSet Chunk::Obstructed(const Pos &pos) const noexcept {
@@ -652,12 +643,18 @@ BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p, Block::Face face) noexce
 }
 
 
-ChunkLoader::ChunkLoader(const Config &config, const BlockTypeRegistry &reg, const Generator &gen) noexcept
+ChunkLoader::ChunkLoader(
+       const Config &config,
+       const BlockTypeRegistry &reg,
+       const Generator &gen,
+       const WorldSave &save
+) noexcept
 : base(0, 0, 0)
 , reg(reg)
 , gen(gen)
+, save(save)
 , loaded()
-, to_generate()
+, to_load()
 , to_free()
 , gen_timer(config.gen_limit)
 , load_dist(config.load_dist)
@@ -686,7 +683,7 @@ struct ChunkLess {
 
 }
 
-void ChunkLoader::Generate(const Chunk::Pos &from, const Chunk::Pos &to) {
+void ChunkLoader::Queue(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) {
@@ -694,7 +691,7 @@ void ChunkLoader::Generate(const Chunk::Pos &from, const Chunk::Pos &to) {
                                if (Known(pos)) {
                                        continue;
                                } else if (pos == base) {
-                                       Generate(pos);
+                                       Load(pos);
 
                                //      light testing
                                //      for (int i = 0; i < 16; ++i) {
@@ -741,19 +738,23 @@ void ChunkLoader::Generate(const Chunk::Pos &from, const Chunk::Pos &to) {
                                //      loaded.back().Invalidate();
                                //      loaded.back().CheckUpdate();
                                } else {
-                                       to_generate.emplace_back(pos);
+                                       to_load.emplace_back(pos);
                                }
                        }
                }
        }
-       to_generate.sort(ChunkLess(base));
+       to_load.sort(ChunkLess(base));
 }
 
-Chunk &ChunkLoader::Generate(const Chunk::Pos &pos) {
+Chunk &ChunkLoader::Load(const Chunk::Pos &pos) {
        loaded.emplace_back(reg);
        Chunk &chunk = loaded.back();
        chunk.Position(pos);
-       gen(chunk);
+       if (save.Exists(pos)) {
+               save.Read(chunk);
+       } else {
+               gen(chunk);
+       }
        Insert(chunk);
        return chunk;
 }
@@ -764,8 +765,19 @@ void ChunkLoader::Insert(Chunk &chunk) noexcept {
        }
 }
 
-void ChunkLoader::Remove(Chunk &chunk) noexcept {
-       chunk.Unlink();
+std::list<Chunk>::iterator ChunkLoader::Remove(std::list<Chunk>::iterator chunk) noexcept {
+       // fetch next entry while chunk's still in the list
+       std::list<Chunk>::iterator next = chunk;
+       ++next;
+       // unlink neighbors so they won't reference a dead chunk
+       chunk->ClearNeighbors();
+       // if it should be saved, do it now
+       if (chunk->ShouldUpdateSave()) {
+               save.Write(*chunk);
+       }
+       // and move it from loaded to free list
+       to_free.splice(to_free.end(), loaded, chunk);
+       return next;
 }
 
 Chunk *ChunkLoader::Loaded(const Chunk::Pos &pos) noexcept {
@@ -778,7 +790,7 @@ Chunk *ChunkLoader::Loaded(const Chunk::Pos &pos) noexcept {
 }
 
 bool ChunkLoader::Queued(const Chunk::Pos &pos) noexcept {
-       for (const Chunk::Pos &chunk : to_generate) {
+       for (const Chunk::Pos &chunk : to_load) {
                if (chunk == pos) {
                        return true;
                }
@@ -797,14 +809,14 @@ Chunk &ChunkLoader::ForceLoad(const Chunk::Pos &pos) {
                return *chunk;
        }
 
-       for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end; ++iter) {
+       for (auto iter(to_load.begin()), end(to_load.end()); iter != end; ++iter) {
                if (*iter == pos) {
-                       to_generate.erase(iter);
+                       to_load.erase(iter);
                        break;
                }
        }
 
-       return Generate(pos);
+       return Load(pos);
 }
 
 bool ChunkLoader::OutOfRange(const Chunk::Pos &pos) const noexcept {
@@ -822,57 +834,104 @@ void ChunkLoader::Rebase(const Chunk::Pos &new_base) {
        // unload far away chunks
        for (auto iter(loaded.begin()), end(loaded.end()); iter != end;) {
                if (OutOfRange(*iter)) {
-                       auto saved = iter;
-                       Remove(*saved);
-                       ++iter;
-                       to_free.splice(to_free.end(), loaded, saved);
+                       iter = Remove(iter);
                } else {
                        ++iter;
                }
        }
        // abort far away queued chunks
-       for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end;) {
+       for (auto iter(to_load.begin()), end(to_load.end()); iter != end;) {
                if (OutOfRange(*iter)) {
-                       iter = to_generate.erase(iter);
+                       iter = to_load.erase(iter);
                } else {
                        ++iter;
                }
        }
        // add missing new chunks
-       GenerateSurrounding(base);
+       QueueSurrounding(base);
 }
 
-void ChunkLoader::GenerateSurrounding(const Chunk::Pos &pos) {
+void ChunkLoader::QueueSurrounding(const Chunk::Pos &pos) {
        const Chunk::Pos offset(load_dist, load_dist, load_dist);
-       Generate(pos - offset, pos + offset);
+       Queue(pos - offset, pos + offset);
 }
 
 void ChunkLoader::Update(int dt) {
+       // check if a chunk load is scheduled for this frame
+       // and if there's chunks waiting to be loaded
        gen_timer.Update(dt);
-       if (!gen_timer.Hit() || to_generate.empty()) {
-               return;
+       if (gen_timer.Hit()) {
+               // we may
+               // 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 (!to_load.empty() && loaded < max_load && generated < max_gen) {
+                       if (LoadOne()) {
+                               ++generated;
+                       } else {
+                               ++loaded;
+                       }
+               }
        }
 
-       Chunk::Pos pos(to_generate.front());
-       to_generate.pop_front();
+       constexpr int max_save = 10;
+       int saved = 0;
+       for (Chunk &chunk : loaded) {
+               if (chunk.ShouldUpdateSave()) {
+                       save.Write(chunk);
+                       ++saved;
+                       if (saved >= max_save) {
+                               break;
+                       }
+               }
+       }
+}
 
+void ChunkLoader::LoadN(std::size_t n) {
+       std::size_t end = std::min(n, ToLoad());
+       for (std::size_t i = 0; i < end; ++i) {
+               LoadOne();
+       }
+}
+
+bool ChunkLoader::LoadOne() {
+       if (to_load.empty()) return false;
+
+       // take position of next chunk in queue
+       Chunk::Pos pos(to_load.front());
+       to_load.pop_front();
+
+       // look if the same chunk was already generated and still lingering
        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);
+                       Insert(loaded.back());
+                       return false;
                }
        }
 
+       // if the free list is empty, allocate a new chunk
+       // otherwise clear an unused one
        if (to_free.empty()) {
                loaded.emplace_back(reg);
        } else {
                to_free.front().ClearNeighbors();
                loaded.splice(loaded.end(), to_free, to_free.begin());
        }
+
+       bool generated = false;
        Chunk &chunk = loaded.back();
        chunk.Position(pos);
-       gen(chunk);
+       if (save.Exists(pos)) {
+               save.Read(chunk);
+       } else {
+               gen(chunk);
+               generated = true;
+       }
        Insert(chunk);
+       return generated;
 }
 
 }