| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- // Copyright 2017 Citra Emulator Project
- // Licensed under GPLv2 or any later version
- // Refer to the license.txt file included.
- #include <algorithm>
- #include <chrono>
- #include <mutex>
- #include <thread>
- #include <tuple>
- #include "common/math_util.h"
- #include "common/quaternion.h"
- #include "common/thread.h"
- #include "common/vector_math.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 = Common::MakeVec(x, y);
- is_tilting = true;
- }
- void Tilt(int x, int y) {
- auto mouse_move = Common::MakeVec(x, y) - mouse_origin;
- if (is_tilting) {
- std::lock_guard guard{tilt_mutex};
- if (mouse_move.x == 0 && mouse_move.y == 0) {
- tilt_angle = 0;
- } else {
- tilt_direction = mouse_move.Cast<float>();
- tilt_angle =
- std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f, Common::PI * 0.5f);
- }
- }
- }
- void EndTilt() {
- std::lock_guard guard{tilt_mutex};
- tilt_angle = 0;
- is_tilting = false;
- }
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() {
- std::lock_guard guard{status_mutex};
- return status;
- }
- private:
- const int update_millisecond;
- const std::chrono::steady_clock::duration update_duration;
- const float sensitivity;
- Common::Vec2<int> mouse_origin;
- std::mutex tilt_mutex;
- Common::Vec2<float> tilt_direction;
- float tilt_angle = 0;
- bool is_tilting = false;
- Common::Event shutdown_event;
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> status;
- std::mutex status_mutex;
- // Note: always keep the thread declaration at the end so that other objects are initialized
- // before this!
- std::thread motion_emu_thread;
- void MotionEmuThread() {
- auto update_time = std::chrono::steady_clock::now();
- Common::Quaternion<float> q = Common::MakeQuaternion(Common::Vec3<float>(), 0);
- Common::Quaternion<float> old_q;
- while (!shutdown_event.WaitUntil(update_time)) {
- update_time += update_duration;
- old_q = q;
- {
- std::lock_guard guard{tilt_mutex};
- // Find the quaternion describing current 3DS tilting
- q = Common::MakeQuaternion(
- Common::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 = Common::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 / Common::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 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<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
- 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 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
|