@@ -139,10 +139,14 @@ void HotkeyScheduler::Run()

while (!m_stop_requested.IsSet())
{
Common::SleepCurrentThread(1000 / 60);
Common::SleepCurrentThread(5);

if (Core::GetState() == Core::State::Uninitialized || Core::GetState() == Core::State::Paused)
g_controller_interface.UpdateInput();
g_controller_interface.SetCurrentInputChannel(ciface::InputChannel::FreeLook);
g_controller_interface.UpdateInput();
FreeLook::UpdateInput();

g_controller_interface.SetCurrentInputChannel(ciface::InputChannel::Host);
g_controller_interface.UpdateInput();

if (!HotkeyManagerEmu::IsEnabled())
continue;
@@ -546,8 +550,6 @@ void HotkeyScheduler::Run()
OSD::AddMessage(StringFromFormat("Free Look: %s", new_value ? "Enabled" : "Disabled"));
}

FreeLook::UpdateInput();

// Savestates
for (u32 i = 0; i < State::NUM_STATES; i++)
{
@@ -8,6 +8,7 @@

#include <QObject>

#include "Common/CommonTypes.h"
#include "Common/Flag.h"
#include "InputCommon/InputProfile.h"

@@ -32,9 +32,7 @@
#include "DolphinQt/Resources.h"
#include "DolphinQt/Settings.h"

#include "VideoCommon/FreeLookCamera.h"
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/VertexShaderManager.h"
#include "VideoCommon/VideoConfig.h"

RenderWidget::RenderWidget(QWidget* parent) : QWidget(parent)
@@ -180,11 +178,6 @@ bool RenderWidget::event(QEvent* event)

break;
}
case QEvent::MouseMove:
if (g_freelook_camera.IsActive())
OnFreeLookMouseMove(static_cast<QMouseEvent*>(event));
[[fallthrough]];

case QEvent::MouseButtonPress:
if (!Settings::Instance().GetHideCursor() && isActiveWindow())
{
@@ -237,23 +230,6 @@ bool RenderWidget::event(QEvent* event)
return QWidget::event(event);
}

void RenderWidget::OnFreeLookMouseMove(QMouseEvent* event)
{
const auto mouse_move = event->pos() - m_last_mouse;
m_last_mouse = event->pos();

if (event->buttons() & Qt::RightButton)
{
// Camera Pitch and Yaw:
g_freelook_camera.Rotate(Common::Vec3{mouse_move.y() / 200.f, mouse_move.x() / 200.f, 0.f});
}
else if (event->buttons() & Qt::MiddleButton)
{
// Camera Roll:
g_freelook_camera.Rotate({0.f, 0.f, mouse_move.x() / 200.f});
}
}

