motion_emu.cpp 5.3 KB

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