Commit 0dcff4b8 authored by Shu's avatar Shu 🍠
Browse files

Split client server

parent f0f12da6
#include "Client.hpp"
#include "render/gl/Renderer.hpp"
#include "render/gl/UI.hpp"
#include "render/gl/Pipeline.hpp"
#include "InputMap.hpp"
Client::Client() { }
Client::~Client() { }
void Client::run() {
if (!render::gl::Renderer::Load(window, {options.renderer.clear_color}))
return;
window.setTargetFPS(options.target_fps);
InputMap inputs(window.getPtr());
Controllable player(window.getPtr(), inputs, options.control);
Camera camera(&player, options.camera);
auto *pipeline = static_cast<render::gl::Pipeline*>(render::Renderer::Get()->createPipeline(options.renderer));
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();
//TODO: loop
do {
window.startFrame();
{ // Update
ZoneScopedN("Update");
static double lastTime = window.getTime();
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);
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))
) {
player.position += player.velocity;
state.position = player.position;
}
camera.update();
pipeline->lookFrom(camera);
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);
if(auto target = std::get_if<world::Universe::ray_target>(&ray_result)) {
state.look_at = *target;
} else {
state.look_at = {};
}
}
if (state.capture_mouse) {
if (state.look_at.has_value()) {
ZoneScopedN("Edit");
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);
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);
}
if (inputs.isDown(Input::Throw)) {
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);
inputs.saveKeys();
lastTime = partTime;
}
{
ZoneScopedN("UI");
const auto actions = render::UI::Get()->draw(options, state, reports);
if (actions && render::UI::Actions::FPS) {
window.setTargetFPS(options.target_fps);
}
if (actions && render::UI::Actions::FullScreen) {
window.setFullscreen(options.fullscreen);
}
if(actions && render::UI::Actions::ClearColor) {
pipeline->setClearColor(options.renderer.clear_color);
}
if(actions && render::UI::Actions::RendererSharders) {
pipeline->reloadShaders(options.renderer.voxel);
}
if(actions && render::UI::Actions::RendererTextures) {
pipeline->reloadTextures(options.renderer.textures, options.renderer.mipMapLOD, options.renderer.anisotropy);
}
if(actions && render::UI::Actions::World) {
world.setOptions(options.world);
}
if(actions && render::UI::Actions::Camera) {
camera.setOptions(options.camera);
}
if(actions && render::UI::Actions::Control) {
player.setOptions(options.control);
}
if(actions && render::UI::Actions::ChangeContouring) {
state.contouring = NULL;
world.setContouring(contouring::load(options.contouring_idx, options.contouring_data));
state.contouring = world.getContouring();
}
pipeline->SkyEnable = options.renderer.skybox;
}
{ // Rendering
ZoneScopedN("Render");
pipeline->beginFrame();
reports.models_count = 0;
reports.tris_count = 0;
std::optional<geometry::Frustum> frustum;
if(options.culling >= 0) {
frustum = {camera.getFrustum()};
}
const auto offset = state.position.raw_as_long();
{ // Chunks
const auto pass = pipeline->beginWorldPass();
const auto draw = [&](glm::mat4 model, buffer::Abstract *const buffer, const contouring::Abstract::area_info &area, const voxel_pos &pos) {
pipeline->SphereProj = glm::vec4(pos, std::get<1>(area));
pipeline->Curvature = std::get<2>(area);
reports.models_count++;
reports.tris_count += buffer->draw(pass(model));
};
if (options.culling > 0) {
std::vector<glm::vec3> occlusion;
const auto ratio = options.culling * 2;
occlusion.reserve(glm::pow2(ratio * 2 - 1));
const auto [ch, cv] = player.getAngles();
const auto max_v = tan(options.camera.fov / 2.), max_h = Window::RATIO * max_v;
for(int iv = -ratio + 1; iv < ratio; iv++) {
const auto v = cv + max_v * iv / ratio;
for(int ih = -ratio + 1; ih < ratio; ih++) {
const auto h = ch + max_h * ih / ratio;
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);
} else {
world.getContouring()->getModels(draw, frustum, offset, options.voxel_density);
}
}
{ // Entities
const auto pass = pipeline->beginEntityPass();
const auto draw = [&](const std::vector<glm::mat4>& models, buffer::Abstract *const buffer) {
reports.models_count += models.size();
reports.tris_count += buffer->draw(pass(models));
};
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));
reports.models_count++;
reports.tris_count += pipeline->drawIndicatorCube(model);
}
pipeline->endPass();
render::UI::Get()->render();
}
{ // Swap buffers
ZoneScopedN("Swap");
pipeline->swapBuffer(window.getPtr());
inputs.poll();
FrameMark;
}
window.waitTargetFPS();
} while (!(inputs.isDown(Input::Quit) || window.shouldClose()));
render::UI::Unload();
render::Renderer::Unload();
window.destroy();
contouring::save(options.contouring_idx, state.contouring, options.contouring_data);
options.save();
}
\ No newline at end of file
#pragma once
#include "state.h"
#include "Window.hpp"
namespace render {
class Renderer;
};
/// Client view
class Client {
public:
Client();
~Client();
void run();
protected:
private:
config::options options;
config::state state;
config::reports reports;
Window window;
};
\ No newline at end of file
......@@ -49,4 +49,6 @@ bool InputMap::isPressing(Mouse input) const {
}
bool InputMap::isReleasing(Mouse input) const {
return !isDown(input) && wasDown(input);
}
\ No newline at end of file
}
void InputMap::poll() const { glfwPollEvents(); }
\ No newline at end of file
......@@ -3,12 +3,11 @@
#include <robin_hood.h>
#include <vector>
#include <GL/gl3w.h>
#include <GLFW/glfw3.h>
enum class Input {
Forward, Backward, Left, Right, Up, Down,
Mouse, Debug, Throw
Mouse, Debug, Throw, Quit
};
enum class Mouse {
Left = GLFW_MOUSE_BUTTON_LEFT,
......@@ -35,6 +34,8 @@ public:
bool isPressing(Mouse input) const;
bool isReleasing(Mouse input) const;
void poll() const;
private:
GLFWwindow *window;
robin_hood::unordered_map<Input, int> Map = {
......@@ -46,7 +47,8 @@ private:
{Input::Down, GLFW_KEY_LEFT_SHIFT},
{Input::Mouse, GLFW_KEY_E},
{Input::Debug, GLFW_KEY_F3},
{Input::Throw, GLFW_KEY_B}
{Input::Throw, GLFW_KEY_B},
{Input::Quit, GLFW_KEY_ESCAPE}
};
const std::vector<Input> Toggles = {
Input::Mouse, Input::Debug, Input::Throw
......
#include "Window.hpp"
#include <GL/gl3w.h>
#include <GLFW/glfw3.h>
#include <exception>
#include <thread>
#include <chrono>
#include "../core/utils/logger.hpp"
/// Default floating window width
constexpr auto DEFAULT_WIDTH = 1280;
/// Default floating window height
constexpr auto DEFAULT_HEIGHT = 720;
constexpr auto MIN_WIDTH = 854;
constexpr auto MIN_HEIGHT = 480;
constexpr auto APP_NAME = "Univerxel";
Window::Window() : ptr(nullptr), targetFPS(60) {
if (!glfwInit()) {
FATAL("Failed to initialize GLFW");
}
}
Window::~Window() {
glfwTerminate();
}
bool Window::create(const CreateInfo &opt) {
if(isReady()) {
LOG_W("Previous window in use");
return false;
}
glfwWindowHint(GLFW_SAMPLES, opt.samples);
switch (opt.client.type) {
case CreateInfo::Client::Type::GL:
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, opt.client.major);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, opt.client.minor);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
break;
default:
FATAL("Unsupported client type");
break;
}
#if FIXED_WINDOW
glfwWindowHint(GLFW_RESIZABLE, false); //Note: Dev-only: Force floating on i3
#endif
ptr = glfwCreateWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT, APP_NAME, NULL, NULL);
if (ptr == NULL) {
LOG_E("Failed to open GLFW window");
return false;
}
glfwMakeContextCurrent(ptr);
// Ensure we can capture the escape key being pressed below
glfwSetInputMode(ptr, GLFW_STICKY_KEYS, GL_TRUE);
// Hide the mouse and enable unlimited mouvement
glfwSetInputMode(ptr, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// Set the mouse at the center of the screen
// TODO: move to input manager
glfwPollEvents();
glfwSetCursorPos(ptr, DEFAULT_WIDTH / 2, DEFAULT_HEIGHT / 2);
// Force aspect ratio
glfwSetWindowSizeLimits(ptr, MIN_WIDTH, MIN_HEIGHT, GLFW_DONT_CARE, GLFW_DONT_CARE);
glfwSetWindowAspectRatio(ptr, RATIO_WIDTH, RATIO_HEIGHT);
glfwSetFramebufferSizeCallback(ptr, opt.pfnResize);
glfwSetWindowTitle(ptr, APP_NAME);
glfwSetWindowAttrib(ptr, GLFW_RESIZABLE, true);
return true;
}
bool Window::isReady() const { return ptr != nullptr; }
void Window::setTargetFPS(int val) {
targetFPS = val;
glfwSwapInterval(static_cast<int>(val < MIN_FPS));
}
double Window::getTime() const {
return glfwGetTime();
}
void Window::startFrame() {
frameStart = getTime();
}
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));
}
}
}
void Window::destroy() {
glfwDestroyWindow(ptr);
}
bool Window::shouldClose() { return glfwWindowShouldClose(ptr); }
void Window::setFullscreen(bool val) {
// MAYBE: real fullscreen
if(fullscreen != val) {
fullscreen = val;
if (val) {
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
const GLFWvidmode *mode = glfwGetVideoMode(monitor);
glfwSetWindowMonitor(ptr, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
} else {
glfwSetWindowMonitor(ptr, nullptr, 0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT, GLFW_DONT_CARE);
}
}
}
\ No newline at end of file
#pragma once
#include "../core/flags.hpp"
typedef struct GLFWwindow GLFWwindow;
typedef void (*GLFWframebuffersizefun)(GLFWwindow*, int, int);
/// GLFW context and window
class Window {
public:
Window();
~Window();
static constexpr auto MIN_FPS = 24;
static constexpr auto MAX_FPS = 240;
static constexpr auto RATIO_WIDTH = 16;
static constexpr auto RATIO_HEIGHT = 9;
/// Fixed window ratio
static constexpr auto RATIO = RATIO_WIDTH / (RATIO_HEIGHT * 1.0f);
struct CreateInfo {
GLFWframebuffersizefun pfnResize;
struct Client {
enum class Type { GL, VK } type;
int major;
int minor;
} client;
int samples;
};
bool create(const CreateInfo &opt);
void destroy();
bool isReady() const;
void setTargetFPS(int val);
void setFullscreen(bool val);
_FORCE_INLINE_ GLFWwindow *getPtr() { return ptr; }
void startFrame();
void waitTargetFPS();
bool shouldClose();
double getTime() const;
private:
GLFWwindow *ptr;
int targetFPS;
bool fullscreen;
double frameStart;
};
#include "Camera.hpp"
#include <glm/gtc/matrix_transform.hpp>
#include "../render/window.hpp"
#include "../Window.hpp"
Camera::Camera(const Controllable* origin, const Camera::options& opt): origin(origin), o(opt) {
updateProjection();
......@@ -9,7 +9,7 @@ Camera::Camera(const Controllable* origin, const Camera::options& opt): origin(o
Camera::~Camera() { }
void Camera::updateProjection() {
ProjectionMatrix = glm::perspective(o.fov, RATIO, o.near, o.far);
ProjectionMatrix = glm::perspective(o.fov, Window::RATIO, o.near, o.far);
}
void Camera::update() {
......
#pragma once
#include "Controllable.hpp"
#include "../data/geometry/Frustum.hpp"
#include "../data/geometry/Ray.hpp"
#include "../../core/geometry/Frustum.hpp"
#include "../../core/geometry/Ray.hpp"
/// Moving perspective camera
class Camera {
......
#include "Controllable.hpp"
Controllable::Controllable(GLFWwindow *window, const InputMap& inputs, const Controllable::options& opt): position(voxel_pos(0), 1), window(window), inputs(inputs), o(opt){ }
Controllable::Controllable(GLFWwindow *window, const InputMap& inputs, const Controllable::options& opt): position(voxel_pos(100, 0, 0), 1), window(window), inputs(inputs), o(opt){ }
Controllable::~Controllable() { }
Controllable::axis Controllable::getAxis() const {
......
#pragma once
#include "InputMap.hpp"
#include "../InputMap.hpp"
#include <GL/gl3w.h>
#include <GLFW/glfw3.h>
#include "../data/glm.hpp"
#include "../world/position.h"
#include "../../core/data/glm.hpp"
#include "../../core/world/position.h"
/// Player controller moving entity
struct Controllable {
......
#pragma once
#include "gl/pass/VoxelProgram.hpp"
struct GLFWwindow;
class Camera;
namespace render {
/// Handle rendering passes and params
class Pipeline {
public:
/// Rendering options
struct options {
/// Voxel passes
pass::VoxelProgram::options voxel;
/// Display skybox
bool skybox = true;
/// Display only wires
bool wireframe = false;
/// Texture pack name
std::string textures = "1024-realistic";
/// Textures quality
float mipMapLOD = -.5;
/// Textures anisotropic mapping
int anisotropy = 0;
/// Depth color
glm::vec4 clear_color;
};
virtual ~Pipeline() { }
/// Start new frame and setup
virtual void beginFrame() = 0;
/// Apply postprocessing
virtual void endPass() = 0;
/// Swap displayed image
virtual void swapBuffer(GLFWwindow*) = 0;
/// Apply camera matrices
virtual void lookFrom(const Camera&) = 0;
virtual void setClearColor(glm::vec4) = 0;
};
}
\ No newline at end of file
#include "Renderer.hpp"
using namespace render;
Renderer *Renderer::sInstance = nullptr;
void Renderer::Unload() {
delete sInstance;
sInstance = nullptr;
}
\ No newline at end of file
#pragma once
#include "../Window.hpp"
#include "Pipeline.hpp"
#include <cassert>
namespace render {
/// Rendering plateform interface
class Renderer {
public:
virtual ~Renderer() { }
virtual Pipeline* createPipeline(const Pipeline::options&) = 0;
virtual void loadUI(Window&) = 0;
static _FORCE_INLINE_ Renderer *Get() {
assert(sInstance != nullptr && "Uninitialized renderer");
return sInstance;
}
static void Unload();
protected:
static Renderer *sInstance;
};
}
\ No newline at end of file
#include "UI.hpp"
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include "../state.h"
#include "../contouring/Abstract.hpp"
#include "../world/materials.hpp"
#include "../../core/world/materials.hpp"
void UI::setup(GLFWwindow* window) {
using namespace render;
UI *UI::sInstance = nullptr;
UI::UI() {
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::StyleColorsDark();
// Setup Platform/Renderer bindings
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 130");
}
void UI::unload() {
ImGui_ImplGlfw_Shutdown();
ImGui_ImplOpenGL3_Shutdown();
UI::~UI() {
ImGui::DestroyContext();
}
UI::Actions UI::draw(options &options, state &state, const reports &reports, GLuint aim) {
UI::Actions UI::draw(config::options &options, config::state &state, const config::reports &reports, intptr_t aim) {