motion_emu.cpp 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // Copyright 2017 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <chrono>
  5. #include <mutex>
  6. #include <thread>
  7. #include <tuple>
  8. #include "common/math_util.h"
  9. #include "common/quaternion.h"
  10. #include "common/thread.h"
  11. #include "common/vector_math.h"
  12. #include "input_common/motion_emu.h"
  13. namespace InputCommon {
  14. // Implementation class of the motion emulation device
  15. class MotionEmuDevice {
  16. public:
  17. MotionEmuDevice(int update_millisecond, float sensitivity)
  18. : update_millisecond(update_millisecond),
  19. update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(
  20. std::chrono::milliseconds(update_millisecond))),
  21. sensitivity(sensitivity), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {}
  22. ~MotionEmuDevice() {
  23. if (motion_emu_thread.joinable()) {
  24. shutdown_event.Set();
  25. motion_emu_thread.join();
  26. }
  27. }
  28. void BeginTilt(int x, int y) {
  29. mouse_origin = Math::MakeVec(x, y);
  30. is_tilting = true;
  31. }
  32. void Tilt(int x, int y) {
  33. auto mouse_move = Math::MakeVec(x, y) - mouse_origin;
  34. if (is_tilting) {
  35. std::lock_guard<std::mutex> guard(tilt_mutex);
  36. if (mouse_move.x == 0 && mouse_move.y == 0) {
  37. tilt_angle = 0;
  38. } else {
  39. tilt_direction = mouse_move.Cast<float>();
  40. tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * sensitivity, 0.0f,
  41. MathUtil::PI * 0.5f);
  42. }
  43. }
  44. }
  45. void EndTilt() {
  46. std::lock_guard<std::mutex> guard(tilt_mutex);
  47. tilt_angle = 0;
  48. is_tilting = false;
  49. }
  50. std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() {
  51. std::lock_guard<std::mutex> guard(status_mutex);
  52. return status;
  53. }
  54. private:
  55. const int update_millisecond;
  56. const std::chrono::steady_clock::duration update_duration;
  57. const float sensitivity;
  58. Math::Vec2<int> mouse_origin;
  59. std::mutex tilt_mutex;
  60. Math::Vec2<float> tilt_direction;
  61. float tilt_angle = 0;
  62. bool is_tilting = false;
  63. Common::Event shutdown_event;
  64. std::tuple<Math::Vec3<float>, Math::Vec3<float>> status;
  65. std::mutex status_mutex;
  66. // Note: always keep the thread declaration at the end so that other objects are initialized
  67. // before this!
  68. std::thread motion_emu_thread;
  69. void MotionEmuThread() {
  70. auto update_time = std::chrono::steady_clock::now();
  71. Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0);
  72. Math::Quaternion<float> old_q;
  73. while (!shutdown_event.WaitUntil(update_time)) {
  74. update_time += update_duration;
  75. old_q = q;
  76. {
  77. std::lock_guard<std::mutex> guard(tilt_mutex);
  78. // Find the quaternion describing current 3DS tilting
  79. q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x),
  80. tilt_angle);
  81. }
  82. auto inv_q = q.Inverse();
  83. // Set the gravity vector in world space
  84. auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f);
  85. // Find the angular rate vector in world space
  86. auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
  87. angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180;
  88. // Transform the two vectors from world space to 3DS space
  89. gravity = QuaternionRotate(inv_q, gravity);
  90. angular_rate = QuaternionRotate(inv_q, angular_rate);
  91. // Update the sensor state
  92. {
  93. std::lock_guard<std::mutex> guard(status_mutex);
  94. status = std::make_tuple(gravity, angular_rate);
  95. }
  96. }
  97. }
  98. };
  99. // Interface wrapper held by input receiver as a unique_ptr. It holds the implementation class as
  100. // a shared_ptr, which is also observed by the factory class as a weak_ptr. In this way the factory
  101. // can forward all the inputs to the implementation only when it is valid.
  102. class MotionEmuDeviceWrapper : public Input::MotionDevice {
  103. public:
  104. MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) {
  105. device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
  106. }
  107. std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() const {
  108. return device->GetStatus();
  109. }
  110. std::shared_ptr<MotionEmuDevice> device;
  111. };
  112. std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) {
  113. int update_period = params.Get("update_period", 100);
  114. float sensitivity = params.Get("sensitivity", 0.01f);
  115. auto device_wrapper = std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity);
  116. // Previously created device is disconnected here. Having two motion devices for 3DS is not
  117. // expected.
  118. current_device = device_wrapper->device;
  119. return std::move(device_wrapper);
  120. }
  121. void MotionEmu::BeginTilt(int x, int y) {
  122. if (auto ptr = current_device.lock()) {
  123. ptr->BeginTilt(x, y);
  124. }
  125. }
  126. void MotionEmu::Tilt(int x, int y) {
  127. if (auto ptr = current_device.lock()) {
  128. ptr->Tilt(x, y);
  129. }
  130. }
  131. void MotionEmu::EndTilt() {
  132. if (auto ptr = current_device.lock()) {
  133. ptr->EndTilt();
  134. }
  135. }
  136. } // namespace InputCommon