Introduce event-based input system

This commit is contained in:
2023-06-11 00:23:44 +02:00
parent 3fcf6414f8
commit 1f89ca59f0
18 changed files with 434 additions and 423 deletions

View File

@@ -2,6 +2,7 @@
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: Never
BreakBeforeBraces: Mozilla
BreakConstructorInitializers: AfterColon
BinPackParameters: 'false'
BinPackArguments: 'false'
ColumnLimit: '100'

View File

@@ -14,42 +14,28 @@
"deprecated": true,
"unusedCli": true,
"systemVars": false
},
"errors": {
"dev": true,
"deprecated": true
}
},
{
"name": "dev-mode",
"hidden": true,
"inherits": "cmake-pedantic",
"cacheVariables": {
"CMAKE_COLOR_DIAGNOSTICS": true
}
},
{
"name": "ci-std",
"name": "std",
"description": "This preset makes sure the project actually builds with at least the specified standard",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_EXTENSIONS": "OFF"
"CMAKE_CXX_EXTENSIONS": "OFF",
"CMAKE_CXX_STANDARD": "20",
"CMAKE_CXX_STANDARD_REQUIRED": "ON"
}
},
{
"name": "flags-unix",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "-Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wshadow -Wformat=2 -Wundef"
}
},
{
"name": "ci-unix",
"name": "dev",
"generator": "Unix Makefiles",
"hidden": true,
"inherits": ["flags-unix", "ci-std"],
"binaryDir": "${sourceDir}/build",
"inherits": [
"std"
],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_FLAGS": "-Wall -Wextra -Wpedantic"
}
}
]

View File

@@ -1,15 +0,0 @@
#version 330 core
layout(location = 0) out vec4 f_color;
in vec3 v_texCoord;
uniform samplerCube u_skybox;
void main() {
vec3 fragmentColor = vec3(texture(u_skybox, v_texCoord));
f_color = vec4(fragmentColor, 1.0f);
}

View File

@@ -1,17 +0,0 @@
#version 330 core
layout(location = 0) in vec3 a_position;
out vec3 v_texCoord;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 u_viewProjectionMatrix;
void main() {
gl_Position = u_viewProjectionMatrix * vec4(a_position, 1.0);
v_texCoord = a_position;
}

View File

@@ -14,7 +14,7 @@ add_library(fever_engine
scene/gltf_loader.cpp
scene/scene.cpp
util/log.cpp
window/Window.cpp
window/window.cpp
)
target_compile_features(fever_engine PUBLIC cxx_std_20)

View File

