From 92f63bbdbfc214849c203511bbcb1be0a4865588 Mon Sep 17 00:00:00 2001 From: Mel Date: Thu, 15 Feb 2024 11:33:11 +0100 Subject: Proper input system --- CMakeLists.txt | 2 +- src/Entities/Player.cpp | 64 +++++++------- src/Entities/Player.hpp | 27 +++--- src/GFX/Mouse.cpp | 24 ------ src/GFX/Mouse.hpp | 20 ----- src/GFX/Window.cpp | 26 ++---- src/GFX/Window.hpp | 26 +++--- src/Game.cpp | 9 +- src/Input.cpp | 221 ++++++++++++++++++++++++++++++++++++++++++++++++ src/Input.hpp | 116 +++++++++++++++++++++++++ src/Time.cpp | 7 +- src/Time.hpp | 17 +++- 12 files changed, 429 insertions(+), 130 deletions(-) delete mode 100644 src/GFX/Mouse.cpp delete mode 100644 src/GFX/Mouse.hpp create mode 100644 src/Input.cpp create mode 100644 src/Input.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 93976e2..5117ea3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,6 @@ add_executable(meowcraft src/GFX/Camera.cpp src/GFX/Camera.hpp src/Math/Rotation.hpp src/GFX/Shading/Uniform.cpp src/GFX/Shading/Uniform.hpp - src/GFX/Mouse.cpp src/GFX/Mouse.hpp src/GFX/Texture.cpp src/GFX/Texture.hpp src/Assets.cpp src/Assets.hpp src/GFX/Image/RawImage.cpp src/GFX/Image/RawImage.hpp @@ -87,6 +86,7 @@ add_executable(meowcraft src/GFX/Actions.hpp src/GFX/Resources.cpp src/GFX/Resources.hpp src/ThreadRole.hpp + src/Input.cpp src/Input.hpp ) if (WIN32) diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 7a1c1af..83c3f5e 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -1,13 +1,13 @@ #include "Player.hpp" -#include "../Common/Casts.hpp" +#include "../Input.hpp" #include namespace MC::Entities { -void Player::update(const Time& time, GFX::Window& window, GFX::Camera& camera, World::World& world) { - auto const input_direction = directional_input(window); +void Player::update(Time const& time, Input const& input, GFX::Camera& camera, World::World& world) { + auto const input_direction = directional_input(input); - auto destination = movement(window, time, input_direction); + auto destination = movement(time, input_direction); if (can_collide()) { if (m_velocity.y() < 0.0) @@ -25,12 +25,12 @@ void Player::update(const Time& time, GFX::Window& window, GFX::Camera& camera, } move_to(destination); - rotate(rotational_input(window)); + rotate(rotational_input(input)); update_camera_position(camera); update_targeted_block(world); - actions(window, world); + actions(input, world); } void Player::render(GFX::Actions& actions) { @@ -72,17 +72,17 @@ Bool Player::can_collide() const { return m_movement != MovementMode::NoClip; } -Position::World Player::movement(GFX::Window& window, const Time& time, Vec3 input_direction) { +Position::World Player::movement(Time const& time, Vec3 input_direction) { switch (m_movement) { - case MovementMode::Walking: m_velocity = walking_velocity(window, time, input_direction); break; - case MovementMode::Flying: m_velocity = flying_velocity(window, time, input_direction); break; - case MovementMode::NoClip: m_velocity = noclip_velocity(window, time, input_direction); break; + case MovementMode::Walking: m_velocity = walking_velocity(time, input_direction); break; + case MovementMode::Flying: m_velocity = flying_velocity(time, input_direction); break; + case MovementMode::NoClip: m_velocity = noclip_velocity(time, input_direction); break; } return m_transform.position() + m_velocity; } -Vec3 Player::walking_velocity(GFX::Window& window, const Time& time, Vec3 input_direction) { +Vec3 Player::walking_velocity(Time const& time, Vec3 input_direction) { constexpr auto base_move_speed = 8.0; constexpr auto initial_jump_velocity = 0.16; constexpr auto gravity = 0.6; @@ -109,7 +109,7 @@ Vec3 Player::walking_velocity(GFX::Window& window, const Time& time, Vec3 input_ }; } -Vec3 Player::flying_velocity(GFX::Window& window, const Time& time, Vec3 input_direction) { +Vec3 Player::flying_velocity(Time const& time, Vec3 input_direction) const { constexpr auto base_move_speed = 10.0; auto flying_direction = m_transform.right() * input_direction.x() + m_transform.forward() * input_direction.z(); @@ -126,15 +126,13 @@ Vec3 Player::flying_velocity(GFX::Window& window, const Time& time, Vec3 input_d }; } -Vec3 Player::noclip_velocity(GFX::Window& window, const Time& time, Vec3 input_direction) { +Vec3 Player::noclip_velocity(Time const& time, Vec3 input_direction) const { constexpr auto base_move_speed = 10.0; - Real const boost = TO(Real, window.key(GLFW_KEY_LEFT_CONTROL, GLFW_PRESS)) * 75.0; - auto const [x, y, z] = input_direction.elements; auto const direction = m_transform.right() * x + Vec3(0, y, 0) + m_transform.forward() * z; - return direction * (base_move_speed + boost) * time.delta(); + return direction * base_move_speed * time.delta(); } void Player::update_targeted_block(World::World& world) { @@ -152,10 +150,10 @@ void Player::update_targeted_block(World::World& world) { else m_targeted_block = {}; } -void Player::actions(GFX::Window& window, World::World& world) { +void Player::actions(Input const& input, World::World& world) { // Breaking and placing blocks. - auto left_click = window.mouse(GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS); - auto right_click = window.mouse(GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS); + auto left_click = input.pressed(Mouse::Left); + auto right_click = input.pressed(Mouse::Right); if (left_click || right_click) { if (m_targeted_block.has_value()) { auto targeted_block = m_targeted_block.value(); @@ -173,11 +171,15 @@ void Player::actions(GFX::Window& window, World::World& world) { } // Toggle movement modes. - // TODO: Need an input system for this, can't get an event only at - // the moment the key is pressed, so can't toggle between modes on single press. - if (window.key(GLFW_KEY_1, GLFW_PRESS)) m_movement = MovementMode::Walking; - else if (window.key(GLFW_KEY_2, GLFW_PRESS)) m_movement = MovementMode::Flying; - else if (window.key(GLFW_KEY_3, GLFW_PRESS)) m_movement = MovementMode::NoClip; + if (input.double_pressed(Key::Space)) { + if (m_movement == MovementMode::Walking) m_movement = MovementMode::Flying; + else if (m_movement == MovementMode::Flying) m_movement = MovementMode::Walking; + } + + if (input.pressed(Key::N)) { + if (m_movement == MovementMode::NoClip) m_movement = MovementMode::Walking; + else m_movement = MovementMode::NoClip; + } } #define STUCK_THRESHOLD 100 @@ -319,20 +321,18 @@ void Player::update_camera_position(GFX::Camera& camera) const { camera.set_angles(cam_transform.rotation()); } -Vec3 Player::directional_input(GFX::Window& window) { - auto key = [&](Int k) -> Real { return window.key(k, GLFW_PRESS); }; - - Real x = key(GLFW_KEY_D) - key(GLFW_KEY_A); - Real y = key(GLFW_KEY_SPACE) - key(GLFW_KEY_LEFT_SHIFT); - Real z = key(GLFW_KEY_S) - key(GLFW_KEY_W); +Vec3 Player::directional_input(Input const& input) { + Real x = input.held(Key::D) - input.held(Key::A); + Real y = input.held(Key::Space) - input.held(Key::LeftShift); + Real z = input.held(Key::S) - input.held(Key::W); return {x, y, z}; } -Rotation Player::rotational_input(GFX::Window& window) { +Rotation Player::rotational_input(Input const& input) { constexpr auto base_rotation_speed = 0.1f; - auto r = window.mouse_delta(); + auto r = input.mouse_movement(); return {r.y() * base_rotation_speed, r.x() * base_rotation_speed, 0.0f}; } diff --git a/src/Entities/Player.hpp b/src/Entities/Player.hpp index 2b69d57..342d965 100644 --- a/src/Entities/Player.hpp +++ b/src/Entities/Player.hpp @@ -1,14 +1,15 @@ #pragma once +#include "../Common/Pure.hpp" #include "../Time.hpp" #include "../Transform.hpp" #include "../GFX/Actions.hpp" #include "../GFX/Camera.hpp" #include "../World/World.hpp" -#include "../GFX/Window.hpp" #include "../Math/AABB.hpp" #include "../Math/Rotation.hpp" #include "../World/Position.hpp" +#include "../Input.hpp" namespace MC::Entities { class Player { @@ -16,9 +17,9 @@ public: explicit Player(Position::World position) : m_transform(position), m_outline_mesh(create_outline_cube_mesh()) {} - Position::World position() const { return m_transform.position(); } + PURE Position::World position() const { return m_transform.position(); } - void update(const Time& time, GFX::Window& window, GFX::Camera& camera, World::World& world); + void update(Time const& time, Input const& input, GFX::Camera& camera, World::World& world); void render(GFX::Actions& actions); void move(Position::WorldOffset by); @@ -27,14 +28,14 @@ public: void rotate(Rotation by); void rotate_to(Rotation to); - AABB bounds() const; + PURE AABB bounds() const; private: struct BlockedAxis { Bool positive, negative; }; - Bool can_collide() const; + PURE Bool can_collide() const; struct ProcessCollisionsResult { Position::World position; @@ -45,19 +46,19 @@ private: Position::World rescue_from_void_on_new_chunk(Time const& time, World::World& world, Position::World position); - Position::World movement(GFX::Window& window, const Time& time, Vec3 input_direction); - Vec3 walking_velocity(GFX::Window& window, const Time& time, Vec3 input_direction); - Vec3 flying_velocity(GFX::Window& window, const Time& time, Vec3 input_direction); - Vec3 noclip_velocity(GFX::Window& window, const Time& time, Vec3 input_direction); + Position::World movement(Time const& time, Vec3 input_direction); + Vec3 walking_velocity(Time const& time, Vec3 input_direction); + PURE Vec3 flying_velocity(Time const& time, Vec3 input_direction) const; + PURE Vec3 noclip_velocity(Time const& time, Vec3 input_direction) const; void update_targeted_block(World::World& world); - void actions(GFX::Window& window, World::World& world); + void actions(Input const& input, World::World& world); - Transform camera_transform() const; + PURE Transform camera_transform() const; void update_camera_position(GFX::Camera& camera) const; - static Vec3 directional_input(GFX::Window& window); - static Rotation rotational_input(GFX::Window& window); + static Vec3 directional_input(Input const& input); + static Rotation rotational_input(Input const& input); // Creates a bounding box where `position` is at the center of the bottom face. static AABB bounding_box_for_position(Position::World position); diff --git a/src/GFX/Mouse.cpp b/src/GFX/Mouse.cpp deleted file mode 100644 index 49f6972..0000000 --- a/src/GFX/Mouse.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "Mouse.hpp" - -namespace MC::GFX { - -Vector<2> Mouse::update(GLFWwindow* window) { - Real x, y; - glfwGetCursorPos(window, &x, &y); - - if (m_first_event) { - m_last_x = x; - m_last_y = y; - - m_first_event = false; - } - - Vector<2> movement{static_cast(x) - m_last_x, static_cast(y) - m_last_y}; - - m_last_x = x; - m_last_y = y; - - return movement; -} - -} \ No newline at end of file diff --git a/src/GFX/Mouse.hpp b/src/GFX/Mouse.hpp deleted file mode 100644 index ad940d4..0000000 --- a/src/GFX/Mouse.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include "../Math/Vector.hpp" - -namespace MC::GFX { - -class Mouse { -public: - Mouse() = default; - - Vector<2> update(GLFWwindow* window); -private: - Bool m_first_event = true; - - Real m_last_x = 0.0f; - Real m_last_y = 0.0f; -}; - -} \ No newline at end of file diff --git a/src/GFX/Window.cpp b/src/GFX/Window.cpp index 33e76d5..5c391c8 100644 --- a/src/GFX/Window.cpp +++ b/src/GFX/Window.cpp @@ -3,10 +3,12 @@ #include "../Common/Assert.hpp" #include "../ThreadRole.hpp" #include "Window.hpp" +#include "../Common/Casts.hpp" +#include namespace MC::GFX { -Window::Window(const Char* title, U32 width, U32 height) { +Window::Window(std::string const& title, U32 const width, U32 const height) { ASSERT_MAIN_THREAD(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); @@ -14,7 +16,7 @@ Window::Window(const Char* title, U32 width, U32 height) { glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_DOUBLEBUFFER, GL_TRUE); - m_window = glfwCreateWindow(width, height, title, nullptr, nullptr); + m_window = glfwCreateWindow(TO(I32, width), TO(I32, height), title.c_str(), nullptr, nullptr); if (m_window == nullptr) { throw std::runtime_error("Failed to create window."); } @@ -36,32 +38,20 @@ GLFWwindow* Window::get() const { return m_window; } -void Window::close() { +void Window::close() const { glfwSetWindowShouldClose(m_window, true); } -Vector<2> Window::mouse_delta() { - return m_mouse.update(m_window); -} - -Bool Window::key(I32 key, I32 type) const { - return glfwGetKey(m_window, key) == type; -} - -Bool Window::mouse(I32 key, I32 type) const { - return glfwGetMouseButton(m_window, key) == type; -} - -void Window::start_render() { +void Window::start_render() const { glfwSwapBuffers(m_window); } -void Window::poll_events() { +void Window::poll_events() const { ASSERT_MAIN_THREAD(); glfwPollEvents(); } -void Window::on_size_change(void (callback)(GLFWwindow*, I32, I32)) { +void Window::on_size_change(void (callback)(GLFWwindow*, I32, I32)) const { glfwSetFramebufferSizeCallback(m_window, callback); } diff --git a/src/GFX/Window.hpp b/src/GFX/Window.hpp index c26b0fd..86920b5 100644 --- a/src/GFX/Window.hpp +++ b/src/GFX/Window.hpp @@ -1,35 +1,31 @@ #pragma once -#include "../Common/Sizes.hpp" -#include "../Math/Vector.hpp" +#include #include -#include "Mouse.hpp" +#include "../Common/Pure.hpp" +#include "../Common/Sizes.hpp" namespace MC::GFX { class Window { public: - Window(const Char* title, U32 width, U32 height); + Window(std::string const& title, U32 width, U32 height); ~Window(); - GLFWwindow* get() const; + PURE GLFWwindow* get() const; - void on_size_change(void (* callback)(GLFWwindow*, I32, I32)); + void on_size_change(void (* callback)(GLFWwindow*, I32, I32)) const; void attach() const; void detach() const; - void close(); - void start_render(); - void poll_events(); - Vector<2> mouse_delta(); + void close() const; + void start_render() const; + void poll_events() const; - Bool key(I32 key, I32 type) const; - Bool mouse(I32 key, I32 type) const; - Bool should_close() const; + PURE Bool should_close() const; private: GLFWwindow* m_window; - Mouse m_mouse; }; -} \ No newline at end of file +} diff --git a/src/Game.cpp b/src/Game.cpp index 231c681..dd04b2f 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -21,6 +21,9 @@ void Game::run() const { World::Clouds clouds{}; Entities::Player player{{0, World::Chunk::Height / 2.0, 0}}; + Input::register_callbacks(m_window); + + Input input; Time time; while (!m_window.should_close()) { @@ -33,11 +36,13 @@ void Game::run() const { time.start_frame(); - if (m_window.key(GLFW_KEY_ESCAPE, GLFW_PRESS)) { + input.update(time.frame_start()); + + if (input.pressed(Key::Escape)) { m_window.close(); } - player.update(time, m_window, camera, world); + player.update(time, input, camera, world); clouds.update(time); GFX::Actions actions; diff --git a/src/Input.cpp b/src/Input.cpp new file mode 100644 index 0000000..2362794 --- /dev/null +++ b/src/Input.cpp @@ -0,0 +1,221 @@ +#include "Input.hpp" +#include "ThreadRole.hpp" + +namespace MC { + +Bool Input::s_key_buffer[KeyCount] = { false }; +Bool Input::s_mouse_buffer[MouseCount] = { false }; +Vec2 Input::s_mouse_position = { 0, 0 }; +Input::MouseEventType Input::s_mouse_event_type = MouseEventType::None; + +void Input::register_callbacks(GFX::Window const& window) { + ASSERT_MAIN_THREAD(); + + glfwSetKeyCallback(window.get(), key_callback); + glfwSetCursorPosCallback(window.get(), cursor_position_callback); + glfwSetMouseButtonCallback(window.get(), mouse_button_callback); +} + +// I think this is the Windows default for double click... +#define DOUBLE_PRESS_DELTA_MS 500 + +void Input::update(Time::Timestamp const current_time) { + ASSERT_MAIN_THREAD(); + + std::swap(m_state, m_previous_state); + + for (auto& key : m_double_press->keys) key = false; + for (auto& mouse_button : m_double_press->mouse_buttons) mouse_button = false; + + for (USize i = 0; i < KeyCount; ++i) { + m_state->map.keys[i] = s_key_buffer[i]; + if (key_pressed(m_state, m_previous_state, TO(Key, i))) { + auto previous_release_time = m_last_key_release->keys[i]; + m_double_press->keys[i] = current_time - previous_release_time < DOUBLE_PRESS_DELTA_MS; + m_last_key_release->keys[i] = current_time; + } + } + + for (USize i = 0; i < MouseCount; ++i) { + m_state->map.mouse_buttons[i] = s_mouse_buffer[i]; + if (mouse_button_pressed(m_state, m_previous_state, TO(Mouse, i))) { + auto previous_release_time = m_last_key_release->keys[i]; + m_double_press->keys[i] = current_time - previous_release_time < DOUBLE_PRESS_DELTA_MS; + m_last_key_release->keys[i] = current_time; + } + } + + if (s_mouse_event_type == MouseEventType::First) + m_previous_state->mouse_position = s_mouse_position; + m_state->mouse_position = s_mouse_position; +} + +Bool Input::pressed(Key const key) const { + return key_pressed(m_state, m_previous_state, key); +} + +Bool Input::pressed(Mouse const button) const { + return mouse_button_pressed(m_state, m_previous_state, button); +} + +Bool Input::held(Key const key) const { + return m_state->map.keys[key_value(key)]; +} + +Bool Input::held(Mouse const button) const { + return m_state->map.mouse_buttons[mouse_button_value(button)]; +} + +Bool Input::released(Key const key) const { + return key_released(m_state, m_previous_state, key); +} + +Bool Input::released(Mouse const button) const { + return mouse_button_released(m_state, m_previous_state, button); +} + +Bool Input::double_pressed(Key const key) const { + return m_double_press->keys[key_value(key)]; +} + +Bool Input::double_pressed(Mouse const button) const { + return m_double_press->mouse_buttons[mouse_button_value(button)]; +} + +Vector<2> Input::mouse_position() const { + return m_state->mouse_position; +} + +Vector<2> Input::mouse_movement() const { + return m_state->mouse_position - m_previous_state->mouse_position; +} + +Bool Input::key_pressed( + std::unique_ptr const& current, std::unique_ptr const& previous, Key key +) { + return current->map.keys[key_value(key)] && !previous->map.keys[key_value(key)]; +} + +Bool Input::mouse_button_pressed( + std::unique_ptr const& current, std::unique_ptr const& previous, Mouse mouse +) { + return current->map.mouse_buttons[mouse_button_value(mouse)] && !previous->map.mouse_buttons[mouse_button_value(mouse)]; +} + +Bool Input::key_released( + std::unique_ptr const& current, std::unique_ptr const& previous, Key key +) { + return !current->map.keys[key_value(key)] && previous->map.keys[key_value(key)]; +} + +Bool Input::mouse_button_released( + std::unique_ptr const& current, std::unique_ptr const& previous, Mouse mouse +) { + return !current->map.mouse_buttons[mouse_button_value(mouse)] && previous->map.mouse_buttons[mouse_button_value(mouse)]; +} + +USize Input::key_value(Key key) { + return TO(USize, key); +} + +USize Input::mouse_button_value(Mouse button) { + return TO(USize, button); +} + +Key Input::from_glfw_key(I32 const key) { + switch (key) { + case GLFW_KEY_A: return Key::A; + case GLFW_KEY_B: return Key::B; + case GLFW_KEY_C: return Key::C; + case GLFW_KEY_D: return Key::D; + case GLFW_KEY_E: return Key::E; + case GLFW_KEY_F: return Key::F; + case GLFW_KEY_G: return Key::G; + case GLFW_KEY_H: return Key::H; + case GLFW_KEY_I: return Key::I; + case GLFW_KEY_J: return Key::J; + case GLFW_KEY_K: return Key::K; + case GLFW_KEY_L: return Key::L; + case GLFW_KEY_M: return Key::M; + case GLFW_KEY_N: return Key::N; + case GLFW_KEY_O: return Key::O; + case GLFW_KEY_P: return Key::P; + case GLFW_KEY_Q: return Key::Q; + case GLFW_KEY_R: return Key::R; + case GLFW_KEY_S: return Key::S; + case GLFW_KEY_T: return Key::T; + case GLFW_KEY_U: return Key::U; + case GLFW_KEY_V: return Key::V; + case GLFW_KEY_W: return Key::W; + case GLFW_KEY_X: return Key::X; + case GLFW_KEY_Y: return Key::Y; + case GLFW_KEY_Z: return Key::Z; + case GLFW_KEY_0: return Key::Zero; + case GLFW_KEY_1: return Key::One; + case GLFW_KEY_2: return Key::Two; + case GLFW_KEY_3: return Key::Three; + case GLFW_KEY_4: return Key::Four; + case GLFW_KEY_5: return Key::Five; + case GLFW_KEY_6: return Key::Six; + case GLFW_KEY_7: return Key::Seven; + case GLFW_KEY_8: return Key::Eight; + case GLFW_KEY_9: return Key::Nine; + case GLFW_KEY_ESCAPE: return Key::Escape; + case GLFW_KEY_ENTER: return Key::Enter; + case GLFW_KEY_TAB: return Key::Tab; + case GLFW_KEY_BACKSPACE: return Key::Backspace; + case GLFW_KEY_INSERT: return Key::Insert; + case GLFW_KEY_DELETE: return Key::Delete; + case GLFW_KEY_RIGHT: return Key::Right; + case GLFW_KEY_LEFT: return Key::Left; + case GLFW_KEY_DOWN: return Key::Down; + case GLFW_KEY_UP: return Key::Up; + case GLFW_KEY_LEFT_SHIFT: return Key::LeftShift; + case GLFW_KEY_RIGHT_SHIFT: return Key::RightShift; + case GLFW_KEY_LEFT_CONTROL: return Key::LeftControl; + case GLFW_KEY_RIGHT_CONTROL: return Key::RightControl; + case GLFW_KEY_LEFT_ALT: return Key::LeftAlt; + case GLFW_KEY_RIGHT_ALT: return Key::RightAlt; + case GLFW_KEY_SPACE: return Key::Space; + case GLFW_KEY_F1: return Key::F1; + case GLFW_KEY_F2: return Key::F2; + case GLFW_KEY_F3: return Key::F3; + case GLFW_KEY_F4: return Key::F4; + case GLFW_KEY_F5: return Key::F5; + case GLFW_KEY_F6: return Key::F6; + case GLFW_KEY_F7: return Key::F7; + case GLFW_KEY_F8: return Key::F8; + case GLFW_KEY_F9: return Key::F9; + case GLFW_KEY_F10: return Key::F10; + case GLFW_KEY_F11: return Key::F11; + case GLFW_KEY_F12: return Key::F12; + default: UNREACHABLE("Unknown key."); return TO(Key, 0); + } +} + +Mouse Input::from_glfw_mouse(I32 const button) { + switch (button) { + case GLFW_MOUSE_BUTTON_LEFT: return Mouse::Left; + case GLFW_MOUSE_BUTTON_RIGHT: return Mouse::Right; + case GLFW_MOUSE_BUTTON_MIDDLE: return Mouse::Middle; + default: UNREACHABLE("Unknown mouse button."); return TO(Mouse, 0); + } +} + +void Input::key_callback(GLFWwindow* _window, I32 const key, I32 _scancode, I32 const action, I32 _mods) { + if (action == GLFW_PRESS) s_key_buffer[key_value(from_glfw_key(key))] = true; + else if (action == GLFW_RELEASE) s_key_buffer[key_value(from_glfw_key(key))] = false; +} + +void Input::cursor_position_callback(GLFWwindow* _window, F64 x, F64 y) { + if (s_mouse_event_type == MouseEventType::None) s_mouse_event_type = MouseEventType::First; + else if (s_mouse_event_type == MouseEventType::First) s_mouse_event_type = MouseEventType::Subsequent; + s_mouse_position = { x, y }; +} + +void Input::mouse_button_callback(GLFWwindow* _window, I32 const button, I32 const action, I32 _mods) { + if (action == GLFW_PRESS) s_mouse_buffer[mouse_button_value(from_glfw_mouse(button))] = true; + else if (action == GLFW_RELEASE) s_mouse_buffer[mouse_button_value(from_glfw_mouse(button))] = false; +} + +} diff --git a/src/Input.hpp b/src/Input.hpp new file mode 100644 index 0000000..683ee55 --- /dev/null +++ b/src/Input.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include "Time.hpp" +#include "Common/Casts.hpp" +#include "Common/Sizes.hpp" +#include "Common/Pure.hpp" +#include "GFX/Window.hpp" +#include "Math/Vector.hpp" + +namespace MC { + +// TODO: Add some more keys. +// TODO: I think some of these keys are "modifiers" under GLFW, and should be handled differently. +enum class Key { + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine, + Escape, Enter, Tab, Backspace, Insert, Delete, Right, Left, Down, Up, + LeftShift, RightShift, LeftControl, RightControl, LeftAlt, RightAlt, + Space, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, +}; + +constexpr USize KeyCount = 65; +static_assert(KeyCount == TO(USize, Key::F12) + 1, "Key count mismatch."); + +enum class Mouse { + Left, Right, Middle, +}; + +constexpr USize MouseCount = 3; +static_assert(MouseCount == TO(USize, Mouse::Middle) + 1, "Mouse count mismatch."); + +// The simple input system. +// All methods are thread safe, except the initial registration of the input callbacks +// and the state update. Both must be called from the main thread. +class Input { +public: + explicit Input() = default; + + // Registers the input callbacks for the given window. + // This function must be called from the main thread. + static void register_callbacks(GFX::Window const& window); + + // Updates the input state from the callback buffers. + // This function must be called from the main thread. + void update(Time::Timestamp current_time); + + // Returns true if the key was pressed during the last frame. + PURE Bool pressed(Key key) const; + // Returns true if the mouse button was pressed during the last frame. + PURE Bool pressed(Mouse button) const; + + // Returns true if the key is currently held down. + PURE Bool held(Key key) const; + // Returns true if the mouse button is currently held down. + PURE Bool held(Mouse button) const; + + // Returns true if the key was released during the last frame. + PURE Bool released(Key key) const; + // Returns true if the mouse button was released during the last frame. + PURE Bool released(Mouse button) const; + + // Returns true if the key was double pressed during the last frame. + // A double press is defined as two presses within a certain time frame. + PURE Bool double_pressed(Key key) const; + // Returns true if the mouse button was double pressed during the last frame. + // A double press is defined as two presses within a certain time frame. + PURE Bool double_pressed(Mouse button) const; + + // Returns the position of the mouse cursor in the window. + PURE Vector<2> mouse_position() const; + + // Returns the change in the position of the mouse cursor since the last frame. + PURE Vector<2> mouse_movement() const; + +private: + template + struct ButtonMap { + T keys[KeyCount] = { 0 }; + T mouse_buttons[MouseCount] = { 0 }; + }; + + struct FrameState { + ButtonMap map; + Vec2 mouse_position = { 0, 0 }; + }; + + std::unique_ptr m_state = std::make_unique(); + std::unique_ptr m_previous_state = std::make_unique(); + + std::unique_ptr> m_last_key_release = std::make_unique>(); + std::unique_ptr> m_double_press = std::make_unique>(); + + static Bool key_pressed(std::unique_ptr const& current, std::unique_ptr const& previous, Key key); + static Bool mouse_button_pressed(std::unique_ptr const& current, std::unique_ptr const& previous, Mouse mouse); + static Bool key_released(std::unique_ptr const& current, std::unique_ptr const& previous, Key key); + static Bool mouse_button_released(std::unique_ptr const& current, std::unique_ptr const& previous, Mouse mouse); + + static Bool s_key_buffer[KeyCount]; + static Bool s_mouse_buffer[MouseCount]; + static Vec2 s_mouse_position; + enum class MouseEventType { None, First, Subsequent }; + static MouseEventType s_mouse_event_type; + + static USize key_value(Key key); + static USize mouse_button_value(Mouse button); + + static Key from_glfw_key(I32 key); + static Mouse from_glfw_mouse(I32 button); + + static void key_callback(GLFWwindow* _window, I32 key, I32 _scancode, I32 action, I32 _mods); + static void cursor_position_callback(GLFWwindow* _window, F64 x, F64 y); + static void mouse_button_callback(GLFWwindow* _window, I32 button, I32 action, I32 _mods); +}; + +} diff --git a/src/Time.cpp b/src/Time.cpp index e459374..1953fed 100644 --- a/src/Time.cpp +++ b/src/Time.cpp @@ -1,4 +1,5 @@ #include "Time.hpp" +#include "Common/Casts.hpp" #include #include @@ -10,7 +11,7 @@ void Time::start_frame() { void Time::end_frame() { auto frame_end = now(); - m_delta = (frame_end - m_current_frame_start) / 1000.0; + m_delta = TO(Real, frame_end - m_current_frame_start) / 1000.0; m_delta = std::clamp(m_delta, delta_min, delta_max); m_total_frames++; @@ -28,6 +29,10 @@ Real Time::delta() const { return m_delta; } +Time::Timestamp Time::frame_start() const { + return m_current_frame_start; +} + Time::Timestamp Time::now() { auto time = std::chrono::system_clock::now().time_since_epoch(); auto ms = std::chrono::duration_cast(time); diff --git a/src/Time.hpp b/src/Time.hpp index 04f22c8..64ab4a2 100644 --- a/src/Time.hpp +++ b/src/Time.hpp @@ -1,5 +1,6 @@ #pragma once +#include "Common/Pure.hpp" #include "Common/Sizes.hpp" namespace MC { @@ -14,9 +15,17 @@ public: void start_frame(); void end_frame(); - U64 total_frames() const; - Tick tick() const; - Real delta() const; + // The total number of frames that have been rendered. + // Starts at 1, with 0 being reserved as the empty value. + PURE U64 total_frames() const; + // The current frame number. + // Starts at 1, with 0 being reserved as the empty value. + // This is the same as total_frames(), but is more descriptive, sometimes. + // :) + PURE Tick tick() const; + PURE Real delta() const; + + PURE Timestamp frame_start() const; static Timestamp now(); @@ -24,7 +33,7 @@ private: static constexpr Real delta_max = 1.0 / 10.0; static constexpr Real delta_min = 1.0 / 1000.0; - U64 m_total_frames = 0; + U64 m_total_frames = 1; Timestamp m_current_frame_start = 0; Real m_delta = 0; -- cgit 1.4.1