motion_emu.cpp 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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::thread motion_emu_thread;
  65. std::tuple<Math::Vec3<float>, Math::Vec3<float>> status;
  66. std::mutex status_mutex;
  67. void MotionEmuThread() {
  68. auto update_time = std::chrono::steady_clock::now();
  69. Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0);
  70. Math::Quaternion<float> old_q;
  71. while (!shutdown_event.WaitUntil(update_time)) {
  72. update_time += update_duration;
  73. old_q = q;
  74. {
  75. std::lock_guard<std::mutex> guard(tilt_mutex);
  76. // Find the quaternion describing current 3DS tilting
  77. q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x),
  78. tilt_angle);
  79. }
  80. auto inv_q = q.Inverse();
  81. // Set the gravity vector in world space
  82. auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f);
  83. // Find the angular rate vector in world space
  84. auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
  85. angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180;
  86. // Transform the two vectors from world space to 3DS space
  87. gravity = QuaternionRotate(inv_q, gravity);
  88. angular_rate = QuaternionRotate(inv_q, angular_rate);
  89. // Update the sensor state
  90. {
  91. std::lock_guard<std::mutex> guard(status_mutex);
  92. status = std::make_tuple(gravity, angular_rate);
  93. }
  94. }
  95. }
  96. };
  97. // Interface wrapper held by input receiver as a unique_ptr. It holds the implementation class as
  98. // a shared_ptr, which is also observed by the factory class as a weak_ptr. In this way the factory
  99. // can forward all the inputs to the implementation only when it is valid.
  100. class MotionEmuDeviceWrapper : public Input::MotionDevice {
  101. public:
  102. MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) {
  103. device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
  104. }
  105. std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() const {
  106. return device->GetStatus();
  107. }
  108. std::shared_ptr<MotionEmuDevice> device;
  109. };
  110. std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) {
  111. int update_period = params.Get("update_period", 100);
  112. float sensitivity = params.Get("sensitivity", 0.01f);
  113. auto device_wrapper = std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity);
  114. // Previously created device is disconnected here. Having two motion devices for 3DS is not
  115. // expected.
  116. current_device = device_wrapper->device;
  117. return std::move(device_wrapper);
  118. }
  119. void MotionEmu::BeginTilt(int x, int y) {
  120. if (auto ptr = current_device.lock()) {
  121. ptr->BeginTilt(x, y);
  122. }
  123. }
  124. void MotionEmu::Tilt(int x, int y) {
  125. if (auto ptr = current_device.lock()) {
  126. ptr->Tilt(x, y);
  127. }
  128. }
  129. void MotionEmu::EndTilt() {
  130. if (auto ptr = current_device.lock()) {
  131. ptr->EndTilt();
  132. }
  133. }
  134. } // namespace InputCommon