@@ -4,8 +4,8 @@
#include "core/shader.h"
#include "core/time.h"
#include "input/input.h"
#include "scene/gltf_loader.h"
#include "window/Window.h"
#include "scene/scene.h"
#include "window/window.h"
#include <GLFW/glfw3.h>
#include <array>
@@ -22,27 +22,34 @@
using namespace entt::literals;
static constexpr unsigned MAX_FPS = 60;
Controller::Controller()
: m_gameWindow(std::make_shared<Window>()),
post_processing_framebuffer(m_gameWindow->physical_dimensions()),
m_gltf_loader{.image_cache = m_image_cache,
.material_cache = m_material_cache,
.mesh_cache = m_mesh_cache,
.shader_cache = m_shader_cache,
.scene_cache = m_scene_cache,
.gltf_mesh_cache = m_gltf_mesh_cache,
.gltf_node_cache = m_gltf_node_cache},
m_gltf_cache(m_gltf_loader)
Controller::Controller() :
m_gameWindow(std::make_shared<Window>(event_dispatcher)),
post_processing_framebuffer(m_gameWindow->physical_dimensions()),
m_gltf_loader{.image_cache = m_image_cache,
.material_cache = m_material_cache,
.mesh_cache = m_mesh_cache,
.shader_cache = m_shader_cache,
.scene_cache = m_scene_cache,
.gltf_mesh_cache = m_gltf_mesh_cache,
.gltf_node_cache = m_gltf_node_cache,
.registry = registry},
m_gltf_cache(m_gltf_loader),
key_listener{.registry = registry},
cursor_listener{.registry = registry}
{
std::filesystem::path document_path("ABeautifulGame.glb");
std::filesystem::path document_path("WaterBottle/glTF-Binary/WaterBottle.glb");
entt::hashed_string document_hash(document_path.c_str());
entt::resource<Gltf> gltf_document =
m_gltf_cache.load(document_hash, document_path).first->second;
m_scene = gltf_document->default_scene.value_or(gltf_document->scenes.at(0)).handle();
Input::State<Input::KeyCode>::init_state(registry);
event_dispatcher.sink<Window::ResizeEvent>().connect<&Controller::recreate_framebuffer>(this);
event_dispatcher.sink<Input::KeyInput>().connect<&Input::KeyListener::key_event>(key_listener);
event_dispatcher.sink<Input::MouseMotion>().connect<&Input::CursorListener::cursor_event>(cursor_listener);
}
void Controller::run()
@@ -54,41 +61,44 @@ void Controller::run()
spdlog::info("Startup complete. Enter game loop.");
// This is the game loop
while (glfwWindowShouldClose(&m_gameWindow->glfw_window()) == GLFW_FALSE) {
while (glfwWindowShouldClose(&m_gameWindow->handle()) == GLFW_FALSE) {
// --- Timing ---
Time::update_delta_time(m_scene->registry());
Time::update_delta_time(registry);
// --- Check events, handle input ---
m_gameWindow->clear_mouse_cursor_input();
// m_gameWindow->clear_mouse_cursor_input();
glfwPollEvents();
Input::handle_keyboard_input(m_scene->registry(), m_gameWindow->key_input());
Input::handle_mouse_cursor_input(m_scene->registry(), m_gameWindow->mouse_cursor_input());
Input::handle_mouse_button_input(m_scene->registry(), m_gameWindow->mouse_button_input());
m_gameWindow->update_catched_mouse(m_scene->registry());
m_gameWindow->update_descriptor(m_scene->registry());
// --- Update game state ---
event_dispatcher.update();
m_gameWindow->update_descriptor(registry);
m_gameWindow->mouse_catching(registry);
m_gameWindow->close_on_esc(registry);
m_scene->update();
Input::State<Input::KeyCode>::update_state(registry);
Input::reset_mouse_motion(registry);
// --- Render and buffer swap ---
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
post_processing_framebuffer.bind();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Light::update_lights(m_scene->registry(), standard_material_shader);
Render::render(m_scene->registry());
Light::update_lights(registry, standard_material_shader);
Render::render(registry);
Framebuffer::unbind();
post_processing_framebuffer.draw(post_processing_shader);
glfwSwapBuffers(&m_gameWindow->glfw_window());
// Update window size
if (m_gameWindow->dimensions_changed()) {
auto dimensions = m_gameWindow->physical_dimensions();
post_processing_framebuffer = Framebuffer(dimensions);
}
glfwSwapBuffers(&m_gameWindow->handle());
}
}
void Controller::recreate_framebuffer()
{
auto dimensions = m_gameWindow->physical_dimensions();
post_processing_framebuffer = Framebuffer(dimensions);
}

View File

@@ -2,8 +2,9 @@
#include "core/graphics/framebuffer.h"
#include "core/shader.h"
#include "entt/entity/fwd.hpp"
#include "scene/gltf_loader.h"
#include "scene/scene.h"
#include "input/input.h"
#include <entt/entt.hpp>
#include <glm/glm.hpp>
@@ -11,9 +12,9 @@
#include <unordered_map>
#include <vector>
class Window;
class Scene;
class Camera;
class Window;
class Framebuffer;
class Controller
@@ -24,14 +25,19 @@ public:
void run();
private:
void recreate_framebuffer();
std::shared_ptr<Window> m_gameWindow;
std::shared_ptr<Scene> m_scene;
Shader skybox_shader{"skybox", "data/shaders"};
Shader post_processing_shader{"post_processing", "data/shaders"};
Framebuffer post_processing_framebuffer;
double m_deltaTime{};
entt::registry registry;
entt::dispatcher event_dispatcher{};
Input::KeyListener key_listener;
Input::CursorListener cursor_listener;
// Resource caches
entt::resource_cache<Image> m_image_cache;

View File

