#include "ChunkLoader.hpp"
#include "Generator.hpp"
+#include "WorldCollision.hpp"
+#include "../io/WorldSave.hpp"
#include <algorithm>
#include <limits>
+#include <ostream>
#include <queue>
, neighbor{0}
, blocks{}
, light{0}
-, model()
, position(0, 0, 0)
-, dirty(false) {
+, 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);
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;
}
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);
const BlockType &new_type = Type(block);
blocks[index] = block;
+ Invalidate();
if (&old_type == &new_type) return;
}
}
+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) {
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();
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();
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();
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();
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();
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();
}
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;
}
}
}
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);
}
-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<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) {
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
-) const noexcept {
- if (!blank::Intersection(box, Mbox, Bounds(), Mchunk)) {
+ const glm::mat4 &Mchunk,
+ std::vector<WorldCollision> &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.visible) {
+ if (!type.collision) {
continue;
}
- if (type.shape->Intersects(Mchunk * ToTransform(Pos(x, y, z), idx), box, Mbox)) {
- return true;
+ 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 false;
+ return any;
}
}
-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;
}
model.Update(buf);
- dirty = false;
+ ClearModel();
}
Block::FaceSet Chunk::Obstructed(const Pos &pos) const noexcept {
}
-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)
}
-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) {
if (Known(pos)) {
continue;
} else if (pos == base) {
- Generate(pos);
+ Load(pos);
// light testing
// for (int i = 0; i < 16; ++i) {
// 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;
}
}
}
-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 {
}
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;
}
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 {
// 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;
}
}