X-Git-Url: http://git.localhorst.tv/?a=blobdiff_plain;f=src%2Fworld%2Fchunk.cpp;h=83c94e3403a14ddd97283739dfe34e0e50fa9696;hb=d2fa8ca97d291508ce3812fb052a8255d3190d00;hp=fe4f80267ab00d1f4bf2c572c7f278168cf03915;hpb=57ff8c89c8e172b0988a09490a2bc19d740d1c79;p=blank.git diff --git a/src/world/chunk.cpp b/src/world/chunk.cpp index fe4f802..83c94e3 100644 --- a/src/world/chunk.cpp +++ b/src/world/chunk.cpp @@ -3,9 +3,12 @@ #include "ChunkLoader.hpp" #include "Generator.hpp" +#include "WorldCollision.hpp" +#include "../io/WorldSave.hpp" #include #include +#include #include @@ -24,7 +27,8 @@ Chunk::Chunk(const BlockTypeRegistry &types) noexcept , light{0} , model() , position(0, 0, 0) -, dirty(false) { +, dirty_model(false) +, dirty_save(false) { } @@ -32,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); @@ -45,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; } @@ -63,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); @@ -142,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; @@ -169,20 +180,41 @@ void Chunk::SetBlock(int index, const Block &block) noexcept { } else if (!new_type.block_light && old_type.block_light) { // obstacle removed int level = 0; + Pos pos(ToPos(index)); for (int face = 0; face < Block::FACE_COUNT; ++face) { - BlockLookup next_block(this, ToPos(index), Block::Face(face)); + BlockLookup next_block(this, pos, Block::Face(face)); if (next_block) { - level = std::min(level, next_block.GetLight()); + level = std::max(level, next_block.GetLight()); } } if (level > 1) { SetLight(index, level - 1); - light_queue.emplace(this, ToPos(index)); + light_queue.emplace(this, pos); work_light(); } } } +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) { @@ -192,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(); @@ -210,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(); @@ -228,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(); @@ -246,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(); @@ -264,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(); @@ -282,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(); @@ -296,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; } } } @@ -329,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); @@ -435,7 +430,7 @@ bool Chunk::IsSurface(const Pos &pos) const noexcept { void Chunk::Draw() noexcept { - if (dirty) { + if (ShouldUpdateModel()) { Update(); } model.Draw(); @@ -445,14 +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 { - // TODO: should be possible to heavily optimize this + 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 +456,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 { @@ -489,7 +512,7 @@ BlockModel::Buffer buf; } void Chunk::CheckUpdate() noexcept { - if (dirty) { + if (ShouldUpdateModel()) { Update(); } } @@ -530,7 +553,7 @@ void Chunk::Update() noexcept { } model.Update(buf); - dirty = false; + ClearModel(); } Block::FaceSet Chunk::Obstructed(const Pos &pos) const noexcept { @@ -620,16 +643,23 @@ BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p, Block::Face face) noexce } -ChunkLoader::ChunkLoader(const Config &config, const BlockTypeRegistry ®, const Generator &gen) noexcept +ChunkLoader::ChunkLoader( + const Config &config, + const BlockTypeRegistry ®, + 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) , unload_dist(config.unload_dist) { - + gen_timer.Start(); } namespace { @@ -653,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) { @@ -661,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) { @@ -708,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; } @@ -731,8 +765,19 @@ void ChunkLoader::Insert(Chunk &chunk) noexcept { } } -void ChunkLoader::Remove(Chunk &chunk) noexcept { - chunk.Unlink(); +std::list::iterator ChunkLoader::Remove(std::list::iterator chunk) noexcept { + // fetch next entry while chunk's still in the list + std::list::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 { @@ -745,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; } @@ -764,14 +809,20 @@ 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 { + return std::abs(base.x - pos.x) > unload_dist + || std::abs(base.y - pos.y) > unload_dist + || std::abs(base.z - pos.z) > unload_dist; } void ChunkLoader::Rebase(const Chunk::Pos &new_base) { @@ -782,62 +833,105 @@ void ChunkLoader::Rebase(const Chunk::Pos &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); + if (OutOfRange(*iter)) { + iter = Remove(iter); } else { ++iter; } } // 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); + for (auto iter(to_load.begin()), end(to_load.end()); iter != end;) { + if (OutOfRange(*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()) { + // 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; + } + } + } + + 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::Update() { - if (to_generate.empty()) { - return; +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; - Chunk::Pos pos(to_generate.front()); - to_generate.pop_front(); + // 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); - return; + 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; } }