@@ -1,7 +1,7 @@
#include "camera.h"
#include "core/time.h"
#include "input/input.h"
#include "window/Window.h"
#include "window/window.h"
#include <GLFW/glfw3.h>
#include <algorithm>
@@ -40,7 +40,7 @@ void Camera::keyboard_movement(entt::registry& registry)
};
auto& movement_context = registry.ctx().emplace<KeyboardMovementContext>();
auto const& key_input = registry.ctx().get<Input::Key>();
auto const& key_state = registry.ctx().get<Input::State<Input::KeyCode>>();
auto const& delta_time = registry.ctx().get<Time::Delta>();
auto camera_view = registry.view<Camera const, Transform, GlobalTransform const>();
@@ -55,29 +55,28 @@ void Camera::keyboard_movement(entt::registry& registry)
SPEED * delta_time.delta.count() * (movement_context.accelerate ? ACCELERATION : 1.0F);
movement_context.accelerate = false;
for (auto const& [key, pressed] : key_input.key_map) {
if (key == GLFW_KEY_W && pressed) {
delta_pos += delta_factor * glm::normalize(front_vec);
}
if (key == GLFW_KEY_S && pressed) {
delta_pos -= delta_factor * glm::normalize(front_vec);
}
if (key == GLFW_KEY_A && pressed) {
delta_pos -= delta_factor * glm::normalize(glm::cross(front_vec, Camera::UP_VECTOR));
}
if (key == GLFW_KEY_D && pressed) {
delta_pos += delta_factor * glm::normalize(glm::cross(front_vec, Camera::UP_VECTOR));
}
if (key == GLFW_KEY_SPACE && pressed) {
delta_pos += delta_factor * UP_VECTOR;
}
if (key == GLFW_KEY_LEFT_SHIFT && pressed) {
delta_pos -= delta_factor * UP_VECTOR;
}
if (key == GLFW_KEY_LEFT_ALT && pressed) {
movement_context.accelerate = true;
}
if (key_state.pressed(Input::KeyCode{GLFW_KEY_W})) {
delta_pos += delta_factor * glm::normalize(front_vec);
}
if (key_state.pressed(Input::KeyCode{GLFW_KEY_S})) {
delta_pos -= delta_factor * glm::normalize(front_vec);
}
if (key_state.pressed(Input::KeyCode{GLFW_KEY_A})) {
delta_pos -= delta_factor * glm::normalize(glm::cross(front_vec, Camera::UP_VECTOR));
}
if (key_state.pressed(Input::KeyCode{GLFW_KEY_D})) {
delta_pos += delta_factor * glm::normalize(glm::cross(front_vec, Camera::UP_VECTOR));
}
if (key_state.pressed(Input::KeyCode{GLFW_KEY_SPACE})) {
delta_pos += delta_factor * UP_VECTOR;
}
if (key_state.pressed(Input::KeyCode{GLFW_KEY_LEFT_SHIFT})) {
delta_pos -= delta_factor * UP_VECTOR;
}
if (key_state.pressed(Input::KeyCode{GLFW_KEY_LEFT_ALT})) {
movement_context.accelerate = true;
}
camera_transform.translation += delta_pos;
}
@@ -87,16 +86,12 @@ void Camera::mouse_orientation(entt::registry& registry)
auto camera_entity = camera_view.front();
auto [camera, camera_transform] = camera_view.get(camera_entity);
auto const& mouse_cursor_input = registry.ctx().get<Input::MouseCursor>();
auto [deltaX, deltaY] = mouse_cursor_input.cursor_movement;
auto const& mouse_cursor_input = registry.ctx().get<Input::MouseMotion>();
auto delta_x = mouse_cursor_input.delta.x;
auto delta_y = mouse_cursor_input.delta.y;
if (std::abs(deltaX) < std::numeric_limits<double>::epsilon() &&
std::abs(deltaY) < std::numeric_limits<double>::epsilon()) {
return;
}
auto pitch = static_cast<float>(deltaY);
auto yaw = static_cast<float>(deltaX);
auto pitch = static_cast<float>(-delta_y);
auto yaw = static_cast<float>(delta_x);
// Orthographic projection currently unsupported
auto& camera_perspective = std::get<Perspective>(camera.projection);

View File

@@ -1,21 +1,44 @@
#include "input.h"
void Input::handle_keyboard_input(entt::registry &registry, Key const &key_input)
#include <GLFW/glfw3.h>
namespace Input {
void KeyListener::key_event(KeyInput const& key_input_event)
{
registry.ctx().erase<Key>();
registry.ctx().emplace<Key>(key_input);
auto& key_state = registry.ctx().emplace<State<KeyCode>>();
if (key_input_event.action == static_cast<Action>(GLFW_PRESS)) {
key_state.press(key_input_event.key_code);
} else if (key_input_event.action == static_cast<Action>(GLFW_RELEASE)) {
key_state.release(key_input_event.key_code);
}
}
void Input::handle_mouse_button_input(entt::registry &registry,
MouseButton const &mouse_button_input)
void CursorListener::cursor_event(MouseMotion const& mouse_motion_event)
{
registry.ctx().erase<MouseButton>();
registry.ctx().emplace<MouseButton>(mouse_button_input);
auto& mouse_motion = registry.ctx().emplace<MouseMotion>();
mouse_motion.delta += mouse_motion_event.delta;
}
void Input::handle_mouse_cursor_input(entt::registry &registry,
MouseCursor const &mouse_cursor_input)
void reset_mouse_motion(entt::registry& registry)
{
registry.ctx().erase<MouseCursor>();
registry.ctx().emplace<MouseCursor>(mouse_cursor_input);
auto& mouse_motion = registry.ctx().emplace<MouseMotion>();
mouse_motion = {};
}
template <typename T> void State<T>::init_state(entt::registry& registry)
{
registry.ctx().emplace<State<T>>();
}
template <typename T> void State<T>::update_state(entt::registry& registry)
{
auto& state = registry.ctx().get<State<T>>();
state.just_pressed_keys.clear();
state.just_released_keys.clear();
}
template class State<KeyCode>;
} // namespace Input

