Merge pull request #2861 from wwylele/motion-refactor
Refactor MotionEmu into a InputDevice
This commit is contained in:
commit
bbfa9d0635
|
@ -76,6 +76,9 @@ void Config::ReadValues() {
|
||||||
Settings::values.analogs[i] = default_param;
|
Settings::values.analogs[i] = default_param;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Settings::values.motion_device = sdl2_config->Get(
|
||||||
|
"Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01");
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
|
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ button_zr=
|
||||||
button_home=
|
button_home=
|
||||||
|
|
||||||
# for analog input, the following devices are available:
|
# for analog input, the following devices are available:
|
||||||
# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
|
# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
|
||||||
# - "up", "down", "left", "right": sub-devices for each direction.
|
# - "up", "down", "left", "right": sub-devices for each direction.
|
||||||
# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
|
# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
|
||||||
# - "modifier": sub-devices as a modifier.
|
# - "modifier": sub-devices as a modifier.
|
||||||
|
@ -56,6 +56,12 @@ button_home=
|
||||||
circle_pad=
|
circle_pad=
|
||||||
c_stick=
|
c_stick=
|
||||||
|
|
||||||
|
# for motion input, the following devices are available:
|
||||||
|
# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
|
||||||
|
# - "update_period": update period in milliseconds (default to 100)
|
||||||
|
# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01)
|
||||||
|
motion_device=
|
||||||
|
|
||||||
[Core]
|
[Core]
|
||||||
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
|
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
|
||||||
# 0: Interpreter (slow), 1 (default): JIT (fast)
|
# 0: Interpreter (slow), 1 (default): JIT (fast)
|
||||||
|
|
|
@ -16,11 +16,12 @@
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "input_common/keyboard.h"
|
#include "input_common/keyboard.h"
|
||||||
#include "input_common/main.h"
|
#include "input_common/main.h"
|
||||||
|
#include "input_common/motion_emu.h"
|
||||||
#include "network/network.h"
|
#include "network/network.h"
|
||||||
|
|
||||||
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
|
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
|
||||||
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
|
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
|
||||||
motion_emu->Tilt(x, y);
|
InputCommon::GetMotionEmu()->Tilt(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
|
void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
|
||||||
|
@ -32,9 +33,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
|
||||||
}
|
}
|
||||||
} else if (button == SDL_BUTTON_RIGHT) {
|
} else if (button == SDL_BUTTON_RIGHT) {
|
||||||
if (state == SDL_PRESSED) {
|
if (state == SDL_PRESSED) {
|
||||||
motion_emu->BeginTilt(x, y);
|
InputCommon::GetMotionEmu()->BeginTilt(x, y);
|
||||||
} else {
|
} else {
|
||||||
motion_emu->EndTilt();
|
InputCommon::GetMotionEmu()->EndTilt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,8 +62,6 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
|
||||||
InputCommon::Init();
|
InputCommon::Init();
|
||||||
Network::Init();
|
Network::Init();
|
||||||
|
|
||||||
motion_emu = std::make_unique<Motion::MotionEmu>(*this);
|
|
||||||
|
|
||||||
SDL_SetMainReady();
|
SDL_SetMainReady();
|
||||||
|
|
||||||
// Initialize the window
|
// Initialize the window
|
||||||
|
@ -117,7 +116,6 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
|
||||||
EmuWindow_SDL2::~EmuWindow_SDL2() {
|
EmuWindow_SDL2::~EmuWindow_SDL2() {
|
||||||
SDL_GL_DeleteContext(gl_context);
|
SDL_GL_DeleteContext(gl_context);
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
motion_emu = nullptr;
|
|
||||||
|
|
||||||
Network::Shutdown();
|
Network::Shutdown();
|
||||||
InputCommon::Shutdown();
|
InputCommon::Shutdown();
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
#include "core/frontend/motion_emu.h"
|
|
||||||
|
|
||||||
struct SDL_Window;
|
struct SDL_Window;
|
||||||
|
|
||||||
|
@ -57,7 +56,4 @@ private:
|
||||||
using SDL_GLContext = void*;
|
using SDL_GLContext = void*;
|
||||||
/// The OpenGL context associated with the window
|
/// The OpenGL context associated with the window
|
||||||
SDL_GLContext gl_context;
|
SDL_GLContext gl_context;
|
||||||
|
|
||||||
/// Motion sensors emulation
|
|
||||||
std::unique_ptr<Motion::MotionEmu> motion_emu;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "input_common/keyboard.h"
|
#include "input_common/keyboard.h"
|
||||||
#include "input_common/main.h"
|
#include "input_common/main.h"
|
||||||
|
#include "input_common/motion_emu.h"
|
||||||
#include "network/network.h"
|
#include "network/network.h"
|
||||||
|
|
||||||
EmuThread::EmuThread(GRenderWindow* render_window)
|
EmuThread::EmuThread(GRenderWindow* render_window)
|
||||||
|
@ -201,7 +202,6 @@ qreal GRenderWindow::windowPixelRatio() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::closeEvent(QCloseEvent* event) {
|
void GRenderWindow::closeEvent(QCloseEvent* event) {
|
||||||
motion_emu = nullptr;
|
|
||||||
emit Closed();
|
emit Closed();
|
||||||
QWidget::closeEvent(event);
|
QWidget::closeEvent(event);
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
|
||||||
this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
|
this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
|
||||||
static_cast<unsigned>(pos.y() * pixelRatio));
|
static_cast<unsigned>(pos.y() * pixelRatio));
|
||||||
} else if (event->button() == Qt::RightButton) {
|
} else if (event->button() == Qt::RightButton) {
|
||||||
motion_emu->BeginTilt(pos.x(), pos.y());
|
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,14 +230,14 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
|
||||||
qreal pixelRatio = windowPixelRatio();
|
qreal pixelRatio = windowPixelRatio();
|
||||||
this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
|
this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
|
||||||
std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
|
std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
|
||||||
motion_emu->Tilt(pos.x(), pos.y());
|
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
|
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
|
||||||
if (event->button() == Qt::LeftButton)
|
if (event->button() == Qt::LeftButton)
|
||||||
this->TouchReleased();
|
this->TouchReleased();
|
||||||
else if (event->button() == Qt::RightButton)
|
else if (event->button() == Qt::RightButton)
|
||||||
motion_emu->EndTilt();
|
InputCommon::GetMotionEmu()->EndTilt();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::focusOutEvent(QFocusEvent* event) {
|
void GRenderWindow::focusOutEvent(QFocusEvent* event) {
|
||||||
|
@ -290,13 +290,11 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
|
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
|
||||||
motion_emu = std::make_unique<Motion::MotionEmu>(*this);
|
|
||||||
this->emu_thread = emu_thread;
|
this->emu_thread = emu_thread;
|
||||||
child->DisablePainting();
|
child->DisablePainting();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::OnEmulationStopping() {
|
void GRenderWindow::OnEmulationStopping() {
|
||||||
motion_emu = nullptr;
|
|
||||||
emu_thread = nullptr;
|
emu_thread = nullptr;
|
||||||
child->EnablePainting();
|
child->EnablePainting();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
#include "core/frontend/motion_emu.h"
|
|
||||||
|
|
||||||
class QKeyEvent;
|
class QKeyEvent;
|
||||||
class QScreen;
|
class QScreen;
|
||||||
|
@ -158,9 +157,6 @@ private:
|
||||||
|
|
||||||
EmuThread* emu_thread;
|
EmuThread* emu_thread;
|
||||||
|
|
||||||
/// Motion sensors emulation
|
|
||||||
std::unique_ptr<Motion::MotionEmu> motion_emu;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void showEvent(QShowEvent* event) override;
|
void showEvent(QShowEvent* event) override;
|
||||||
};
|
};
|
||||||
|
|
|
@ -57,6 +57,11 @@ void Config::ReadValues() {
|
||||||
Settings::values.analogs[i] = default_param;
|
Settings::values.analogs[i] = default_param;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Settings::values.motion_device =
|
||||||
|
qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
|
||||||
|
.toString()
|
||||||
|
.toStdString();
|
||||||
|
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("Core");
|
qt_config->beginGroup("Core");
|
||||||
|
@ -203,6 +208,7 @@ void Config::SaveValues() {
|
||||||
qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]),
|
qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]),
|
||||||
QString::fromStdString(Settings::values.analogs[i]));
|
QString::fromStdString(Settings::values.analogs[i]));
|
||||||
}
|
}
|
||||||
|
qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device));
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("Core");
|
qt_config->beginGroup("Core");
|
||||||
|
|
|
@ -33,7 +33,6 @@ set(SRCS
|
||||||
frontend/camera/interface.cpp
|
frontend/camera/interface.cpp
|
||||||
frontend/emu_window.cpp
|
frontend/emu_window.cpp
|
||||||
frontend/framebuffer_layout.cpp
|
frontend/framebuffer_layout.cpp
|
||||||
frontend/motion_emu.cpp
|
|
||||||
gdbstub/gdbstub.cpp
|
gdbstub/gdbstub.cpp
|
||||||
hle/config_mem.cpp
|
hle/config_mem.cpp
|
||||||
hle/applets/applet.cpp
|
hle/applets/applet.cpp
|
||||||
|
@ -226,7 +225,6 @@ set(HEADERS
|
||||||
frontend/emu_window.h
|
frontend/emu_window.h
|
||||||
frontend/framebuffer_layout.h
|
frontend/framebuffer_layout.h
|
||||||
frontend/input.h
|
frontend/input.h
|
||||||
frontend/motion_emu.h
|
|
||||||
gdbstub/gdbstub.h
|
gdbstub/gdbstub.h
|
||||||
hle/config_mem.h
|
hle/config_mem.h
|
||||||
hle/function_wrappers.h
|
hle/function_wrappers.h
|
||||||
|
|
|
@ -62,29 +62,6 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
|
||||||
TouchPressed(framebuffer_x, framebuffer_y);
|
TouchPressed(framebuffer_x, framebuffer_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuWindow::AccelerometerChanged(float x, float y, float z) {
|
|
||||||
constexpr float coef = 512;
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(accel_mutex);
|
|
||||||
|
|
||||||
// TODO(wwylele): do a time stretch as it in GyroscopeChanged
|
|
||||||
// The time stretch formula should be like
|
|
||||||
// stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
|
|
||||||
accel_x = static_cast<s16>(x * coef);
|
|
||||||
accel_y = static_cast<s16>(y * coef);
|
|
||||||
accel_z = static_cast<s16>(z * coef);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuWindow::GyroscopeChanged(float x, float y, float z) {
|
|
||||||
constexpr float FULL_FPS = 60;
|
|
||||||
float coef = GetGyroscopeRawToDpsCoefficient();
|
|
||||||
float stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale();
|
|
||||||
std::lock_guard<std::mutex> lock(gyro_mutex);
|
|
||||||
gyro_x = static_cast<s16>(x * coef * stretch);
|
|
||||||
gyro_y = static_cast<s16>(y * coef * stretch);
|
|
||||||
gyro_z = static_cast<s16>(z * coef * stretch);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {
|
void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {
|
||||||
Layout::FramebufferLayout layout;
|
Layout::FramebufferLayout layout;
|
||||||
if (Settings::values.custom_layout == true) {
|
if (Settings::values.custom_layout == true) {
|
||||||
|
|
|
@ -68,27 +68,6 @@ public:
|
||||||
*/
|
*/
|
||||||
void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
|
void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
|
||||||
|
|
||||||
/**
|
|
||||||
* Signal accelerometer state has changed.
|
|
||||||
* @param x X-axis accelerometer value
|
|
||||||
* @param y Y-axis accelerometer value
|
|
||||||
* @param z Z-axis accelerometer value
|
|
||||||
* @note all values are in unit of g (gravitational acceleration).
|
|
||||||
* e.g. x = 1.0 means 9.8m/s^2 in x direction.
|
|
||||||
* @see GetAccelerometerState for axis explanation.
|
|
||||||
*/
|
|
||||||
void AccelerometerChanged(float x, float y, float z);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signal gyroscope state has changed.
|
|
||||||
* @param x X-axis accelerometer value
|
|
||||||
* @param y Y-axis accelerometer value
|
|
||||||
* @param z Z-axis accelerometer value
|
|
||||||
* @note all values are in deg/sec.
|
|
||||||
* @see GetGyroscopeState for axis explanation.
|
|
||||||
*/
|
|
||||||
void GyroscopeChanged(float x, float y, float z);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).
|
* Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).
|
||||||
* @note This should be called by the core emu thread to get a state set by the window thread.
|
* @note This should be called by the core emu thread to get a state set by the window thread.
|
||||||
|
@ -100,52 +79,6 @@ public:
|
||||||
return std::make_tuple(touch_x, touch_y, touch_pressed);
|
return std::make_tuple(touch_x, touch_y, touch_pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current accelerometer state (acceleration along each three axis).
|
|
||||||
* Axis explained:
|
|
||||||
* +x is the same direction as LEFT on D-pad.
|
|
||||||
* +y is normal to the touch screen, pointing outward.
|
|
||||||
* +z is the same direction as UP on D-pad.
|
|
||||||
* Units:
|
|
||||||
* 1 unit of return value = 1/512 g (measured by hw test),
|
|
||||||
* where g is the gravitational acceleration (9.8 m/sec2).
|
|
||||||
* @note This should be called by the core emu thread to get a state set by the window thread.
|
|
||||||
* @return std::tuple of (x, y, z)
|
|
||||||
*/
|
|
||||||
std::tuple<s16, s16, s16> GetAccelerometerState() {
|
|
||||||
std::lock_guard<std::mutex> lock(accel_mutex);
|
|
||||||
return std::make_tuple(accel_x, accel_y, accel_z);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current gyroscope state (angular rates about each three axis).
|
|
||||||
* Axis explained:
|
|
||||||
* +x is the same direction as LEFT on D-pad.
|
|
||||||
* +y is normal to the touch screen, pointing outward.
|
|
||||||
* +z is the same direction as UP on D-pad.
|
|
||||||
* Orientation is determined by right-hand rule.
|
|
||||||
* Units:
|
|
||||||
* 1 unit of return value = (1/coef) deg/sec,
|
|
||||||
* where coef is the return value of GetGyroscopeRawToDpsCoefficient().
|
|
||||||
* @note This should be called by the core emu thread to get a state set by the window thread.
|
|
||||||
* @return std::tuple of (x, y, z)
|
|
||||||
*/
|
|
||||||
std::tuple<s16, s16, s16> GetGyroscopeState() {
|
|
||||||
std::lock_guard<std::mutex> lock(gyro_mutex);
|
|
||||||
return std::make_tuple(gyro_x, gyro_y, gyro_z);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the coefficient for units conversion of gyroscope state.
|
|
||||||
* The conversion formula is r = coefficient * v,
|
|
||||||
* where v is angular rate in deg/sec,
|
|
||||||
* and r is the gyroscope state.
|
|
||||||
* @return float-type coefficient
|
|
||||||
*/
|
|
||||||
f32 GetGyroscopeRawToDpsCoefficient() const {
|
|
||||||
return 14.375f; // taken from hw test, and gyroscope's document
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns currently active configuration.
|
* Returns currently active configuration.
|
||||||
* @note Accesses to the returned object need not be consistent because it may be modified in
|
* @note Accesses to the returned object need not be consistent because it may be modified in
|
||||||
|
@ -187,12 +120,6 @@ protected:
|
||||||
touch_x = 0;
|
touch_x = 0;
|
||||||
touch_y = 0;
|
touch_y = 0;
|
||||||
touch_pressed = false;
|
touch_pressed = false;
|
||||||
accel_x = 0;
|
|
||||||
accel_y = -512;
|
|
||||||
accel_z = 0;
|
|
||||||
gyro_x = 0;
|
|
||||||
gyro_y = 0;
|
|
||||||
gyro_z = 0;
|
|
||||||
}
|
}
|
||||||
virtual ~EmuWindow() {}
|
virtual ~EmuWindow() {}
|
||||||
|
|
||||||
|
@ -255,16 +182,6 @@ private:
|
||||||
u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
|
u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
|
||||||
u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
|
u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
|
||||||
|
|
||||||
std::mutex accel_mutex;
|
|
||||||
s16 accel_x; ///< Accelerometer X-axis value in native 3DS units
|
|
||||||
s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units
|
|
||||||
s16 accel_z; ///< Accelerometer Z-axis value in native 3DS units
|
|
||||||
|
|
||||||
std::mutex gyro_mutex;
|
|
||||||
s16 gyro_x; ///< Gyroscope X-axis value in native 3DS units
|
|
||||||
s16 gyro_y; ///< Gyroscope Y-axis value in native 3DS units
|
|
||||||
s16 gyro_z; ///< Gyroscope Z-axis value in native 3DS units
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clip the provided coordinates to be inside the touchscreen area.
|
* Clip the provided coordinates to be inside the touchscreen area.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/param_package.h"
|
#include "common/param_package.h"
|
||||||
|
#include "common/vector_math.h"
|
||||||
|
|
||||||
namespace Input {
|
namespace Input {
|
||||||
|
|
||||||
|
@ -107,4 +108,22 @@ using ButtonDevice = InputDevice<bool>;
|
||||||
*/
|
*/
|
||||||
using AnalogDevice = InputDevice<std::tuple<float, float>>;
|
using AnalogDevice = InputDevice<std::tuple<float, float>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A motion device is an input device that returns a tuple of accelerometer state vector and
|
||||||
|
* gyroscope state vector.
|
||||||
|
*
|
||||||
|
* For both vectors:
|
||||||
|
* x+ is the same direction as LEFT on D-pad.
|
||||||
|
* y+ is normal to the touch screen, pointing outward.
|
||||||
|
* z+ is the same direction as UP on D-pad.
|
||||||
|
*
|
||||||
|
* For accelerometer state vector
|
||||||
|
* Units: g (gravitational acceleration)
|
||||||
|
*
|
||||||
|
* For gyroscope state vector:
|
||||||
|
* Orientation is determined by right-hand rule.
|
||||||
|
* Units: deg/sec
|
||||||
|
*/
|
||||||
|
using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float>>>;
|
||||||
|
|
||||||
} // namespace Input
|
} // namespace Input
|
||||||
|
|
|
@ -1,89 +0,0 @@
|
||||||
// Copyright 2016 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include "common/math_util.h"
|
|
||||||
#include "common/quaternion.h"
|
|
||||||
#include "core/frontend/emu_window.h"
|
|
||||||
#include "core/frontend/motion_emu.h"
|
|
||||||
|
|
||||||
namespace Motion {
|
|
||||||
|
|
||||||
static constexpr int update_millisecond = 100;
|
|
||||||
static constexpr auto update_duration =
|
|
||||||
std::chrono::duration_cast<std::chrono::steady_clock::duration>(
|
|
||||||
std::chrono::milliseconds(update_millisecond));
|
|
||||||
|
|
||||||
MotionEmu::MotionEmu(EmuWindow& emu_window)
|
|
||||||
: motion_emu_thread(&MotionEmu::MotionEmuThread, this, std::ref(emu_window)) {}
|
|
||||||
|
|
||||||
MotionEmu::~MotionEmu() {
|
|
||||||
if (motion_emu_thread.joinable()) {
|
|
||||||
shutdown_event.Set();
|
|
||||||
motion_emu_thread.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MotionEmu::MotionEmuThread(EmuWindow& emu_window) {
|
|
||||||
auto update_time = std::chrono::steady_clock::now();
|
|
||||||
Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0);
|
|
||||||
Math::Quaternion<float> old_q;
|
|
||||||
|
|
||||||
while (!shutdown_event.WaitUntil(update_time)) {
|
|
||||||
update_time += update_duration;
|
|
||||||
old_q = q;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(tilt_mutex);
|
|
||||||
|
|
||||||
// Find the quaternion describing current 3DS tilting
|
|
||||||
q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x),
|
|
||||||
tilt_angle);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto inv_q = q.Inverse();
|
|
||||||
|
|
||||||
// Set the gravity vector in world space
|
|
||||||
auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f);
|
|
||||||
|
|
||||||
// Find the angular rate vector in world space
|
|
||||||
auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
|
|
||||||
angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180;
|
|
||||||
|
|
||||||
// Transform the two vectors from world space to 3DS space
|
|
||||||
gravity = QuaternionRotate(inv_q, gravity);
|
|
||||||
angular_rate = QuaternionRotate(inv_q, angular_rate);
|
|
||||||
|
|
||||||
// Update the sensor state
|
|
||||||
emu_window.AccelerometerChanged(gravity.x, gravity.y, gravity.z);
|
|
||||||
emu_window.GyroscopeChanged(angular_rate.x, angular_rate.y, angular_rate.z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MotionEmu::BeginTilt(int x, int y) {
|
|
||||||
mouse_origin = Math::MakeVec(x, y);
|
|
||||||
is_tilting = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MotionEmu::Tilt(int x, int y) {
|
|
||||||
constexpr float SENSITIVITY = 0.01f;
|
|
||||||
auto mouse_move = Math::MakeVec(x, y) - mouse_origin;
|
|
||||||
if (is_tilting) {
|
|
||||||
std::lock_guard<std::mutex> guard(tilt_mutex);
|
|
||||||
if (mouse_move.x == 0 && mouse_move.y == 0) {
|
|
||||||
tilt_angle = 0;
|
|
||||||
} else {
|
|
||||||
tilt_direction = mouse_move.Cast<float>();
|
|
||||||
tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * SENSITIVITY, 0.0f,
|
|
||||||
MathUtil::PI * 0.5f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MotionEmu::EndTilt() {
|
|
||||||
std::lock_guard<std::mutex> guard(tilt_mutex);
|
|
||||||
tilt_angle = 0;
|
|
||||||
is_tilting = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Motion
|
|
|
@ -1,52 +0,0 @@
|
||||||
// Copyright 2016 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "common/thread.h"
|
|
||||||
#include "common/vector_math.h"
|
|
||||||
|
|
||||||
class EmuWindow;
|
|
||||||
|
|
||||||
namespace Motion {
|
|
||||||
|
|
||||||
class MotionEmu final {
|
|
||||||
public:
|
|
||||||
MotionEmu(EmuWindow& emu_window);
|
|
||||||
~MotionEmu();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals that a motion sensor tilt has begun.
|
|
||||||
* @param x the x-coordinate of the cursor
|
|
||||||
* @param y the y-coordinate of the cursor
|
|
||||||
*/
|
|
||||||
void BeginTilt(int x, int y);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals that a motion sensor tilt is occurring.
|
|
||||||
* @param x the x-coordinate of the cursor
|
|
||||||
* @param y the y-coordinate of the cursor
|
|
||||||
*/
|
|
||||||
void Tilt(int x, int y);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals that a motion sensor tilt has ended.
|
|
||||||
*/
|
|
||||||
void EndTilt();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Math::Vec2<int> mouse_origin;
|
|
||||||
|
|
||||||
std::mutex tilt_mutex;
|
|
||||||
Math::Vec2<float> tilt_direction;
|
|
||||||
float tilt_angle = 0;
|
|
||||||
|
|
||||||
bool is_tilting = false;
|
|
||||||
|
|
||||||
Common::Event shutdown_event;
|
|
||||||
std::thread motion_emu_thread;
|
|
||||||
|
|
||||||
void MotionEmuThread(EmuWindow& emu_window);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Motion
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
#include "core/frontend/input.h"
|
#include "core/frontend/input.h"
|
||||||
|
@ -50,10 +51,14 @@ constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234;
|
||||||
constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104;
|
constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104;
|
||||||
constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101;
|
constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101;
|
||||||
|
|
||||||
|
constexpr float accelerometer_coef = 512.0f; // measured from hw test result
|
||||||
|
constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call
|
||||||
|
|
||||||
static std::atomic<bool> is_device_reload_pending;
|
static std::atomic<bool> is_device_reload_pending;
|
||||||
static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
|
static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
|
||||||
buttons;
|
buttons;
|
||||||
static std::unique_ptr<Input::AnalogDevice> circle_pad;
|
static std::unique_ptr<Input::AnalogDevice> circle_pad;
|
||||||
|
static std::unique_ptr<Input::MotionDevice> motion_device;
|
||||||
|
|
||||||
DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
|
DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
|
||||||
// 30 degree and 60 degree are angular thresholds for directions
|
// 30 degree and 60 degree are angular thresholds for directions
|
||||||
|
@ -90,6 +95,7 @@ static void LoadInputDevices() {
|
||||||
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
|
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
|
||||||
circle_pad = Input::CreateDevice<Input::AnalogDevice>(
|
circle_pad = Input::CreateDevice<Input::AnalogDevice>(
|
||||||
Settings::values.analogs[Settings::NativeAnalog::CirclePad]);
|
Settings::values.analogs[Settings::NativeAnalog::CirclePad]);
|
||||||
|
motion_device = Input::CreateDevice<Input::MotionDevice>(Settings::values.motion_device);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void UnloadInputDevices() {
|
static void UnloadInputDevices() {
|
||||||
|
@ -97,6 +103,7 @@ static void UnloadInputDevices() {
|
||||||
button.reset();
|
button.reset();
|
||||||
}
|
}
|
||||||
circle_pad.reset();
|
circle_pad.reset();
|
||||||
|
motion_device.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void UpdatePadCallback(u64 userdata, int cycles_late) {
|
static void UpdatePadCallback(u64 userdata, int cycles_late) {
|
||||||
|
@ -193,10 +200,19 @@ static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) {
|
||||||
mem->accelerometer.index = next_accelerometer_index;
|
mem->accelerometer.index = next_accelerometer_index;
|
||||||
next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size();
|
next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size();
|
||||||
|
|
||||||
|
Math::Vec3<float> accel;
|
||||||
|
std::tie(accel, std::ignore) = motion_device->GetStatus();
|
||||||
|
accel *= accelerometer_coef;
|
||||||
|
// TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback
|
||||||
|
// The time stretch formula should be like
|
||||||
|
// stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
|
||||||
|
|
||||||
AccelerometerDataEntry& accelerometer_entry =
|
AccelerometerDataEntry& accelerometer_entry =
|
||||||
mem->accelerometer.entries[mem->accelerometer.index];
|
mem->accelerometer.entries[mem->accelerometer.index];
|
||||||
std::tie(accelerometer_entry.x, accelerometer_entry.y, accelerometer_entry.z) =
|
|
||||||
VideoCore::g_emu_window->GetAccelerometerState();
|
accelerometer_entry.x = static_cast<s16>(accel.x);
|
||||||
|
accelerometer_entry.y = static_cast<s16>(accel.y);
|
||||||
|
accelerometer_entry.z = static_cast<s16>(accel.z);
|
||||||
|
|
||||||
// Make up "raw" entry
|
// Make up "raw" entry
|
||||||
// TODO(wwylele):
|
// TODO(wwylele):
|
||||||
|
@ -227,8 +243,14 @@ static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) {
|
||||||
next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size();
|
next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size();
|
||||||
|
|
||||||
GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index];
|
GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index];
|
||||||
std::tie(gyroscope_entry.x, gyroscope_entry.y, gyroscope_entry.z) =
|
|
||||||
VideoCore::g_emu_window->GetGyroscopeState();
|
Math::Vec3<float> gyro;
|
||||||
|
std::tie(std::ignore, gyro) = motion_device->GetStatus();
|
||||||
|
double stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale();
|
||||||
|
gyro *= gyroscope_coef * stretch;
|
||||||
|
gyroscope_entry.x = static_cast<s16>(gyro.x);
|
||||||
|
gyroscope_entry.y = static_cast<s16>(gyro.y);
|
||||||
|
gyroscope_entry.z = static_cast<s16>(gyro.z);
|
||||||
|
|
||||||
// Make up "raw" entry
|
// Make up "raw" entry
|
||||||
mem->gyroscope.raw_entry.x = gyroscope_entry.x;
|
mem->gyroscope.raw_entry.x = gyroscope_entry.x;
|
||||||
|
@ -326,7 +348,7 @@ void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) {
|
||||||
|
|
||||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||||
|
|
||||||
f32 coef = VideoCore::g_emu_window->GetGyroscopeRawToDpsCoefficient();
|
f32 coef = gyroscope_coef;
|
||||||
memcpy(&cmd_buff[2], &coef, 4);
|
memcpy(&cmd_buff[2], &coef, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,7 @@ struct Values {
|
||||||
// Controls
|
// Controls
|
||||||
std::array<std::string, NativeButton::NumButtons> buttons;
|
std::array<std::string, NativeButton::NumButtons> buttons;
|
||||||
std::array<std::string, NativeAnalog::NumAnalogs> analogs;
|
std::array<std::string, NativeAnalog::NumAnalogs> analogs;
|
||||||
|
std::string motion_device;
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
bool use_cpu_jit;
|
bool use_cpu_jit;
|
||||||
|
|
|
@ -2,12 +2,14 @@ set(SRCS
|
||||||
analog_from_button.cpp
|
analog_from_button.cpp
|
||||||
keyboard.cpp
|
keyboard.cpp
|
||||||
main.cpp
|
main.cpp
|
||||||
|
motion_emu.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
analog_from_button.h
|
analog_from_button.h
|
||||||
keyboard.h
|
keyboard.h
|
||||||
main.h
|
main.h
|
||||||
|
motion_emu.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if(SDL2_FOUND)
|
if(SDL2_FOUND)
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "input_common/analog_from_button.h"
|
#include "input_common/analog_from_button.h"
|
||||||
#include "input_common/keyboard.h"
|
#include "input_common/keyboard.h"
|
||||||
#include "input_common/main.h"
|
#include "input_common/main.h"
|
||||||
|
#include "input_common/motion_emu.h"
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
#include "input_common/sdl/sdl.h"
|
#include "input_common/sdl/sdl.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -14,12 +15,16 @@
|
||||||
namespace InputCommon {
|
namespace InputCommon {
|
||||||
|
|
||||||
static std::shared_ptr<Keyboard> keyboard;
|
static std::shared_ptr<Keyboard> keyboard;
|
||||||
|
static std::shared_ptr<MotionEmu> motion_emu;
|
||||||
|
|
||||||
void Init() {
|
void Init() {
|
||||||
keyboard = std::make_shared<InputCommon::Keyboard>();
|
keyboard = std::make_shared<Keyboard>();
|
||||||
Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
|
Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
|
||||||
Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
|
Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
|
||||||
std::make_shared<InputCommon::AnalogFromButton>());
|
std::make_shared<AnalogFromButton>());
|
||||||
|
motion_emu = std::make_shared<MotionEmu>();
|
||||||
|
Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
|
||||||
|
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
SDL::Init();
|
SDL::Init();
|
||||||
#endif
|
#endif
|
||||||
|
@ -29,6 +34,8 @@ void Shutdown() {
|
||||||
Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
|
Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
|
||||||
keyboard.reset();
|
keyboard.reset();
|
||||||
Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
|
Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
|
||||||
|
Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
|
||||||
|
motion_emu.reset();
|
||||||
|
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
SDL::Shutdown();
|
SDL::Shutdown();
|
||||||
|
@ -39,6 +46,10 @@ Keyboard* GetKeyboard() {
|
||||||
return keyboard.get();
|
return keyboard.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MotionEmu* GetMotionEmu() {
|
||||||
|
return motion_emu.get();
|
||||||
|
}
|
||||||
|
|
||||||
std::string GenerateKeyboardParam(int key_code) {
|
std::string GenerateKeyboardParam(int key_code) {
|
||||||
Common::ParamPackage param{
|
Common::ParamPackage param{
|
||||||
{"engine", "keyboard"}, {"code", std::to_string(key_code)},
|
{"engine", "keyboard"}, {"code", std::to_string(key_code)},
|
||||||
|
|
|
@ -19,6 +19,11 @@ class Keyboard;
|
||||||
/// Gets the keyboard button device factory.
|
/// Gets the keyboard button device factory.
|
||||||
Keyboard* GetKeyboard();
|
Keyboard* GetKeyboard();
|
||||||
|
|
||||||
|
class MotionEmu;
|
||||||
|
|
||||||
|
/// Gets the motion emulation factory.
|
||||||
|
MotionEmu* GetMotionEmu();
|
||||||
|
|
||||||
/// Generates a serialized param package for creating a keyboard button device
|
/// Generates a serialized param package for creating a keyboard button device
|
||||||
std::string GenerateKeyboardParam(int key_code);
|
std::string GenerateKeyboardParam(int key_code);
|
||||||
|
|
||||||
|
|
165
src/input_common/motion_emu.cpp
Normal file
165
src/input_common/motion_emu.cpp
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#include <tuple>
|
||||||
|
#include "common/math_util.h"
|
||||||
|
#include "common/quaternion.h"
|
||||||
|
#include "common/thread.h"
|
||||||
|
#include "common/vector_math.h"
|
||||||
|
#include "input_common/motion_emu.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
// Implementation class of the motion emulation device
|
||||||
|
class MotionEmuDevice {
|
||||||
|
public:
|
||||||
|
MotionEmuDevice(int update_millisecond, float sensitivity)
|
||||||
|
: update_millisecond(update_millisecond),
|
||||||
|
update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(
|
||||||
|
std::chrono::milliseconds(update_millisecond))),
|
||||||
|
sensitivity(sensitivity), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {}
|
||||||
|
|
||||||
|
~MotionEmuDevice() {
|
||||||
|
if (motion_emu_thread.joinable()) {
|
||||||
|
shutdown_event.Set();
|
||||||
|
motion_emu_thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BeginTilt(int x, int y) {
|
||||||
|
mouse_origin = Math::MakeVec(x, y);
|
||||||
|
is_tilting = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tilt(int x, int y) {
|
||||||
|
auto mouse_move = Math::MakeVec(x, y) - mouse_origin;
|
||||||
|
if (is_tilting) {
|
||||||
|
std::lock_guard<std::mutex> guard(tilt_mutex);
|
||||||
|
if (mouse_move.x == 0 && mouse_move.y == 0) {
|
||||||
|
tilt_angle = 0;
|
||||||
|
} else {
|
||||||
|
tilt_direction = mouse_move.Cast<float>();
|
||||||
|
tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * sensitivity, 0.0f,
|
||||||
|
MathUtil::PI * 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndTilt() {
|
||||||
|
std::lock_guard<std::mutex> guard(tilt_mutex);
|
||||||
|
tilt_angle = 0;
|
||||||
|
is_tilting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() {
|
||||||
|
std::lock_guard<std::mutex> guard(status_mutex);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const int update_millisecond;
|
||||||
|
const std::chrono::steady_clock::duration update_duration;
|
||||||
|
const float sensitivity;
|
||||||
|
|
||||||
|
Math::Vec2<int> mouse_origin;
|
||||||
|
|
||||||
|
std::mutex tilt_mutex;
|
||||||
|
Math::Vec2<float> tilt_direction;
|
||||||
|
float tilt_angle = 0;
|
||||||
|
|
||||||
|
bool is_tilting = false;
|
||||||
|
|
||||||
|
Common::Event shutdown_event;
|
||||||
|
std::thread motion_emu_thread;
|
||||||
|
|
||||||
|
std::tuple<Math::Vec3<float>, Math::Vec3<float>> status;
|
||||||
|
std::mutex status_mutex;
|
||||||
|
|
||||||
|
void MotionEmuThread() {
|
||||||
|
auto update_time = std::chrono::steady_clock::now();
|
||||||
|
Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0);
|
||||||
|
Math::Quaternion<float> old_q;
|
||||||
|
|
||||||
|
while (!shutdown_event.WaitUntil(update_time)) {
|
||||||
|
update_time += update_duration;
|
||||||
|
old_q = q;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(tilt_mutex);
|
||||||
|
|
||||||
|
// Find the quaternion describing current 3DS tilting
|
||||||
|
q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x),
|
||||||
|
tilt_angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto inv_q = q.Inverse();
|
||||||
|
|
||||||
|
// Set the gravity vector in world space
|
||||||
|
auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f);
|
||||||
|
|
||||||
|
// Find the angular rate vector in world space
|
||||||
|
auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
|
||||||
|
angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180;
|
||||||
|
|
||||||
|
// Transform the two vectors from world space to 3DS space
|
||||||
|
gravity = QuaternionRotate(inv_q, gravity);
|
||||||
|
angular_rate = QuaternionRotate(inv_q, angular_rate);
|
||||||
|
|
||||||
|
// Update the sensor state
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(status_mutex);
|
||||||
|
status = std::make_tuple(gravity, angular_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interface wrapper held by input receiver as a unique_ptr. It holds the implementation class as
|
||||||
|
// a shared_ptr, which is also observed by the factory class as a weak_ptr. In this way the factory
|
||||||
|
// can forward all the inputs to the implementation only when it is valid.
|
||||||
|
class MotionEmuDeviceWrapper : public Input::MotionDevice {
|
||||||
|
public:
|
||||||
|
MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) {
|
||||||
|
device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() const {
|
||||||
|
return device->GetStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<MotionEmuDevice> device;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) {
|
||||||
|
int update_period = params.Get("update_period", 100);
|
||||||
|
float sensitivity = params.Get("sensitivity", 0.01f);
|
||||||
|
auto device_wrapper = std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity);
|
||||||
|
// Previously created device is disconnected here. Having two motion devices for 3DS is not
|
||||||
|
// expected.
|
||||||
|
current_device = device_wrapper->device;
|
||||||
|
return std::move(device_wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEmu::BeginTilt(int x, int y) {
|
||||||
|
if (auto ptr = current_device.lock()) {
|
||||||
|
ptr->BeginTilt(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEmu::Tilt(int x, int y) {
|
||||||
|
if (auto ptr = current_device.lock()) {
|
||||||
|
ptr->Tilt(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEmu::EndTilt() {
|
||||||
|
if (auto ptr = current_device.lock()) {
|
||||||
|
ptr->EndTilt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
46
src/input_common/motion_emu.h
Normal file
46
src/input_common/motion_emu.h
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/frontend/input.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
class MotionEmuDevice;
|
||||||
|
|
||||||
|
class MotionEmu : public Input::Factory<Input::MotionDevice> {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Creates a motion device emulated from mouse input
|
||||||
|
* @param params contains parameters for creating the device:
|
||||||
|
* - "update_period": update period in milliseconds
|
||||||
|
* - "sensitivity": the coefficient converting mouse movement to tilting angle
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that a motion sensor tilt has begun.
|
||||||
|
* @param x the x-coordinate of the cursor
|
||||||
|
* @param y the y-coordinate of the cursor
|
||||||
|
*/
|
||||||
|
void BeginTilt(int x, int y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that a motion sensor tilt is occurring.
|
||||||
|
* @param x the x-coordinate of the cursor
|
||||||
|
* @param y the y-coordinate of the cursor
|
||||||
|
*/
|
||||||
|
void Tilt(int x, int y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that a motion sensor tilt has ended.
|
||||||
|
*/
|
||||||
|
void EndTilt();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::weak_ptr<MotionEmuDevice> current_device;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
Loading…
Reference in a new issue