Commit 765e9da0 authored by Shu's avatar Shu 🍠
Browse files

Optimizations

Reorder chunk_voxel_idx
Region exp saving ratio
Single level chunk tree
parent 62886065
......@@ -73,6 +73,10 @@ Released as `0.0.1`: `Pre alpha 1`
- [ ] Inventory
- [ ] Octree
- [ ] Surface aware LOD
- [ ] All levels (0-5)
- [ ] Tree fill
- [ ] Big edit queue
- [ ] Update region level average
- [ ] Area converter
- [ ] Minecraft import
- [ ] Base unit size change
......
......@@ -240,7 +240,7 @@ public:
float opacity = .8f;
} console;
struct {
struct overlay_t {
bool visible = true;
int corner = 3;
} overlay;
......
......@@ -50,6 +50,9 @@ namespace contouring {
/// Get pending elements
virtual size_t getQueueSize() = 0;
/// Get max visible distance in chunks
virtual uint16_t getVisibleDist() const = 0;
struct area_info {
world::transform absolute;
world::cell_pos::value_type radius;
......
......@@ -311,24 +311,26 @@ namespace contouring {
}
void FlatDualMC::render(const surrounding::corners &surrounding, render::Model::Data::indices_t &out, std::vector<render::VertexData> &tmp, Layer layer) const {
const int SIZE = world::CHUNK_LENGTH + 3;
constexpr uint8_t SIZE = world::CHUNK_LENGTH + 3;
std::array<dualmc::DualMC<float>::Point, SIZE * SIZE * SIZE> grid;
const auto &materials = world::module::Registry::Get()->getMaterials();
{
ZoneScopedN("Load");
const auto setCell = [&](int x, int y, int z, const world::Voxel &voxel) {
auto &cell = grid[((z * SIZE) + y) * SIZE + x];
const auto setCell = [&](glm::idx i, const world::Voxel &voxel) {
auto &cell = grid[i];
cell.w = voxel.material();
cell.x = voxel.density_ratio() * (!materials.invisibilities[cell.w] &&
((materials.transparencies[cell.w] && (layer && Layer::Transparent)) ||
(!materials.transparencies[cell.w] && (layer && Layer::Solid))));
};
for (int z = 0; z < SIZE; z++) {
for (int y = 0; y < SIZE; y++) {
for (int x = 0; x < SIZE; x++) {
size_t i = 0;
for (uint8_t z = 0; z < SIZE; z++) {
for (uint8_t y = 0; y < SIZE; y++) {
for (uint8_t x = 0; x < SIZE; x++) {
const auto &chunk = surrounding[(z >= world::CHUNK_LENGTH) + (y >= world::CHUNK_LENGTH)*2 + (x >= world::CHUNK_LENGTH)*4];
const auto &voxel = chunk->get(glm::toIdx(x % world::CHUNK_LENGTH, y % world::CHUNK_LENGTH, z % world::CHUNK_LENGTH));
setCell(x, y, z, voxel);
const auto &voxel = chunk->get(glm::toIdx(x & glm::IDX_MASK, y & glm::IDX_MASK, z & glm::IDX_MASK));
setCell(i, voxel);
i++;
}}}
for (size_t i = 0; i < surrounding.size(); i++) {
......@@ -337,7 +339,7 @@ namespace contouring {
for (auto it = edits.begin(); it != edits.end(); ++it) {
auto p = offset + glm::ivec3(glm::fromIdx(it->first));
if(p.x < SIZE && p.y < SIZE && p.z < SIZE) {
setCell(p.x, p.y, p.z, it->second.value);
setCell(glm::toIdx(p, glm::ucvec3(SIZE)), it->second.value);
}
}
}
......
......@@ -42,6 +42,8 @@ namespace contouring {
render::Model* getModel(const world::model_id&) const override;
void getInstancedModels(instanced_draw_call draw, const world::Elements&, const std::optional<geometry::Frustum>& frustum, const world::cell_pos& offset, world::node_id ignore) const override;
uint16_t getVisibleDist() const override { return keepDistance; }
private:
struct area_models {
std::unique_ptr<render::LodModel> main;
......
......@@ -395,10 +395,7 @@ void DualMC<T>::calculateDualPoint(int32_t const cx, int32_t const cy, int32_t c
// compute the dual point as the mean of the face vertices belonging to the
// original marching cubes face
Vertex p;
p.x = 0;
p.y = 0;
p.z = 0;
Vertex p(0, 0, 0, 0);
int points = 0;
T max = 0;
......
......@@ -4,6 +4,8 @@
#include <imgui_stdlib.h>
#include "../state.hpp"
#include "Window.hpp"
#include "core/utils/os.hpp"
#include "api/Buffers.hpp"
#include "core/world/Elements.hpp"
#include "version.h"
#include <filesystem>
......@@ -115,38 +117,47 @@ void UI::Unload() {
sInstance = nullptr;
}
UI::Actions drawMenu(config::client::options &options, state::state &state, const std::vector<std::string>& packs) {
const ImGuiIO &io = ImGui::GetIO();
if (options.overlay.visible) {
if (options.overlay.corner != -1) {
ImVec2 window_pos = ImVec2((options.overlay.corner & 1) ? io.DisplaySize.x - UI_MARGIN : UI_MARGIN, (options.overlay.corner & 2) ? io.DisplaySize.y - UI_MARGIN : UI_MARGIN);
ImVec2 window_pos_pivot = ImVec2((options.overlay.corner & 1) ? 1.0f : 0.0f, (options.overlay.corner & 2) ? 1.0f : 0.0f);
template<typename F>
void drawOverlay(config::client::options::overlay_t& overlay, F inner) {
if (overlay.visible) {
const ImGuiIO &io = ImGui::GetIO();
if (overlay.corner != -1) {
ImVec2 window_pos = ImVec2((overlay.corner & 1) ? io.DisplaySize.x - UI_MARGIN : UI_MARGIN, (overlay.corner & 2) ? io.DisplaySize.y - UI_MARGIN : UI_MARGIN);
ImVec2 window_pos_pivot = ImVec2((overlay.corner & 1) ? 1.0f : 0.0f, (overlay.corner & 2) ? 1.0f : 0.0f);
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
}
ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background
ImGui::Begin("Overlay", &options.overlay.visible, (options.overlay.corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav);
ImGui::Begin("Overlay", &overlay.visible, (overlay.corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav);
ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
inner();
ImGui::Text("RAM: %.3f/%.3f GB", os::GetProcess().memused / 1000000000.f, os::GetGlobal().ram.total / 1000000000.);
if (ImGui::BeginPopupContextWindow()) {
if (ImGui::MenuItem("Custom", NULL, options.overlay.corner == -1))
options.overlay.corner = -1;
if (ImGui::MenuItem("Top-left", NULL, options.overlay.corner == 0))
options.overlay.corner = 0;
if (ImGui::MenuItem("Top-right", NULL, options.overlay.corner == 1))
options.overlay.corner = 1;
if (ImGui::MenuItem("Bottom-left", NULL, options.overlay.corner == 2))
options.overlay.corner = 2;
if (ImGui::MenuItem("Bottom-right", NULL, options.overlay.corner == 3))
options.overlay.corner = 3;
if (options.overlay.visible && ImGui::MenuItem("Close"))
options.overlay.visible = false;
if (ImGui::MenuItem("Custom", NULL, overlay.corner == -1))
overlay.corner = -1;
if (ImGui::MenuItem("Top-left", NULL, overlay.corner == 0))
overlay.corner = 0;
if (ImGui::MenuItem("Top-right", NULL, overlay.corner == 1))
overlay.corner = 1;
if (ImGui::MenuItem("Bottom-left", NULL, overlay.corner == 2))
overlay.corner = 2;
if (ImGui::MenuItem("Bottom-right", NULL, overlay.corner == 3))
overlay.corner = 3;
if (overlay.visible && ImGui::MenuItem("Close"))
overlay.visible = false;
ImGui::EndPopup();
}
ImGui::End();
}
}
UI::Actions drawMenu(config::client::options &options, state::state &state, const std::vector<std::string>& packs) {
drawOverlay(options.overlay, []{});
auto actions = drawCommon(options, state, packs);
const ImGuiIO &io = ImGui::GetIO();
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x / 2, io.DisplaySize.y / 2), ImGuiCond_Always, ImVec2(.5f, .5f));
ImGui::Begin((std::string("Univerxel ") + UNIVERXEL_VERSION).c_str(), NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse);
state.login.name.resize(128);
......@@ -327,33 +338,9 @@ UI::Actions drawInGame(config::client::options &options, state::state &state, co
ImGui::End();
}
if (options.overlay.visible) {
if (options.overlay.corner != -1) {
ImVec2 window_pos = ImVec2((options.overlay.corner & 1) ? io.DisplaySize.x - UI_MARGIN : UI_MARGIN, (options.overlay.corner & 2) ? io.DisplaySize.y - UI_MARGIN : UI_MARGIN);
ImVec2 window_pos_pivot = ImVec2((options.overlay.corner & 1) ? 1.0f : 0.0f, (options.overlay.corner & 2) ? 1.0f : 0.0f);
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
}
ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background
ImGui::Begin("Overlay", &options.overlay.visible, (options.overlay.corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav);
ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
drawOverlay(options.overlay, [&]{
ImGui::Text("%ld tris(%ld models)", reports.tris_count, reports.models_count);
if (ImGui::BeginPopupContextWindow()) {
if (ImGui::MenuItem("Custom", NULL, options.overlay.corner == -1))
options.overlay.corner = -1;
if (ImGui::MenuItem("Top-left", NULL, options.overlay.corner == 0))
options.overlay.corner = 0;
if (ImGui::MenuItem("Top-right", NULL, options.overlay.corner == 1))
options.overlay.corner = 1;
if (ImGui::MenuItem("Bottom-left", NULL, options.overlay.corner == 2))
options.overlay.corner = 2;
if (ImGui::MenuItem("Bottom-right", NULL, options.overlay.corner == 3))
options.overlay.corner = 3;
if (options.overlay.visible && ImGui::MenuItem("Close"))
options.overlay.visible = false;
ImGui::EndPopup();
}
ImGui::End();
}
});
return actions;
}
......
......@@ -60,8 +60,8 @@ struct ServerFactory: public world::AbstractServerFactory {
{
int load = options.world.loadDistance;
int keep = options.world.keepDistance;
if (ImGui::SliderInt("Load distance", &load, 1, options.world.keepDistance) |
ImGui::SliderInt("Keep distance", &keep, options.world.loadDistance + 1, 21)) {
if (ImGui::SliderInt("Load distance##srv", &load, 1, options.world.keepDistance) |
ImGui::SliderInt("Keep distance##srv", &keep, options.world.loadDistance + 1, 21)) {
options.world.loadDistance = load;
options.world.keepDistance = keep;
}
......
......@@ -39,10 +39,9 @@ class Chunk final: public world::EdittableChunk {
public:
Chunk(std::istream &is): EdittableChunk(new ChunkFutureEdits(this), is) { }
/// Create from average
Chunk(Voxel val): EdittableChunk(new ChunkFutureEdits(this)), isAverage(true), isMajorant(val.swap()) { voxels.fill(Voxel(val.material(), val.density())); }
Chunk(): EdittableChunk(new ChunkFutureEdits(this)) { }
Chunk(Voxel val): EdittableChunk(new ChunkFutureEdits(this), Voxel(val.material(), val.density())), isMajorant(val.swap()) { }
constexpr bool isTrusted(bool allowMajorant) const { return !isAverage || (allowMajorant && isMajorant); }
inline bool isTrusted(bool allowMajorant) const noexcept { return isHeavy() || (allowMajorant && isMajorant); }
void unsetMajorant() {
assert(isMajorant);
isMajorant = false;
......@@ -51,13 +50,11 @@ public:
ChunkFutureEdits &setEdits() { assert(edits); return *(ChunkFutureEdits*)edits.get(); }
private:
/// Is temporary average
const bool isAverage = false;
/// Is temporary full valued
bool isMajorant = false;
};
/// Chunk full of air
static const std::shared_ptr<const Chunk> EMPTY_CHUNK = std::make_shared<Chunk>();
/// Chunk full of void
static const std::shared_ptr<const Chunk> EMPTY_CHUNK = std::make_shared<Chunk>(Voxel());
}
\ No newline at end of file
......@@ -351,7 +351,7 @@ bool DistantUniverse::onPacket(const memory::read_view& buf, net::PacketFlags) {
}
if (auto ck = readChunk()) {
part->allocate();
part->chunks.at(part->getIdx(pos.chunk)) = ck;
part->emplace(pos.chunk, ck);
}
break;
}
......
......@@ -42,6 +42,7 @@ void LocalUniverse::update(cell_pos pos, float deltaTime) {
const auto lock = getElements();
const auto &elements = *lock;
const auto visibleDist2 = glm::pow2<glm::l>(contouring->getVisibleDist());
{ // Update alive areas
ZoneScopedN("World");
auto rng = std::mt19937(std::rand());
......@@ -60,7 +61,8 @@ void LocalUniverse::update(cell_pos pos, float deltaTime) {
//MAYBE: compute absolute manually from chunk_pos(1) * pivot
const cell_pos chunkPos = area_tf.computeChild(glm::multiply(chunk.first));
const auto chunkDist = glm::length2(glm::divide(chunkPos - pos));
//MAYBE: if in load range
if (chunkDist > visibleDist2) // FIXME: World still > 20ms with big contouring.keepDist
continue;
if(const auto neighbors = chunk.second->setEdits()->update(deltaTime, rng() < contouringThreshold)) {
contouring->onUpdate(area_chunk_pos{id, chunk.first}, chunkDist, chunks, neighbors.value());
......
......@@ -16,7 +16,9 @@ namespace glm {
typedef vec<3, uc> ucvec3;
const auto IDX_LENGTH = 32;
const auto IDX_SHIFT = 5;
const auto IDX_LENGTH2 = IDX_LENGTH * IDX_LENGTH;
const auto IDX_SIZE = IDX_LENGTH2 * IDX_LENGTH;
using idx = glm::u16;
const idx IDX_MASK = (1u<<IDX_SHIFT)-1;
}
......@@ -15,6 +15,10 @@ namespace glm {
constexpr U inline pow2(U v) {
return v * v;
}
template<typename U>
constexpr U inline pow3(U v) {
return v * v * v;
}
constexpr ll inline length2(const llvec3& v) {
return pow2(v.x) + pow2(v.y) + pow2(v.z);
}
......@@ -64,32 +68,43 @@ namespace glm {
constexpr std::pair<lvec3, ucvec3> inline split(const llvec3 &value, const ucvec3 &m = ucvec3(IDX_LENGTH)) {
return {divide(value, m), modulo(value, m)};
}
/// idx order is z-y-x
constexpr ucvec3 inline fromIdx(idx idx) {
assert(idx < IDX_SIZE);
return ucvec3(idx / IDX_LENGTH2, (idx / IDX_LENGTH) % IDX_LENGTH, idx % IDX_LENGTH);
return ucvec3(idx % IDX_LENGTH, (idx / IDX_LENGTH) % IDX_LENGTH, idx / IDX_LENGTH2);
}
template <idx S = IDX_SHIFT>
constexpr ucvec3 inline fromIdxShift(idx idx) {
assert(idx < IDX_SIZE);
constexpr glm::idx MASK = (1u<<S)-1u;
return ucvec3(idx & MASK, (idx >> S) & MASK, (idx >> (2*S)) & MASK);
}
constexpr usvec3 inline fromIdx(size_t idx, lvec3 size) {
assert(size.x>=0 && size.y>=0 && size.z>=0);
assert((lvec3::value_type)idx < size.x * size.y * size.z);
return usvec3(idx / (size.y * size.z), (idx / size.z) % size.y, idx % size.z);
return usvec3(idx % size.x, (idx / size.x) % size.y, idx / (size.y * size.x));
}
template <idx S = IDX_SHIFT>
constexpr idx inline toIdxShift(glm::us x, glm::us y, glm::us z) {
return (z << (2*S)) | (y << S) | x;
}
constexpr idx inline toIdx(glm::uc x, glm::uc y, glm::uc z) {
return (x * IDX_LENGTH + y) * IDX_LENGTH + z;
return (z * IDX_LENGTH + y) * IDX_LENGTH + x;
}
constexpr idx inline toIdx(glm::uc x, glm::uc y, glm::uc z, glm::uc sy, glm::uc sz) {
return x * sy * sz + y * sz + z;
constexpr idx inline toIdx(glm::uc x, glm::uc y, glm::uc z, glm::uc sy, glm::uc sx) {
return z * sy * sx + y * sx + x;
}
constexpr idx inline toIdx(ucvec3 pos) {
return toIdx(pos.x, pos.y, pos.z);
}
constexpr idx inline toIdx(ucvec3 pos, ucvec3 size) {
return toIdx(pos.x, pos.y, pos.z, size.y, size.z);
return toIdx(pos.x, pos.y, pos.z, size.y, size.x);
}
constexpr l inline toIdx(lvec3 pos) {
return toIdx(pos.x, pos.y, pos.z);
}
constexpr l inline toIdx(lvec3 pos, lvec3 size) {
return toIdx(pos.x, pos.y, pos.z, size.y, size.z);
return toIdx(pos.x, pos.y, pos.z, size.y, size.x);
}
constexpr std::pair<lvec3, idx> inline splitIdx(const llvec3 &value, const ucvec3 &m = ucvec3(IDX_LENGTH)) {
return {divide(value, m), toIdx(rem(value.x, m.x), rem(value.y, m.y), rem(value.z, m.z))};
......
#include "os.hpp"
#ifdef _WINDOWS
#include <Windows.h>
#include <psapi.h>
os::global os::GetGlobal() {
global out{};
MEMORYSTATUSEX memInfo;
memInfo.dwLength = sizeof(MEMORYSTATUSEX);
GlobalMemoryStatusEx(&memInfo);
DWORDLONG totalVirtualMem = memInfo.ullTotalPageFile;
out.ram.total = memInfo.ullTotalPhys;
out.ram.free = memInfo.ullAvailPhys
out.swap.total = memInfo.ullTotalPageFile;
out.swap.total -= out.ram.total;
out.swap.free = memInfo.ullAvailPageFile;
out.swap.free -= out.ram.free;
return out;
}
os::process os::GetProcess() {
PROCESS_MEMORY_COUNTERS_EX pmc;
GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc));
process out{};
out.memused = pmc.PrivateUsage;
return out;
}
#else
#include <sys/types.h>
#include <sys/sysinfo.h>
#include <sys/resource.h>
os::global os::GetGlobal() {
struct sysinfo memInfo;
sysinfo(&memInfo);
global out{};
out.ram.total = memInfo.totalram;
out.ram.total *= memInfo.mem_unit;
out.ram.free = memInfo.freeram;
out.ram.free *= memInfo.mem_unit;
out.swap.total = memInfo.totalswap;
out.swap.total *= memInfo.mem_unit;
out.swap.free = memInfo.freeswap;
out.swap.free *= memInfo.mem_unit;
return out;
}
os::process os::GetProcess() {
struct rusage r_usage;
process out{};
getrusage(RUSAGE_SELF, &r_usage);
out.memused = r_usage.ru_maxrss;
out.memused *= 1000;
return out;
}
#endif
\ No newline at end of file
#pragma once
namespace os {
struct global {
struct memory {
/// In bytes
unsigned long long total;
unsigned long long free;
};
memory ram;
memory swap;
};
global GetGlobal();
struct process {
unsigned long long memused;
};
process GetProcess();
}
\ No newline at end of file
......@@ -5,8 +5,9 @@
using namespace world;
Chunk::Chunk(std::istream& str, bool rle) {
if(rle) {
Chunk::Chunk(std::istream& str) {
voxels.resize(CHUNK_SIZE);
if constexpr(RLE) {
uint16_t i = 0;
while(!str.eof()) {
uint16_t count;
......@@ -19,13 +20,18 @@ Chunk::Chunk(std::istream& str, bool rle) {
i++;
}
}
assert(i == CHUNK_SIZE && "Mismatch data length");
assert((i == 1 || i == CHUNK_SIZE) && "Mismatch data length");
if (i == 1) {
voxels.resize(1);
voxels.shrink_to_fit();
}
} else {
for(auto& voxel: voxels) {
str.read(reinterpret_cast<char *>(&voxel), sizeof(voxel));
}
}
}
Chunk::Chunk(Voxel v): voxels({v}) { }
Chunk::~Chunk() { }
const Voxel &Chunk::getAt(const chunk_voxel_pos &pos) const {
......
......@@ -6,22 +6,28 @@
namespace world {
constexpr auto RLE = true; //NOTE: only ~2.5% gain after zstd
/// World part as linear 3d voxel array
/// World part as linear 3d voxel array or just a single one (32x LOD)
class Chunk {
public:
Chunk(std::istream& str, bool rle = RLE);
Chunk(std::istream& str);
virtual ~Chunk();
/// Get voxel from index
inline const Voxel& get(chunk_voxel_idx idx) const {
return voxels[idx];
assert(isAllocated());
return voxels[isHeavy() ? idx : 0];
}
/// Get voxel from position
const Voxel &getAt(const chunk_voxel_pos &pos) const;
inline bool isAllocated() const noexcept { return !voxels.empty(); }
inline bool isHeavy() const noexcept { return voxels.size() > 1; }
protected:
Chunk(Voxel v);
/// NOTE: voxels must be allocated by child constructor
Chunk() { }
/// Chunk data
std::array<Voxel, CHUNK_SIZE> voxels;
std::vector<Voxel> voxels; //MAYBE: manual handle
};
}
\ No newline at end of file
......@@ -6,16 +6,31 @@
using namespace world;
EdittableChunk::EdittableChunk(owner<ChunkEdits*> edits, std::istream& str, bool rle): world::Chunk(str, rle), edits(edits) { }
EdittableChunk::EdittableChunk(owner<ChunkEdits*> edits, std::istream& str): world::Chunk(str), edits(edits) { }
EdittableChunk::EdittableChunk(owner<ChunkEdits*> edits, Voxel v): world::Chunk(v), edits(edits) { }
EdittableChunk::EdittableChunk(owner<ChunkEdits*> edits): world::Chunk(), edits(edits) { }
EdittableChunk::~EdittableChunk() { }
void EdittableChunk::set(chunk_voxel_idx idx, const Voxel& voxel) {
if (!isHeavy()) {
if (get(idx).value == voxel.value)
return;
flatten();
}
voxels[idx] = voxel;
}
void EdittableChunk::setAt(const chunk_voxel_pos &pos, const Voxel& voxel) {
return set(glm::toIdx(pos), voxel);
}
void EdittableChunk::flatten() {
assert(isAllocated());
voxels.resize(CHUNK_SIZE, voxels.front());
}
std::optional<Faces> ChunkEdits::update(float deltaTime, bool animate) {
ZoneScopedN("Chunk");
//ZoneScopedN("Chunk");
for(auto it = edits.begin(); it != edits.end();) {
it->second.delay -= deltaTime;
if(it->second.delay <= 0 && animate) {
......
......@@ -61,14 +61,21 @@ namespace world {
const ChunkEdits& getEdits() const { assert(edits); return *edits.get(); }
/// Direct set without update of voxel at index
inline void set(chunk_voxel_idx idx, const Voxel& voxel) {
voxels[idx] = voxel;
}
void set(chunk_voxel_idx idx, const Voxel &voxel);
/// Direct set without update of voxel at position
void setAt(const chunk_voxel_pos &pos, const Voxel& voxel);
/// Convert light to heavy
/// Faster to edit
void flatten();
/// Convert heavy to light
/// Uses less memory
void shrink();
protected:
EdittableChunk(owner<ChunkEdits*>, std::istream& str, bool rle = RLE);
EdittableChunk(owner<ChunkEdits*>, std::istream& str);
EdittableChunk(owner<ChunkEdits*>, Voxel);
/// NOTE: voxels must be allocated by child constructor
EdittableChunk(owner<ChunkEdits*>);
const std::unique_ptr<ChunkEdits> edits;
};
......
......@@ -141,10 +141,18 @@ namespace world {
assert(inRange(c));
const auto idx = getIdx(c);
if (idx < chunks.size())
return chunks.at(idx);
return chunks[idx];
return nullptr;
}