View File

@@ -1,27 +1,77 @@
#pragma once
#include "entt/entity/fwd.hpp"
#include <entt/entt.hpp>
#include <unordered_map>
#include <glm/glm.hpp>
#include <set>
#include <utility>
namespace Input {
struct Key
enum class KeyCode : int
{
std::unordered_map<int, bool> key_map;
};
struct MouseButton
enum class Action : int
{
std::unordered_map<int, bool> button_map;
};
struct MouseCursor
{
std::pair<double, double> cursor_movement;
};
void handle_keyboard_input(entt::registry &registry, Key const &key_input);
void handle_mouse_button_input(entt::registry &registry, MouseButton const &mouse_button_input);
void handle_mouse_cursor_input(entt::registry &registry, MouseCursor const &mouse_cursor_input);
struct KeyInput
{
KeyCode key_code;
Action action;
};
struct MouseMotion
{
glm::vec2 delta{};
};
template <typename T> class State
{
public:
auto pressed(T input) const -> bool { return pressed_keys.contains(input); }
auto just_pressed(T input) const -> bool { return just_pressed_keys.contains(input); }
auto just_released(T input) const -> bool { return just_pressed_keys.contains(input); }
static void init_state(entt::registry& registry);
static void update_state(entt::registry& registry);
private:
void press(T input)
{
if (pressed_keys.insert(input).second) {
just_pressed_keys.insert(input);
}
}
void release(T input)
{
if (pressed_keys.erase(input) != 0) {
just_released_keys.insert(input);
}
}
std::set<T> pressed_keys;
std::set<T> just_pressed_keys;
std::set<T> just_released_keys;
friend class KeyListener;
friend class CursorListener;
};
struct KeyListener
{
entt::registry& registry;
void key_event(KeyInput const& key_input_event);
};
struct CursorListener
{
entt::registry& registry;
void cursor_event(MouseMotion const& mouse_motion_event);
};
void reset_mouse_motion(entt::registry& registry);
}; // namespace Input

View File

