Big bad shared server rework
This commit is contained in:
parent
0dcff4b885
commit
ddbcd04721
|
@ -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",
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@ public:
|
|||
|
||||
double getTime() const;
|
||||
|
||||
static void wait(int microseconds = 100);
|
||||
|
||||
private:
|
||||
GLFWwindow *ptr;
|
||||
int targetFPS;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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 { }
|
|
@ -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>(¶ms)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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>(¶ms)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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>();
|
||||
|
||||
}
|
|
@ -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:
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
*/
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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++) {
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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));
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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 {};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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)));
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <robin_hood.h>
|
||||
#include "../../core/world/materials.hpp"
|
||||
#include <cassert>
|
||||
|
||||
namespace world {
|
||||
/// Universe unit
|
|
@ -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>;
|
||||
}
|
|
@ -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>>;
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <climits>
|
||||
|
||||
namespace world::materials {
|
||||
//TODO: AoS to SoA compile time
|
||||
|
|
|
@ -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;
|
||||
|
|
44
src/main.cpp
44
src/main.cpp
|
@ -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;
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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 {};
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
|
|
|
@ -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:");
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#include "StandaloneUniverse.hpp"
|
||||
#include "Chunk.hpp"
|
||||
|
||||
using namespace world::server;
|
||||
|
||||
StandaloneUniverse::StandaloneUniverse(const options &o): Universe(o) { }
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include "Universe.hpp"
|
||||
|
||||
namespace world::server {
|
||||
/// Logic only server::Universe
|
||||
class StandaloneUniverse: public Universe {
|
||||
public:
|
||||
StandaloneUniverse(const options &);
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <filesystem>
|
||||
|
||||
using namespace world;
|
||||
using namespace world::server;
|
||||
|
||||
#define REMOVE_CORRUPTED 1
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include "Memory.hpp"
|
||||
|
||||
using namespace world;
|
||||
using namespace world::server;
|
||||
|
||||
#define REMOVE_CORRUPTED 1
|
||||
#define LAZYNESS 8
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue