diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 201727a..b8397ab 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,8 @@ add_executable (vulkan_engine core/engine.h core/engine.cpp core/engine_ui.cpp + core/input/input_system.h + core/input/input_system.cpp core/ui/imgui_system.h core/ui/imgui_system.cpp core/picking/picking_system.h diff --git a/src/core/context.h b/src/core/context.h index 4f3f2c8..71e1287 100644 --- a/src/core/context.h +++ b/src/core/context.h @@ -32,6 +32,7 @@ class RenderGraph; class RayTracingManager; class TextureCache; class IBLManager; +class InputSystem; struct ShadowSettings { @@ -66,6 +67,7 @@ public: PipelineManager* pipelines = nullptr; // graphics pipeline manager RenderGraph* renderGraph = nullptr; // render graph (built per-frame) SDL_Window* window = nullptr; // SDL window handle + InputSystem* input = nullptr; // input system (engine-owned) // Frequently used values VkExtent2D drawExtent{}; diff --git a/src/core/engine.cpp b/src/core/engine.cpp index b23fe1f..fc7bddc 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -15,6 +15,8 @@ //> includes #include "engine.h" +#include "core/input/input_system.h" + #include "SDL2/SDL.h" #include "SDL2/SDL_vulkan.h" @@ -237,6 +239,8 @@ void VulkanEngine::init() // Build dependency-injection context _context = std::make_shared(); + _input = std::make_unique(); + _context->input = _input.get(); _context->device = _deviceManager; _context->resources = _resourceManager; _context->descriptors = std::make_shared(); { @@ -922,6 +926,16 @@ void VulkanEngine::cleanup() dump_vma_json(_deviceManager.get(), "before_DeviceManager"); _deviceManager->cleanup(); + if (_input) + { + _input->set_cursor_mode(CursorMode::Normal); + _input.reset(); + } + if (_context) + { + _context->input = nullptr; + } + SDL_DestroyWindow(_window); } } @@ -1264,75 +1278,78 @@ void VulkanEngine::draw() _frameNumber++; } +namespace +{ + struct NativeEventDispatchCtx + { + VulkanEngine *engine = nullptr; + bool ui_capture_mouse = false; + }; + + void dispatch_native_event(void *user, InputSystem::NativeEventView view) + { + auto *ctx = static_cast(user); + if (!ctx || !ctx->engine || view.backend != InputSystem::NativeBackend::SDL2 || view.data == nullptr) + { + return; + } + + const SDL_Event &e = *static_cast(view.data); + + if (ctx->engine->ui()) + { + ctx->engine->ui()->process_event(e); + } + if (ctx->engine->picking()) + { + ctx->engine->picking()->process_event(e, ctx->ui_capture_mouse); + } + } +} // namespace + void VulkanEngine::run() { - SDL_Event e; bool bQuit = false; //main loop while (!bQuit) { auto start = std::chrono::system_clock::now(); - //Handle events on queue - while (SDL_PollEvent(&e) != 0) + + if (_input) { - //close the window when user alt-f4s or clicks the X button - if (e.type == SDL_QUIT) + _input->begin_frame(); + _input->pump_events(); + + if (_input->quit_requested()) { bQuit = true; } - if (e.type == SDL_WINDOWEVENT) - { - switch (e.window.event) - { - case SDL_WINDOWEVENT_MINIMIZED: - freeze_rendering = true; - break; - case SDL_WINDOWEVENT_RESTORED: - freeze_rendering = false; - resize_requested = true; - _last_resize_event_ms = SDL_GetTicks(); - break; - case SDL_WINDOWEVENT_SIZE_CHANGED: - case SDL_WINDOWEVENT_RESIZED: - resize_requested = true; - _last_resize_event_ms = SDL_GetTicks(); - break; - case SDL_WINDOWEVENT_MOVED: - // Moving between monitors can change DPI scale; ensure swapchain is refreshed. - resize_requested = true; - _last_resize_event_ms = SDL_GetTicks(); - break; - default: - break; - } - } + freeze_rendering = _input->window_minimized(); - const bool ui_capture_mouse = _ui && _ui->want_capture_mouse(); - const bool ui_capture_keyboard = _ui && _ui->want_capture_keyboard(); + if (_input->resize_requested()) + { + resize_requested = true; + _last_resize_event_ms = _input->last_resize_event_ms(); + _input->clear_resize_request(); + } + } - if (_ui) - { - _ui->process_event(e); - } - if (_picking) - { - _picking->process_event(e, ui_capture_mouse); - } - if (_sceneManager) - { - const bool key_event = (e.type == SDL_KEYDOWN) || (e.type == SDL_KEYUP); - const bool mouse_event = (e.type == SDL_MOUSEBUTTONDOWN) || - (e.type == SDL_MOUSEBUTTONUP) || - (e.type == SDL_MOUSEMOTION) || - (e.type == SDL_MOUSEWHEEL); + const bool ui_capture_mouse = _ui && _ui->want_capture_mouse(); + const bool ui_capture_keyboard = _ui && _ui->want_capture_keyboard(); - if (!(ui_capture_keyboard && key_event) && !(ui_capture_mouse && mouse_event)) - { - _sceneManager->getMainCamera().processSDLEvent(e); - } - } + if (_input) + { + NativeEventDispatchCtx ctx{}; + ctx.engine = this; + ctx.ui_capture_mouse = ui_capture_mouse; + _input->for_each_native_event(dispatch_native_event, &ctx); + } + + if (_sceneManager && _input) + { + _sceneManager->getMainCamera().process_input(*_input, ui_capture_keyboard, ui_capture_mouse); } if (freeze_rendering) diff --git a/src/core/engine.h b/src/core/engine.h index 3e48c1b..a26c787 100644 --- a/src/core/engine.h +++ b/src/core/engine.h @@ -40,6 +40,8 @@ #include "core/ui/imgui_system.h" #include "core/picking/picking_system.h" +class InputSystem; + // Number of frames-in-flight. Affects per-frame command buffers, fences, // semaphores, and transient descriptor pools in FrameResources. constexpr unsigned int FRAME_OVERLAP = 2; @@ -81,9 +83,10 @@ public: std::unique_ptr _swapchainManager; std::shared_ptr _resourceManager; std::unique_ptr _renderPassManager; - std::unique_ptr _ui; + std::unique_ptr _ui; std::unique_ptr _sceneManager; std::unique_ptr _picking; + std::unique_ptr _input; std::unique_ptr _pipelineManager; std::unique_ptr _assetManager; std::unique_ptr _asyncLoader; @@ -170,6 +173,9 @@ public: PickingSystem *picking() { return _picking.get(); } const PickingSystem *picking() const { return _picking.get(); } + InputSystem *input() { return _input.get(); } + const InputSystem *input() const { return _input.get(); } + // Debug: persistent pass enable overrides (by pass name) std::unordered_map _rgPassToggles; diff --git a/src/core/input/input_system.cpp b/src/core/input/input_system.cpp new file mode 100644 index 0000000..d95247d --- /dev/null +++ b/src/core/input/input_system.cpp @@ -0,0 +1,364 @@ +#include "input_system.h" + +#include "SDL2/SDL.h" + +#include +#include + +struct InputSystem::Impl +{ + std::vector sdl_events{}; +}; + +namespace +{ + InputModifiers mods_from_sdl(const SDL_Keymod mod) + { + InputModifiers out{}; + out.shift = (mod & KMOD_SHIFT) != 0; + out.ctrl = (mod & KMOD_CTRL) != 0; + out.alt = (mod & KMOD_ALT) != 0; + out.super = (mod & KMOD_GUI) != 0; + return out; + } + + bool map_sdl_mouse_button(uint8_t sdl_button, MouseButton &out) + { + switch (sdl_button) + { + case SDL_BUTTON_LEFT: + out = MouseButton::Left; + return true; + case SDL_BUTTON_MIDDLE: + out = MouseButton::Middle; + return true; + case SDL_BUTTON_RIGHT: + out = MouseButton::Right; + return true; + case SDL_BUTTON_X1: + out = MouseButton::X1; + return true; + case SDL_BUTTON_X2: + out = MouseButton::X2; + return true; + default: + return false; + } + } + + glm::vec2 wheel_from_sdl(const SDL_MouseWheelEvent &wheel) + { + glm::vec2 delta(static_cast(wheel.x), static_cast(wheel.y)); + if (wheel.direction == SDL_MOUSEWHEEL_FLIPPED) + { + delta = -delta; + } + return delta; + } +} // namespace + +void InputState::begin_frame() +{ + std::fill(_keys_pressed.begin(), _keys_pressed.end(), 0); + std::fill(_keys_released.begin(), _keys_released.end(), 0); + + std::fill(_mouse_pressed.begin(), _mouse_pressed.end(), 0); + std::fill(_mouse_released.begin(), _mouse_released.end(), 0); + + _mouse_delta = glm::vec2(0.0f); + _wheel_delta = glm::vec2(0.0f); +} + +bool InputState::key_down(Key key) const +{ + const size_t idx = key_index(key); + if (idx >= _keys_down.size()) return false; + return _keys_down[idx] != 0; +} + +bool InputState::key_pressed(Key key) const +{ + const size_t idx = key_index(key); + if (idx >= _keys_pressed.size()) return false; + return _keys_pressed[idx] != 0; +} + +bool InputState::key_released(Key key) const +{ + const size_t idx = key_index(key); + if (idx >= _keys_released.size()) return false; + return _keys_released[idx] != 0; +} + +bool InputState::mouse_down(MouseButton button) const +{ + const size_t idx = mouse_index(button); + if (idx >= _mouse_down.size()) return false; + return _mouse_down[idx] != 0; +} + +bool InputState::mouse_pressed(MouseButton button) const +{ + const size_t idx = mouse_index(button); + if (idx >= _mouse_pressed.size()) return false; + return _mouse_pressed[idx] != 0; +} + +bool InputState::mouse_released(MouseButton button) const +{ + const size_t idx = mouse_index(button); + if (idx >= _mouse_released.size()) return false; + return _mouse_released[idx] != 0; +} + +size_t InputState::key_index(Key key) +{ + return static_cast(static_cast(key)); +} + +size_t InputState::mouse_index(MouseButton button) +{ + return static_cast(static_cast(button)); +} + +void InputState::set_key(Key key, bool down, bool repeat) +{ + const size_t idx = key_index(key); + if (idx >= _keys_down.size()) return; + + const bool was_down = _keys_down[idx] != 0; + if (down) + { + _keys_down[idx] = 1; + if (!was_down && !repeat) + { + _keys_pressed[idx] = 1; + } + } + else + { + _keys_down[idx] = 0; + if (was_down) + { + _keys_released[idx] = 1; + } + } +} + +void InputState::set_mouse_button(MouseButton button, bool down) +{ + const size_t idx = mouse_index(button); + if (idx >= _mouse_down.size()) return; + + const bool was_down = _mouse_down[idx] != 0; + if (down) + { + _mouse_down[idx] = 1; + if (!was_down) + { + _mouse_pressed[idx] = 1; + } + } + else + { + _mouse_down[idx] = 0; + if (was_down) + { + _mouse_released[idx] = 1; + } + } +} + +void InputState::add_mouse_motion(const glm::vec2 &pos, const glm::vec2 &delta) +{ + _mouse_pos = pos; + _mouse_delta += delta; +} + +void InputState::add_mouse_wheel(const glm::vec2 &delta) +{ + _wheel_delta += delta; +} + +void InputState::set_modifiers(const InputModifiers &mods) +{ + _mods = mods; +} + +InputSystem::InputSystem() + : _impl(std::make_unique()) +{ +} + +InputSystem::~InputSystem() = default; + +void InputSystem::begin_frame() +{ + _state.begin_frame(); + _events.clear(); + if (_impl) + { + _impl->sdl_events.clear(); + } +} + +void InputSystem::pump_events() +{ + if (!_impl) + { + return; + } + + SDL_Event e{}; + while (SDL_PollEvent(&e) != 0) + { + _impl->sdl_events.push_back(e); + + // Track app/window state + if (e.type == SDL_QUIT) + { + _quit_requested = true; + } + if (e.type == SDL_WINDOWEVENT) + { + switch (e.window.event) + { + case SDL_WINDOWEVENT_MINIMIZED: + _window_minimized = true; + break; + case SDL_WINDOWEVENT_RESTORED: + _window_minimized = false; + _resize_requested = true; + _last_resize_event_ms = SDL_GetTicks(); + break; + case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_WINDOWEVENT_RESIZED: + _resize_requested = true; + _last_resize_event_ms = SDL_GetTicks(); + break; + case SDL_WINDOWEVENT_MOVED: + _resize_requested = true; + _last_resize_event_ms = SDL_GetTicks(); + break; + default: + break; + } + } + + // Convert to engine input events + state + if (e.type == SDL_KEYDOWN || e.type == SDL_KEYUP) + { + const bool down = (e.type == SDL_KEYDOWN); + const bool repeat = down && (e.key.repeat != 0); + const uint16_t code = static_cast(e.key.keysym.scancode); + const Key key = static_cast(code); + const InputModifiers mods = mods_from_sdl(static_cast(e.key.keysym.mod)); + + _state.set_modifiers(mods); + _state.set_key(key, down, repeat); + + InputEvent ev{}; + ev.type = down ? InputEvent::Type::KeyDown : InputEvent::Type::KeyUp; + ev.timestamp_ms = e.key.timestamp; + ev.mods = mods; + ev.key = key; + _events.push_back(ev); + } + else if (e.type == SDL_MOUSEBUTTONDOWN || e.type == SDL_MOUSEBUTTONUP) + { + MouseButton btn{}; + if (!map_sdl_mouse_button(e.button.button, btn)) + { + continue; + } + const bool down = (e.type == SDL_MOUSEBUTTONDOWN); + const InputModifiers mods = mods_from_sdl(static_cast(SDL_GetModState())); + + _state.set_modifiers(mods); + _state.set_mouse_button(btn, down); + _state.add_mouse_motion(glm::vec2(static_cast(e.button.x), static_cast(e.button.y)), + glm::vec2(0.0f)); + + InputEvent ev{}; + ev.type = down ? InputEvent::Type::MouseButtonDown : InputEvent::Type::MouseButtonUp; + ev.timestamp_ms = e.button.timestamp; + ev.mods = mods; + ev.mouse_button = btn; + ev.mouse_pos = glm::vec2(static_cast(e.button.x), static_cast(e.button.y)); + _events.push_back(ev); + } + else if (e.type == SDL_MOUSEMOTION) + { + const InputModifiers mods = mods_from_sdl(static_cast(SDL_GetModState())); + _state.set_modifiers(mods); + _state.add_mouse_motion(glm::vec2(static_cast(e.motion.x), static_cast(e.motion.y)), + glm::vec2(static_cast(e.motion.xrel), static_cast(e.motion.yrel))); + + InputEvent ev{}; + ev.type = InputEvent::Type::MouseMove; + ev.timestamp_ms = e.motion.timestamp; + ev.mods = mods; + ev.mouse_pos = glm::vec2(static_cast(e.motion.x), static_cast(e.motion.y)); + ev.mouse_delta = glm::vec2(static_cast(e.motion.xrel), static_cast(e.motion.yrel)); + _events.push_back(ev); + } + else if (e.type == SDL_MOUSEWHEEL) + { + const InputModifiers mods = mods_from_sdl(static_cast(SDL_GetModState())); + const glm::vec2 delta = wheel_from_sdl(e.wheel); + + _state.set_modifiers(mods); + _state.add_mouse_wheel(delta); + + InputEvent ev{}; + ev.type = InputEvent::Type::MouseWheel; + ev.timestamp_ms = e.wheel.timestamp; + ev.mods = mods; + ev.wheel_delta = delta; + _events.push_back(ev); + } + } +} + +void InputSystem::set_cursor_mode(CursorMode mode) +{ + if (_cursor_mode == mode) + { + return; + } + + switch (mode) + { + case CursorMode::Normal: + SDL_SetRelativeMouseMode(SDL_FALSE); + SDL_ShowCursor(SDL_ENABLE); + break; + case CursorMode::Hidden: + SDL_SetRelativeMouseMode(SDL_FALSE); + SDL_ShowCursor(SDL_DISABLE); + break; + case CursorMode::Relative: + SDL_ShowCursor(SDL_DISABLE); + SDL_SetRelativeMouseMode(SDL_TRUE); + break; + } + + _cursor_mode = mode; +} + +void InputSystem::for_each_native_event(NativeEventCallback callback, void *user) const +{ + if (!_impl || !callback) + { + return; + } + + for (const SDL_Event &e: _impl->sdl_events) + { + NativeEventView view{}; + view.backend = NativeBackend::SDL2; + view.data = &e; + callback(user, view); + } +} + diff --git a/src/core/input/input_system.h b/src/core/input/input_system.h new file mode 100644 index 0000000..f5b05b0 --- /dev/null +++ b/src/core/input/input_system.h @@ -0,0 +1,220 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +// Cross-platform input codes loosely based on USB HID usage IDs (and SDL scancodes). +// Keep this list minimal and extend as needed. +enum class Key : uint16_t +{ + Unknown = 0, + + A = 4, + B = 5, + C = 6, + D = 7, + E = 8, + F = 9, + G = 10, + H = 11, + I = 12, + J = 13, + K = 14, + L = 15, + M = 16, + N = 17, + O = 18, + P = 19, + Q = 20, + R = 21, + S = 22, + T = 23, + U = 24, + V = 25, + W = 26, + X = 27, + Y = 28, + Z = 29, + + Num1 = 30, + Num2 = 31, + Num3 = 32, + Num4 = 33, + Num5 = 34, + Num6 = 35, + Num7 = 36, + Num8 = 37, + Num9 = 38, + Num0 = 39, + + Enter = 40, + Escape = 41, + Backspace = 42, + Tab = 43, + Space = 44, + + LeftCtrl = 224, + LeftShift = 225, + LeftAlt = 226, + LeftSuper = 227, + RightCtrl = 228, + RightShift = 229, + RightAlt = 230, + RightSuper = 231, +}; + +enum class MouseButton : uint8_t +{ + Left = 0, + Middle = 1, + Right = 2, + X1 = 3, + X2 = 4, +}; + +enum class CursorMode : uint8_t +{ + Normal = 0, + Hidden = 1, + Relative = 2, +}; + +struct InputModifiers +{ + bool shift = false; + bool ctrl = false; + bool alt = false; + bool super = false; +}; + +struct InputEvent +{ + enum class Type : uint8_t + { + KeyDown, + KeyUp, + MouseButtonDown, + MouseButtonUp, + MouseMove, + MouseWheel, + }; + + Type type = Type::KeyDown; + uint32_t timestamp_ms = 0; + InputModifiers mods{}; + + Key key = Key::Unknown; + MouseButton mouse_button = MouseButton::Left; + + glm::vec2 mouse_pos{0.0f}; + glm::vec2 mouse_delta{0.0f}; + glm::vec2 wheel_delta{0.0f}; +}; + +class InputState +{ +public: + static constexpr uint16_t kMaxKeys = 512; + static constexpr uint8_t kMouseButtonCount = 5; + + void begin_frame(); + + bool key_down(Key key) const; + bool key_pressed(Key key) const; + bool key_released(Key key) const; + + bool mouse_down(MouseButton button) const; + bool mouse_pressed(MouseButton button) const; + bool mouse_released(MouseButton button) const; + + glm::vec2 mouse_position() const { return _mouse_pos; } + glm::vec2 mouse_delta() const { return _mouse_delta; } + glm::vec2 wheel_delta() const { return _wheel_delta; } + InputModifiers modifiers() const { return _mods; } + +private: + friend class InputSystem; + + static size_t key_index(Key key); + static size_t mouse_index(MouseButton button); + + void set_key(Key key, bool down, bool repeat); + void set_mouse_button(MouseButton button, bool down); + void add_mouse_motion(const glm::vec2 &pos, const glm::vec2 &delta); + void add_mouse_wheel(const glm::vec2 &delta); + void set_modifiers(const InputModifiers &mods); + + std::array _keys_down{}; + std::array _keys_pressed{}; + std::array _keys_released{}; + + std::array _mouse_down{}; + std::array _mouse_pressed{}; + std::array _mouse_released{}; + + glm::vec2 _mouse_pos{0.0f}; + glm::vec2 _mouse_delta{0.0f}; + glm::vec2 _wheel_delta{0.0f}; + InputModifiers _mods{}; +}; + +class InputSystem +{ +public: + enum class NativeBackend : uint8_t + { + SDL2 = 0, + }; + + struct NativeEventView + { + NativeBackend backend = NativeBackend::SDL2; + const void *data = nullptr; + }; + + using NativeEventCallback = void (*)(void *user, NativeEventView event); + + InputSystem(); + ~InputSystem(); + + InputSystem(const InputSystem &) = delete; + InputSystem &operator=(const InputSystem &) = delete; + + void begin_frame(); + void pump_events(); + + const InputState &state() const { return _state; } + std::span events() const { return _events; } + + bool quit_requested() const { return _quit_requested; } + bool window_minimized() const { return _window_minimized; } + + bool resize_requested() const { return _resize_requested; } + uint32_t last_resize_event_ms() const { return _last_resize_event_ms; } + void clear_resize_request() { _resize_requested = false; } + + CursorMode cursor_mode() const { return _cursor_mode; } + void set_cursor_mode(CursorMode mode); + + // Engine-internal: dispatch native platform events (SDL events today). + void for_each_native_event(NativeEventCallback callback, void *user) const; + +private: + struct Impl; + std::unique_ptr _impl; + + InputState _state{}; + std::vector _events{}; + + bool _quit_requested = false; + bool _window_minimized = false; + bool _resize_requested = false; + uint32_t _last_resize_event_ms = 0; + CursorMode _cursor_mode = CursorMode::Normal; +}; + diff --git a/src/scene/camera.cpp b/src/scene/camera.cpp index 8a30eac..68cbb8d 100644 --- a/src/scene/camera.cpp +++ b/src/scene/camera.cpp @@ -1,7 +1,6 @@ #include "camera.h" #include #include -#include #include #include @@ -12,63 +11,90 @@ void Camera::update() position_world += glm::dvec3(delta); } -void Camera::processSDLEvent(SDL_Event& e) +void Camera::process_input(InputSystem &input, bool ui_capture_keyboard, bool ui_capture_mouse) { - if (e.type == SDL_KEYDOWN) { - // Camera uses -Z forward convention (right-handed) - if (e.key.keysym.sym == SDLK_w) { velocity.z = -1; } - if (e.key.keysym.sym == SDLK_s) { velocity.z = 1; } - if (e.key.keysym.sym == SDLK_a) { velocity.x = -1; } - if (e.key.keysym.sym == SDLK_d) { velocity.x = 1; } + const InputState &st = input.state(); + + // Movement is state-based so simultaneous keys work naturally. + if (ui_capture_keyboard) + { + velocity = glm::vec3(0.0f); + } + else + { + glm::vec3 v(0.0f); + if (st.key_down(Key::W)) { v.z -= 1.0f; } + if (st.key_down(Key::S)) { v.z += 1.0f; } + if (st.key_down(Key::A)) { v.x -= 1.0f; } + if (st.key_down(Key::D)) { v.x += 1.0f; } + velocity = v; } - if (e.type == SDL_KEYUP) { - if (e.key.keysym.sym == SDLK_w) { velocity.z = 0; } - if (e.key.keysym.sym == SDLK_s) { velocity.z = 0; } - if (e.key.keysym.sym == SDLK_a) { velocity.x = 0; } - if (e.key.keysym.sym == SDLK_d) { velocity.x = 0; } - } - - if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_RIGHT) { - rmbDown = true; - SDL_SetRelativeMouseMode(SDL_TRUE); - } - if (e.type == SDL_MOUSEBUTTONUP && e.button.button == SDL_BUTTON_RIGHT) { - rmbDown = false; - SDL_SetRelativeMouseMode(SDL_FALSE); - } - - if (e.type == SDL_MOUSEMOTION && rmbDown) { - // Convert mouse motion to incremental yaw/pitch angles. - float dx = static_cast(e.motion.xrel) * lookSensitivity; - float dy = static_cast(e.motion.yrel) * lookSensitivity; - - // Mouse right (xrel > 0) turns view right with -Z-forward: yaw around +Y. - glm::quat yawRotation = glm::angleAxis(dx, glm::vec3 { 0.f, 1.f, 0.f }); - - // Mouse up (yrel < 0) looks up with -Z-forward: negative dy. - float pitchDelta = -dy; - // Pitch around the camera's local X (right) axis in world space. - glm::vec3 right = glm::rotate(orientation, glm::vec3 { 1.f, 0.f, 0.f }); - glm::quat pitchRotation = glm::angleAxis(pitchDelta, glm::vec3(right)); - - // Apply yaw, then pitch, to the current orientation. - orientation = glm::normalize(pitchRotation * yawRotation * orientation); - } - - if (e.type == SDL_MOUSEWHEEL) { - // Ctrl modifies FOV, otherwise adjust move speed - const bool ctrl = (SDL_GetModState() & KMOD_CTRL) != 0; - const int steps = e.wheel.y; // positive = wheel up - if (ctrl) { - // Wheel up -> zoom in (smaller FOV) - fovDegrees -= steps * 2.0f; - fovDegrees = std::clamp(fovDegrees, 30.0f, 110.0f); - } else { - // Exponential scale for pleasant feel - float factor = std::pow(1.15f, (float)steps); - moveSpeed = std::clamp(moveSpeed * factor, 0.001f, 5.0f); + // Event-based mouse handling so we don't apply motion that happened before RMB was pressed in the same frame. + for (const InputEvent &e : input.events()) + { + if (ui_capture_mouse) + { + continue; } + + if (e.type == InputEvent::Type::MouseButtonDown && e.mouse_button == MouseButton::Right) + { + rmbDown = true; + input.set_cursor_mode(CursorMode::Relative); + } + else if (e.type == InputEvent::Type::MouseButtonUp && e.mouse_button == MouseButton::Right) + { + rmbDown = false; + input.set_cursor_mode(CursorMode::Normal); + } + else if (e.type == InputEvent::Type::MouseMove && rmbDown) + { + // Convert mouse motion to incremental yaw/pitch angles. + float dx = e.mouse_delta.x * lookSensitivity; + float dy = e.mouse_delta.y * lookSensitivity; + + // Mouse right (xrel > 0) turns view right with -Z-forward: yaw around +Y. + glm::quat yawRotation = glm::angleAxis(dx, glm::vec3 { 0.f, 1.f, 0.f }); + + // Mouse up (yrel < 0) looks up with -Z-forward: negative dy. + float pitchDelta = -dy; + // Pitch around the camera's local X (right) axis in world space. + glm::vec3 right = glm::rotate(orientation, glm::vec3 { 1.f, 0.f, 0.f }); + glm::quat pitchRotation = glm::angleAxis(pitchDelta, glm::vec3(right)); + + // Apply yaw, then pitch, to the current orientation. + orientation = glm::normalize(pitchRotation * yawRotation * orientation); + } + else if (e.type == InputEvent::Type::MouseWheel) + { + const float steps = e.wheel_delta.y; // positive = wheel up + if (std::abs(steps) < 0.001f) + { + continue; + } + + // Ctrl modifies FOV, otherwise adjust move speed + if (e.mods.ctrl) + { + // Wheel up -> zoom in (smaller FOV) + fovDegrees -= steps * 2.0f; + fovDegrees = std::clamp(fovDegrees, 30.0f, 110.0f); + } + else + { + // Exponential scale for pleasant feel + float factor = std::pow(1.15f, steps); + moveSpeed = std::clamp(moveSpeed * factor, 0.001f, 5.0f); + } + } + } + + // Safety: if mouse state shows RMB is no longer down, release relative mode. + if (rmbDown && !st.mouse_down(MouseButton::Right)) + { + rmbDown = false; + input.set_cursor_mode(CursorMode::Normal); } } diff --git a/src/scene/camera.h b/src/scene/camera.h index 0b90583..ac67279 100644 --- a/src/scene/camera.h +++ b/src/scene/camera.h @@ -1,7 +1,8 @@ #pragma once #include -#include + +#include #include "glm/vec3.hpp" @@ -23,7 +24,7 @@ public: glm::mat4 getViewMatrix(const glm::vec3 &position_local) const; glm::mat4 getRotationMatrix() const; - void processSDLEvent(SDL_Event& e); + void process_input(InputSystem &input, bool ui_capture_keyboard, bool ui_capture_mouse); void update(); };