+namespace {
+
+struct SetNode {
+
+ Chunk *chunk;
+ Chunk::Pos pos;
+
+ SetNode(Chunk *chunk, Chunk::Pos pos)
+ : chunk(chunk), pos(pos) { }
+
+ int Get() const { return chunk->GetLight(pos); }
+ void Set(int level) { chunk->SetLight(pos, level); }
+
+ bool HasNext(Block::Face face) {
+ const BlockLookup next(chunk, pos, face);
+ return next && !next.GetType().block_light;
+ }
+ SetNode GetNext(Block::Face face) {
+ const BlockLookup next(chunk, pos, face);
+ return SetNode(&next.GetChunk(), next.GetBlockPos());
+ }
+
+};
+
+struct UnsetNode
+: public SetNode {
+
+ int level;
+
+ UnsetNode(Chunk *chunk, Chunk::Pos pos)
+ : SetNode(chunk, pos), level(Get()) { }
+
+ UnsetNode(const SetNode &set)
+ : SetNode(set), level(Get()) { }
+
+
+ bool HasNext(Block::Face face) {
+ const BlockLookup next(chunk, pos, face);
+ return next;
+ }
+ UnsetNode GetNext(Block::Face face) { return UnsetNode(SetNode::GetNext(face)); }
+
+};
+
+std::queue<SetNode> light_queue;
+std::queue<UnsetNode> dark_queue;
+
+void work_light() {
+ while (!light_queue.empty()) {
+ SetNode node = light_queue.front();
+ light_queue.pop();
+
+ int level = node.Get() - 1;
+ for (int face = 0; face < Block::FACE_COUNT; ++face) {
+ if (node.HasNext(Block::Face(face))) {
+ SetNode other = node.GetNext(Block::Face(face));
+ if (other.Get() < level) {
+ other.Set(level);
+ light_queue.emplace(other);
+ }
+ }
+ }
+ }
+}
+
+void work_dark() {
+ while (!dark_queue.empty()) {
+ UnsetNode node = dark_queue.front();
+ dark_queue.pop();
+
+ for (int face = 0; face < Block::FACE_COUNT; ++face) {
+ if (node.HasNext(Block::Face(face))) {
+ UnsetNode other = node.GetNext(Block::Face(face));
+ // TODO: if there a light source here with the same level this will err
+ if (other.Get() != 0 && other.Get() < node.level) {
+ other.Set(0);
+ dark_queue.emplace(other);
+ } else {
+ light_queue.emplace(other);
+ }
+ }
+ }
+ }
+}
+
+}
+
+void Chunk::SetBlock(int index, const Block &block) {
+ const BlockType &old_type = Type(blocks[index]);
+ const BlockType &new_type = Type(block);
+
+ blocks[index] = block;
+
+ if (&old_type == &new_type) return;
+
+ if (new_type.luminosity > old_type.luminosity) {
+ // light added
+ SetLight(index, new_type.luminosity);
+ light_queue.emplace(this, ToPos(index));
+ work_light();
+ } else if (new_type.luminosity < old_type.luminosity) {
+ // light removed
+ dark_queue.emplace(this, ToPos(index));
+ SetLight(index, 0);
+ work_dark();
+ SetLight(index, new_type.luminosity);
+ light_queue.emplace(this, ToPos(index));
+ work_light();
+ } else if (new_type.block_light && !old_type.block_light) {
+ // obstacle added
+ if (GetLight(index) > 0) {
+ dark_queue.emplace(this, ToPos(index));
+ SetLight(index, 0);
+ work_dark();
+ work_light();
+ }
+ } else if (!new_type.block_light && old_type.block_light) {
+ // obstacle removed
+ int level = 0;
+ for (int face = 0; face < Block::FACE_COUNT; ++face) {
+ BlockLookup next_block(this, ToPos(index), Block::Face(face));
+ if (next_block) {
+ level = std::min(level, next_block.GetLight());
+ }
+ }
+ if (level > 1) {
+ SetLight(index, level - 1);
+ light_queue.emplace(this, ToPos(index));
+ work_light();
+ }
+ }
+}
+
+void Chunk::SetNeighbor(Chunk &other) {
+ 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);
+ }
+ }
+ }
+ work_light();
+ }
+ }
+}
+
+void Chunk::ClearNeighbors() {
+ for (int i = 0; i < Block::FACE_COUNT; ++i) {
+ neighbor[i] = nullptr;
+ }
+}
+
+void Chunk::Unlink() {
+ for (int face = 0; face < Block::FACE_COUNT; ++face) {
+ if (neighbor[face]) {
+ neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = nullptr;
+ }
+ }
+}
+
+void Chunk::Relink() {
+ for (int face = 0; face < Block::FACE_COUNT; ++face) {
+ if (neighbor[face]) {
+ neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = this;
+ }
+ }
+}
+
+
+void Chunk::SetLight(int index, int level) {
+ if (light[index] != level) {
+ light[index] = level;
+ Invalidate();
+ }
+}
+
+int Chunk::GetLight(int index) const {
+ return light[index];
+}
+
+float Chunk::GetVertexLight(int index, const BlockModel::Position &vtx, const Model::Normal &norm) const {
+ float light = GetLight(index);
+ Chunk::Pos pos(ToPos(index));
+
+ Block::Face direct_face(Block::NormalFace(norm));
+ // tis okay
+ BlockLookup direct(const_cast<Chunk *>(this), pos, Block::NormalFace(norm));
+ if (direct) {
+ float direct_light = direct.GetLight();
+ if (direct_light > light) {
+ light = direct_light;
+ }
+ }
+
+ // cheap alternative until AO etc are implemented
+ // to tell the faces apart
+
+ if (direct_face == Block::FACE_LEFT || direct_face == Block::FACE_RIGHT) {
+ light -= 0.2;
+ } else if (direct_face == Block::FACE_FRONT || direct_face == Block::FACE_BACK) {
+ light -= 0.4;
+ }
+
+ return light;
+}
+
+
+bool Chunk::IsSurface(const Pos &pos) const {
+ const Block &block = BlockAt(pos);
+ if (!Type(block).visible) {
+ return false;
+ }
+ for (int face = 0; face < Block::FACE_COUNT; ++face) {
+ BlockLookup next = BlockLookup(const_cast<Chunk *>(this), pos, Block::Face(face));
+ if (!next || !next.GetType().visible) {
+ return true;
+ }
+ }
+ return false;