@@ -405,13 +405,10 @@ auto GltfLoader::operator()(std::filesystem::path const& document_path) -> resul
spdlog::warn("glTF scene has no name.");
}
entt::registry registry;
// Spawn an entity for every node in scene
for (auto const& node : nodes) {
std::function<entt::entity(GltfNode const&, std::optional<entt::entity>)> spawn_node =
[this, &spawn_node, &registry](GltfNode const& node,
std::optional<entt::entity> parent) {
[this, &spawn_node](GltfNode const& node, std::optional<entt::entity> parent) {
auto entity = registry.create();
registry.emplace<Name>(entity, node.name);
registry.emplace<Transform>(entity, node.transform);
@@ -475,7 +472,7 @@ auto GltfLoader::operator()(std::filesystem::path const& document_path) -> resul
entt::hashed_string scene_hash(gltf_scene.name.c_str());
entt::resource<Scene> scene_resource =
scene_cache.load(scene_hash, Scene{std::move(registry)}).first->second;
scene_cache.load(scene_hash, Scene{registry}).first->second;
scenes.push_back(scene_resource);
}

View File

@@ -1,7 +1,7 @@
#pragma once
#include "entt/entity/fwd.hpp"
#include "gltf.h"
#include "components/transform.h"
#include <entt/entt.hpp>
#include <filesystem>
@@ -9,18 +9,20 @@
static constexpr auto MAX_SIZE = 512 * 1024 * 1024;
struct GltfLoader final
struct GltfLoader
{
using result_type = std::shared_ptr<Gltf>;
auto operator()(std::filesystem::path const &document_path) -> result_type;
auto operator()(std::filesystem::path const& document_path) -> result_type;
entt::resource_cache<Image> &image_cache;
entt::resource_cache<Material> &material_cache;
entt::resource_cache<Mesh> &mesh_cache;
entt::resource_cache<Shader, ShaderLoader> &shader_cache;
entt::resource_cache<Scene> &scene_cache;
entt::resource_cache<Image>& image_cache;
entt::resource_cache<Material>& material_cache;
entt::resource_cache<Mesh>& mesh_cache;
entt::resource_cache<Shader, ShaderLoader>& shader_cache;
entt::resource_cache<Scene>& scene_cache;
entt::resource_cache<GltfMesh> &gltf_mesh_cache;
entt::resource_cache<GltfNode> &gltf_node_cache;
entt::resource_cache<GltfMesh>& gltf_mesh_cache;
entt::resource_cache<GltfNode>& gltf_node_cache;
entt::registry& registry;
};

View File

@@ -2,55 +2,56 @@
#include "components/name.h"
#include "components/transform.h"
#include "core/camera.h"
#include "core/graphics/material.h"
#include "core/graphics/mesh.h"
#include "core/light.h"
#include "window/Window.h"
#include "window/window.h"
Scene::Scene(entt::registry registry) : m_registry(std::move(registry))
Scene::Scene(entt::registry& registry) : registry(registry)
{
auto mesh_view = m_registry.view<entt::resource<Mesh>>();
auto mesh_view = registry.view<entt::resource<Mesh>>();
for (auto [entity, mesh] : mesh_view.each()) {
m_registry.emplace<GpuMesh>(entity, GpuMesh(mesh));
registry.emplace<GpuMesh>(entity, GpuMesh(mesh));
// Remove Mesh resource as it is no longer needed.
m_registry.erase<entt::resource<Mesh>>(entity);
registry.erase<entt::resource<Mesh>>(entity);
}
auto material_view = m_registry.view<entt::resource<Material>>();
auto material_view = registry.view<entt::resource<Material>>();
for (auto [entity, material] : material_view.each()) {
m_registry.emplace<GpuMaterial>(entity, GpuMaterial(material));
registry.emplace<GpuMaterial>(entity, GpuMaterial(material));
// Remove Material resource as it is no longer needed.
m_registry.erase<entt::resource<Material>>(entity);
registry.erase<entt::resource<Material>>(entity);
}
// Spawn default lights
auto directional_light = m_registry.create();
m_registry.emplace<Name>(directional_light, "Directional Light");
m_registry.emplace<Transform>(
auto directional_light = registry.create();
registry.emplace<Name>(directional_light, "Directional Light");
registry.emplace<Transform>(
directional_light,
Transform{.orientation = glm::toQuat(
glm::lookAt({}, DirectionalLight::DEFAULT_DIRECTION, Camera::UP_VECTOR))});
m_registry.emplace<GlobalTransform>(directional_light, GlobalTransform{});
m_registry.emplace<DirectionalLight>(
registry.emplace<GlobalTransform>(directional_light, GlobalTransform{});
registry.emplace<DirectionalLight>(
directional_light, DirectionalLight{.illuminance = DirectionalLight::DEFAULT_ILLUMINANCE});
auto point_light = m_registry.create();
m_registry.emplace<Name>(point_light, "Point Light");
m_registry.emplace<Transform>(point_light,
Transform{.translation = PointLight::DEFAULT_POSITION});
m_registry.emplace<GlobalTransform>(point_light, GlobalTransform{});
m_registry.emplace<PointLight>(point_light,
PointLight{.intensity = PointLight::DEFAULT_INTENSITY});
auto point_light = registry.create();
registry.emplace<Name>(point_light, "Point Light");
registry.emplace<Transform>(point_light,
Transform{.translation = PointLight::DEFAULT_POSITION});
registry.emplace<GlobalTransform>(point_light, GlobalTransform{});
registry.emplace<PointLight>(point_light,
PointLight{.intensity = PointLight::DEFAULT_INTENSITY});
}
void Scene::update()
{
GlobalTransform::update(m_registry);
Camera::aspect_ratio_update(m_registry);
Camera::keyboard_movement(m_registry);
GlobalTransform::update(registry);
Camera::aspect_ratio_update(registry);
Camera::keyboard_movement(registry);
if (m_registry.ctx().get<Window::MouseCatched>().catched) {
Camera::mouse_orientation(m_registry);
if (registry.ctx().get<Window::MouseCatched>().catched) {
Camera::mouse_orientation(registry);
}
}

View File

@@ -1,18 +1,15 @@
#pragma once
#include "gltf.h"
#include <chrono>
#include <entt/entt.hpp>
class Scene
{
public:
Scene(entt::registry registry);
Scene(entt::registry& registry);
void update();
auto registry() -> auto & { return m_registry; }
private:
entt::registry m_registry;
entt::registry& registry;
};

View File

@@ -1,161 +0,0 @@
#include "Window.h"
#include "core/glad.h"
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include <spdlog/spdlog.h>
static constexpr unsigned INIT_WINDOW_WIDTH = 1280;
static constexpr unsigned INIT_WINDOW_HEIGHT = 720;
Window::Window()
{
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifndef NDEBUG
glfwSetErrorCallback(glfw_error_callback);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
#else
// Maximize in release build
glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
#endif
m_glfw_window = std::shared_ptr<GLFWwindow>(
glfwCreateWindow(INIT_WINDOW_WIDTH, INIT_WINDOW_HEIGHT, "OpenGL", nullptr, nullptr),
[](GLFWwindow* window) { glfwDestroyWindow(window); });
if (!m_glfw_window) {
spdlog::critical("Failed to create window");
}
// Create OpenGL context
glfwMakeContextCurrent(m_glfw_window.get());
#ifndef NDEBUG
// Disable mouse cursor
m_mouse_catched.catched = false;
#endif
set_catched_cursor(m_mouse_catched.catched);
// Callbacks
glfwSetWindowUserPointer(m_glfw_window.get(), this);
glfwSetKeyCallback(m_glfw_window.get(), key_callback);
glfwSetCursorPosCallback(m_glfw_window.get(), mouse_cursor_callback);
glfwSetFramebufferSizeCallback(m_glfw_window.get(), framebuffer_size_callback);
init_glad();
}
auto Window::dimensions_changed() -> bool
{
bool temp = m_dimensions_changed;
m_dimensions_changed = false;
return temp;
}
void Window::set_catched_cursor(bool value)
{
glfwSetInputMode(
m_glfw_window.get(), GLFW_CURSOR, value ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL);
m_mouse_catched.catched = value;
}
void Window::glfw_error_callback(int error, char const* description)
{
spdlog::warn("GLFW [{:d}]: {:s}\n", error, description);
}
void Window::framebuffer_size_callback(GLFWwindow* glfw_window, int width, int height)
{
auto& window = *static_cast<Window*>(glfwGetWindowUserPointer(glfw_window));
window.m_dimensions_changed = true;
glViewport(0, 0, width, height);
}
void Window::key_callback(GLFWwindow* glfw_window,
int key,
[[maybe_unused]] int scancode,
int action,
[[maybe_unused]] int mods)
{
auto& window = *static_cast<Window*>(glfwGetWindowUserPointer(glfw_window));
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
glfwSetWindowShouldClose(glfw_window, GLFW_TRUE);
} else if (key == GLFW_KEY_LEFT_CONTROL && action == GLFW_PRESS) {
window.set_catched_cursor(!window.m_mouse_catched.catched);
} else if (key == GLFW_KEY_O && action == GLFW_PRESS) {
window.m_wire_frame_mode = !window.m_wire_frame_mode;
glPolygonMode(GL_FRONT_AND_BACK, window.m_wire_frame_mode ? GL_LINE : GL_FILL);
} else if (action == GLFW_PRESS || action == GLFW_RELEASE) {
window.m_key_input.key_map[key] = (action == GLFW_PRESS);
}
}
void Window::mouse_cursor_callback(GLFWwindow* glfw_window, double xpos, double ypos)
{
auto& window = *static_cast<Window*>(glfwGetWindowUserPointer(glfw_window));
double deltaCursorPosX = xpos - window.m_last_cursor_pos_x;
double deltaCursorPosY = -(ypos - window.m_last_cursor_pos_y);
window.m_last_cursor_pos_x = xpos;
window.m_last_cursor_pos_y = ypos;
// Check if this is the first VALID mouse event after window being resized
if (window.m_first_mouse_input &&
(std::abs(deltaCursorPosX) >= std::numeric_limits<double>::epsilon() ||
std::abs(deltaCursorPosY) >= std::numeric_limits<double>::epsilon())) {
window.m_first_mouse_input = false;
deltaCursorPosX = 0.0;
deltaCursorPosY = 0.0;
}
deltaCursorPosX *= MOUSE_SENSITIVITY;
deltaCursorPosY *= MOUSE_SENSITIVITY;
auto& [deltaX, deltaY] = window.m_mouse_cursor_input.cursor_movement;
deltaX += deltaCursorPosX;
deltaY += deltaCursorPosY;
}
auto Window::logical_dimensions() const -> glm::u32vec2
{
int width{};
int height{};
glfwGetWindowSize(m_glfw_window.get(), &width, &height);
return {width, height};
}
auto Window::physical_dimensions() const -> glm::u32vec2
{
int width{};
int height{};
glfwGetFramebufferSize(m_glfw_window.get(), &width, &height);
return {width, height};
}
void Window::clear_mouse_cursor_input()
{
m_mouse_cursor_input = {};
};
void Window::update_catched_mouse(entt::registry& registry) const
{
registry.ctx().erase<MouseCatched>();
registry.ctx().emplace<MouseCatched>(m_mouse_catched);
}
void Window::update_descriptor(entt::registry& registry) const
{
auto dimensions = logical_dimensions();
registry.ctx().erase<Descriptor>();
registry.ctx().emplace<Descriptor>(Descriptor{
.logical_dimensions = dimensions,
.aspect_ratio = static_cast<float>(dimensions.x) / static_cast<float>(dimensions.y)});
}

View File

@@ -1,65 +0,0 @@
#pragma once
#include "input/input.h"
#include <glm/glm.hpp>
#include <memory>
#include <unordered_map>
#include <utility>
class GLFWwindow;
class Window
{
public:
struct MouseCatched
{
bool catched = true;
};
struct Descriptor
{
glm::u32vec2 logical_dimensions;
float aspect_ratio{};
};
Window();
[[nodiscard]] auto glfw_window() -> GLFWwindow& { return *m_glfw_window; }
[[nodiscard]] auto physical_dimensions() const -> glm::u32vec2;
[[nodiscard]] auto logical_dimensions() const -> glm::u32vec2;
[[nodiscard]] auto dimensions_changed() -> bool;
[[nodiscard]] auto key_input() const -> auto const& { return m_key_input; }
[[nodiscard]] auto mouse_cursor_input() const -> auto const& { return m_mouse_cursor_input; }
[[nodiscard]] auto mouse_button_input() const -> auto const& { return m_mouse_button_input; }
[[nodiscard]] auto cursor_catched() const -> auto { return m_mouse_catched; }
void clear_mouse_cursor_input();
void update_catched_mouse(entt::registry& registry) const;
void update_descriptor(entt::registry& registry) const;
private:
static void key_callback(GLFWwindow* glfw_window, int key, int scancode, int action, int mods);
static void mouse_cursor_callback(GLFWwindow* glfw_window, double xpos, double ypos);
static void framebuffer_size_callback(GLFWwindow* glfw_window, int width, int height);
static void glfw_error_callback(int error, char const* description);
static constexpr float MOUSE_SENSITIVITY = 0.15F;
void set_catched_cursor(bool value);
std::shared_ptr<GLFWwindow> m_glfw_window;
// <Key, Action>
Input::Key m_key_input;
Input::MouseButton m_mouse_button_input;
Input::MouseCursor m_mouse_cursor_input;
bool m_dimensions_changed = false;
double m_last_cursor_pos_x = 0.0;
double m_last_cursor_pos_y = 0.0;
bool m_first_mouse_input = false;
MouseCatched m_mouse_catched;
bool m_wire_frame_mode = false;
};

149
src/window/window.cpp Normal file
View File

@@ -0,0 +1,149 @@
#include "window.h"
#include "core/glad.h"
#include "input/input.h"
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include <spdlog/spdlog.h>
static constexpr unsigned INIT_WINDOW_WIDTH = 1280;
static constexpr unsigned INIT_WINDOW_HEIGHT = 720;
Window::Window(entt::dispatcher& event_dispatcher) : event_dispatcher(event_dispatcher)
{
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifndef NDEBUG
glfwSetErrorCallback(glfw_error_callback);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
#else
// Maximize in release build
glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
#endif
glfw_window = std::shared_ptr<GLFWwindow>(
glfwCreateWindow(INIT_WINDOW_WIDTH, INIT_WINDOW_HEIGHT, "OpenGL", nullptr, nullptr),
[](GLFWwindow* window) { glfwDestroyWindow(window); });
if (!glfw_window) {
spdlog::critical("Failed to create window");
}
// Create OpenGL context
glfwMakeContextCurrent(glfw_window.get());
// Callbacks
glfwSetWindowUserPointer(glfw_window.get(), this);
glfwSetKeyCallback(glfw_window.get(), key_callback);
glfwSetCursorPosCallback(glfw_window.get(), mouse_cursor_callback);
glfwSetFramebufferSizeCallback(glfw_window.get(), framebuffer_size_callback);
init_glad();
}
void Window::glfw_error_callback(int error, char const* description)
{
spdlog::warn("GLFW [{:d}]: {:s}\n", error, description);
}
void Window::framebuffer_size_callback(GLFWwindow* glfw_window, int width, int height)
{
auto& window = *static_cast<Window*>(glfwGetWindowUserPointer(glfw_window));
glViewport(0, 0, width, height);
window.event_dispatcher.enqueue<ResizeEvent>();
}
void Window::key_callback(GLFWwindow* glfw_window,
int key,
[[maybe_unused]] int scancode,
int action,
[[maybe_unused]] int mods)
{
auto& window = *static_cast<Window*>(glfwGetWindowUserPointer(glfw_window));
window.event_dispatcher.enqueue<Input::KeyInput>(
Input::KeyInput{.key_code = static_cast<Input::KeyCode>(key),
.action = static_cast<Input::Action>(action)});
}
void Window::mouse_cursor_callback(GLFWwindow* glfw_window, double xpos, double ypos)
{
auto& window = *static_cast<Window*>(glfwGetWindowUserPointer(glfw_window));
glm::vec2 delta{xpos - window.last_cursor_pos.x, ypos - window.last_cursor_pos.y};
window.last_cursor_pos = {xpos, ypos};
delta *= MOUSE_SENSITIVITY;
// Check if this is the first VALID mouse event after window being resized
if (window.first_mouse_input && (delta.x >= std::numeric_limits<double>::epsilon() ||
delta.y >= std::numeric_limits<double>::epsilon())) {
window.first_mouse_input = false;
return;
}
window.event_dispatcher.enqueue<Input::MouseMotion>(Input::MouseMotion{.delta = delta});
}
auto Window::logical_dimensions() const -> glm::u32vec2
{
int width{};
int height{};
glfwGetWindowSize(glfw_window.get(), &width, &height);
return {width, height};
}
auto Window::physical_dimensions() const -> glm::u32vec2
{
int width{};
int height{};
glfwGetFramebufferSize(glfw_window.get(), &width, &height);
return {width, height};
}
void Window::mouse_catching(entt::registry& registry) const
{
if (!registry.ctx().contains<MouseCatched>()) {
bool catched = true;
#ifndef NDEBUG
catched = false;
#endif
registry.ctx().emplace<MouseCatched>(MouseCatched{.catched = catched});
}
auto& mouse_catched = registry.ctx().get<MouseCatched>();
auto const& key_state = registry.ctx().get<Input::State<Input::KeyCode>>();
if (key_state.just_pressed(Input::KeyCode{GLFW_KEY_LEFT_CONTROL})) {
mouse_catched.catched = !mouse_catched.catched;
glfwSetInputMode(glfw_window.get(),
GLFW_CURSOR,
mouse_catched.catched ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL);
}
}
void Window::close_on_esc(entt::registry& registry) const
{
auto const& key_state = registry.ctx().get<Input::State<Input::KeyCode>>();
if (key_state.just_pressed(Input::KeyCode{GLFW_KEY_ESCAPE})) {
glfwSetWindowShouldClose(glfw_window.get(), GLFW_TRUE);
}
}
void Window::update_descriptor(entt::registry& registry) const
{
auto dimensions = logical_dimensions();
registry.ctx().erase<Descriptor>();
registry.ctx().emplace<Descriptor>(Descriptor{
.logical_dimensions = dimensions,
.aspect_ratio = static_cast<float>(dimensions.x) / static_cast<float>(dimensions.y)});
}

52
src/window/window.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <entt/entt.hpp>
#include <glm/glm.hpp>
#include <memory>
#include <unordered_map>
#include <utility>
class GLFWwindow;
class Window
{
public:
struct MouseCatched
{
bool catched = true;
};
struct Descriptor
{
glm::u32vec2 logical_dimensions;
float aspect_ratio{};
};
struct ResizeEvent
{};
Window(entt::dispatcher& event_dispatcher);
[[nodiscard]] auto handle() -> GLFWwindow& { return *glfw_window; }
[[nodiscard]] auto physical_dimensions() const -> glm::u32vec2;
[[nodiscard]] auto logical_dimensions() const -> glm::u32vec2;
void update_descriptor(entt::registry& registry) const;
void mouse_catching(entt::registry& registry) const;
void close_on_esc(entt::registry& registry) const;
private:
static void key_callback(GLFWwindow* glfw_window, int key, int scancode, int action, int mods);
static void mouse_cursor_callback(GLFWwindow* glfw_window, double xpos, double ypos);
static void framebuffer_size_callback(GLFWwindow* glfw_window, int width, int height);
static void glfw_error_callback(int error, char const* description);
static constexpr float MOUSE_SENSITIVITY = 0.15F;
std::shared_ptr<GLFWwindow> glfw_window;
entt::dispatcher& event_dispatcher;
glm::vec2 last_cursor_pos{};
bool first_mouse_input = true;
};