1
0
Fork 0

Big bad shared server rework

This commit is contained in:
May B. 2020-09-20 18:41:54 +02:00
parent 0dcff4b885
commit ddbcd04721
73 changed files with 1465 additions and 893 deletions

8
.vscode/tasks.json vendored
View File

@ -108,6 +108,14 @@
"group": "build",
"dependsOrder": "sequence",
"dependsOn": ["Build debug", "exec memcheck"]
},
{
"label": "Dependencies",
"type": "shell",
"command": ["~/Téléchargements/cinclude2dot.pl", "--paths", "--include ../include/zstd/dictBuilder,../include/zstd,/usr/include/c++/10.2.0,../include/robin_hood,../include/imgui,../include/meshoptimizer,../include,../include/tracy,../include/gl3w,../include/FastNoiseSIMD", ">", "../build/dep.dot"],
"options": {
"cwd": "${workspaceRoot}/src",
}
}
]
}

View File

@ -1,5 +1,7 @@
#version 330 core
#define UNIT_SIZE 8
// Ouput data
layout(location = 0) out vec3 color;
@ -112,7 +114,7 @@ vec3 getTriTexture(sampler2DArray sample, vec2 crdx, vec2 crdy, vec2 crdz, vec3
}
void main() {
float texScale = .5;
float texScale = 1. / UNIT_SIZE;
#ifdef TRIPLANAR
// Triplanar
float plateauSize = 0.001;
@ -144,7 +146,7 @@ void main() {
#else
// Cheap planar
vec3 blendWeights = abs(vs.FaceNormal_modelspace);
vec3 nrm = normalize(pow(blendWeights, vec3(80)));
vec3 nrm = normalize(pow(blendWeights, vec3(80 / sqrt(UNIT_SIZE))));
vec2 UV = (vec2(vs.Position_modelspace.xy * nrm.z) + vec2(vs.Position_modelspace.yz * nrm.x) + vec2(vs.Position_modelspace.zx * nrm.y)) * texScale;
vec3 tex = getTexture(TextureAtlas, UV).rgb;

View File

@ -4,15 +4,19 @@
#include "render/gl/UI.hpp"
#include "render/gl/Pipeline.hpp"
#include "InputMap.hpp"
#include "world/index.hpp"
#include <glm/gtc/matrix_transform.hpp>
#include "../core/data/math.hpp"
#include <Tracy.hpp>
Client::Client() { }
Client::Client(config::client::options& options): options(options) { }
Client::~Client() { }
void Client::run() {
void Client::run(server_handle* const localHandle) {
if (!render::gl::Renderer::Load(window, {options.renderer.clear_color}))
return;
window.setTargetFPS(options.target_fps);
window.setTargetFPS(options.window.targetFPS);
InputMap inputs(window.getPtr());
@ -23,10 +27,9 @@ void Client::run() {
pipeline->LightInvDir = glm::normalize(glm::vec3(-.5f, 2, -2));
render::Renderer::Get()->loadUI(window);
//TODO: extract to server
world::Universe world = world::Universe(options.world);
world.setContouring(contouring::load(options.contouring_idx, options.contouring_data));
state.contouring = world.getContouring();
auto world = world::client::Load(options.useLocal, localHandle, options.world);
world->setContouring(contouring::load(options.contouring.idx, options.contouring.data));
state.contouring = world->getContouring();
//TODO: loop
do {
@ -37,12 +40,12 @@ void Client::run() {
const double partTime = window.getTime();
const float deltaTime = partTime - lastTime;
inputs.toggle(state.capture_mouse, Input::Mouse);
inputs.toggle(options.show_debug_menu, Input::Debug);
inputs.toggle(options.debugMenu.bar, Input::Debug);
player.capture(state.capture_mouse, !render::UI::IsFocus(), deltaTime);
if(player.velocity != glm::vec3(0) && (
!options.control.collide ||
!world.collide(player.position, player.velocity, options.voxel_density, 1))
!world->collide(player.position, player.velocity, options.voxel_density, options.voxel_density))
) {
player.position += player.velocity;
state.position = player.position;
@ -52,7 +55,7 @@ void Client::run() {
pipeline->LightInvDir = glm::vec3(glm::rotate(glm::mat4(1), deltaTime * .1f, glm::vec3(1, .5, .1)) * glm::vec4(pipeline->LightInvDir, 0));
{
const auto ray_result = world.raycast(camera.getRay() * options.voxel_density);
const auto ray_result = world->raycast(camera.getRay() * options.voxel_density);
if(auto target = std::get_if<world::Universe::ray_target>(&ray_result)) {
state.look_at = *target;
} else {
@ -62,16 +65,19 @@ void Client::run() {
if (state.capture_mouse) {
if (state.look_at.has_value()) {
ZoneScopedN("Edit");
const auto &tool = options.editor.tool;
if (inputs.isPressing(Mouse::Left))
world.setCube(state.look_at.value().pos, world::Voxel(world::materials::AIR, options.tool.empty_air * world::Voxel::DENSITY_MAX), options.tool.radius);
world->emit(world::action::FillCube(
state.look_at.value().pos, world::Voxel(world::materials::AIR, tool.emptyAir * world::Voxel::DENSITY_MAX), tool.radius));
else if (inputs.isPressing(Mouse::Right))
world.setCube(state.look_at.value().pos, world::Voxel(options.tool.material, world::Voxel::DENSITY_MAX), options.tool.radius);
world->emit(world::action::FillCube(
state.look_at.value().pos, world::Voxel(tool.material, world::Voxel::DENSITY_MAX), tool.radius));
}
if (inputs.isDown(Input::Throw)) {
world.addEntity(entity_id(0), {state.position * options.voxel_density, glm::vec3(10, 0, 0)});
//FIXME: register entity type world->addEntity(entity_id(0), {state.position * options.voxel_density, glm::vec3(10, 0, 0)});
}
}
world.update((state.position * options.voxel_density).as_voxel(), deltaTime);
world->update((state.position * options.voxel_density).as_voxel(), deltaTime);
inputs.saveKeys();
lastTime = partTime;
}
@ -80,10 +86,10 @@ void Client::run() {
ZoneScopedN("UI");
const auto actions = render::UI::Get()->draw(options, state, reports);
if (actions && render::UI::Actions::FPS) {
window.setTargetFPS(options.target_fps);
window.setTargetFPS(options.window.targetFPS);
}
if (actions && render::UI::Actions::FullScreen) {
window.setFullscreen(options.fullscreen);
window.setFullscreen(options.window.fullscreen);
}
if(actions && render::UI::Actions::ClearColor) {
pipeline->setClearColor(options.renderer.clear_color);
@ -95,7 +101,7 @@ void Client::run() {
pipeline->reloadTextures(options.renderer.textures, options.renderer.mipMapLOD, options.renderer.anisotropy);
}
if(actions && render::UI::Actions::World) {
world.setOptions(options.world);
//FIXME: server options world->setOptions(options.world);
}
if(actions && render::UI::Actions::Camera) {
camera.setOptions(options.camera);
@ -105,8 +111,8 @@ void Client::run() {
}
if(actions && render::UI::Actions::ChangeContouring) {
state.contouring = NULL;
world.setContouring(contouring::load(options.contouring_idx, options.contouring_data));
state.contouring = world.getContouring();
world->setContouring(contouring::load(options.contouring.idx, options.contouring.data));
state.contouring = world->getContouring();
}
pipeline->SkyEnable = options.renderer.skybox;
}
@ -142,9 +148,9 @@ void Client::run() {
occlusion.emplace_back(cos(v) * sin(h), sin(v), cos(v) * cos(h));
}
}
world.getContouring()->getModels(draw, player.position, options.camera.far, occlusion, offset, options.voxel_density);
state.contouring->getModels(draw, player.position, options.camera.far, occlusion, offset, options.voxel_density);
} else {
world.getContouring()->getModels(draw, frustum, offset, options.voxel_density);
state.contouring->getModels(draw, frustum, offset, options.voxel_density);
}
}
{ // Entities
@ -153,10 +159,10 @@ void Client::run() {
reports.models_count += models.size();
reports.tris_count += buffer->draw(pass(models));
};
world.getEntitiesModels(draw, frustum, offset, options.voxel_density);
//world->getEntitiesModels(draw, frustum, offset, options.voxel_density);
}
if(state.look_at.has_value()) { // Indicator
const auto model = glm::scale(glm::translate(glm::scale(glm::mat4(1), 1.f / glm::vec3(options.voxel_density)), glm::vec3(state.look_at.value().pos.second + state.look_at.value().offset - offset * glm::llvec3(options.voxel_density)) - glm::vec3(.5 + options.tool.radius)), glm::vec3(1 + options.tool.radius * 2));
const auto model = glm::scale(glm::translate(glm::scale(glm::mat4(1), 1.f / glm::vec3(options.voxel_density)), glm::vec3(state.look_at.value().pos.second + state.look_at.value().offset - offset * glm::llvec3(options.voxel_density)) - glm::vec3(.5 + options.editor.tool.radius)), glm::vec3(1 + options.editor.tool.radius * 2));
reports.models_count++;
reports.tris_count += pipeline->drawIndicatorCube(model);
}
@ -176,9 +182,9 @@ void Client::run() {
} while (!(inputs.isDown(Input::Quit) || window.shouldClose()));
render::UI::Unload();
delete pipeline;
render::Renderer::Unload();
window.destroy();
contouring::save(options.contouring_idx, state.contouring, options.contouring_data);
options.save();
contouring::save(options.contouring.idx, state.contouring, options.contouring.data);
}

View File

@ -1,7 +1,8 @@
#pragma once
#include "state.h"
#include "state.hpp"
#include "Window.hpp"
#include "../core/server_handle.hpp"
namespace render {
class Renderer;
@ -10,17 +11,17 @@ namespace render {
/// Client view
class Client {
public:
Client();
Client(config::client::options &options);
~Client();
void run();
void run(server_handle *const);
protected:
private:
config::options options;
config::state state;
config::reports reports;
config::client::options &options;
state::state state;
state::reports reports;
Window window;
};

View File

@ -93,10 +93,13 @@ double Window::getTime() const {
void Window::startFrame() {
frameStart = getTime();
}
void Window::wait(int microseconds) {
std::this_thread::sleep_for(std::chrono::microseconds(microseconds));
}
void Window::waitTargetFPS() {
if (targetFPS >= MIN_FPS && targetFPS <= MAX_FPS) {
while (glfwGetTime() < frameStart + 1.0 / targetFPS) {
std::this_thread::sleep_for(std::chrono::microseconds(100));
wait();
}
}
}

View File

@ -45,6 +45,8 @@ public:
double getTime() const;
static void wait(int microseconds = 100);
private:
GLFWwindow *ptr;
int targetFPS;

View File

@ -1,19 +1,15 @@
#pragma once
#define UI_MARGIN 5
#include <toml.h>
#include <fstream>
#include <filesystem>
#include <optional>
#include <glm/vec4.hpp>
#include "render/Renderer.hpp"
#include <limits.h>
#include <iomanip>
#include "world/Universe.hpp"
#include "render/Pipeline.hpp"
#include "../server/world/Universe.hpp"
#include "contouring/index.hpp"
#include "control/Camera.hpp"
#include "render/contouring/index.hpp"
namespace config {
namespace config::client {
inline glm::vec4 fromHex(const std::string& str) {
std::array<int, 3> rgb = {UCHAR_MAX};
@ -27,16 +23,16 @@ inline std::string toHexa(const glm::vec4& rgb) {
return sstr.str();
}
/// Savable game options
struct options {
const char *PATH = "config.toml";
/// Load from PATH
options() {
auto config = std::filesystem::exists(PATH) ? toml::parse_file(PATH) : toml::table();
target_fps = config["window"]["fps"].value_or(60);
samples = config["window"]["samples"].value_or(-1);
fullscreen = config["window"]["fullscreen"].value_or(false);
public:
options(toml::node_view<toml::node> config) {
assert(config["enabled"]);
window.targetFPS = config["window"]["target_fps"].value_or(window.targetFPS);
window.samples = config["window"]["samples"].value_or(window.samples);
window.fullscreen = config["window"]["fullscreen"].value_or(window.fullscreen);
preferVulkan = config["render"]["prefer_vulkan"].value_or(preferVulkan);
renderer.textures = config["render"]["textures"].value_or(renderer.textures);
renderer.mipMapLOD = config["render"]["texture_quality"].value_or(renderer.mipMapLOD);
renderer.anisotropy = config["render"]["texture_angular_quality"].value_or(renderer.anisotropy);
@ -51,16 +47,11 @@ struct options {
renderer.skybox = config["render"]["skybox"].value_or(renderer.skybox);
renderer.voxel.curvature = config["render"]["curvature"].value_or(renderer.voxel.curvature);
renderer.voxel.curv_depth = config["render"]["curvature_depth"].value_or(renderer.voxel.curv_depth);
culling = config["render"]["culling"].value_or(culling);
world.loadDistance = config["world"]["load_distance"].value_or(world.loadDistance);
world.keepDistance = config["world"]["keep_distance"].value_or(world.keepDistance);
world.folderPath = config["world"]["path"].value_or(world.folderPath);
voxel_density = config["world"]["voxel_density"].value_or(1);
culling = config["mesh"]["culling"].value_or(culling);
contouring_idx = contouring::idxByName(config["mesh"]["mode"].value_or(std::string("")));
contouring.idx = contouring::idxByName(config["contouring"]["mode"].value_or(std::string("")));
for(const auto& name: contouring::names) {
contouring_data.emplace(name, config["mesh"]["options"][name].value_or(std::string("")));
contouring.data.emplace(name, config["contouring"]["options"][name].value_or(std::string("")));
}
camera.far = config["camera"]["far"].value_or(camera.far);
@ -70,29 +61,36 @@ struct options {
control.speed = config["control"]["speed"].value_or(control.speed);
control.collide = config["control"]["collide"].value_or(control.collide);
overlay_show = config["overlay"]["visible"].value_or(true);
overlay_corner = config["overlay"]["corner"].value_or(3);
//TODO: console
useLocal = config["world"]["use_local"].value_or(useLocal);
world.loadDistance = config["world"]["load_distance"].value_or(world.loadDistance);
world.keepDistance = config["world"]["keep_distance"].value_or(world.keepDistance);
voxel_density = config["world"]["voxel_density"].value_or(voxel_density);
show_debug_menu = config["show_debug"]["menu"].value_or(false);
show_debug_render = config["show_debug"]["render"].value_or(false);
show_debug_world = config["show_debug"]["world"].value_or(false);
show_debug_contouring = config["show_debug"]["contouring"].value_or(false);
show_debug_controls = config["show_debug"]["controls"].value_or(false);
editor.visible = config["editor"]["visible"].value_or(editor.visible);
editor.tool.radius = config["editor"]["tool"]["radius"].value_or(editor.tool.radius);
editor.tool.material = config["editor"]["tool"]["material"].value_or(editor.tool.material);
editor.tool.emptyAir = config["editor"]["tool"]["empty_air"].value_or(editor.tool.emptyAir);
editor_show = config["editor"]["visible"].value_or(false);
tool.material = config["editor"]["tool"]["material"].value_or<int>(tool.material);
tool.radius = config["editor"]["tool"]["radius"].value_or(tool.radius);
debugMenu.bar = config["debug_menu"]["bar"].value_or(debugMenu.bar);
debugMenu.render = config["debug_menu"]["render"].value_or(debugMenu.render);
debugMenu.world = config["debug_menu"]["world"].value_or(debugMenu.world);
debugMenu.contouring = config["debug_menu"]["contouring"].value_or(debugMenu.contouring);
debugMenu.controls = config["debug_menu"]["controls"].value_or(debugMenu.controls);
overlay.visible = config["overlay"]["visible"].value_or(overlay.visible);
overlay.corner = config["overlay"]["corner"].value_or(overlay.corner);
}
/// Write to PATH
void save() {
toml::table save() {
auto config = toml::table();
config.insert_or_assign("enabled", true);
config.insert_or_assign("window", toml::table({
{"fps", target_fps},
{"samples", samples},
{"fullscreen", fullscreen},
{"target_fps", window.targetFPS},
{"samples", window.samples},
{"fullscreen", window.fullscreen}
}));
config.insert_or_assign("render", toml::table({
{"prefer_vulkan", preferVulkan},
{"textures", renderer.textures},
{"texture_quality", renderer.mipMapLOD},
{"texture_angular_quality", renderer.anisotropy},
@ -105,24 +103,17 @@ struct options {
{"fog_color", toHexa(renderer.clear_color)},
{"skybox", renderer.skybox},
{"curvature", renderer.voxel.curvature},
{"curvature_depth", renderer.voxel.curv_depth}
{"curvature_depth", renderer.voxel.curv_depth},
{"culling", culling}
}));
config.insert_or_assign("world", toml::table({
{"load_distance", world.loadDistance},
{"keep_distance", world.keepDistance},
{"voxel_density", voxel_density},
{"path", world.folderPath}
}));
config.insert_or_assign("mesh", toml::table({
{"culling", culling},
{"mode", contouring::names[contouring_idx]},
config.insert_or_assign("contouring", toml::table({
{"mode", contouring::names[contouring.idx]},
{"options", toml::table()}
}));
for(const auto& [key, val]: contouring_data) {
for(const auto& [key, val]: contouring.data) {
if(!val.empty())
config["mesh"]["options"].as_table()->insert_or_assign(key, val);
config["contouring"]["options"].as_table()->insert_or_assign(key, val);
}
config.insert_or_assign("camera", toml::table({
{"far", camera.far},
{"near", camera.near},
@ -133,81 +124,76 @@ struct options {
{"speed", control.speed},
{"collide", control.collide}
}));
config.insert_or_assign("overlay", toml::table({
{"visible", overlay_show},
{"corner", overlay_corner}
config.insert_or_assign("world", toml::table({
{"use_local", useLocal},
{"load_distance", world.loadDistance},
{"keep_distance", world.keepDistance},
{"voxel_density", voxel_density}
}));
config.insert_or_assign("show_debug", toml::table({
{"menu", show_debug_menu},
{"render", show_debug_render},
{"world", show_debug_world},
{"contouring", show_debug_contouring},
{"controls", show_debug_controls}
}));
config.insert_or_assign("editor", toml::table({
{"visible", editor_show},
{"visible", editor.visible},
{"tool", toml::table({
{"material", (int)tool.material},
{"radius", tool.radius}
{"radius", editor.tool.radius},
{"material", editor.tool.material},
{"empty_air", editor.tool.emptyAir}
})}
}));
std::ofstream out;
out.open(PATH, std::ios::out | std::ios::trunc);
out << config << "\n\n";
out.close();
config.insert_or_assign("debug_menu", toml::table({
{"bar", debugMenu.bar},
{"render", debugMenu.render},
{"world", debugMenu.world},
{"contouring", debugMenu.contouring},
{"controls", debugMenu.controls}
}));
config.insert_or_assign("overlay", toml::table({
{"visible", overlay.visible},
{"corner", overlay.corner}
}));
return config;
}
bool show_debug_menu;
bool show_debug_render;
int target_fps;
int samples;
bool fullscreen;
bool useLocal = true;
world::client::Universe::options world;
int voxel_density = 1;
struct {
bool bar = false;
bool render = false;
bool world = false;
bool contouring = false;
bool controls = false;
} debugMenu;
struct {
int targetFPS = 60;
int samples = -1;
bool fullscreen = false;
} window;
bool preferVulkan = true;
render::Pipeline::options renderer;
bool show_debug_world;
world::Universe::options world;
int voxel_density;
bool show_debug_contouring;
int culling = 0;
int contouring_idx;
std::map<std::string, std::string> contouring_data;
bool show_debug_controls;
struct {
int idx;
std::map<std::string, std::string> data;
} contouring;
Controllable::options control;
Camera::options camera;
bool editor_show;
struct tool {
int radius = 2;
unsigned short material = 5;
bool empty_air = true;
} tool;
struct {
bool visible = false;
struct {
int radius = 2;
unsigned short material = 5;
bool emptyAir = true;
} tool;
} editor;
bool overlay_show;
int overlay_corner;
bool console_show = false;
bool console_scrool = true;
struct {
bool visible = true;
int corner = 3;
} overlay;
};
/// Live state
struct state {
bool capture_mouse = true;
camera_pos position = camera_pos(voxel_pos(0), 1);
std::optional<world::Universe::ray_target> look_at = {};
std::shared_ptr<contouring::Abstract> contouring;
std::array<char, 256> console_buffer;
};
/// Readonly metrics
struct reports {
size_t tris_count = 0;
size_t models_count = 0;
};
}

View File

@ -1,12 +1,12 @@
#pragma once
#include "../gl/buffer/Abstract.hpp"
#include "../../../server/world/forward.h"
#include "../../../core/geometry/Frustum.hpp"
#include "../../../core/geometry/Ray.hpp"
#include "../../../core/geometry/Faces.hpp"
#include "../render/gl/buffer/Abstract.hpp"
#include "../../core/world/forward.h"
#include "../../core/geometry/Frustum.hpp"
#include "../../core/geometry/Ray.hpp"
#include "../../core/geometry/Faces.hpp"
/// Mesh creation
/// Mesh creation (from world to render)
namespace contouring {
/// Generating mesh from world data
class Abstract {
@ -16,7 +16,7 @@ namespace contouring {
/// Each frame ping.
/// Mostly used for cleanup and to flush buffers data using main thread
virtual void update(const voxel_pos &pos, const world::area_map &areas) = 0;
virtual void update(const voxel_pos &pos, const world::client::area_map &areas) = 0;
/// Chunk data change
/// @param offset priority position offset

View File

@ -9,7 +9,7 @@ namespace contouring {
Dummy(): Abstract() { }
virtual ~Dummy() { }
void update(const voxel_pos &, const world::area_map &) override {}
void update(const voxel_pos &, const world::client::area_map &) override {}
void onUpdate(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &, geometry::Faces) override {}
void onNotify(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &) override {}
void onGui() override { }

View File

@ -1,8 +1,8 @@
#include "FlatDualMC.hpp"
#include "../../../server/world/Chunk.hpp"
#include "../../../server/world/Area.hpp"
#include "../../../core/world/materials.hpp"
#include "../../core/world/EdittableChunk.hpp"
#include "../../core/world/Area.hpp"
#include "../../core/world/materials.hpp"
#include <Tracy.hpp> // NOLINT
#include <common/TracySystem.hpp> // NOLINT
#include <imgui.h> // NOLINT
@ -13,8 +13,10 @@
namespace contouring {
const std::vector<std::pair<float, float>> LEVELS = {{.001f, 9e-1f}, {.01f, 5e-1f}, {.1f, 1e-1f}, {.2, 1e-2f}, {.5, 5e-3f}};
FlatDualMC::FlatDualMC(const std::string &str) : AbstractFlat(str) {
FlatDualMC::FlatDualMC(const std::string &str) : Abstract() {
auto opt = toml::parse(str);
loadDistance = opt["load_distance"].value_or(loadDistance);
keepDistance = opt["keep_distance"].value_or(keepDistance);
transparency = opt["transparency"].value_or(transparency);
iso = opt["iso"].value_or(iso);
manifold = opt["manifold"].value_or(manifold);
@ -81,6 +83,9 @@ namespace contouring {
ss << tb;
return ss.str();
}
std::pair<float, float> FlatDualMC::getFarRange() const {
return std::make_pair((loadDistance - 1.5) * CHUNK_LENGTH, (keepDistance + .5) * CHUNK_LENGTH);
}
size_t FlatDualMC::getQueueSize() {
return loadQueue.size();
@ -114,7 +119,7 @@ namespace contouring {
}
}
void FlatDualMC::update(const voxel_pos& pos, const world::area_map& areas) {
void FlatDualMC::update(const voxel_pos& pos, const world::client::area_map& areas) {
ZoneScopedN("Ct");
std::pair<area_<chunk_pos>, buffer::LodShortIndexed::LodData> out;
TracyPlot("CtLoad", static_cast<int64_t>(loadQueue.size()));
@ -143,13 +148,9 @@ namespace contouring {
std::get<0>(it_a->second.first) = area->second->getOffset();
std::get<1>(it_a->second.first) = area->second->getChunks().getRadius() * static_cast<long long>(CHUNK_LENGTH);
const auto centerV = pos - std::get<0>(it_a->second.first).as_voxel();
{
const auto params = area->second->getParams().properties;
if (auto planet = std::get_if<world::generator::CubicPlanet::Params>(&params)) {
const auto dist = glm::max_axis(glm::abs(centerV));
const auto surface = planet->height * (1.1+planet->surface_roughness);
std::get<2>(it_a->second.first) = std::clamp<float>((dist - surface) / (std::get<1>(it_a->second.first) - surface), -0.1f, 1.f);
}
if (const auto surface = area->second->getCurvature()) {
const auto dist = glm::max_axis(glm::abs(centerV));
std::get<2>(it_a->second.first) = std::clamp<float>((dist - surface.value()) / (std::get<1>(it_a->second.first) - surface.value()), -0.1f, 1.f);
}
const auto center = glm::divide(centerV);
auto &bfs = it_a->second.second;
@ -182,8 +183,8 @@ namespace contouring {
}
void FlatDualMC::onGui() {
AbstractFlat::onGui();
ImGui::Separator();
ImGui::SliderInt("Load Distance", &loadDistance, 1, keepDistance);
ImGui::SliderInt("Keep Distance", &keepDistance, loadDistance + 1, 21);
ImGui::Checkbox("Transparency", &transparency);
ImGui::SliderFloat("Iso", &iso, 0, 1);
ImGui::Checkbox("Manifold", &manifold);
@ -286,4 +287,39 @@ namespace contouring {
}
}
void FlatDualMC::getModels(draw_call out, const std::optional<geometry::Frustum> &frustum, const glm::llvec3& offset, int density) {
const auto scaling = glm::scale(glm::mat4(1), glm::vec3(1.f / density));
for (const auto [_, area] : buffers) {
for (const auto [pos, buffer] : area.second) {
const auto vPos = glm::multiply(pos);
const glm::vec3 fPos = (glm::vec3(std::get<0>(area.first).raw_as_long() + vPos - offset * glm::llvec3(density)) + std::get<0>(area.first).offset) / glm::vec3(density);
if (buffer != NULL && (!frustum.has_value() || frustum.value().contains(geometry::Box::fromMin(fPos, glm::vec3(CHUNK_LENGTH / (float)density)))))
out(glm::translate(scaling, fPos * (float)density), buffer, area.first, vPos);
}}
}
void FlatDualMC::getModels(draw_call out, const glm::ifvec3& from, float far, const std::vector<glm::vec3> &occlusion, const glm::llvec3 &offset, int density) {
const auto scaling = glm::scale(glm::mat4(1), glm::vec3(1.f / density));
const auto start = glm::ifvec3(glm::divide(from.as_voxel(density)));
const auto dist = far * density / CHUNK_LENGTH;
for (const auto [_, area] : buffers) {
const auto area_offset = glm::divide(std::get<0>(area.first).as_voxel());
robin_hood::unordered_set<chunk_pos> done;
for (const auto& occ: occlusion) {
const geometry::Ray ray(start, occ, dist);
std::vector<voxel_pos> points; //TODO: iterator
ray.grid(points);
for(auto& point: points) {
auto it = area.second.find(glm::lvec3(point) - area_offset);
if(it != area.second.end() && it->second != NULL && done.insert(it->first).second) {
const auto vPos = glm::multiply(it->first);
const glm::vec3 fPos = glm::vec3(std::get<0>(area.first).raw_as_long() + vPos - offset * glm::llvec3(density)) + std::get<0>(area.first).offset;
out(glm::translate(scaling, fPos), it->second, area.first, vPos);
break;
}
}
}
}
}
}

View File

@ -1,27 +1,28 @@
#pragma once
#include "AbstractFlat.hpp"
#include "Abstract.hpp"
#include "surrounding.hpp"
#include "../../../core/data/safe_queue.hpp"
#include "../../../core/data/safe_priority_queue.hpp"
#include "../../../core/data/circular_buffer.hpp"
#include "../gl/buffer/LodShortIndexed.hpp"
#include "../../core/data/safe_queue.hpp"
#include "../../core/data/safe_priority_queue.hpp"
#include "../render/gl/buffer/LodShortIndexed.hpp"
#include "../../core/data/math.hpp"
#include <thread>
using namespace data;
namespace contouring {
/// Dual Marching Cube 1:1 contouring
class FlatDualMC: public AbstractFlat {
class FlatDualMC: public Abstract {
public:
FlatDualMC(const std::string&);
virtual ~FlatDualMC();
void update(const voxel_pos&, const world::area_map&) override;
void update(const voxel_pos&, const world::client::area_map&) override;
void onGui() override;
std::string getOptions() const override;
std::pair<float, float> getFarRange() const override;
size_t getQueueSize() override;
/// Chunk data change
@ -30,7 +31,16 @@ namespace contouring {
/// @note notify for chunks entering view while moving
void onNotify(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &) override;
/// Get buffers in frustum with model matrices
/// @note buffers invalidated after update
void getModels(draw_call draw, const std::optional<geometry::Frustum> &frustum, const glm::llvec3 &offset, int density) override;
/// Get buffers hitting occlusion rays with model matrices
/// @note buffers invalidated after update
void getModels(draw_call draw, const glm::ifvec3 &from, float far, const std::vector<glm::vec3> &occlusion, const glm::llvec3 &offset, int density) override;
protected:
robin_hood::unordered_map<area_id, robin_hood::pair<area_info, robin_hood::unordered_map<chunk_pos, buffer::Abstract *>>> buffers;
safe_priority_queue_map<area_<chunk_pos>, surrounding::corners, int, area_hash> loadQueue;
safe_queue<std::pair<area_<chunk_pos>, buffer::LodShortIndexed::LodData>> loadedQueue;
@ -39,6 +49,8 @@ namespace contouring {
void enqueue(const area_<chunk_pos> &, const chunk_pos &offset, const world::ChunkContainer &);
int loadDistance = 3;
int keepDistance = 4;
bool transparency = false;
float iso = .1f;
bool manifold = true;

View File

@ -0,0 +1,30 @@
#include "surrounding.hpp"
#include "../../core/world/Area.hpp"
#include "../world/Chunk.hpp"
#include "../../core/data/math.hpp"
using namespace geometry;
namespace contouring::surrounding {
bool load(corners &out, const chunk_pos &chunkPos, const world::ChunkContainer &chunks) {
{
const auto chunk = chunks.findInRange(chunkPos);
if(!chunk.has_value())
return false;
out[0] = std::dynamic_pointer_cast<world::client::EdittableChunk>(chunk.value());
}
for (size_t i = 1; i < 8; i++) {
const auto pos = chunkPos + g_corner_offsets[i];
if (chunks.inRange(pos)) {
if(const auto chunk = chunks.findInRange(pos)) {
out[i] = std::dynamic_pointer_cast<world::client::EdittableChunk>(chunk.value());
} else
return false;
} else {
out[i] = world::client::EMPTY_CHUNK;
}
}
return true;
}
}

View File

@ -0,0 +1,19 @@
#pragma once
#include "../../core/world/forward.h"
namespace contouring::surrounding {
typedef std::array<std::shared_ptr<const world::client::EdittableChunk>, 8> corners;
const chunk_pos g_corner_offsets[8] = {
chunk_pos(0, 0, 0),
chunk_pos(0, 0, 1),
chunk_pos(0, 1, 0),
chunk_pos(0, 1, 1),
chunk_pos(1, 0, 0),
chunk_pos(1, 0, 1),
chunk_pos(1, 1, 0),
chunk_pos(1, 1, 1),
};
bool load(corners &out, const chunk_pos &chunkPos, const world::ChunkContainer &chunks);
}

View File

@ -1,13 +1,16 @@
#include "UI.hpp"
#include <imgui.h>
#include "../state.h"
#include "../state.hpp"
#include "../Window.hpp"
#include "../../core/world/materials.hpp"
using namespace render;
UI *UI::sInstance = nullptr;
constexpr auto UI_MARGIN = 5;
UI::UI() {
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
@ -18,7 +21,7 @@ UI::~UI() {
ImGui::DestroyContext();
}
UI::Actions UI::draw(config::options &options, config::state &state, const config::reports &reports, intptr_t aim) {
UI::Actions UI::draw(config::client::options &options, state::state &state, const state::reports &reports, intptr_t aim) {
auto actions = Actions::None;
ImGui::NewFrame();
@ -31,33 +34,33 @@ UI::Actions UI::draw(config::options &options, config::state &state, const confi
ImGui::End();
}
if (options.show_debug_menu) {
if (options.debugMenu.bar) {
ImGui::BeginMainMenuBar();
if (ImGui::BeginMenu("Debug")) {
ImGui::Checkbox("Render", &options.show_debug_render);
ImGui::Checkbox("World", &options.show_debug_world);
ImGui::Checkbox("Contouring", &options.show_debug_contouring);
ImGui::Checkbox("Controls", &options.show_debug_controls);
ImGui::Checkbox("Render", &options.debugMenu.render);
ImGui::Checkbox("World", &options.debugMenu.world);
ImGui::Checkbox("Contouring", &options.debugMenu.contouring);
ImGui::Checkbox("Controls", &options.debugMenu.controls);
ImGui::EndMenu();
}
ImGui::Checkbox("Editor", &options.editor_show);
ImGui::Checkbox("Editor", &options.editor.visible);
if(ImGui::MenuItem("Close"))
options.show_debug_menu = false;
options.debugMenu.bar = false;
ImGui::EndMainMenuBar();
}
if (options.show_debug_render) {
ImGui::Begin("Debug: Render", &options.show_debug_render, ImGuiWindowFlags_AlwaysAutoResize);
if (options.debugMenu.render) {
ImGui::Begin("Debug: Render", &options.debugMenu.render, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::Text("Tris: %ld (%ld models)", reports.tris_count, reports.models_count);
ImGui::Separator();
ImGui::Checkbox("Overlay", &options.overlay_show);
if (ImGui::SliderInt("FPS", &options.target_fps, Window::MIN_FPS-1, Window::MAX_FPS+1, options.target_fps > Window::MIN_FPS ? (options.target_fps < Window::MAX_FPS ? "%d" : "UNLIMITED") : "VSYNC")){
ImGui::Checkbox("Overlay", &options.overlay.visible);
if (ImGui::SliderInt("FPS", &options.window.targetFPS, Window::MIN_FPS-1, Window::MAX_FPS+1, options.window.targetFPS > Window::MIN_FPS ? (options.window.targetFPS < Window::MAX_FPS ? "%d" : "UNLIMITED") : "VSYNC")){
actions |= Actions::FPS;
}
ImGui::Text("Sampling %d (requires restart)", options.samples);
if (ImGui::Checkbox("Fullscreen", &options.fullscreen)){
ImGui::Text("Sampling %d (requires restart)", options.window.samples);
if (ImGui::Checkbox("Fullscreen", &options.window.fullscreen)){
actions |= Actions::FullScreen;
}
@ -109,9 +112,8 @@ UI::Actions UI::draw(config::options &options, config::state &state, const confi
ImGui::End();
}
if (options.show_debug_world) {
ImGui::Begin("Debug: World", &options.show_debug_world, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::Text("Path: %s", options.world.folderPath.c_str());
if (options.debugMenu.world) {
ImGui::Begin("Debug: World", &options.debugMenu.world, ImGuiWindowFlags_AlwaysAutoResize);
if (ImGui::SliderInt("Load distance", &options.world.loadDistance, 1, options.world.keepDistance) |
ImGui::SliderInt("Keep distance", &options.world.keepDistance, options.world.loadDistance + 1, 21)) {
actions |= Actions::World;
@ -122,15 +124,15 @@ UI::Actions UI::draw(config::options &options, config::state &state, const confi
ImGui::End();
}
if (options.show_debug_contouring) {
ImGui::Begin("Debug: Contouring", &options.show_debug_contouring, ImGuiWindowFlags_AlwaysAutoResize);
if (ImGui::BeginCombo("Contouring", contouring::names[options.contouring_idx].c_str())) {
if (options.debugMenu.contouring) {
ImGui::Begin("Debug: Contouring", &options.debugMenu.contouring, ImGuiWindowFlags_AlwaysAutoResize);
if (ImGui::BeginCombo("Contouring", contouring::names[options.contouring.idx].c_str())) {
for (size_t i = 0; i < contouring::names.size(); i++) {
const bool is_selected = (options.contouring_idx == (int)i);
const bool is_selected = (options.contouring.idx == (int)i);
if (ImGui::Selectable(contouring::names[i].c_str(), is_selected)) {
actions |= Actions::ChangeContouring;
contouring::save(options.contouring_idx, state.contouring, options.contouring_data);
options.contouring_idx = i;
contouring::save(options.contouring.idx, state.contouring, options.contouring.data);
options.contouring.idx = i;
}
if (is_selected)
ImGui::SetItemDefaultFocus();
@ -139,7 +141,7 @@ UI::Actions UI::draw(config::options &options, config::state &state, const confi
}
if(ImGui::Button("Reload")) {
actions |= Actions::ChangeContouring;
contouring::save(options.contouring_idx, state.contouring, options.contouring_data);
contouring::save(options.contouring.idx, state.contouring, options.contouring.data);
}
ImGui::SliderInt("Culling", &options.culling, -1, 10, options.culling > 0 ? "Occlusion %d" : (options.culling < 0 ? "None" : "Frustum"));
state.contouring->onGui();
@ -148,8 +150,8 @@ UI::Actions UI::draw(config::options &options, config::state &state, const confi
{
const auto farRange = state.contouring->getFarRange();
if (options.show_debug_controls) {
ImGui::Begin("Debug: Controls", &options.show_debug_controls, ImGuiWindowFlags_AlwaysAutoResize);
if (options.debugMenu.controls) {
ImGui::Begin("Debug: Controls", &options.debugMenu.controls, ImGuiWindowFlags_AlwaysAutoResize);
const auto p = state.position.as_voxel(options.voxel_density);
ImGui::Text("Position: (%lld, %lld, %lld)", p.x, p.y, p.z);
ImGui::Separator();
@ -179,8 +181,8 @@ UI::Actions UI::draw(config::options &options, config::state &state, const confi
}
}
if (options.editor_show) {
ImGui::Begin("Editor", &options.editor_show, ImGuiWindowFlags_AlwaysAutoResize);
if (options.editor.visible) {
ImGui::Begin("Editor", &options.editor.visible, ImGuiWindowFlags_AlwaysAutoResize);
if (state.look_at.has_value()) {
const auto &look = state.look_at.value();
ImGui::Text("Look at: (%ld: %lld, %lld, %lld) (%s, %.1f)", look.pos.first.index, look.pos.second.x, look.pos.second.y, look.pos.second.z,
@ -191,20 +193,20 @@ UI::Actions UI::draw(config::options &options, config::state &state, const confi
ImGui::Text("Look at: none");
}
ImGui::Separator();
if (ImGui::BeginCombo("Material", world::materials::names[options.tool.material].c_str())) {
if (ImGui::BeginCombo("Material", world::materials::names[options.editor.tool.material].c_str())) {
for (size_t i = 0; i < world::materials::names.size(); i++) {
if (!world::materials::invisibility[i]) {
const bool is_selected = (options.tool.material == i);
const bool is_selected = (options.editor.tool.material == i);
if (ImGui::Selectable(world::materials::names[i].c_str(), is_selected))
options.tool.material = i;
options.editor.tool.material = i;
if (is_selected)
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
ImGui::SliderInt("Radius", &options.tool.radius, 0, 10);
ImGui::Checkbox("Empty air", &options.tool.empty_air);
ImGui::SliderInt("Radius", &options.editor.tool.radius, 0, 10);
ImGui::Checkbox("Empty air", &options.editor.tool.emptyAir);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Is cleaned area breathable");
ImGui::End();
@ -241,29 +243,29 @@ UI::Actions UI::draw(config::options &options, config::state &state, const confi
ImGui::End();
}*/
if (options.overlay_show) {
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);
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_show, (options.overlay_corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav);
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);
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_show && ImGui::MenuItem("Close"))
options.overlay_show = false;
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();

View File

@ -4,12 +4,10 @@
#include <cassert>
#include "../../core/flags.hpp"
namespace config {
struct options;
namespace config::client { struct options; }
namespace state {
struct state;
struct reports;
}
namespace render {
@ -43,7 +41,7 @@ public:
}
/// Compute UI
virtual Actions draw(config::options&, config::state&, const config::reports&) = 0;
virtual Actions draw(config::client::options&, state::state&, const state::reports&) = 0;
/// Display UI
virtual void render() = 0;
@ -58,7 +56,7 @@ public:
static void Unload();
protected:
static Actions draw(config::options &, config::state &, const config::reports &, intptr_t aim);
static Actions draw(config::client::options&, state::state &, const state::reports &, intptr_t aim);
static UI* sInstance;
};

View File

@ -1,108 +0,0 @@
#include "AbstractFlat.hpp"
#include <imgui.h> // NOLINT
#include <toml.h>
#include "../../../server/world/Chunk.hpp"
#include "../../../server/world/Area.hpp"
namespace contouring {
size_t AbstractFlat::clear(const voxel_pos& pos, const world::area_map& areas) {
size_t buffer_count = 0;
auto it_a = buffers.begin();
while (it_a != buffers.end()) { // Remove out of range buffers
if (const auto area = areas.find(it_a->first); area != areas.end()) {
//Update
std::get<0>(it_a->second.first) = area->second->getOffset();
std::get<1>(it_a->second.first) = area->second->getChunks().getRadius() * static_cast<long long>(CHUNK_LENGTH);
const auto centerV = pos - std::get<0>(it_a->second.first).as_voxel();
{
const auto params = area->second->getParams().properties;
if (auto planet = std::get_if<world::generator::CubicPlanet::Params>(&params)) {
const auto dist = glm::max_axis(glm::abs(centerV));
const auto surface = planet->height * planet->surface_roughness;
std::get<2>(it_a->second.first) = std::clamp((dist - surface) / (std::get<1>(it_a->second.first) - surface), -0.1f, 1.f);
}
}
const auto center = glm::divide(centerV);
auto &bfs = it_a->second.second;
auto it = bfs.begin();
while(it != bfs.end()) {
if (glm::length2(center - it->first) > glm::pow2(keepDistance)) {
if(it->second != NULL)
delete it->second;
it = bfs.erase(it);
} else {
buffer_count++;
++it;
}
}
++it_a;
} else {
for(auto& buffer: it_a->second.second) {
if(buffer.second != NULL)
delete buffer.second;
}
it_a = buffers.erase(it_a);
}
}
return buffer_count;
}
AbstractFlat::AbstractFlat(const std::string& str): Abstract() {
auto opt = toml::parse(str);
loadDistance = opt["load_distance"].value_or(loadDistance);
keepDistance = opt["keep_distance"].value_or(keepDistance);
}
std::string AbstractFlat::getOptions() const {
std::ostringstream ss;
ss << toml::table({
{"load_distance", loadDistance},
{"keep_distance", keepDistance}
});
return ss.str();
}
std::pair<float, float> AbstractFlat::getFarRange() const {
return std::make_pair((loadDistance - 1.5) * CHUNK_LENGTH, (keepDistance + .5) * CHUNK_LENGTH);
}
void AbstractFlat::onGui() {
ImGui::SliderInt("Load Distance", &loadDistance, 1, keepDistance);
ImGui::SliderInt("Keep Distance", &keepDistance, loadDistance+1, 21);
}
void AbstractFlat::getModels(draw_call out, const std::optional<geometry::Frustum> &frustum, const glm::llvec3& offset, int density) {
const auto scaling = glm::scale(glm::mat4(1), glm::vec3(1.f / density));
for (const auto [_, area] : buffers) {
for (const auto [pos, buffer] : area.second) {
const auto vPos = glm::multiply(pos);
const glm::vec3 fPos = (glm::vec3(std::get<0>(area.first).raw_as_long() + vPos - offset * glm::llvec3(density)) + std::get<0>(area.first).offset) / glm::vec3(density);
if (buffer != NULL && (!frustum.has_value() || frustum.value().contains(geometry::Box::fromMin(fPos, glm::vec3(CHUNK_LENGTH / (float)density)))))
out(glm::translate(scaling, fPos * (float)density), buffer, area.first, vPos);
}}
}
void AbstractFlat::getModels(draw_call out, const glm::ifvec3& from, float far, const std::vector<glm::vec3> &occlusion, const glm::llvec3 &offset, int density) {
const auto scaling = glm::scale(glm::mat4(1), glm::vec3(1.f / density));
const auto start = glm::ifvec3(glm::divide(from.as_voxel(density)));
const auto dist = far * density / CHUNK_LENGTH;
for (const auto [_, area] : buffers) {
const auto area_offset = glm::divide(std::get<0>(area.first).as_voxel());
robin_hood::unordered_set<chunk_pos> done;
for (const auto& occ: occlusion) {
const geometry::Ray ray(start, occ, dist);
std::vector<voxel_pos> points; //TODO: iterator
ray.grid(points);
for(auto& point: points) {
auto it = area.second.find(glm::lvec3(point) - area_offset);
if(it != area.second.end() && it->second != NULL && done.insert(it->first).second) {
const auto vPos = glm::multiply(it->first);
const glm::vec3 fPos = glm::vec3(std::get<0>(area.first).raw_as_long() + vPos - offset * glm::llvec3(density)) + std::get<0>(area.first).offset;
out(glm::translate(scaling, fPos), it->second, area.first, vPos);
break;
}
}
}
}
}
}

View File

@ -1,34 +0,0 @@
#pragma once
#include "Abstract.hpp"
#include "../../../core/data/math.hpp"
namespace contouring {
/// Generating mesh for chunks 1:1
class AbstractFlat: public Abstract {
public:
AbstractFlat(const std::string &str);
virtual ~AbstractFlat() {}
/// Display ImGui config
void onGui() override;
std::string getOptions() const override;
std::pair<float, float> getFarRange() const override;
/// Get buffers in frustum with model matrices
/// @note buffers invalidated after update
void getModels(draw_call draw, const std::optional<geometry::Frustum> &frustum, const glm::llvec3 &offset, int density) override;
/// Get buffers hitting occlusion rays with model matrices
/// @note buffers invalidated after update
void getModels(draw_call draw, const glm::ifvec3 &from, float far, const std::vector<glm::vec3> &occlusion, const glm::llvec3 &offset, int density) override;
protected:
size_t clear(const voxel_pos &, const world::area_map &areas);
robin_hood::unordered_map<area_id, robin_hood::pair<area_info, robin_hood::unordered_map<chunk_pos, buffer::Abstract *>>> buffers;
int loadDistance = 3;
int keepDistance = 4;
};
}

View File

@ -1,80 +0,0 @@
#include "surrounding.hpp"
#include "../../../server/world/Area.hpp"
#include "../../../core/data/math.hpp"
using namespace geometry;
namespace contouring::surrounding {
bool load(faces &out, const chunk_pos &chunkPos, const world::ChunkContainer &chunks) {
{
const auto chunk = chunks.findInRange(chunkPos);
if (!chunk.has_value())
return false;
out[CENTER] = chunk.value();
}
for (size_t i = 0; i < CENTER; i++) {
const auto chunk = chunks.findOrEmpty(chunkPos + g_face_offsets[i]);
if (!chunk.has_value())
return false;
out[i] = chunk.value();
}
return true;
}
std::pair<glm::idx, glm::idx> getNeighborIdx(glm::idx idx, Face face) {
switch (face) {
case Face::Forward:
if (idx % glm::IDX_LENGTH >= glm::IDX_LENGTH - 1)
return {static_cast<int>(Face::Forward), idx - (glm::IDX_LENGTH - 1)};
return {CENTER, idx + 1};
case Face::Backward:
if (idx % glm::IDX_LENGTH <= 0)
return {static_cast<int>(Face::Backward), idx + (glm::IDX_LENGTH - 1)};
return {CENTER, idx - 1};
case Face::Up:
if ((idx / glm::IDX_LENGTH) % glm::IDX_LENGTH >= glm::IDX_LENGTH - 1)
return {static_cast<int>(Face::Up), idx - (glm::IDX_LENGTH2 - glm::IDX_LENGTH)};
return {CENTER, idx + glm::IDX_LENGTH};
case Face::Down:
if ((idx / glm::IDX_LENGTH) % glm::IDX_LENGTH <= 0)
return {static_cast<int>(Face::Down), idx + (glm::IDX_LENGTH2 - glm::IDX_LENGTH)};
return {CENTER, idx - glm::IDX_LENGTH};
case Face::Right:
if (idx / glm::IDX_LENGTH2 >= glm::IDX_LENGTH - 1)
return {static_cast<int>(Face::Right), idx - (glm::IDX_SIZE - glm::IDX_LENGTH2)};
return {CENTER, idx + glm::IDX_LENGTH2};
case Face::Left:
if (idx / glm::IDX_LENGTH2 <= 0)
return {static_cast<int>(Face::Left), idx + (glm::IDX_SIZE - glm::IDX_LENGTH2)};
return {CENTER, idx - glm::IDX_LENGTH2};
default:
return {CENTER, idx};
}
}
bool load(corners &out, const chunk_pos &chunkPos, const world::ChunkContainer &chunks) {
{
const auto chunk = chunks.findInRange(chunkPos);
if(!chunk.has_value())
return false;
out[0] = chunk.value();
}
for (size_t i = 1; i < 8; i++) {
const auto chunk = chunks.findOrEmpty(chunkPos + g_corner_offsets[i]);
if (!chunk.has_value())
return false;
out[i] = chunk.value();
}
return true;
}
}

View File

@ -1,30 +0,0 @@
#pragma once
#include "../../../core/geometry/Faces.hpp"
#include "../../../server/world/forward.h"
namespace world {
class Chunk;
}
namespace contouring::surrounding {
const auto CENTER = 6;
typedef std::array<std::shared_ptr<const world::Chunk>, CENTER+1> faces;
bool load(faces &out, const chunk_pos &chunkPos, const world::ChunkContainer &chunks);
std::pair<ushort, ushort> getNeighborIdx(ushort idx, geometry::Face face);
typedef std::array<std::shared_ptr<const world::Chunk>, 8> corners;
const chunk_pos g_corner_offsets[8] = {
chunk_pos(0, 0, 0),
chunk_pos(0, 0, 1),
chunk_pos(0, 1, 0),
chunk_pos(0, 1, 1),
chunk_pos(1, 0, 0),
chunk_pos(1, 0, 1),
chunk_pos(1, 1, 0),
chunk_pos(1, 1, 1),
};
bool load(corners &out, const chunk_pos &chunkPos, const world::ChunkContainer &chunks);
}

View File

@ -17,7 +17,7 @@ UI::~UI() {
ImGui_ImplOpenGL3_Shutdown();
}
UI::Actions UI::draw(config::options &o, config::state &s, const config::reports &r) {
UI::Actions UI::draw(config::client::options &o, state::state &s, const state::reports &r) {
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
return render::UI::draw(o, s, r, aim);

View File

@ -14,7 +14,7 @@ public:
static void Load(Window &);
Actions draw(config::options &, config::state &, const config::reports &) override;
Actions draw(config::client::options &, state::state &, const state::reports &) override;
void render() override;
private:

24
src/client/state.hpp Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include "config.hpp"
namespace state {
/// Live state
struct state {
bool capture_mouse = true;
camera_pos position = camera_pos(voxel_pos(0), 1);
std::optional<world::Universe::ray_target> look_at = {};
std::shared_ptr<contouring::Abstract> contouring;
std::array<char, 256> console_buffer;
};
/// Readonly metrics
struct reports {
size_t tris_count = 0;
size_t models_count = 0;
};
}

22
src/client/world/Area.hpp Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include "../../core/world/Area.hpp"
namespace world::client {
/// Area (aka big group of client::Chunk)
struct Area: public world::Area<Chunk>, public virtual IArea {
public:
struct params {
voxel_pos center;
int radius;
std::optional<double> curvature;
};
Area(const params& p): IArea(), world::Area<Chunk>(p.center, p.radius), curvature(p.curvature) { }
std::optional<double> getCurvature() const override { return curvature; }
private:
std::optional<double> curvature;
};
}

View File

@ -0,0 +1,15 @@
#pragma once
#include "../../core/world/EdittableChunk.hpp"
namespace world::client {
class Chunk: public EdittableChunk {
public:
Chunk(): world::Chunk(), EdittableChunk() { }
};
/// Chunk full of air
static const std::shared_ptr<const Chunk> EMPTY_CHUNK = std::make_shared<Chunk>();
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "Universe.hpp"
#include "../../core/utils/logger.hpp"
namespace world::client {
/// Whole universe container in network client
class DistantUniverse final: public Universe {
public:
DistantUniverse(const options &): Universe() {
FATAL("TODO: Internet server unimplemented");
}
void update(voxel_pos, float) override {
assert(false && "TODO");
}
void emit(const action::packet &) override {
assert(false && "TODO");
}
ray_result raycast(const geometry::Ray &) const override { return std::monostate{}; }
protected:
};
}

View File

@ -0,0 +1,54 @@
#include "LocalUniverse.hpp"
#include "../../core/data/math.hpp"
#include "../contouring/Abstract.hpp"
#include "../Window.hpp"
#include "../../core/utils/logger.hpp"
#include "../../core/world/Area.hpp"
using namespace world::client;
LocalUniverse::LocalUniverse(server_handle *const handle): Universe(), handle(handle) {
assert(handle != nullptr);
for (auto i = 0; i < 500 && !handle->running; i++) {
LOG_D("Waiting local server startup");
Window::wait();
}
handle->onUpdate = std::function([&](const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data, geometry::Faces neighbors) {
contouring->onUpdate(pos, offset, data, neighbors);
});
}
LocalUniverse::~LocalUniverse() {
handle->running = false;
}
void LocalUniverse::setContouring(const std::shared_ptr<contouring::Abstract>& ct) {
Universe::setContouring(ct);
last_chunk = chunk_pos(INT_MAX);
}
void LocalUniverse::update(voxel_pos pos, float) {
const auto cur_chunk = glm::divide(pos);
const auto chunkChange = cur_chunk != last_chunk;
last_chunk = cur_chunk;
handle->pos = pos;
if(chunkChange) {
for(const auto& area: *handle->areas) {
const chunk_pos diff = glm::divide(pos - area.second->getOffset().as_voxel());
for(const auto& chunk: area.second->getChunks()) {
contouring->onNotify(std::make_pair(area.first, chunk.first), diff, area.second->getChunks());
}
}
}
contouring->update(pos, *handle->areas);
}
void LocalUniverse::emit(const action::packet &packet) {
handle->emit(packet);
}
Universe::ray_result LocalUniverse::raycast(const geometry::Ray& ray) const {
return handle->raycast(ray);
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "Universe.hpp"
#include "../../core/server_handle.hpp"
namespace world::client {
/// Whole universe container in client with in-memory server
class LocalUniverse final: public Universe {
public:
LocalUniverse(server_handle *const handle);
~LocalUniverse();
void update(voxel_pos pos, float deltaTime) override;
void emit(const action::packet &) override;
void setContouring(const std::shared_ptr<contouring::Abstract> &ct) override;
ray_result raycast(const geometry::Ray &ray) const override;
protected:
server_handle *const handle;
chunk_pos last_chunk = chunk_pos(INT_MAX);
};
}

View File

@ -0,0 +1,29 @@
#include "Universe.hpp"
#include "../contouring/Dummy.hpp"
using namespace world::client;
Universe::Universe(): world::Universe(), contouring(std::make_shared<contouring::Dummy>()) { }
Universe::~Universe() { }
void Universe::setContouring(const std::shared_ptr<contouring::Abstract>& ct) {
contouring = ct;
}
/*
void ServerUniverse::getEntitiesModels(const std::function<void(const std::vector<glm::mat4>&, buffer::Abstract *const)> &draw, const std::optional<geometry::Frustum> &frustum, const glm::llvec3 &offset, int density) {
std::vector<glm::mat4> mats;
entities.iter([&](entity_id, const Entity &entity) {
entity.instances.iter([&](entity_id, const Entity::Instance &inst) {
const glm::vec3 fPos = (glm::vec3(inst.pos.raw_as_long() - offset * glm::llvec3(density)) + inst.pos.offset) / glm::vec3(density);
if (!frustum.has_value() || frustum.value().contains(geometry::Box::fromMin(fPos, entity.size)))
mats.emplace_back(glm::scale(glm::translate(glm::mat4(1), fPos * (float)density), entity.scale));
});
if(!mats.empty()) {
draw(mats, entity.buffer.get());
mats.resize(0);
}
});
}
*/

View File

@ -0,0 +1,37 @@
#pragma once
#include "../../core/world/Universe.hpp"
#include "../../core/world/actions.hpp"
#include <memory>
namespace contouring {
class Abstract;
}
namespace world::client {
/// Whole universe container in abstract client
class Universe: public world::Universe {
public:
Universe();
virtual ~Universe();
/// Update edits and contouring
virtual void update(voxel_pos pos, float deltaTime) = 0;
/// Send action to ServerUniverse
virtual void emit(const action::packet &) = 0;
/// Change contouring worker
virtual void setContouring(const std::shared_ptr<contouring::Abstract> &ct);
/// Get current contouring worker
std::shared_ptr<contouring::Abstract> getContouring() const {
return contouring;
}
//TODO: move to ClientUniverse
//void getEntitiesModels(const std::function<void(const std::vector<glm::mat4> &, buffer::Abstract *const)> &draw, const std::optional<geometry::Frustum> &frustum, const glm::llvec3 &offset, int density);
protected:
/// Contouring worker
std::shared_ptr<contouring::Abstract> contouring;
};
}

View File

@ -0,0 +1,12 @@
#include "index.hpp"
#include "LocalUniverse.hpp"
#include "DistantUniverse.hpp"
namespace world::client {
std::unique_ptr<Universe> Load(bool preferLocal, server_handle *const localHandle, const Universe::options &distOpts) {
if(preferLocal && localHandle != nullptr)
return std::make_unique<LocalUniverse>(localHandle);
else
return std::make_unique<DistantUniverse>(distOpts);
}
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "Universe.hpp"
#include "../../core/server_handle.hpp"
namespace world::client {
std::unique_ptr<Universe> Load(bool preferLocal, server_handle *const localHandle, const Universe::options& distOpts);
}

47
src/core/config.hpp Normal file
View File

@ -0,0 +1,47 @@
#pragma once
#include <toml.h>
#include <fstream>
#include <filesystem>
#include <optional>
#include "../client/config.hpp"
#include "../server/config.hpp"
namespace config {
/// Savable game options
struct options {
public:
/// Load from path
options(const std::string &path = "config.toml"): path(path) {
auto config = std::filesystem::exists(path) ? toml::parse_file(path) : toml::table();
if(config["server"]["enabled"].value_or(false)) {
_server = server::options(config["server"]);
}
if(config["client"]["enabled"].value_or(false)) {
_client = client::options(config["client"]);
}
}
/// Write to path
void save() {
auto config = toml::table();
config.insert_or_assign("client", _client.has_value() ? _client.value().save() : toml::table({{"enabled", false}}));
config.insert_or_assign("server", _server.has_value() ? _server.value().save() : toml::table({{"enabled", false}}));
std::ofstream out;
out.open(path, std::ios::out | std::ios::trunc);
out << config << "\n\n";
out.close();
}
constexpr bool hasServer() const { return _server.has_value(); }
constexpr bool hasClient() const { return _client.has_value(); }
server::options &getServer() { return _server.value(); }
client::options &getClient() { return _client.value(); }
private:
std::string path;
std::optional<server::options> _server;
std::optional<client::options> _client;
};
}

View File

@ -145,6 +145,14 @@ namespace data::generational {
return true;
}
bool empty() const {
for(auto& v: entries) {
if(v.value.has_value())
return false;
}
return true;
}
template<typename apply>
void iter(apply fn) const {
for (size_t i = 0; i < entries.size(); i++) {

View File

@ -0,0 +1,14 @@
#pragma once
#include "world/Universe.hpp"
#include "world/actions.hpp"
#include "geometry/Faces.hpp"
struct server_handle {
bool running = false;
camera_pos pos;
const world::client::area_map *areas;
std::function<void(const area_<chunk_pos> &pos, const chunk_pos &offset, const world::ChunkContainer &data, geometry::Faces neighbors)> onUpdate;
std::function<void(const world::action::packet &packet)> emit;
std::function<world::Universe::ray_result(const geometry::Ray &ray)> raycast;
};

56
src/core/world/Area.hpp Normal file
View File

@ -0,0 +1,56 @@
#pragma once
#include "../../core/world/forward.h"
#include "../../core/geometry/IBox.hpp"
#include "../../core/data/math.hpp"
namespace world {
/// Chunk map with restricted access
struct ChunkContainer: robin_hood::unordered_map<chunk_pos, std::shared_ptr<Chunk>> {
private:
int radius;
public:
/// Area radius in chunks
constexpr inline int getRadius() const { return radius; }
ChunkContainer(int radius): radius(radius) {
assert(radius > 0 && radius < (1 << 22) / CHUNK_LENGTH);
}
inline bool inRange(const chunk_pos& pos) const {
return glm::abs(pos.x) < radius && glm::abs(pos.y) < radius && glm::abs(pos.z) < radius;
}
std::optional<std::shared_ptr<Chunk>> findInRange(const chunk_pos &pos) const {
const auto it = find(pos);
return it != end() ? std::make_optional(it->second) : std::nullopt;
}
};
/// Area (aka big group of Chunk)
struct Area {
public:
/// radius: size in chunk (length = radius * 2 + 1)
Area(voxel_pos center, int radius): center(center), chunks(radius) { }
inline const area_pos &getOffset() const { return center; }
inline geometry::IBox getBounding() const {
const auto c = center.as_voxel();
return geometry::IBox(c - voxel_pos(CHUNK_LENGTH * (chunks.getRadius() - 1)),
c + voxel_pos(CHUNK_LENGTH * chunks.getRadius()));
}
/// Move offset return if chunk_change
bool inline move(const area_pos::offset_t &offset) {
const auto prev = glm::divide(center.as_voxel());
center = center + offset;
return prev != glm::divide(center.as_voxel());
}
inline const ChunkContainer &getChunks() const { return chunks; }
inline ChunkContainer &setChunks() { return chunks; }
virtual std::optional<double> getCurvature() const = 0;
private:
area_pos center;
ChunkContainer chunks;
};
}

34
src/core/world/Chunk.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "Chunk.hpp"
#include <iostream>
#include <algorithm>
#include "../../core/data/math.hpp"
using namespace world;
Chunk::Chunk(std::istream& str, bool rle) {
if(rle) {
ushort i = 0;
while(!str.eof()) {
ushort count;
Voxel voxel;
str.read(reinterpret_cast<char *>(&count), sizeof(count));
str.read(reinterpret_cast<char *>(&voxel), sizeof(voxel));
str.peek();
for (; count > 0; count--) {
voxels[i] = voxel;
i++;
}
}
assert(i == CHUNK_SIZE && "Mismatch data length");
} else {
for(auto& voxel: voxels) {
str.read(reinterpret_cast<char *>(&voxel), sizeof(voxel));
}
}
}
Chunk::~Chunk() { }
const Voxel &Chunk::getAt(const chunk_voxel_pos &pos) const {
return get(glm::toIdx(pos));
}

34
src/core/world/Chunk.hpp Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <sstream>
#include "forward.h"
#include "Voxel.hpp"
namespace world {
constexpr auto RLE = true; //NOTE: only ~2.5% gain after zstd
/// World part as linear 3d voxel array
class Chunk {
public:
Chunk(std::istream& str, bool rle = RLE);
virtual ~Chunk();
struct Edit {
chunk_voxel_idx idx;
Voxel value;
float delay;
};
/// Get voxel from index
inline const Voxel& get(chunk_voxel_idx idx) const {
return voxels[idx];
}
/// Get voxel from position
const Voxel &getAt(const chunk_voxel_pos &pos) const;
protected:
Chunk() { }
/// Chunk data
std::array<Voxel, CHUNK_SIZE> voxels;
};
}

View File

@ -0,0 +1,77 @@
#include "EdittableChunk.hpp"
#include <algorithm>
#include <Tracy.hpp>
#include "../../core/data/math.hpp"
using namespace world::client;
EdittableChunk::~EdittableChunk() { }
std::optional<Faces> EdittableChunk::update(float deltaTime, bool animate) {
ZoneScopedN("Chunk");
for(auto it = edits.begin(); it != edits.end();) {
it->delay -= deltaTime;
if(it->delay <= 0 && animate) {
invalidate(it->idx);
it = edits.erase(it);
} else {
it++;
}
}
if(upToDate) {
return {};
} else {
upToDate = true;
return {toUpdate};
}
}
void EdittableChunk::invalidate(ushort idx) {
invalidate(
((!getNeighborIdx(idx, Face::Up).has_value()) & Faces::Up) |
((!getNeighborIdx(idx, Face::Down).has_value()) & Faces::Down) |
((!getNeighborIdx(idx, Face::Left).has_value()) & Faces::Left) |
((!getNeighborIdx(idx, Face::Right).has_value()) & Faces::Right) |
((!getNeighborIdx(idx, Face::Forward).has_value()) & Faces::Forward) |
((!getNeighborIdx(idx, Face::Backward).has_value()) & Faces::Backward));
}
std::optional<chunk_voxel_idx> EdittableChunk::getNeighborIdx(chunk_voxel_idx idx, Face dir) {
switch (dir) {
case Face::Forward:
if (idx % glm::IDX_LENGTH >= glm::IDX_LENGTH - 1)
return {};
return idx + 1;
case Face::Backward:
if (idx % glm::IDX_LENGTH <= 0)
return {};
return idx - 1;
case Face::Up:
if ((idx / glm::IDX_LENGTH) % glm::IDX_LENGTH >= glm::IDX_LENGTH - 1)
return {};
return idx + glm::IDX_LENGTH;
case Face::Down:
if ((idx / glm::IDX_LENGTH) % glm::IDX_LENGTH <= 0)
return {};
return idx - glm::IDX_LENGTH;
case Face::Right:
if (idx / glm::IDX_LENGTH2 >= glm::IDX_LENGTH - 1)
return {};
return idx + glm::IDX_LENGTH2;
case Face::Left:
if (idx / glm::IDX_LENGTH2 <= 0)
return {};
return idx - glm::IDX_LENGTH2;
default:
return {};
}
}

View File

@ -0,0 +1,40 @@
#pragma once
#include "Chunk.hpp"
#include "../geometry/Faces.hpp"
using namespace geometry;
namespace world::client {
class EdittableChunk: public virtual world::Chunk {
public:
virtual ~EdittableChunk();
/// Update voxels
/// @return if modified neighbors to update
virtual std::optional<Faces> update(float deltaTime, bool animate);
/// Notify for render
inline void invalidate(Faces faces) {
upToDate = false;
toUpdate = toUpdate | faces;
}
void invalidate(chunk_voxel_idx idx);
virtual void apply(const Chunk::Edit &edit) {
assert(false && "TODO");
}
/// Get pending changes
const std::vector<Chunk::Edit> &getEdits() const { return edits; }
static std::optional<chunk_voxel_idx> getNeighborIdx(chunk_voxel_idx idx, Face dir);
protected:
/// Temporary changes
std::vector<Chunk::Edit> edits;
/// Require update
bool upToDate = true;
/// Neighbors to update
Faces toUpdate = Faces::None;
};
}

View File

@ -0,0 +1,30 @@
#include "Universe.hpp"
#include "../data/math.hpp"
using namespace world;
using namespace geometry;
bool Universe::move(glm::ifvec3 &pos, const glm::vec3 &vel, int density, float radius) const {
const auto dir = glm::normalize(vel);
const auto velocity = vel * glm::vec3(density);
const auto from = pos * density + dir;
const auto result = raycast(Ray(from, dir, glm::length(velocity) + radius));
if (auto target = std::get_if<ray_target>(&result)) {
const auto target_dist = from.dist(glm::ifvec3(target->offset + target->pos.second, density)) - radius;
pos += vel * glm::vec3(target_dist / glm::length(vel));
return true;
} else if(auto out = std::get_if<ray_out_of_range>(&result)) {
const auto out_dist = from.dist(glm::ifvec3(out->offset + glm::multiply(out->pos.second), density)) - radius - CHUNK_LENGTH;
pos += vel * glm::vec3(out_dist / glm::length(vel));
return true;
}
pos += vel;
return false;
}
bool Universe::collide(const glm::ifvec3 &pos, const glm::vec3 &vel, int density, float radius) const {
const auto dir = glm::normalize(vel);
const auto velocity = vel * glm::vec3(density);
const auto from = pos * density + dir;
return std::holds_alternative<ray_target>(raycast(Ray(from, dir, glm::length(velocity) + radius)));
}

View File

@ -0,0 +1,64 @@
#pragma once
#include <variant>
#include "forward.h"
#include "Voxel.hpp"
#include "position.h"
#include "../geometry/Ray.hpp"
namespace world {
/// Whole abstract universe container
class Universe {
public:
virtual ~Universe() {}
/// Distance management
struct options {
/// Radius in chunks to load if missing
int loadDistance = 5;
/// Radius in chunks to keep in memory
int keepDistance = 6;
};
/// Universe voxel ray intersection
struct ray_target {
ray_target(area_<voxel_pos> pos, Voxel value, voxel_pos offset):
pos(pos), value(value), offset(offset) { }
area_<voxel_pos> pos;
Voxel value;
voxel_pos offset;
};
/// Universe unloaded chunk ray intersection
struct ray_out_of_range {
ray_out_of_range(area_<chunk_pos> pos, voxel_pos offset):
pos(pos), offset(offset) { }
area_<chunk_pos> pos;
voxel_pos offset;
};
/// Universe ray intersection
/// @return target voxel, unloaded chunk or none
using ray_result = std::variant<std::monostate, ray_target, ray_out_of_range>;
/// Get nearest voxel colliding ray
/// @note ray in world scale
virtual ray_result raycast(const geometry::Ray &ray) const = 0;
/// Check for collision on movement
bool collide(const glm::ifvec3 &pos, const glm::vec3 &vel, int density, float radius = 0) const;
/// Move with collision check
/// @note must remove velocity after colision
bool move(glm::ifvec3 &pos, const glm::vec3 &vel, int density, float radius = 0) const;
/// Entities commun properties
struct Entity {
Entity(const glm::vec3& size = glm::vec3(1), const glm::vec3& scale = glm::vec3(1)): size(size), scale(scale) { };
glm::vec3 size;
glm::vec3 scale;
struct Instance {
glm::ifvec3 pos;
glm::vec3 velocity;
};
data::generational::vector<Instance> instances;
};
};
}

View File

@ -2,6 +2,7 @@
#include <robin_hood.h>
#include "../../core/world/materials.hpp"
#include <cassert>
namespace world {
/// Universe unit

View File

@ -0,0 +1,27 @@
#pragma once
#include "forward.h"
#include "Voxel.hpp"
#include <variant>
/// Client action on world
namespace world::action {
namespace part {
struct Ping { };
}
struct Fill: part::Ping {
Fill(const area_<voxel_pos> &pos, const Voxel &val): pos(pos), val(val) {}
const area_<voxel_pos> pos;
const Voxel val;
};
struct FillCube: Fill {
FillCube(const area_<voxel_pos> &pos, const Voxel &val, int radius): Fill(pos, val), radius(radius) { }
const int radius;
};
using packet = std::variant<Fill, FillCube>;
}

View File

@ -5,7 +5,10 @@
namespace world {
class Chunk;
class Area;
using area_map = robin_hood::unordered_map<area_id, std::shared_ptr<Area>>;
class ChunkContainer;
class Area;
}
namespace world::client {
class EdittableChunk;
using area_map = robin_hood::unordered_map<area_id, std::shared_ptr<Area>>;
}

View File

@ -2,6 +2,7 @@
#include <array>
#include <string>
#include <climits>
namespace world::materials {
//TODO: AoS to SoA compile time

View File

@ -50,5 +50,6 @@ using area_pos = glm::ifvec3;
using entity_id = data::generational::id;
using entity_instance_id = std::pair<entity_id, data::generational::id>;
static const auto PLAYER_ENTITY_ID = data::generational::id(0);
using camera_pos = glm::ifvec3;

View File

@ -8,6 +8,8 @@
*/
#include "client/Client.hpp"
#include "server/Server.hpp"
#include "core/config.hpp"
#include <Tracy.hpp>
#if TRACY_MEMORY
@ -32,8 +34,46 @@ int main(int /*unused*/, char */*unused*/[]){
LOG("Profiling !");
#endif
Client client;
client.run();
config::options options;
std::optional<Server> server = options.hasServer() ? std::make_optional<Server>(options.getServer()) : std::nullopt;
std::optional<Client> client = options.hasClient() ? std::make_optional<Client>(options.getClient()) : std::nullopt;
const auto serverTask = [&] {
#if TRACY_ENABLE
tracy::SetThreadName("Server");
#endif
server.value().run();
};
const auto clientTask = [&](server_handle* const handle) {
#if TRACY_ENABLE
tracy::SetThreadName("Client");
#endif
client.value().run(handle);
};
if (server.has_value()) {
if (client.has_value()) {
auto serverThread = std::thread(serverTask);
clientTask(server.value().getHandle());
if(serverThread.joinable())
serverThread.join();
} else {
serverTask();
}
} else {
if (client.has_value()) {
//MAYBE: check not local
clientTask(nullptr);
} else {
options.save();
FATAL("Nothing to start");
}
}
options.save();
return 0;
}

34
src/server/Server.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "Server.hpp"
#include "world/StandaloneUniverse.hpp"
#include "world/SharedUniverse.hpp"
#include <signal.h>
Server::Server(config::server::options& options): options(options) {
//MAYBE: if allow local
localHandle = options.allowLocal ? new server_handle() : nullptr;
}
Server::~Server() { }
const auto TPS = 10;
static bool running = true;
void handle_signal(int) { running = false; }
void Server::run() {
universe = [&]() -> std::unique_ptr<world::server::Universe> {
if(options.allowLocal)
return std::make_unique<world::server::SharedUniverse>(options.world, localHandle);
return std::make_unique<world::server::StandaloneUniverse>(options.world);
}();
//TODO: open connection
signal(SIGINT, handle_signal);
signal(SIGTERM, handle_signal);
while(running && (localHandle == nullptr || localHandle->running)) {
universe->update(1. / TPS); //FIXME: use chrono
std::this_thread::sleep_for(std::chrono::milliseconds(1000 / TPS));
}
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <memory>
#include "../core/server_handle.hpp"
#include "config.hpp"
namespace world::server {
class Universe;
}
class Server {
public:
Server(config::server::options &);
~Server();
void run();
server_handle *const getHandle() { return localHandle; }
private:
config::server::options& options;
std::unique_ptr<world::server::Universe> universe;
server_handle *localHandle;
};

36
src/server/config.hpp Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include <toml.h>
#include "world/Universe.hpp"
namespace config::server {
struct options {
public:
options(toml::node_view<toml::node> config) {
assert(config["enabled"]);
allowLocal = config["allow_local"].value_or(allowLocal);
world.loadDistance = config["world"]["load_distance"].value_or(world.loadDistance);
world.keepDistance = config["world"]["keep_distance"].value_or(world.keepDistance);
world.folderPath = config["world"]["path"].value_or(world.folderPath);
}
toml::table save() {
return toml::table({
{"enabled", true},
{"allow_local", allowLocal},
{"world", toml::table({
{"load_distance", world.loadDistance},
{"keep_distance", world.keepDistance},
{"path", world.folderPath}
})}
});
}
/// enable SharedServerUniverse and LocalClientUniverse
bool allowLocal = true;
world::server::Universe::options world;
};
}

View File

@ -2,15 +2,7 @@
#include "Chunk.hpp"
using namespace world;
std::optional<std::shared_ptr<Chunk>> ChunkContainer::findInRange(const chunk_pos &pos) const {
const auto it = find(pos);
return it != end() ? std::make_optional(it->second) : std::nullopt;
}
std::optional<std::shared_ptr<const Chunk>> ChunkContainer::findOrEmpty(const chunk_pos &pos) const {
return inRange(pos) ? findInRange(pos) : std::make_optional(world::EMPTY_CHUNK);
}
using namespace world::server;
std::shared_ptr<Region> Area::getRegion(const std::string& folderPath, const area_<region_pos>& pos) {
{ // Found
@ -23,4 +15,11 @@ std::shared_ptr<Region> Area::getRegion(const std::string& folderPath, const are
const auto reg = std::make_shared<Region>(folderPath, pos);
const auto unique = regions.lock();
return unique->insert({pos.second, reg}).first->second;
}
std::optional<double> Area::getCurvature() const {
if (auto planet = std::get_if<world::generator::CubicPlanet::Params>(&generator_properties)) {
return planet->height * (1.1 + planet->surface_roughness);
}
return {};
}

View File

@ -1,33 +1,14 @@
#pragma once
#include "forward.h"
#include "../../core/world/Area.hpp"
#include <shared_mutex_guarded.h>
using namespace libguarded;
#include "region/index.hpp"
#include "generator.hpp"
#include "../../core/geometry/IBox.hpp"
namespace world {
/// Chunk map with restricted access
struct ChunkContainer: robin_hood::unordered_map<chunk_pos, std::shared_ptr<Chunk>> {
private:
int radius;
public:
/// Area radius in chunks
constexpr inline int getRadius() const { return radius; }
ChunkContainer(int radius): radius(radius) {
assert(radius > 0 && radius < (1 << 22) / CHUNK_LENGTH);
}
inline bool inRange(const chunk_pos& pos) const {
return glm::abs(pos.x) < radius && glm::abs(pos.y) < radius && glm::abs(pos.z) < radius;
}
std::optional<std::shared_ptr<Chunk>> findInRange(const chunk_pos &pos) const;
std::optional<std::shared_ptr<const Chunk>> findOrEmpty(const chunk_pos &pos) const;
};
/// Area (aka big group of chunks)
struct Area {
namespace world::server {
/// Area (aka big group of server::Chunk)
struct Area: public world::Area {
public:
using regions_t = robin_hood::unordered_map<region_pos, std::shared_ptr<Region>>;
@ -37,37 +18,18 @@ namespace world {
generator::params properties;
};
/// radius: size in chunk (length = radius * 2 + 1)
Area(const params& p): center(p.center), chunks(p.radius), generator(generator::load(p.properties)), generator_properties(p.properties) { }
inline const area_pos &getOffset() const { return center; }
inline geometry::IBox getBounding() const {
const auto c = center.as_voxel();
return geometry::IBox(c - voxel_pos(CHUNK_LENGTH * (chunks.getRadius() - 1)),
c + voxel_pos(CHUNK_LENGTH * chunks.getRadius()));
}
/// Move offset return if chunk_change
bool inline move(const area_pos::offset_t &offset) {
const auto prev = glm::divide(center.as_voxel());
center = center + offset;
return prev != glm::divide(center.as_voxel());
}
inline const ChunkContainer &getChunks() const { return chunks; }
inline ChunkContainer &setChunks() { return chunks; }
Area(const params& p): world::Area(p.center, p.radius), generator(generator::load(p.properties)), generator_properties(p.properties) { }
std::shared_ptr<Region> getRegion(const std::string& folderPath, const area_<region_pos> &);
shared_guarded<regions_t>::handle getRegions() { return regions.lock(); }
inline const std::unique_ptr<generator::Abstract> &getGenerator() { return generator; }
inline params getParams() const { return params{center.as_voxel(), chunks.getRadius(), generator_properties}; }
inline params getParams() const { return params{getOffset().as_voxel(), getChunks().getRadius(), generator_properties}; }
std::optional<double> getCurvature() const override;
private:
area_pos center;
//TODO: rotation
ChunkContainer chunks;
shared_guarded<regions_t> regions;
std::unique_ptr<generator::Abstract> generator;

View File

@ -4,33 +4,13 @@
#include "../../core/data/math.hpp"
#include <Tracy.hpp>
using namespace world;
using namespace world::server;
Chunk::Chunk(const chunk_pos& pos, const std::unique_ptr<generator::Abstract>& rnd) {
Chunk::Chunk(const chunk_pos& pos, const std::unique_ptr<generator::Abstract>& rnd): world::Chunk() {
rnd->generate(pos, voxels);
}
#include <iostream>
Chunk::Chunk(std::istream& str, bool rle) {
if(rle) {
ushort i = 0;
while(!str.eof()) {
ushort count;
Voxel voxel;
str.read(reinterpret_cast<char *>(&count), sizeof(count));
str.read(reinterpret_cast<char *>(&voxel), sizeof(voxel));
str.peek();
for (; count > 0; count--) {
voxels[i] = voxel;
i++;
}
}
assert(i == CHUNK_SIZE && "Mismatch data length");
} else {
for(auto& voxel: voxels) {
str.read(reinterpret_cast<char *>(&voxel), sizeof(voxel));
}
}
}
Chunk::Chunk(std::istream& str, bool rle): world::Chunk(str, rle) { }
Chunk::~Chunk() { }
void Chunk::write(std::ostream& str, bool rle) const {
@ -60,85 +40,16 @@ void Chunk::write(std::ostream& str, bool rle) const {
}
}
std::optional<Faces> Chunk::update(float deltaTime, bool animate) {
ZoneScopedN("Chunk");
for(auto it = edits.begin(); it != edits.end();) {
it->delay -= deltaTime;
if(it->delay <= 0 && animate) {
invalidate(it->idx);
it = edits.erase(it);
} else {
it++;
}
}
if(upToDate) {
return {};
} else {
upToDate = true;
return {toUpdate};
}
}
void Chunk::invalidate(ushort idx) {
invalidate(
((!getNeighborIdx(idx, Face::Up).has_value()) & Faces::Up) |
((!getNeighborIdx(idx, Face::Down).has_value()) & Faces::Down) |
((!getNeighborIdx(idx, Face::Left).has_value()) & Faces::Left) |
((!getNeighborIdx(idx, Face::Right).has_value()) & Faces::Right) |
((!getNeighborIdx(idx, Face::Forward).has_value()) & Faces::Forward) |
((!getNeighborIdx(idx, Face::Backward).has_value()) & Faces::Backward));
}
void Chunk::set(ushort idx, const Voxel& val) {
modified = modified || (voxels[idx].value != val.value);
voxels[idx] = val;
invalidate(idx);
}
std::optional<Item> Chunk::replace(chunk_voxel_idx idx, const Voxel& val, float delay) {
void Chunk::setAt(const chunk_voxel_pos& pos, const Voxel& val) {
set(glm::toIdx(pos), val);
}
//TODO: extract delay+Edit to Universe
std::optional<world::Item> Chunk::replace(chunk_voxel_idx idx, const Voxel& val, float delay) {
const auto res = voxels[idx];
if (val.value != res.value) {
if(delay > 0) {
voxels[idx] = val;
edits.emplace_back<Edit>({idx, res, delay});
} else {
set(idx, val);
}
}
return {Item{res.density(), res.material()}}; //TODO: materials break table
}
std::optional<chunk_voxel_idx> Chunk::getNeighborIdx(chunk_voxel_idx idx, Face dir) {
switch (dir) {
case Face::Forward:
if (idx % glm::IDX_LENGTH >= glm::IDX_LENGTH - 1)
return {};
return idx + 1;
case Face::Backward:
if (idx % glm::IDX_LENGTH <= 0)
return {};
return idx - 1;
case Face::Up:
if ((idx / glm::IDX_LENGTH) % glm::IDX_LENGTH >= glm::IDX_LENGTH - 1)
return {};
return idx + glm::IDX_LENGTH;
case Face::Down:
if ((idx / glm::IDX_LENGTH) % glm::IDX_LENGTH <= 0)
return {};
return idx - glm::IDX_LENGTH;
case Face::Right:
if (idx / glm::IDX_LENGTH2 >= glm::IDX_LENGTH - 1)
return {};
return idx + glm::IDX_LENGTH2;
case Face::Left:
if (idx / glm::IDX_LENGTH2 <= 0)
return {};
return idx - glm::IDX_LENGTH2;
default:
return {};
}
set(idx, val);
return {world::Item{res.density(), res.material()}}; //TODO: materials break table
}

View File

@ -1,79 +1,36 @@
#pragma once
#include "../../core/world/Chunk.hpp"
#include <memory>
#include <sstream>
#include "generator.hpp"
#include "Voxel.hpp"
#include "../../core/geometry/Faces.hpp"
#include "../../core/data/math.hpp"
/// Chunk length
namespace world {
constexpr auto RLE = true; //NOTE: only ~2.5% gain after zstd
using namespace geometry;
/// World part as linear 3d voxel array
class Chunk {
namespace world::server {
/// Server size chunk
class Chunk: public virtual world::Chunk {
public:
Chunk() {}
Chunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd);
Chunk(std::istream& str, bool rle = RLE);
~Chunk();
virtual ~Chunk();
struct Edit {
chunk_voxel_idx idx;
Voxel value;
float delay;
};
/// Update voxels
/// @return if modified neighbors to update
std::optional<Faces> update(float deltaTime, bool animate);
/// Notify for render
inline void invalidate(Faces faces) {
upToDate = false;
toUpdate = toUpdate | faces;
modified = true;
}
inline void invalidate(chunk_voxel_idx idx);
/// Get voxel from index
inline const Voxel& get(chunk_voxel_idx idx) const {
return voxels[idx];
}
/// Get voxel from position
inline const Voxel& getAt(const chunk_voxel_pos& pos) const {
return get(glm::toIdx(pos));
}
/// Get pending changes
const std::vector<Edit> &getEdits() const { return edits; }
/// Set voxel from index
void set(chunk_voxel_idx idx, const Voxel& val);
/// Set voxel from position
void setAt(const chunk_voxel_pos& pos, const Voxel& val) {
set(glm::toIdx(pos), val);
}
void setAt(const chunk_voxel_pos& pos, const Voxel& val);
/// Break voxel
std::optional<Item> replace(chunk_voxel_idx idx, const Voxel &val, float delay = 0);
virtual std::optional<Item> replace(chunk_voxel_idx idx, const Voxel &val, float delay = 0);
/// Is player modified
inline bool isModified() const { return modified; }
/// Write to file.
void write(std::ostream& str, bool rle = RLE) const;
static std::optional<chunk_voxel_idx> getNeighborIdx(chunk_voxel_idx idx, Face dir);
protected:
Chunk(): world::Chunk() { }
private:
/// Chunk data
std::array<Voxel, CHUNK_SIZE> voxels;
/// Temporary changes
std::vector<Edit> edits;
/// Require update
bool upToDate = true;
/// Neighbors to update
Faces toUpdate = Faces::None;
/// Modified by player
bool modified = false;
};
/// Chunk full of air
static const std::shared_ptr<const Chunk> EMPTY_CHUNK = std::make_shared<Chunk>();
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "Chunk.hpp"
#include "Area.hpp"
#include "../../core/world/EdittableChunk.hpp"
namespace world::server {
// Server and Client merged chunk
class SharedChunk: public Chunk, public world::client::EdittableChunk {
public:
SharedChunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd): world::Chunk(), Chunk(pos, rnd), world::client::EdittableChunk() { }
SharedChunk(std::istream &str, bool rle = RLE): world::Chunk(str, rle), Chunk(), world::client::EdittableChunk() { }
/// Break voxel
std::optional<Item> replace(chunk_voxel_idx idx, const Voxel &val, float delay = 0) override {
const auto res = voxels[idx];
if (val.value != res.value) {
set(idx, val);
if(delay > 0) {
edits.emplace_back<Edit>({idx, res, delay});
} else {
invalidate(idx);
}
}
return {Item{res.density(), res.material()}};
}
void apply(const Edit &edit) {
assert(false && "TODO:");
}
};
}

View File

@ -0,0 +1,52 @@
#include "SharedUniverse.hpp"
#include "SharedParts.hpp"
using namespace world::server;
SharedUniverse::SharedUniverse(const options &o, server_handle *const localHandle): Universe(o), localHandle(localHandle) {
// Local player
[[maybe_unused]]
const auto id = entities.at(PLAYER_ENTITY_ID).instances.emplace(Entity::Instance{});
assert(id == PLAYER_ENTITY_ID);
localHandle->areas = (world::client::area_map*)(&areas); //FIXME: templated area
localHandle->emit = std::function([&](const world::action::packet &packet) {
if(const auto fill = std::get_if<world::action::Fill>(&packet)) {
this->set(fill->pos, fill->val);
} else if(const auto fillCube = std::get_if<world::action::FillCube>(&packet)) {
this->setCube(fillCube->pos, fillCube->val, fillCube->radius);
} else {
LOG_W("Bad packet");
}
});
localHandle->raycast = std::function([&](const geometry::Ray& ray){
return this->raycast(ray); });
localHandle->running = true;
}
SharedUniverse::~SharedUniverse() {
LOG_D("Breaking shared universe");
saveAll(true); //NOTE: save thread requires some virtual function calls
}
void SharedUniverse::update(float deltaTime) {
if(!movePlayer(PLAYER_ENTITY_ID, localHandle->pos)) {
LOG_W("Bad move of player " << PLAYER_ENTITY_ID.index);
}
Universe::update(deltaTime);
}
void SharedUniverse::loadChunk(area_<chunk_pos> area, chunk_pos diff, const world::ChunkContainer& chunks) {
localHandle->onUpdate(area, diff, chunks, Faces::All);
}
void SharedUniverse::updateChunk(area_map::iterator &it, world::ChunkContainer::iterator &it_c, chunk_pos diff, float deltaTime) {
if (const auto neighbors = std::dynamic_pointer_cast<SharedChunk>(it_c->second)->update(deltaTime, true)) {
localHandle->onUpdate(std::make_pair(it->first, it_c->first), diff, it->second->getChunks(), neighbors.value());
}
}
std::shared_ptr<Chunk> SharedUniverse::createChunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd) const {
return std::make_shared<SharedChunk>(pos, rnd);
}
std::shared_ptr<Chunk> SharedUniverse::createChunk(std::istream &str) const {
return std::make_shared<SharedChunk>(str);
}

View File

@ -0,0 +1,30 @@
#pragma once
#include "Universe.hpp"
#include "../../core/server_handle.hpp"
namespace world::server {
class SharedArea;
/// Server with data for LocalClientUniverse binding
class SharedUniverse: public Universe {
public:
//TODO: override area type
//TODO: update edits
SharedUniverse(const options &, server_handle *const);
virtual ~SharedUniverse();
void update(float deltaTime) override;
protected:
std::shared_ptr<Chunk> createChunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd) const override;
std::shared_ptr<Chunk> createChunk(std::istream &str) const override;
void loadChunk(area_<chunk_pos>, chunk_pos, const world::ChunkContainer &) override;
void updateChunk(area_map::iterator &, world::ChunkContainer::iterator &, chunk_pos, float deltaTime) override;
private:
server_handle *const localHandle;
};
}

View File

@ -0,0 +1,6 @@
#include "StandaloneUniverse.hpp"
#include "Chunk.hpp"
using namespace world::server;
StandaloneUniverse::StandaloneUniverse(const options &o): Universe(o) { }

View File

@ -0,0 +1,11 @@
#pragma once
#include "Universe.hpp"
namespace world::server {
/// Logic only server::Universe
class StandaloneUniverse: public Universe {
public:
StandaloneUniverse(const options &);
};
}

View File

@ -5,16 +5,14 @@
#include <filesystem>
#include <random>
#include "../../client/render/contouring/Dummy.hpp"
#include "../../client/render/gl/buffer/Abstract.hpp"
#include "Chunk.hpp"
#include "../../client/render/gl/buffer/ShortIndexed.hpp"
using namespace world;
using namespace world::server;
const auto AREAS_FILE = "/areas.idx";
Universe::Universe(const Universe::options &options): dicts("content/zstd.dict"), contouring(std::make_shared<contouring::Dummy>()) {
Universe::Universe(const Universe::options &options): dicts({options.folderPath + "/zstd.dict", "content/zstd.dict"}) {
setOptions(options);
folderPath = options.folderPath;
struct vec_istream: std::streambuf {
@ -60,14 +58,9 @@ Universe::Universe(const Universe::options &options): dicts("content/zstd.dict"
}
{
std::ifstream ivb("content/model.ivb");
if(ivb.good()) {
buffer::ShortIndexed::Data data(ivb);
entities.emplace(new buffer::ShortIndexed(GL_TRIANGLES, data), glm::vec3(1), glm::vec3(2));
} else {
LOG_E("No model file!!! Please run render_models");
}
ivb.close();
[[maybe_unused]]
const auto type_id = entities.emplace(glm::vec3(1), glm::vec3(2));
assert(type_id == PLAYER_ENTITY_ID);
}
// Workers
@ -90,10 +83,10 @@ Universe::Universe(const Universe::options &options): dicts("content/zstd.dict"
ZoneScopedN("ProcessRead");
vec_istream idata(data);
std::istream iss(&idata);
loadedQueue.push({pos, std::make_shared<Chunk>(iss)});
loadedQueue.push({pos, createChunk(iss)});
} else {
ZoneScopedN("ProcessGenerate");
loadedQueue.push({pos, std::make_shared<Chunk>(pos.second, task.second->getGenerator())});
loadedQueue.push({pos, createChunk(pos.second, task.second->getGenerator())});
}
} else if(save_task_t task; saveQueue.pop(task)) {
//MAYBE: queue.take to avoid concurent write or duplicated work on fast move
@ -113,12 +106,24 @@ Universe::Universe(const Universe::options &options): dicts("content/zstd.dict"
}
}
Universe::~Universe() {
contouring = nullptr;
saveAll(false);
saveAreas();
// Save all
running = false;
loadQueue.notify_all();
for (auto &worker: workers) {
if (worker.joinable())
worker.join();
}
LOG_D("Universe disappeared");
}
void Universe::saveAll(bool remove) {
for(auto& area: areas) {
for(const auto& chunk: area.second->getChunks()) {
saveQueue.emplace(area, chunk);
auto& chunks = area.second->setChunks();
for (auto it_c = chunks.begin(); it_c != chunks.end(); remove ? it_c = chunks.erase(it_c) : ++it_c) {
saveQueue.emplace(area, std::make_pair(it_c->first, std::dynamic_pointer_cast<Chunk>(it_c->second)));
}
}
loadQueue.notify_all();
@ -134,16 +139,6 @@ Universe::~Universe() {
} while (size > 0);
std::cout << std::endl;
}
saveAreas();
running = false;
loadQueue.notify_all();
for (auto &worker: workers) {
if (worker.joinable())
worker.join();
}
LOG_D("Universe disappeared");
}
// Write areas index (warn: file io)
@ -176,11 +171,14 @@ void Universe::saveAreas() const {
index.close();
}
void Universe::update(const voxel_pos& pos, float deltaTime) {
void Universe::update(float deltaTime) {
ZoneScopedN("Universe");
const chunk_pos newPos = glm::divide(pos);
const auto chunkChange = last_pos != newPos;
last_pos = newPos;
if(entities.at(PLAYER_ENTITY_ID).instances.empty())
return;
const auto pos = entities.at(PLAYER_ENTITY_ID).instances.at(PLAYER_ENTITY_ID).pos.as_voxel();
const auto chunkChange = !movedPlayers.empty();
movedPlayers.clear();
if(chunkChange) {
ZoneScopedN("Far");
@ -200,8 +198,6 @@ void Universe::update(const voxel_pos& pos, float deltaTime) {
size_t chunk_count = 0;
size_t region_count = 0;
#endif
auto rng = std::mt19937(std::rand());
const auto contouringThreshold = rng.max() / (1 + contouring->getQueueSize());
const bool queuesEmpty = loadQueue.empty() && saveQueue.empty();
bool allLazy = true;
auto it = areas.begin();
@ -213,7 +209,7 @@ void Universe::update(const voxel_pos& pos, float deltaTime) {
if (glm::length2(diff) > glm::pow2(keepDistance + it->second->getChunks().getRadius())) {
auto it_c = chunks.begin();
while(it_c != chunks.end()) {
saveQueue.emplace(*it, *it_c);
saveQueue.emplace(*it, std::make_pair(it_c->first, std::dynamic_pointer_cast<Chunk>(it_c->second)));
it_c = chunks.erase(it_c);
}
LOG_I("Unload area " << it->first.index);
@ -229,16 +225,11 @@ void Universe::update(const voxel_pos& pos, float deltaTime) {
auto it_c = chunks.begin();
while(it_c != chunks.end()) {
if (glm::length2(diff - it_c->first) > glm::pow2(keepDistance)) {
saveQueue.emplace(*it, *it_c); //MAYBE: take look
saveQueue.emplace(*it, std::make_pair(it_c->first, std::dynamic_pointer_cast<Chunk>(it_c->second))); //MAYBE: take look
lazyArea = false;
it_c = chunks.erase(it_c);
}else {
const area_<chunk_pos> acPos = std::make_pair(it->first, it_c->first);
if (const auto neighbors = it_c->second->update(deltaTime, rng() < contouringThreshold)) {
contouring->onUpdate(acPos, diff, chunks, neighbors.value());
} else if (chunkChangeArea) {
contouring->onNotify(acPos, diff, chunks);
}
updateChunk(it, it_c, diff, deltaTime);
++it_c;
#if TRACY_ENABLE
chunk_count++;
@ -291,11 +282,6 @@ void Universe::update(const voxel_pos& pos, float deltaTime) {
TracyPlot("ChunkUnload", static_cast<int64_t>(saveQueue.size()));
#endif
}
{
ZoneScopedN("Contouring");
contouring->update(pos, areas);
//MAYBE: if(chunkChange) contouring->notify(chunks);
}
{ // Update entities
ZoneScopedN("Entities");
#if TRACY_ENABLE
@ -320,35 +306,33 @@ void Universe::update(const voxel_pos& pos, float deltaTime) {
robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>> loaded;
for (auto handle = loadedQueue.extractor(); handle.first(loaded);) {
if (const auto it = areas.find(loaded.first.first); it != areas.end()) {
auto &chunks = it->second->setChunks();
chunks.emplace(loaded.first.second, loaded.second);
it->second->setChunks().emplace(loaded.first.second, loaded.second);
const chunk_pos diff = glm::divide(pos - it->second->getOffset().as_voxel());
contouring->onUpdate(loaded.first, diff, chunks, Faces::All);
loadChunk(loaded.first, diff, it->second->getChunks());
}
}
}
}
void Universe::updateChunk(area_map::iterator &, world::ChunkContainer::iterator &, chunk_pos, float deltaTime) {}
void Universe::loadChunk(area_<chunk_pos>, chunk_pos, const world::ChunkContainer &) {}
void Universe::setOptions(const Universe::options& options) {
loadDistance = options.loadDistance;
keepDistance = options.keepDistance;
}
void Universe::setContouring(const std::shared_ptr<contouring::Abstract>& ct) {
contouring = ct;
last_pos = chunk_pos(INT_MAX); // trigger chunkChange on next update
}
Universe::ray_result Universe::raycast(const Ray &ray) const {
Universe::ray_result Universe::raycast(const geometry::Ray &ray) const {
//MAYBE: ray + offset to get float precision
std::vector<voxel_pos> points;
ray.grid(points);
ray_result target;
size_t dist = points.size();
for(auto& area: areas) {
if(ray.intersect(area.second->getBounding()) != IBox::ContainmentType::Disjoint) {
if(ray.intersect(area.second->getBounding()) != geometry::IBox::ContainmentType::Disjoint) {
const auto &offset = area.second->getOffset().as_voxel();
const auto &chunks = area.second->getChunks();
std::shared_ptr<Chunk> chunk = nullptr;
std::shared_ptr<world::Chunk> chunk = nullptr;
chunk_pos chunk_vec(INT_MAX);
for (size_t i = 0; i < dist; i++) {
const auto pos = points[i] - offset;
@ -378,17 +362,17 @@ Universe::ray_result Universe::raycast(const Ray &ray) const {
return target;
}
std::optional<Item> Universe::set(const area_<voxel_pos>& pos, const Voxel& val) {
std::optional<world::Item> Universe::set(const area_<voxel_pos>& pos, const Voxel& val) {
if(const auto it = areas.find(pos.first); it != areas.end()) {
auto &chunks = it->second->setChunks();
const auto split = glm::splitIdx(pos.second);
if(chunks.inRange(split.first))
if(const auto chunk = chunks.findInRange(split.first))
return {chunk.value()->replace(split.second, val)};
return {std::dynamic_pointer_cast<Chunk>(chunk.value())->replace(split.second, val)};
}
return {};
}
ItemList Universe::setCube(const area_<voxel_pos>& pos, const Voxel& val, int radius) {
world::ItemList Universe::setCube(const area_<voxel_pos>& pos, const Voxel& val, int radius) {
ItemList list;
if(const auto it = areas.find(pos.first); it != areas.end()) {
auto& chunks = it->second->setChunks();
@ -400,37 +384,14 @@ ItemList Universe::setCube(const area_<voxel_pos>& pos, const Voxel& val, int ra
const auto split = glm::splitIdx(pos.second + offset);
if(chunks.inRange(split.first))
if(const auto chunk = it->second->setChunks().findInRange(split.first))
list.add(chunk.value()->replace(split.second, val, glm::length2(offset) / radius * .05f));
list.add(std::dynamic_pointer_cast<Chunk>(chunk.value())->replace(split.second, val, glm::length2(offset) / radius * .05f));
}}}
}
return list;
}
bool Universe::move(glm::ifvec3 &pos, const glm::vec3 &vel, int density, float radius) const {
const auto dir = glm::normalize(vel);
const auto velocity = vel * glm::vec3(density);
const auto from = pos * density + dir;
const auto result = raycast(Ray(from, dir, glm::length(velocity) + radius));
if (auto target = std::get_if<ray_target>(&result)) {
const auto target_dist = from.dist(glm::ifvec3(target->offset + target->pos.second, density)) - radius;
pos += vel * glm::vec3(target_dist / glm::length(vel));
return true;
} else if(auto out = std::get_if<ray_out_of_range>(&result)) {
const auto out_dist = from.dist(glm::ifvec3(out->offset + glm::multiply(out->pos.second), density)) - radius - CHUNK_LENGTH;
pos += vel * glm::vec3(out_dist / glm::length(vel));
return true;
}
pos += vel;
return false;
}
bool Universe::collide(const glm::ifvec3 &pos, const glm::vec3 &vel, int density, float radius) const {
const auto dir = glm::normalize(vel);
const auto velocity = vel * glm::vec3(density);
const auto from = pos * density + dir;
return std::holds_alternative<ray_target>(raycast(Ray(from, dir, glm::length(velocity) + radius)));
}
bool Universe::collide_end(const glm::ifvec3 &pos, const glm::vec3 &vel, int density, float radius) const {
return std::holds_alternative<ray_target>(raycast(Ray((pos + vel) * density, vel, radius)));
return std::holds_alternative<ray_target>(raycast(geometry::Ray((pos + vel) * density, vel, radius)));
}
bool Universe::collide_point(const glm::ifvec3 &pos, const glm::vec3 &vel, int density) const {
const auto target = ((pos + vel) * density).as_voxel();
@ -456,17 +417,27 @@ bool Universe::collide_point(const glm::ifvec3 &pos, const glm::vec3 &vel, int d
entity_instance_id Universe::addEntity(entity_id type, const Entity::Instance &instance) {
return std::make_pair(type, entities.at(type).instances.push(instance));
}
void Universe::getEntitiesModels(const std::function<void(const std::vector<glm::mat4>&, buffer::Abstract *const)> &draw, const std::optional<geometry::Frustum> &frustum, const glm::llvec3 &offset, int density) {
std::vector<glm::mat4> mats;
entities.iter([&](entity_id, const Entity &entity) {
entity.instances.iter([&](entity_id, const Entity::Instance &inst) {
const glm::vec3 fPos = (glm::vec3(inst.pos.raw_as_long() - offset * glm::llvec3(density)) + inst.pos.offset) / glm::vec3(density);
if (!frustum.has_value() || frustum.value().contains(geometry::Box::fromMin(fPos, entity.size)))
mats.emplace_back(glm::scale(glm::translate(glm::mat4(1), fPos * (float)density), entity.scale));
});
if(!mats.empty()) {
draw(mats, entity.buffer.get());
mats.resize(0);
}
});
bool Universe::movePlayer(data::generational::id id, glm::ifvec3 pos) {
if(!entities.contains(PLAYER_ENTITY_ID))
return false;
if(!entities.at(PLAYER_ENTITY_ID).instances.contains(id))
return false;
auto &player = entities.at(PLAYER_ENTITY_ID).instances.at(id);
if(player.pos.as_voxel() == pos.as_voxel())
return true;
//TODO: check dist + collision from a to b
movedPlayers.push_back(id);
player.pos = pos;
return true;
}
std::shared_ptr<Chunk> Universe::createChunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd) const {
return std::make_shared<Chunk>(pos, rnd);
}
std::shared_ptr<Chunk> Universe::createChunk(std::istream &str) const {
return std::make_shared<Chunk>(str);
}

View File

@ -2,111 +2,76 @@
#include <string>
#include <thread>
#include <variant>
#include "../../core/world/Universe.hpp"
#include "../../core/data/math.hpp"
#include "../../core/data/safe_queue.hpp"
#include "../../core/data/safe_priority_queue.hpp"
#include "../../core/data/circular_buffer.hpp"
#include "../../core/geometry/Ray.hpp"
#include "../../core/geometry/Frustum.hpp"
#include "forward.h"
#include "Area.hpp"
#include "Voxel.hpp"
namespace contouring {
class Abstract;
};
namespace buffer {
class Abstract;
}
using namespace data;
/// Universe data
namespace world {
/// Whole universe container
class Universe {
namespace world::server {
class Chunk;
/// Whole universe container in abstract server
class Universe: public world::Universe {
public:
/// Distance management
struct options {
/// Radius in chunks to load if missing
int loadDistance = 5;
/// Radius in chunks to keep in memory
int keepDistance = 6;
/// Server config
struct options: world::Universe::options {
/// Storage path
std::string folderPath = "world";
};
Universe(const options &);
~Universe();
virtual ~Universe();
/// Update physics and contouring
void update(const voxel_pos &pos, float deltaTime);
/// Update physics
virtual void update(float deltaTime);
/// Apply new options
void setOptions(const options &);
struct ray_target {
ray_target(area_<voxel_pos> pos, Voxel value, voxel_pos offset):
pos(pos), value(value), offset(offset) { }
area_<voxel_pos> pos;
Voxel value;
voxel_pos offset;
};
struct ray_out_of_range {
ray_out_of_range(area_<chunk_pos> pos, voxel_pos offset):
pos(pos), offset(offset) { }
area_<chunk_pos> pos;
voxel_pos offset;
};
/// @return target voxel, unloaded chunk or monostate
using ray_result = std::variant<std::monostate, ray_target, ray_out_of_range>;
/// Get nearest voxel colliding ray
/// @note ray in world scale
ray_result raycast(const geometry::Ray &ray) const;
/// Set voxel at pos
std::optional<Item> set(const area_<voxel_pos> &pos, const Voxel &val);
/// Set cube of voxel with pos as center
/// MAYBE: allow set multi area
ItemList setCube(const area_<voxel_pos> &pos, const Voxel &val, int radius);
/// Check for collision on movement
bool collide(const glm::ifvec3 &pos, const glm::vec3 &vel, int density, float radius = 0) const;
/// Move with collision check
/// @note must remove velocity after colision
bool move(glm::ifvec3 &pos, const glm::vec3 &vel, int density, float radius = 0) const;
/// Instante entity
entity_instance_id addEntity(entity_id type, const Entity::Instance &instance);
/// Move player
bool movePlayer(data::generational::id id, glm::ifvec3 pos);
/// Get nearest voxel colliding ray
/// @note ray in world scale
ray_result raycast(const geometry::Ray &ray) const override;
/// Check for collision on destination
bool collide_end(const glm::ifvec3 &pos, const glm::vec3 &vel, int density, float radius) const;
/// Check for collision at position
bool collide_point(const glm::ifvec3 &pos, const glm::vec3 &vel, int density) const;
/// Entities commun properties
struct Entity {
Entity(buffer::Abstract* buffer, const glm::vec3& size = glm::vec3(1), const glm::vec3& scale = glm::vec3(1)):
buffer(buffer), size(size), scale(scale) { };
std::unique_ptr<buffer::Abstract> buffer;
glm::vec3 size;
glm::vec3 scale;
struct Instance {
glm::ifvec3 pos;
glm::vec3 velocity;
};
data::generational::vector<Instance> instances;
};
/// Instante entity
entity_instance_id addEntity(entity_id type, const Entity::Instance &instance);
void getEntitiesModels(const std::function<void(const std::vector<glm::mat4>&, buffer::Abstract *const)> &draw, const std::optional<geometry::Frustum> &frustum, const glm::llvec3 &offset, int density);
protected:
/// Save all chunks (saveThread uses virtual calls)
void saveAll(bool remove);
/// Change contouring worker
void setContouring(const std::shared_ptr<contouring::Abstract>& ct);
/// Get current contouring worker
std::shared_ptr<contouring::Abstract> getContouring() const {
return contouring;
}
using area_map = robin_hood::unordered_map<area_id, std::shared_ptr<Area>>;
private:
chunk_pos last_pos = chunk_pos(INT_MAX);
virtual std::shared_ptr<Chunk> createChunk(const chunk_pos &pos, const std::unique_ptr<generator::Abstract> &rnd) const;
virtual std::shared_ptr<Chunk> createChunk(std::istream &str) const;
virtual void updateChunk(area_map::iterator&, world::ChunkContainer::iterator&, chunk_pos, float deltaTime);
virtual void loadChunk(area_<chunk_pos>, chunk_pos, const world::ChunkContainer &);
std::vector<data::generational::id> movedPlayers;
/// Alive areas containing chunks
area_map areas;
using area_it_t = robin_hood::pair<area_id, std::shared_ptr<Area>>;
/// Dead areas
data::generational::vector<Area::params> far_areas;
@ -119,7 +84,7 @@ namespace world {
safe_priority_queue_map<area_<chunk_pos>, std::shared_ptr<Area>, int, area_hash> loadQueue; //NOTE: consider Area const (getRegion uses mutex)
safe_queue<robin_hood::pair<area_<chunk_pos>, std::shared_ptr<Chunk>>> loadedQueue;
using save_task_t = std::pair<area_it_t, robin_hood::pair<chunk_pos, std::shared_ptr<Chunk>>>;
using save_task_t = std::pair<area_it_t, robin_hood::pair<chunk_pos, std::shared_ptr<world::server::Chunk>>>;
data::safe_queue<save_task_t> saveQueue; //NOTE: consider Area and Chunk const
int loadDistance;
@ -127,8 +92,5 @@ namespace world {
std::string folderPath;
dict_set dicts;
/// Contouring worker
std::shared_ptr<contouring::Abstract> contouring;
};
}

View File

@ -2,8 +2,7 @@
#include <variant>
#include "Noise.hpp"
#include "../../core/world/materials.hpp"
#include "Voxel.hpp"
#include "../../core/world/Voxel.hpp"
#include "../../core/data/math.hpp"
namespace world::generator {

View File

@ -2,7 +2,7 @@
#include <filesystem>
using namespace world;
using namespace world::server;
#define REMOVE_CORRUPTED 1

View File

@ -3,11 +3,11 @@
#include <vector>
#include <string>
#include <shared_mutex>
#include "../forward.h"
#include "../../../core/world/forward.h"
#include "common.hpp"
#include "../../../core/data/math.hpp"
namespace world {
namespace world::server {
///Group of chunks saved as a single file only pointer
class FileRegion {
public:

View File

@ -1,6 +1,6 @@
#include "Memory.hpp"
using namespace world;
using namespace world::server;
#define REMOVE_CORRUPTED 1
#define LAZYNESS 8

View File

@ -4,11 +4,11 @@
#include <string>
#include <shared_mutex>
#include <zstd.h>
#include "../forward.h"
#include "../../../core/world/forward.h"
#include "common.hpp"
#include "../../../core/data/math.hpp"
namespace world {
namespace world::server {
///Group of chunks saved as a single file in memory
class MemoryRegion {
public:

View File

@ -6,7 +6,7 @@
#include <cassert>
#include "../../../core/utils/logger.hpp"
namespace world {
namespace world::server {
struct read_ctx {
~read_ctx() {
ZSTD_freeDCtx(ctx);
@ -24,12 +24,17 @@ namespace world {
class dict_set {
public:
dict_set(const std::string& path) {
std::ifstream is(path, std::ios::in | std::ios::binary | std::ios::ate);
if(!is.good()) {
LOG_E("Missing dict " << path);
exit(1);
}
dict_set(const std::vector<std::string>& paths) {
std::ifstream is = [&]() {
for(auto& path: paths) {
std::ifstream is(path, std::ios::in | std::ios::binary | std::ios::ate);
if(is.good()) {
return is;
}
is.close();
}
FATAL("Missing dict " << paths.back());
}();
const auto end = is.tellg();
is.seekg(0, std::ios::beg);
std::vector<char> dict(end - is.tellg());

View File

@ -1,7 +1,7 @@
#ifdef LOW_MEMORY
#include "File.hpp"
namespace world {typedef FileRegion Region;}
namespace world::server {typedef FileRegion Region;}
#else
#include "Memory.hpp"
namespace world {typedef MemoryRegion Region;}
namespace world::server {typedef MemoryRegion Region;}
#endif