diff options
| -rw-r--r-- | src/citra/config.cpp | 3 | ||||
| -rw-r--r-- | src/citra/default_ini.h | 8 | ||||
| -rw-r--r-- | src/citra/emu_window/emu_window_sdl2.cpp | 10 | ||||
| -rw-r--r-- | src/citra/emu_window/emu_window_sdl2.h | 4 | ||||
| -rw-r--r-- | src/citra_qt/bootmanager.cpp | 10 | ||||
| -rw-r--r-- | src/citra_qt/bootmanager.h | 4 | ||||
| -rw-r--r-- | src/citra_qt/configuration/config.cpp | 6 | ||||
| -rw-r--r-- | src/core/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/core/frontend/emu_window.cpp | 23 | ||||
| -rw-r--r-- | src/core/frontend/emu_window.h | 83 | ||||
| -rw-r--r-- | src/core/frontend/input.h | 9 | ||||
| -rw-r--r-- | src/core/frontend/motion_emu.cpp | 89 | ||||
| -rw-r--r-- | src/input_common/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/input_common/main.cpp | 15 | ||||
| -rw-r--r-- | src/input_common/main.h | 5 | ||||
| -rw-r--r-- | src/input_common/motion_emu.cpp | 159 | ||||
| -rw-r--r-- | src/input_common/motion_emu.h (renamed from src/core/frontend/motion_emu.h) | 33 | 
17 files changed, 221 insertions, 244 deletions
diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 69247b166..73846ed91 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -76,6 +76,9 @@ void Config::ReadValues() {              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      Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index b0a0ebd3b..9ea779dd8 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -43,7 +43,7 @@ button_zr=  button_home=  # 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.  #          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. @@ -56,6 +56,12 @@ button_home=  circle_pad=  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]  # Whether to use the Just-In-Time (JIT) compiler for CPU emulation  # 0: Interpreter (slow), 1 (default): JIT (fast) diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index b0f808399..25643715a 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -16,11 +16,12 @@  #include "core/settings.h"  #include "input_common/keyboard.h"  #include "input_common/main.h" +#include "input_common/motion_emu.h"  #include "network/network.h"  void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {      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) { @@ -32,9 +33,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {          }      } else if (button == SDL_BUTTON_RIGHT) {          if (state == SDL_PRESSED) { -            motion_emu->BeginTilt(x, y); +            InputCommon::GetMotionEmu()->BeginTilt(x, y);          } else { -            motion_emu->EndTilt(); +            InputCommon::GetMotionEmu()->EndTilt();          }      }  } @@ -61,8 +62,6 @@ EmuWindow_SDL2::EmuWindow_SDL2() {      InputCommon::Init();      Network::Init(); -    motion_emu = std::make_unique<Motion::MotionEmu>(*this); -      SDL_SetMainReady();      // Initialize the window @@ -117,7 +116,6 @@ EmuWindow_SDL2::EmuWindow_SDL2() {  EmuWindow_SDL2::~EmuWindow_SDL2() {      SDL_GL_DeleteContext(gl_context);      SDL_Quit(); -    motion_emu = nullptr;      Network::Shutdown();      InputCommon::Shutdown(); diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h index 1ce2991f7..3664d2fbe 100644 --- a/src/citra/emu_window/emu_window_sdl2.h +++ b/src/citra/emu_window/emu_window_sdl2.h @@ -7,7 +7,6 @@  #include <memory>  #include <utility>  #include "core/frontend/emu_window.h" -#include "core/frontend/motion_emu.h"  struct SDL_Window; @@ -57,7 +56,4 @@ private:      using SDL_GLContext = void*;      /// The OpenGL context associated with the window      SDL_GLContext gl_context; - -    /// Motion sensors emulation -    std::unique_ptr<Motion::MotionEmu> motion_emu;  }; diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 30554890f..7107bfc60 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -17,6 +17,7 @@  #include "core/settings.h"  #include "input_common/keyboard.h"  #include "input_common/main.h" +#include "input_common/motion_emu.h"  #include "network/network.h"  EmuThread::EmuThread(GRenderWindow* render_window) @@ -201,7 +202,6 @@ qreal GRenderWindow::windowPixelRatio() {  }  void GRenderWindow::closeEvent(QCloseEvent* event) { -    motion_emu = nullptr;      emit Closed();      QWidget::closeEvent(event);  } @@ -221,7 +221,7 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {          this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),                             static_cast<unsigned>(pos.y() * pixelRatio));      } 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();      this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * 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) {      if (event->button() == Qt::LeftButton)          this->TouchReleased();      else if (event->button() == Qt::RightButton) -        motion_emu->EndTilt(); +        InputCommon::GetMotionEmu()->EndTilt();  }  void GRenderWindow::focusOutEvent(QFocusEvent* event) { @@ -290,13 +290,11 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(  }  void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { -    motion_emu = std::make_unique<Motion::MotionEmu>(*this);      this->emu_thread = emu_thread;      child->DisablePainting();  }  void GRenderWindow::OnEmulationStopping() { -    motion_emu = nullptr;      emu_thread = nullptr;      child->EnablePainting();  } diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 4b3a3b3cc..6974edcbb 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -12,7 +12,6 @@  #include "common/thread.h"  #include "core/core.h"  #include "core/frontend/emu_window.h" -#include "core/frontend/motion_emu.h"  class QKeyEvent;  class QScreen; @@ -158,9 +157,6 @@ private:      EmuThread* emu_thread; -    /// Motion sensors emulation -    std::unique_ptr<Motion::MotionEmu> motion_emu; -  protected:      void showEvent(QShowEvent* event) override;  }; diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 75abb4ce6..6e42db007 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -57,6 +57,11 @@ void Config::ReadValues() {              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->beginGroup("Core"); @@ -203,6 +208,7 @@ void Config::SaveValues() {          qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]),                              QString::fromStdString(Settings::values.analogs[i]));      } +    qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device));      qt_config->endGroup();      qt_config->beginGroup("Core"); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 360f407f3..ea36b7d95 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -33,7 +33,6 @@ set(SRCS              frontend/camera/interface.cpp              frontend/emu_window.cpp              frontend/framebuffer_layout.cpp -            frontend/motion_emu.cpp              gdbstub/gdbstub.cpp              hle/config_mem.cpp              hle/applets/applet.cpp @@ -226,7 +225,6 @@ set(HEADERS              frontend/emu_window.h              frontend/framebuffer_layout.h              frontend/input.h -            frontend/motion_emu.h              gdbstub/gdbstub.h              hle/config_mem.h              hle/function_wrappers.h diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 4f7d54a33..60b20d4e2 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -62,29 +62,6 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned 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) {      Layout::FramebufferLayout layout;      if (Settings::values.custom_layout == true) { diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 9414123a4..7bdee251c 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -69,27 +69,6 @@ public:      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).       * @note This should be called by the core emu thread to get a state set by the window thread.       * @todo Fix this function to be thread-safe. @@ -101,52 +80,6 @@ public:      }      /** -     * 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.       * @note Accesses to the returned object need not be consistent because it may be modified in       * another thread @@ -187,12 +120,6 @@ protected:          touch_x = 0;          touch_y = 0;          touch_pressed = false; -        accel_x = 0; -        accel_y = -512; -        accel_z = 0; -        gyro_x = 0; -        gyro_y = 0; -        gyro_z = 0;      }      virtual ~EmuWindow() {} @@ -255,16 +182,6 @@ private:      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) -    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.       */ diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index a8be49440..5916a901d 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -112,16 +112,15 @@ 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 accelerometer 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. - *   Units: measured in unit of gravitational acceleration + * + * For accelerometer state vector + *   Units: g (gravitational acceleration)   *   * For gyroscope state vector: - *   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: deg/sec   */ diff --git a/src/core/frontend/motion_emu.cpp b/src/core/frontend/motion_emu.cpp deleted file mode 100644 index 9a5b3185d..000000000 --- a/src/core/frontend/motion_emu.cpp +++ /dev/null @@ -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 diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index e3e36ada7..92792a702 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -2,12 +2,14 @@ set(SRCS              analog_from_button.cpp              keyboard.cpp              main.cpp +            motion_emu.cpp              )  set(HEADERS              analog_from_button.h              keyboard.h              main.h +            motion_emu.h              )  if(SDL2_FOUND) diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 699f41e6b..557353740 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -7,6 +7,7 @@  #include "input_common/analog_from_button.h"  #include "input_common/keyboard.h"  #include "input_common/main.h" +#include "input_common/motion_emu.h"  #ifdef HAVE_SDL2  #include "input_common/sdl/sdl.h"  #endif @@ -14,12 +15,16 @@  namespace InputCommon {  static std::shared_ptr<Keyboard> keyboard; +static std::shared_ptr<MotionEmu> motion_emu;  void Init() { -    keyboard = std::make_shared<InputCommon::Keyboard>(); +    keyboard = std::make_shared<Keyboard>();      Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);      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      SDL::Init();  #endif @@ -29,6 +34,8 @@ void Shutdown() {      Input::UnregisterFactory<Input::ButtonDevice>("keyboard");      keyboard.reset();      Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); +    Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); +    motion_emu.reset();  #ifdef HAVE_SDL2      SDL::Shutdown(); @@ -39,6 +46,10 @@ Keyboard* GetKeyboard() {      return keyboard.get();  } +MotionEmu* GetMotionEmu() { +    return motion_emu.get(); +} +  std::string GenerateKeyboardParam(int key_code) {      Common::ParamPackage param{          {"engine", "keyboard"}, {"code", std::to_string(key_code)}, diff --git a/src/input_common/main.h b/src/input_common/main.h index 140bbd014..74283f7c6 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -19,6 +19,11 @@ class Keyboard;  /// Gets the keyboard button device factory.  Keyboard* GetKeyboard(); +class MotionEmu; + +/// Gets the motion emulation factory. +MotionEmu* GetMotionEmu(); +  /// Generates a serialized param package for creating a keyboard button device  std::string GenerateKeyboardParam(int key_code); diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp new file mode 100644 index 000000000..65e80529d --- /dev/null +++ b/src/input_common/motion_emu.cpp @@ -0,0 +1,159 @@ +// Copyright 2017 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 "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 diff --git a/src/core/frontend/motion_emu.h b/src/input_common/motion_emu.h index 99d41a726..195fb69d2 100644 --- a/src/core/frontend/motion_emu.h +++ b/src/input_common/motion_emu.h @@ -1,19 +1,25 @@ -// Copyright 2016 Citra Emulator Project +// Copyright 2017 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" +#include "core/frontend/input.h" -class EmuWindow; +namespace InputCommon { -namespace Motion { +class MotionEmuDevice; -class MotionEmu final { +class MotionEmu : public Input::Factory<Input::MotionDevice> {  public: -    MotionEmu(EmuWindow& emu_window); -    ~MotionEmu(); +    /** +     * 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. @@ -35,18 +41,7 @@ public:      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); +    std::weak_ptr<MotionEmuDevice> current_device;  }; -} // namespace Motion +} // namespace InputCommon  | 
