|
|
@@ -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
|