ADD: Input system
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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<EngineContext>();
|
||||
_input = std::make_unique<InputSystem>();
|
||||
_context->input = _input.get();
|
||||
_context->device = _deviceManager;
|
||||
_context->resources = _resourceManager;
|
||||
_context->descriptors = std::make_shared<DescriptorAllocatorGrowable>(); {
|
||||
@@ -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<NativeEventDispatchCtx *>(user);
|
||||
if (!ctx || !ctx->engine || view.backend != InputSystem::NativeBackend::SDL2 || view.data == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const SDL_Event &e = *static_cast<const SDL_Event *>(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)
|
||||
|
||||
@@ -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> _swapchainManager;
|
||||
std::shared_ptr<ResourceManager> _resourceManager;
|
||||
std::unique_ptr<RenderPassManager> _renderPassManager;
|
||||
std::unique_ptr<ImGuiSystem> _ui;
|
||||
std::unique_ptr<ImGuiSystem> _ui;
|
||||
std::unique_ptr<SceneManager> _sceneManager;
|
||||
std::unique_ptr<PickingSystem> _picking;
|
||||
std::unique_ptr<InputSystem> _input;
|
||||
std::unique_ptr<PipelineManager> _pipelineManager;
|
||||
std::unique_ptr<AssetManager> _assetManager;
|
||||
std::unique_ptr<AsyncAssetLoader> _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<std::string, bool> _rgPassToggles;
|
||||
|
||||
|
||||
364
src/core/input/input_system.cpp
Normal file
364
src/core/input/input_system.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
#include "input_system.h"
|
||||
|
||||
#include "SDL2/SDL.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
struct InputSystem::Impl
|
||||
{
|
||||
std::vector<SDL_Event> 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<float>(wheel.x), static_cast<float>(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<size_t>(static_cast<uint16_t>(key));
|
||||
}
|
||||
|
||||
size_t InputState::mouse_index(MouseButton button)
|
||||
{
|
||||
return static_cast<size_t>(static_cast<uint8_t>(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<Impl>())
|
||||
{
|
||||
}
|
||||
|
||||
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<uint16_t>(e.key.keysym.scancode);
|
||||
const Key key = static_cast<Key>(code);
|
||||
const InputModifiers mods = mods_from_sdl(static_cast<SDL_Keymod>(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_Keymod>(SDL_GetModState()));
|
||||
|
||||
_state.set_modifiers(mods);
|
||||
_state.set_mouse_button(btn, down);
|
||||
_state.add_mouse_motion(glm::vec2(static_cast<float>(e.button.x), static_cast<float>(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<float>(e.button.x), static_cast<float>(e.button.y));
|
||||
_events.push_back(ev);
|
||||
}
|
||||
else if (e.type == SDL_MOUSEMOTION)
|
||||
{
|
||||
const InputModifiers mods = mods_from_sdl(static_cast<SDL_Keymod>(SDL_GetModState()));
|
||||
_state.set_modifiers(mods);
|
||||
_state.add_mouse_motion(glm::vec2(static_cast<float>(e.motion.x), static_cast<float>(e.motion.y)),
|
||||
glm::vec2(static_cast<float>(e.motion.xrel), static_cast<float>(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<float>(e.motion.x), static_cast<float>(e.motion.y));
|
||||
ev.mouse_delta = glm::vec2(static_cast<float>(e.motion.xrel), static_cast<float>(e.motion.yrel));
|
||||
_events.push_back(ev);
|
||||
}
|
||||
else if (e.type == SDL_MOUSEWHEEL)
|
||||
{
|
||||
const InputModifiers mods = mods_from_sdl(static_cast<SDL_Keymod>(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);
|
||||
}
|
||||
}
|
||||
|
||||
220
src/core/input/input_system.h
Normal file
220
src/core/input/input_system.h
Normal file
@@ -0,0 +1,220 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/types.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
// 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<uint8_t, kMaxKeys> _keys_down{};
|
||||
std::array<uint8_t, kMaxKeys> _keys_pressed{};
|
||||
std::array<uint8_t, kMaxKeys> _keys_released{};
|
||||
|
||||
std::array<uint8_t, kMouseButtonCount> _mouse_down{};
|
||||
std::array<uint8_t, kMouseButtonCount> _mouse_pressed{};
|
||||
std::array<uint8_t, kMouseButtonCount> _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<const InputEvent> 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> _impl;
|
||||
|
||||
InputState _state{};
|
||||
std::vector<InputEvent> _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;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "camera.h"
|
||||
#include <glm/gtx/transform.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
@@ -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<float>(e.motion.xrel) * lookSensitivity;
|
||||
float dy = static_cast<float>(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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/types.h>
|
||||
#include <SDL_events.h>
|
||||
|
||||
#include <core/input/input_system.h>
|
||||
|
||||
#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();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user