void RenderWidget::PassEventToImGui(const QEvent* event)
{
if (!Core::IsRunningAndStarted())
@@ -33,7 +33,6 @@ class RenderWidget final : public QWidget
void HandleCursorTimer();
void OnHideCursorChanged();
void OnKeepOnTopChanged(bool top);
void OnFreeLookMouseMove(QMouseEvent* event);
void PassEventToImGui(const QEvent* event);
void SetImGuiKeyMap();
void dragEnterEvent(QDragEnterEvent* event) override;
@@ -4,6 +4,7 @@

#include "InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h"

#include <algorithm>
#include <memory>

#include "Common/Common.h"
@@ -124,7 +125,8 @@ auto IMUGyroscope::GetRawState() const -> StateData

std::optional<IMUGyroscope::StateData> IMUGyroscope::GetState() const
{
if (controls[0]->control_ref->BoundCount() == 0)
if (std::all_of(controls.begin(), controls.end(),
[](const auto& control) { return control->control_ref->BoundCount() == 0; }))
{
// Set calibration to zero.
m_calibration = {};
@@ -37,6 +37,8 @@

ControllerInterface g_controller_interface;

static thread_local ciface::InputChannel tls_input_channel = ciface::InputChannel::Host;

void ControllerInterface::Initialize(const WindowSystemInfo& wsi)
{
if (m_is_init)
@@ -274,6 +276,16 @@ void ControllerInterface::UpdateInput()
}
}

void ControllerInterface::SetCurrentInputChannel(ciface::InputChannel input_channel)
{
tls_input_channel = input_channel;
}

ciface::InputChannel ControllerInterface::GetCurrentInputChannel()
{
return tls_input_channel;
}

void ControllerInterface::SetAspectRatioAdjustment(float value)
{
m_aspect_ratio_adjustment = value;
@@ -32,6 +32,23 @@
#endif
#define CIFACE_USE_DUALSHOCKUDPCLIENT

namespace ciface
{
// A thread local "input channel" is maintained to handle the state of relative inputs.
// This allows simultaneous use of relative inputs across different input contexts.
// e.g. binding relative mouse movements to both GameCube controllers and FreeLook.
// These operate at different rates and processing one would break the other without separate state.
enum class InputChannel
{
Host,
SerialInterface,
Bluetooth,
FreeLook,
Count,
};

} // namespace ciface

//
// ControllerInterface
//
@@ -66,6 +83,9 @@ class ControllerInterface : public ciface::Core::DeviceContainer
void UnregisterDevicesChangedCallback(const HotplugCallbackHandle& handle);
void InvokeDevicesChangedCallbacks() const;

static void SetCurrentInputChannel(ciface::InputChannel);
static ciface::InputChannel GetCurrentInputChannel();

private:
std::list<std::function<void()>> m_devices_changed_callbacks;
mutable std::mutex m_callbacks_mutex;
@@ -75,4 +95,37 @@ class ControllerInterface : public ciface::Core::DeviceContainer
std::atomic<float> m_aspect_ratio_adjustment = 1;
};

namespace ciface
{
template <typename T>
class RelativeInputState
{
public:
void Update()
{
const auto channel = int(ControllerInterface::GetCurrentInputChannel());

m_value[channel] = m_delta[channel];
m_delta[channel] = {};
}

T GetValue() const
{
const auto channel = int(ControllerInterface::GetCurrentInputChannel());

return m_value[channel];
}

void Move(T delta)
{
for (auto& d : m_delta)
d += delta;
}

private:
std::array<T, int(InputChannel::Count)> m_value;
std::array<T, int(InputChannel::Count)> m_delta;
};
} // namespace ciface

extern ControllerInterface g_controller_interface;
@@ -93,6 +93,12 @@ class Device
virtual bool IsChild(const Input*) const { return false; }
};

class RelativeInput : public Input
{
public:
bool IsDetectable() const override { return false; }
};

//
// Output
//
@@ -2,11 +2,14 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h"

#include <algorithm>

#include <fmt/format.h>

#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/DInput/DInput.h"
#include "InputCommon/ControllerInterface/DInput/DInputKeyboardMouse.h"

// (lower would be more sensitive) user can lower sensitivity by setting range
// seems decent here ( at 8 ), I don't think anyone would need more sensitive than this
@@ -19,6 +22,30 @@

namespace ciface::DInput
{
class RelativeMouseAxis final : public Core::Device::RelativeInput
{
public:
std::string GetName() const override
{
return fmt::format("RelativeMouse {}{}", char('X' + m_index), (m_scale > 0) ? '+' : '-');
}

RelativeMouseAxis(u8 index, bool positive, const RelativeMouseState* state)
: m_state(*state), m_index(index), m_scale(positive * 2 - 1)
{
}

ControlState GetState() const override
{
return ControlState(m_state.GetValue().data[m_index] * m_scale);
}

private:
const RelativeMouseState& m_state;
const u8 m_index;
const s8 m_scale;
};

static const struct
{
const BYTE code;
@@ -108,9 +135,17 @@ KeyboardMouse::KeyboardMouse(const LPDIRECTINPUTDEVICE8 kb_device,
AddInput(new Axis(i, ax, (2 == i) ? -1 : -MOUSE_AXIS_SENSITIVITY));
AddInput(new Axis(i, ax, -(2 == i) ? 1 : MOUSE_AXIS_SENSITIVITY));
}

// cursor, with a hax for-loop
for (unsigned int i = 0; i < 4; ++i)
AddInput(new Cursor(!!(i & 2), (&m_state_in.cursor.x)[i / 2], !!(i & 1)));

// Raw relative mouse movement.
for (unsigned int i = 0; i != mouse_caps.dwAxes; ++i)
{
AddInput(new RelativeMouseAxis(i, false, &m_state_in.relative_mouse));
AddInput(new RelativeMouseAxis(i, true, &m_state_in.relative_mouse));
}
}

void KeyboardMouse::UpdateCursorInput()
@@ -147,6 +182,8 @@ void KeyboardMouse::UpdateInput()
{
// set axes to zero
m_state_in.mouse = {};
m_state_in.relative_mouse = {};

// skip this input state
m_mo_device->GetDeviceState(sizeof(tmp_mouse), &tmp_mouse);
}
@@ -162,14 +199,17 @@ void KeyboardMouse::UpdateInput()
if (DIERR_INPUTLOST == mo_hr || DIERR_NOTACQUIRED == mo_hr)
m_mo_device->Acquire();

if (SUCCEEDED(kb_hr) && SUCCEEDED(mo_hr))
if (SUCCEEDED(mo_hr))
{
m_state_in.relative_mouse.Move({tmp_mouse.lX, tmp_mouse.lY, tmp_mouse.lZ});
m_state_in.relative_mouse.Update();

// need to smooth out the axes, otherwise it doesn't work for shit
for (unsigned int i = 0; i < 3; ++i)
((&m_state_in.mouse.lX)[i] += (&tmp_mouse.lX)[i]) /= 2;

// copy over the buttons
memcpy(m_state_in.mouse.rgbButtons, tmp_mouse.rgbButtons, sizeof(m_state_in.mouse.rgbButtons));
std::copy_n(tmp_mouse.rgbButtons, std::size(tmp_mouse.rgbButtons), m_state_in.mouse.rgbButtons);

UpdateCursorInput();
}
@@ -7,21 +7,31 @@
#include <windows.h>

#include "Common/Matrix.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"
#include "InputCommon/ControllerInterface/DInput/DInput8.h"

namespace ciface::DInput
{
void InitKeyboardMouse(IDirectInput8* const idi8, HWND hwnd);

using RelativeMouseState = RelativeInputState<Common::TVec3<LONG>>;

class KeyboardMouse : public Core::Device
{
private:
struct State
{
BYTE keyboard[256];

// Old smoothed relative mouse movement.
DIMOUSESTATE2 mouse;

// Normalized mouse cursor position.
Common::TVec2<ControlState> cursor;

// Raw relative mouse movement.
RelativeMouseState relative_mouse;
};

class Key : public Input
@@ -53,6 +63,7 @@ class KeyboardMouse : public Core::Device
public:
Axis(u8 index, const LONG& axis, LONG range) : m_axis(axis), m_range(range), m_index(index) {}
std::string GetName() const override;
bool IsDetectable() const override { return false; }
ControlState GetState() const override;

private:
@@ -197,6 +197,11 @@ KeyboardMouse::KeyboardMouse(Window window, int opcode, int pointer, int keyboar
// Mouse Axis, X-/+ and Y-/+
for (int i = 0; i != 4; ++i)
AddInput(new Axis(!!(i & 2), !!(i & 1), (i & 2) ? &m_state.axis.y : &m_state.axis.x));

// Relative Mouse, X-/+ and Y-/+
for (int i = 0; i != 4; ++i)
AddInput(new RelativeMouse(!!(i & 2), !!(i & 1),
(i & 2) ? &m_state.relative_mouse.y : &m_state.relative_mouse.x));
}

KeyboardMouse::~KeyboardMouse()
@@ -302,6 +307,9 @@ void KeyboardMouse::UpdateInput()
XFreeEventData(m_display, &event.xcookie);
}

m_state.relative_mouse.x = delta_x;
m_state.relative_mouse.y = delta_y;

// apply axis smoothing
m_state.axis.x *= MOUSE_AXIS_SMOOTHING;
m_state.axis.x += delta_x;
@@ -391,8 +399,20 @@ KeyboardMouse::Axis::Axis(u8 index, bool positive, const float* axis)
name = fmt::format("Axis {}{}", static_cast<char>('X' + m_index), (m_positive ? '+' : '-'));
}

KeyboardMouse::RelativeMouse::RelativeMouse(u8 index, bool positive, const float* axis)
: m_axis(axis), m_index(index), m_positive(positive)
{
name =
fmt::format("RelativeMouse {}{}", static_cast<char>('X' + m_index), (m_positive ? '+' : '-'));
}

ControlState KeyboardMouse::Axis::GetState() const
{
return std::max(0.0f, *m_axis / (m_positive ? MOUSE_AXIS_SENSITIVITY : -MOUSE_AXIS_SENSITIVITY));
}

ControlState KeyboardMouse::RelativeMouse::GetState() const
{
return std::max(0.0f, *m_axis / (m_positive ? MOUSE_AXIS_SENSITIVITY : -MOUSE_AXIS_SENSITIVITY));
}
} // namespace ciface::XInput2
@@ -30,6 +30,7 @@ class KeyboardMouse : public Core::Device
unsigned int buttons;
Common::Vec2 cursor;
Common::Vec2 axis;
Common::Vec2 relative_mouse;
};

