mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Merge pull request #2861 from wwylele/motion-refactor
Refactor MotionEmu into a InputDevice
This commit is contained in:
		
						commit
						bbfa9d0635
					
				
					 20 changed files with 302 additions and 277 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue