1
0
Fork 0

Optimizations

Reorder chunk_voxel_idx
Region exp saving ratio
Single level chunk tree
master
May B. 2021-01-26 16:18:30 +01:00
parent 6288606505
commit 765e9da05f
29 changed files with 354 additions and 167 deletions

View File

@ -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

View File

@ -240,7 +240,7 @@ public:
float opacity = .8f;
} console;
struct {
struct overlay_t {
bool visible = true;
int corner = 3;
} overlay;

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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());
}

View 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;
}

View File

@ -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());

View File

@ -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;
}

View File

@ -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))};

60
src/core/utils/os.cpp Normal file
View File

@ -0,0 +1,60 @@
#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

20
src/core/utils/os.hpp Normal file
View File

@ -0,0 +1,20 @@
#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();
}

View 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 {

View File

@ -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
};
}

View 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) {

View File

@ -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;
};

View File

@ -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;
}
void emplace(const chunk_pos& c, const std::shared_ptr<world::EdittableChunk>& ck) {
allocate();
chunks[getIdx(c)] = ck;
}
void emplace(const chunk_pos& c, std::shared_ptr<world::EdittableChunk>&& ck) {
allocate();
chunks[getIdx(c)] = std::move(ck);
}
glm::usvec3 size;
};

View File

@ -8,8 +8,11 @@ namespace world::generator {
/// Abstract Noise generator
class Abstract {
public:
using voxels = std::vector<Voxel>;
/// Generate chunk voxels
virtual void generate(const chunk_pos &at, std::array<Voxel, CHUNK_SIZE> &out) = 0;
/// level 5: upto 32^3 voxels
virtual void generate5(const voxel_pos &min, voxels &out) = 0;
/// Get gravity vector at given point
virtual glm::vec3 getGravity(const voxel_pos& point) const = 0;
/// Area visual curvature
@ -22,8 +25,8 @@ namespace world::generator {
struct Params { };
Void() = default;
void generate(const chunk_pos &, std::array<Voxel, CHUNK_SIZE> &out) override {
out.fill(Voxel());
void generate5(const voxel_pos &, voxels &out) override {
out = {Voxel()};
}
glm::vec3 getGravity(const voxel_pos&) const override { return glm::vec3(0); }
};

View File

@ -6,6 +6,25 @@
namespace world::generator {
/// Endless cave network
class Cave: public Abstract {
private:
template<uint32_t level>
inline void generate(const voxel_pos& min, voxels& out) const {
constexpr uint32_t LENGTH = 1u << level;
constexpr size_t SIZE = glm::pow3(LENGTH);
constexpr auto VEC = glm::ivec3(LENGTH);
const auto densitySet = density.getRaw(min, VEC);
const auto materialSet = material.getRaw(min, VEC);
constexpr auto MATERIAL_SKIP = 2;
const int materialMax = module::Registry::Get()->getMaterials().names.size() - (MATERIAL_SKIP + 1);
out.resize(SIZE);
for (size_t i = 0; i < SIZE; i++) {
const auto density = std::clamp((densitySet.get()[i] + params.density) * params.granularity, 0.f, 1.f) * Voxel::DENSITY_MAX;
const auto material = density > 0 ? MATERIAL_SKIP + std::clamp(static_cast<int>(std::lrint((materialSet.get()[i] + 1) / 2 * materialMax)),
0, materialMax) : 1; //NOTE: map (approx -1, 1) to (1, mat_max)
out[i] = Voxel(material, density);
}
}
public:
struct Params {
Params(int seed = 42, float density = 0, float gran = 30): seed(seed), density(density), granularity(gran) { }
@ -19,17 +38,8 @@ namespace world::generator {
};
Cave(const Params p): params(p), density(Noise::SimplexFractal(p.seed)), material(Noise::Cellular(p.seed * 5, .1)) { }
void generate(const chunk_pos &pos, std::array<Voxel, CHUNK_SIZE> &out) override {
const auto densitySet = density.getBlock(pos, CHUNK_LENGTH);
const auto materialSet = material.getBlock(pos, CHUNK_LENGTH);
constexpr auto MATERIAL_SKIP = 2;
const int materialMax = module::Registry::Get()->getMaterials().names.size() - (MATERIAL_SKIP + 1);
for (size_t i = 0; i < CHUNK_SIZE; i++) {
const auto density = std::clamp((densitySet.get()[i] + params.density) * params.granularity, 0.f, 1.f) * Voxel::DENSITY_MAX;
const auto material = density > 0 ? MATERIAL_SKIP + std::clamp(static_cast<int>(std::lrint((materialSet.get()[i] + 1) / 2 * materialMax)),
0, materialMax) : 1; //NOTE: map (approx -1, 1) to (1, mat_max)
out[i] = Voxel(material, density);
}
void generate5(const voxel_pos& min, voxels& out) override {
generate<5>(min, out);
}
glm::vec3 getGravity(const voxel_pos&) const override { return glm::vec3(-1, 0, 0); }
private:

View File

@ -10,7 +10,54 @@ namespace world::generator {
/// Abstract shaped planet generator
template <PlanetShape PS>
class Planet: public Abstract {
class Planet final: public Abstract {
private:
template<uint32_t level>
inline void generate(const voxel_pos& min, voxels& out) const {
constexpr uint32_t LENGTH = 1u << level;
constexpr size_t SIZE = glm::pow3(LENGTH);
constexpr auto VEC = glm::i32vec3(LENGTH);
const auto densitySet = density.getRaw(min, VEC);
const auto displacementSet = displacement.getRaw(min, VEC);
const auto& IDS = world::module::core::Core::Get()->ids;
const auto genAt = [&](size_t i) -> Voxel {
const auto heightRatio = ((float)getHeight(min + voxel_pos(glm::fromIdx(i))) - params.height) / params.height;
const auto verticalDensityOffset = heightRatio / (heightRatio >= 0 ? params.surface_roughness : params.depth_roughness);
const auto density = std::min((densitySet.get()[i] + params.density - verticalDensityOffset) * params.granularity, 1.f) * Voxel::DENSITY_MAX;
if (density > 0) {
const auto material = [&]() -> int {
const auto noisedHeightRatio = heightRatio - displacementSet.get()[i] * params.beach_displacement;
if(noisedHeightRatio >= 0) {
return densitySet.get()[i] + params.density < ((heightRatio + 0.007f) / params.surface_roughness) ? IDS.GRASS : IDS.DIRT;
} else {
return noisedHeightRatio >= -params.beach_depth ? IDS.SAND : IDS.ROCK;
}
}();
return Voxel(material, density);
} else {
if (heightRatio >= 0) {
return Voxel(IDS.AIR, (heightRatio < params.surface_roughness) * Voxel::DENSITY_MAX);
} else {
return Voxel(IDS.WATER, std::clamp<double>(-heightRatio * params.height, 0.f, 1.f) * Voxel::DENSITY_MAX);
}
}
};
const auto sample = genAt(0);
bool unique = true;
out.resize(SIZE);
for (size_t i = 0; i < SIZE; i++) {
out[i] = genAt(i);
if (unique && out[i].value != sample.value)
unique = false;
}
if (unique) {
out = voxels{sample};
}
}
public:
struct Params: Cave::Params {
Params(voxel_pos::value_type height, int seed = 42, float surface_roughness = .1, float depth_roughness = .05, float density = 0, float gran = 30):
@ -29,39 +76,8 @@ namespace world::generator {
};
Planet(const Params& p) : params(p), density(Noise::SimplexFractal(p.seed)), displacement(Noise::Simplex(p.seed * 5, .01)) {}
void generate(const chunk_pos &pos, std::array<Voxel, CHUNK_SIZE> &out) override {
auto densitySet = density.getBlock(pos, CHUNK_LENGTH);
auto displacementSet = displacement.getBlock(pos, CHUNK_LENGTH);
const auto& IDS = world::module::core::Core::Get()->ids;
for (size_t i = 0; i < CHUNK_SIZE; i++) {
const auto vPos = glm::fromIdx(i);
const auto noise_i = glm::toIdx(vPos.z, vPos.y, vPos.x);
const auto heightRatio = ((float)getHeight(glm::multiply(pos) + glm::llvec3(vPos)) - params.height) / params.height;
const auto verticalDensityOffset = heightRatio / (heightRatio >= 0 ? params.surface_roughness : params.depth_roughness);
const auto density = std::min((densitySet.get()[noise_i] + params.density - verticalDensityOffset) * params.granularity, 1.f) * Voxel::DENSITY_MAX;
out[i] = [&]() -> Voxel {
if (density > 0) {
const auto material = [&]() -> int {
const auto noisedHeightRatio = heightRatio - displacementSet.get()[noise_i] * params.beach_displacement;
if(noisedHeightRatio >= 0) {
return densitySet.get()[noise_i] + params.density < ((heightRatio + 0.007f) / params.surface_roughness) ? IDS.GRASS : IDS.DIRT;
} else {
return noisedHeightRatio >= -params.beach_depth ? IDS.SAND : IDS.ROCK;
}
}();
return Voxel(material, density);
} else {
if (heightRatio >= 0) {
return Voxel(IDS.AIR, (heightRatio < params.surface_roughness) * Voxel::DENSITY_MAX);
} else {
return Voxel(IDS.WATER, std::clamp<double>(-heightRatio * params.height, 0.f, 1.f) * Voxel::DENSITY_MAX);
}
}
}();
}
void generate5(const voxel_pos &min, voxels &out) override {
generate<5>(min, out);
}
glm::vec3 getGravity(const voxel_pos& pos) const override {
const auto heightRatio = static_cast<float>((getHeight(pos) - params.height) / params.height);

View File

@ -7,14 +7,14 @@
using namespace world::server;
Chunk::Chunk(owner<world::ChunkEdits*> edits): world::EdittableChunk(edits) { }
Chunk::Chunk(owner<world::ChunkEdits*> edits): world::EdittableChunk(edits, Voxel(0)) { }
Chunk::Chunk(owner<world::ChunkEdits*> edits, const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd): world::EdittableChunk(edits) {
rnd->generate(pos, voxels);
rnd->generate5(glm::multiply(pos), voxels);
}
Chunk::Chunk(owner<world::ChunkEdits*> edits, std::istream& str, bool rle): world::EdittableChunk(edits, str, rle) { }
Chunk::Chunk(owner<world::ChunkEdits*> edits, std::istream& str): world::EdittableChunk(edits, str) { }
Chunk::~Chunk() { }
world::Voxel Chunk::write(std::ostream& str, bool rle) const {
world::Voxel Chunk::write(std::ostream& str) const {
Voxel::material_t majMat = UINT16_MAX;
uint16_t majCounter = 1;
size_t visibleDensity = 0;
@ -30,7 +30,7 @@ world::Voxel Chunk::write(std::ostream& str, bool rle) const {
}
visibleDensity += current.density() * current.is_visible();
};
if (rle) {
if constexpr(RLE) {
auto it = voxels.begin();
uint16_t counter = 1;
Voxel current = *it;
@ -63,14 +63,21 @@ world::Voxel Chunk::write(std::ostream& str, bool rle) const {
}
void Chunk::set(uint16_t idx, const world::Voxel& val) {
modified = modified || (voxels[idx].value != val.value);
const auto same = get(idx).value == val.value;
if (!isHeavy()) {
if (same)
return;
flatten();
}
modified = modified || !same;
voxels[idx] = val;
}
void Chunk::setAt(const chunk_voxel_pos& pos, const world::Voxel& val) {
set(glm::toIdx(pos), val);
}
std::optional<world::Item> Chunk::replace(chunk_voxel_idx idx, const world::Voxel& val, float) {
const auto res = voxels[idx];
const auto res = get(idx);
set(idx, val);
return {world::Item{res.density(), res.material()}};
}

View File

@ -13,7 +13,7 @@ namespace world::server {
public:
Chunk(owner<world::ChunkEdits*>);
Chunk(owner<world::ChunkEdits*>, const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd);
Chunk(owner<world::ChunkEdits*>, std::istream& str, bool rle = RLE);
Chunk(owner<world::ChunkEdits*>, std::istream& str);
virtual ~Chunk();
/// Set voxel from index
@ -28,7 +28,7 @@ namespace world::server {
inline bool isModified() const { return modified; }
/// Write to file and return average (majorant material)
/// Voxel swap bit indicate perfect majority
Voxel write(std::ostream& str, bool rle = RLE) const;
Voxel write(std::ostream& str) const;
private:
/// Modified by player
@ -36,8 +36,11 @@ namespace world::server {
};
struct ChunkFactory {
/// Dynamic chunk using rnd
virtual std::shared_ptr<Chunk> create(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd) const = 0;
/// Dynamic chunk from stream
virtual std::shared_ptr<Chunk> create(std::istream &str) const = 0;
/// Light void chunk
virtual std::shared_ptr<Chunk> create() const = 0;
};

View File

@ -9,7 +9,7 @@
using namespace world::server;
#define REMOVE_CORRUPTED 1
#define LAZYNESS 8
Region::save_throttler::save_throttler(): nextSaveExponent(2) { }
Region::Region(const std::string &folderPath, const area_region_ref &id) {
path = folderPath + '/' + std::to_string(id.area.val) + '.' + std::to_string(id.region.x) + '.' +
@ -19,7 +19,7 @@ Region::Region(const std::string &folderPath, const area_region_ref &id) {
}
Region::~Region() {
if(!content.empty())
save(changed);
save(saveThrottler.isUnsaved());
}
size_t Region::Read(const std::string &path, const std::function<void(const region_chunk_pos &, const std::optional<Voxel> &, std::vector<char> &&)>& out) {
@ -197,13 +197,13 @@ void Region::write(const region_chunk_pos& pos, const zstd::write_ctx& ctx, cons
} else {
content.emplace(pos, node(avg, std::move(buffer)));
}
changed = true;
saveThrottler.change();
}
save(false);
}
void Region::save(bool force) {
if(!force && rand() % LAZYNESS == 0)
if(!(force || saveThrottler.mustSave()))
return;
std::unique_lock lock(mutex);
@ -260,5 +260,5 @@ void Region::save(bool force) {
return;
}
file.close();
changed = false;
saveThrottler.save();
}

View File

@ -54,7 +54,23 @@ namespace world::server {
std::vector<char> data;
};
robin_hood::unordered_map<region_chunk_pos, node> content;
bool changed = false;
struct save_throttler {
save_throttler();
/// Counter of currently unsaved
size_t changes = 0;
/// Save when changes > (1 << it)
/// MAYBE: decrease with time
size_t nextSaveExponent;
inline void change() { changes++; }
inline constexpr bool isUnsaved() const { return changes > 0; }
inline constexpr bool mustSave() const { return changes > (1ull << nextSaveExponent); }
inline void save() {
changes = 0;
nextSaveExponent++;
}
} saveThrottler;
void load();
};

View File

@ -9,13 +9,13 @@ namespace world::server {
public:
SharedChunk(): Chunk(new world::ChunkEdits(this)) { }
SharedChunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd): Chunk(new world::ChunkEdits(this), pos, rnd) { }
SharedChunk(std::istream &str, bool rle = RLE): Chunk(new world::ChunkEdits(this), str, rle) { }
SharedChunk(std::istream &str): Chunk(new world::ChunkEdits(this), str) { }
/// Break voxel
std::optional<Item> replace(chunk_voxel_idx idx, const Voxel &val, float delay = 0) override {
const auto res = voxels[idx];
const auto res = get(idx);
set(idx, val);
if (res.value != voxels[idx].value) {
if (res.value != get(idx).value) {
setEdits()->add(world::ChunkEdits::Edit{res, delay, idx});
}
return {Item{res.density(), res.material()}};

View File

@ -370,14 +370,13 @@ void Universe::upgrade() {
{ // Store loaded chunks
ZoneScopedN("Load");
loadedQueue.extractor([&](const std::pair<node_chunk_pos, std::shared_ptr<Chunk>> &loaded) {
loadedQueue.extractor([&](std::pair<node_chunk_pos, std::shared_ptr<Chunk>> &loaded) {
if (Elements::Is<Elements::Type::Area>(loaded.first.node)) {
if (const auto area = elements.findArea(loaded.first.node.val))
area->get()->setChunks().emplace(loaded.first.chunk, loaded.second);
area->get()->setChunks().emplace(loaded.first.chunk, std::move(loaded.second));
} else {
if (const auto part = elements.findPart(loaded.first.node.val)) {
part->get()->chunks[part->get()->getIdx(loaded.first.chunk)] = loaded.second;
}
if (const auto part = elements.findPart(loaded.first.node.val))
part->get()->emplace(loaded.first.chunk, std::move(loaded.second));
}
});
}
@ -404,12 +403,12 @@ void Universe::loadIndex() {
elements->createInstance(Elements::start_point(p.val, transform(glm::vec3(1000, 0, 0))), generational::id(0));
//TODO: generate universe
{
const auto radius = 1 << 4;
generator::RoundPlanet::Params opts(radius * CHUNK_LENGTH * 3 / 4);
const auto radius = 1 << 6;
const auto surface = radius * CHUNK_LENGTH * 3 / 4;
generator::RoundPlanet::Params opts(surface);
Area::params params{radius, 0, std::vector<char>(sizeof(opts))};
memcpy(params.params.data(), &opts, params.params.size());
const auto a = elements->createArea(Elements::start_point(generational::id(), transform(glm::multiply(voxel_pos(radius, 0, 0)), glm::angleAxis(.5f, glm::vec3(0, 1, 0))), velocity(offset_pos(), glm::angleAxis(.1f, glm::vec3(0, 1, 0)))), params);
elements->createInstance(Elements::start_point(a.val, transform(glm::vec3(-radius * CHUNK_LENGTH + 100, 0, 0))), generational::id(0));
elements->createArea(Elements::start_point(generational::id(), transform(world_pos(-surface, 0, 0)), velocity(offset_pos(), glm::angleAxis(.01f, glm::vec3(0, 1, 0)))), params);
}
}
index.close();