class Key : public Input
@@ -91,6 +92,21 @@ class KeyboardMouse : public Core::Device
std::string name;
};

class RelativeMouse : public Input
{
public:
std::string GetName() const override { return name; }
bool IsDetectable() const override { return false; }
RelativeMouse(u8 index, bool positive, const float* axis);
ControlState GetState() const override;

private:
const float* m_axis;
const u8 m_index;
const bool m_positive;
std::string name;
};

private:
void SelectEventsForDevice(XIEventMask* mask, int deviceid);
void UpdateCursor();
@@ -102,15 +102,20 @@ class FPSController final : public CameraController

void Rotate(const Common::Vec3& amt) override
{
if (amt.Length() == 0)
return;

m_rotation += amt;

using Common::Quaternion;
const auto quat =
m_rotate_quat =
(Quaternion::RotateX(m_rotation.x) * Quaternion::RotateY(m_rotation.y)).Normalized();
Rotate(quat);
}

void Rotate(const Common::Quaternion& quat) override { m_rotate_quat = quat; }
void Rotate(const Common::Quaternion& quat) override
{
Rotate(Common::FromQuaternionToEuler(quat));
}

void Reset() override
{
@@ -153,15 +158,20 @@ class OrbitalController final : public CameraController

void Rotate(const Common::Vec3& amt) override
{
if (amt.Length() == 0)
return;

m_rotation += amt;

using Common::Quaternion;
const auto quat =
m_rotate_quat =
(Quaternion::RotateX(m_rotation.x) * Quaternion::RotateY(m_rotation.y)).Normalized();
Rotate(quat);
}

void Rotate(const Common::Quaternion& quat) override { m_rotate_quat = quat; }
void Rotate(const Common::Quaternion& quat) override
{
Rotate(Common::FromQuaternionToEuler(quat));
}

void Reset() override
{
@@ -246,21 +256,27 @@ void FreeLookCamera::Rotate(const Common::Vec3& amt)
m_dirty = true;
}

void FreeLookCamera::Rotate(const Common::Quaternion& amt)
{
m_camera_controller->Rotate(amt);
m_dirty = true;
}

void FreeLookCamera::IncreaseFovX(float fov)
{
m_fov_x += fov;
m_fov_x = std::clamp(m_fov_x, m_fov_step_size, m_fov_x);
m_fov_x = std::clamp(m_fov_x, m_min_fov_multiplier, m_fov_x);
}

void FreeLookCamera::IncreaseFovY(float fov)
{
m_fov_y += fov;
m_fov_y = std::clamp(m_fov_y, m_fov_step_size, m_fov_y);
m_fov_y = std::clamp(m_fov_y, m_min_fov_multiplier, m_fov_y);
}

float FreeLookCamera::GetFovStepSize() const
{
return m_fov_step_size;
return 1.5f;
}

void FreeLookCamera::Reset()
@@ -271,14 +287,15 @@ void FreeLookCamera::Reset()
m_dirty = true;
}

void FreeLookCamera::ModifySpeed(float multiplier)
void FreeLookCamera::ModifySpeed(float amt)
{
m_speed *= multiplier;
m_speed += amt;
m_speed = std::clamp(m_speed, 0.0f, m_speed);
}

void FreeLookCamera::ResetSpeed()
{
m_speed = 1.0f;
m_speed = 60.0f;
}

float FreeLookCamera::GetSpeed() const
@@ -52,6 +52,7 @@ class FreeLookCamera
void MoveForward(float amt);

void Rotate(const Common::Vec3& amt);
void Rotate(const Common::Quaternion& amt);

void IncreaseFovX(float fov);
void IncreaseFovY(float fov);
@@ -77,8 +78,8 @@ class FreeLookCamera
std::optional<FreeLook::ControlType> m_current_type;
std::unique_ptr<CameraController> m_camera_controller;

float m_fov_step_size = 0.025f;
float m_speed = 1.0f;
float m_min_fov_multiplier = 0.025f;
float m_speed = 60.0f;
};

extern FreeLookCamera g_freelook_camera;