From 2a069e76a5093db44a87732edb72a1bb2b771e61 Mon Sep 17 00:00:00 2001 From: wwylele Date: Sun, 11 Dec 2016 23:26:23 +0200 Subject: [PATCH 1/6] Common::Event: add WaitUntil --- src/common/thread.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/common/thread.h b/src/common/thread.h index 9c08be7e3..fa475ab51 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -54,6 +55,15 @@ public: is_set = false; } + template + bool WaitUntil(const std::chrono::time_point& time) { + std::unique_lock lk(mutex); + if (!condvar.wait_until(lk, time, [this] { return is_set; })) + return false; + is_set = false; + return true; + } + void Reset() { std::unique_lock lk(mutex); // no other action required, since wait loops on the predicate and any lingering signal will From 55f5d0f7770ea625e283aa7878340fc80a70cfd7 Mon Sep 17 00:00:00 2001 From: wwylele Date: Sun, 11 Dec 2016 23:27:30 +0200 Subject: [PATCH 2/6] MathUtil: add PI constant --- src/common/math_util.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/math_util.h b/src/common/math_util.h index cdeaeb733..45a1ed367 100644 --- a/src/common/math_util.h +++ b/src/common/math_util.h @@ -10,6 +10,8 @@ namespace MathUtil { +static constexpr float PI = 3.14159265f; + inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start1, unsigned length1) { return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1)); From 2e6d8e1321c81e29a85f46c0ff3c0280e3f95b9f Mon Sep 17 00:00:00 2001 From: wwylele Date: Sun, 11 Dec 2016 23:28:55 +0200 Subject: [PATCH 3/6] vector math: add implementation of Length and Normalize --- src/common/vector_math.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/common/vector_math.h b/src/common/vector_math.h index a57d86d88..7ca8e15f5 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -186,6 +186,18 @@ Vec2 operator*(const V& f, const Vec2& vec) { typedef Vec2 Vec2f; +template <> +inline float Vec2::Length() const { + return std::sqrt(x * x + y * y); +} + +template <> +inline float Vec2::Normalize() { + float length = Length(); + *this /= length; + return length; +} + template class Vec3 { public: @@ -388,6 +400,13 @@ inline Vec3 Vec3::Normalized() const { return *this / Length(); } +template <> +inline float Vec3::Normalize() { + float length = Length(); + *this /= length; + return length; +} + typedef Vec3 Vec3f; template From 6479f63091da35b1ac6f3930532cc8bde466f6c4 Mon Sep 17 00:00:00 2001 From: wwylele Date: Sun, 11 Dec 2016 23:32:01 +0200 Subject: [PATCH 4/6] Common: add Quaternion --- src/common/CMakeLists.txt | 1 + src/common/quaternion.h | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/common/quaternion.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 5aecf6e6e..a7a4a688c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -46,6 +46,7 @@ set(HEADERS microprofileui.h platform.h profiler_reporting.h + quaternion.h scm_rev.h scope_exit.h string_util.h diff --git a/src/common/quaternion.h b/src/common/quaternion.h new file mode 100644 index 000000000..84ac82ed3 --- /dev/null +++ b/src/common/quaternion.h @@ -0,0 +1,44 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/vector_math.h" + +namespace Math { + +template +class Quaternion { +public: + Math::Vec3 xyz; + T w; + + Quaternion Inverse() const { + return {-xyz, w}; + } + + Quaternion operator+(const Quaternion& other) const { + return {xyz + other.xyz, w + other.w}; + } + + Quaternion operator-(const Quaternion& other) const { + return {xyz - other.xyz, w - other.w}; + } + + Quaternion operator*(const Quaternion& other) const { + return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz), + w * other.w - Dot(xyz, other.xyz)}; + } +}; + +template +auto QuaternionRotate(const Quaternion& q, const Math::Vec3& v) { + return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w); +} + +inline Quaternion MakeQuaternion(const Math::Vec3& axis, float angle) { + return {axis * std::sin(angle / 2), std::cos(angle / 2)}; +} + +} // namspace Math From bcf9d20d5713e7003792bb8fa740b19a7204920e Mon Sep 17 00:00:00 2001 From: wwylele Date: Sun, 11 Dec 2016 23:32:41 +0200 Subject: [PATCH 5/6] Frontend: emulate motion sensor --- src/citra/emu_window/emu_window_sdl2.cpp | 22 ++++-- src/citra/emu_window/emu_window_sdl2.h | 5 ++ src/citra_qt/bootmanager.cpp | 10 ++- src/citra_qt/bootmanager.h | 4 ++ src/core/CMakeLists.txt | 2 + src/core/frontend/emu_window.cpp | 22 ++++++ src/core/frontend/emu_window.h | 49 ++++++++++--- src/core/frontend/motion_emu.cpp | 89 ++++++++++++++++++++++++ src/core/frontend/motion_emu.h | 52 ++++++++++++++ 9 files changed, 239 insertions(+), 16 deletions(-) create mode 100644 src/core/frontend/motion_emu.cpp create mode 100644 src/core/frontend/motion_emu.h diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index b0d82b670..81a3abe3f 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -19,16 +19,22 @@ void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); + motion_emu->Tilt(x, y); } void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { - if (button != SDL_BUTTON_LEFT) - return; - - if (state == SDL_PRESSED) { - TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); - } else { - TouchReleased(); + if (button == SDL_BUTTON_LEFT) { + if (state == SDL_PRESSED) { + TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); + } else { + TouchReleased(); + } + } else if (button == SDL_BUTTON_RIGHT) { + if (state == SDL_PRESSED) { + motion_emu->BeginTilt(x, y); + } else { + motion_emu->EndTilt(); + } } } @@ -54,6 +60,7 @@ EmuWindow_SDL2::EmuWindow_SDL2() { keyboard_id = KeyMap::NewDeviceId(); ReloadSetKeymaps(); + motion_emu = std::make_unique(*this); SDL_SetMainReady(); @@ -109,6 +116,7 @@ EmuWindow_SDL2::EmuWindow_SDL2() { EmuWindow_SDL2::~EmuWindow_SDL2() { SDL_GL_DeleteContext(gl_context); SDL_Quit(); + motion_emu = nullptr; } void EmuWindow_SDL2::SwapBuffers() { diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h index c8cd919c6..b1cbf16d7 100644 --- a/src/citra/emu_window/emu_window_sdl2.h +++ b/src/citra/emu_window/emu_window_sdl2.h @@ -4,8 +4,10 @@ #pragma once +#include #include #include "core/frontend/emu_window.h" +#include "core/frontend/motion_emu.h" struct SDL_Window; @@ -61,4 +63,7 @@ private: /// Device id of keyboard for use with KeyMap int keyboard_id; + + /// Motion sensors emulation + std::unique_ptr motion_emu; }; diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 57fde6caa..4ea332a9f 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -191,6 +191,7 @@ qreal GRenderWindow::windowPixelRatio() { } void GRenderWindow::closeEvent(QCloseEvent* event) { + motion_emu = nullptr; emit Closed(); QWidget::closeEvent(event); } @@ -204,11 +205,13 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { } void GRenderWindow::mousePressEvent(QMouseEvent* event) { + auto pos = event->pos(); if (event->button() == Qt::LeftButton) { - auto pos = event->pos(); qreal pixelRatio = windowPixelRatio(); this->TouchPressed(static_cast(pos.x() * pixelRatio), static_cast(pos.y() * pixelRatio)); + } else if (event->button() == Qt::RightButton) { + motion_emu->BeginTilt(pos.x(), pos.y()); } } @@ -217,11 +220,14 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { qreal pixelRatio = windowPixelRatio(); this->TouchMoved(std::max(static_cast(pos.x() * pixelRatio), 0u), std::max(static_cast(pos.y() * pixelRatio), 0u)); + motion_emu->Tilt(pos.x(), pos.y()); } void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) this->TouchReleased(); + else if (event->button() == Qt::RightButton) + motion_emu->EndTilt(); } void GRenderWindow::ReloadSetKeymaps() { @@ -279,11 +285,13 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest( } void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { + motion_emu = std::make_unique(*this); this->emu_thread = emu_thread; child->DisablePainting(); } void GRenderWindow::OnEmulationStopping() { + motion_emu = nullptr; emu_thread = nullptr; child->EnablePainting(); } diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 43015390b..7dac1c480 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -11,6 +11,7 @@ #include #include "common/thread.h" #include "core/frontend/emu_window.h" +#include "core/frontend/motion_emu.h" class QKeyEvent; class QScreen; @@ -156,6 +157,9 @@ private: EmuThread* emu_thread; + /// Motion sensors emulation + std::unique_ptr motion_emu; + protected: void showEvent(QShowEvent* event) override; }; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3621449b3..4c5b633e0 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -31,6 +31,7 @@ set(SRCS file_sys/savedata_archive.cpp frontend/emu_window.cpp frontend/key_map.cpp + frontend/motion_emu.cpp gdbstub/gdbstub.cpp hle/config_mem.cpp hle/applets/applet.cpp @@ -202,6 +203,7 @@ set(HEADERS file_sys/savedata_archive.h frontend/emu_window.h frontend/key_map.h + frontend/motion_emu.h gdbstub/gdbstub.h hle/config_mem.h hle/function_wrappers.h diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index f6f90f9e1..13c7f3de2 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -5,6 +5,7 @@ #include #include #include "common/assert.h" +#include "common/profiler_reporting.h" #include "core/frontend/emu_window.h" #include "core/frontend/key_map.h" #include "video_core/video_core.h" @@ -89,6 +90,27 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { TouchPressed(framebuffer_x, framebuffer_y); } +void EmuWindow::AccelerometerChanged(float x, float y, float z) { + constexpr float coef = 512; + + // TODO(wwylele): do a time stretch as it in GyroscopeChanged + // The time stretch formula should be like + // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity + accel_x = x * coef; + accel_y = y * coef; + accel_z = z * coef; +} + +void EmuWindow::GyroscopeChanged(float x, float y, float z) { + constexpr float FULL_FPS = 60; + float coef = GetGyroscopeRawToDpsCoefficient(); + float stretch = + FULL_FPS / Common::Profiling::GetTimingResultsAggregator()->GetAggregatedResults().fps; + gyro_x = x * coef * stretch; + gyro_y = y * coef * stretch; + gyro_z = z * coef * stretch; +} + void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) { Layout::FramebufferLayout layout; switch (Settings::values.layout_option) { diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 835c4d500..2cdbf1742 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -92,6 +92,27 @@ public: */ void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y); + /** + * Signal accelerometer state has changed. + * @param x X-axis accelerometer value + * @param y Y-axis accelerometer value + * @param z Z-axis accelerometer value + * @note all values are in unit of g (gravitational acceleration). + * e.g. x = 1.0 means 9.8m/s^2 in x direction. + * @see GetAccelerometerState for axis explanation. + */ + void AccelerometerChanged(float x, float y, float z); + + /** + * Signal gyroscope state has changed. + * @param x X-axis accelerometer value + * @param y Y-axis accelerometer value + * @param z Z-axis accelerometer value + * @note all values are in deg/sec. + * @see GetGyroscopeState for axis explanation. + */ + void GyroscopeChanged(float x, float y, float z); + /** * Gets the current pad state (which buttons are pressed). * @note This should be called by the core emu thread to get a state set by the window thread. @@ -134,12 +155,11 @@ public: * 1 unit of return value = 1/512 g (measured by hw test), * where g is the gravitational acceleration (9.8 m/sec2). * @note This should be called by the core emu thread to get a state set by the window thread. - * @todo Implement accelerometer input in front-end. + * @todo Fix this function to be thread-safe. * @return std::tuple of (x, y, z) */ - std::tuple GetAccelerometerState() const { - // stubbed - return std::make_tuple(0, -512, 0); + std::tuple GetAccelerometerState() { + return std::make_tuple(accel_x, accel_y, accel_z); } /** @@ -153,12 +173,11 @@ public: * 1 unit of return value = (1/coef) deg/sec, * where coef is the return value of GetGyroscopeRawToDpsCoefficient(). * @note This should be called by the core emu thread to get a state set by the window thread. - * @todo Implement gyroscope input in front-end. + * @todo Fix this function to be thread-safe. * @return std::tuple of (x, y, z) */ - std::tuple GetGyroscopeState() const { - // stubbed - return std::make_tuple(0, 0, 0); + std::tuple GetGyroscopeState() { + return std::make_tuple(gyro_x, gyro_y, gyro_z); } /** @@ -216,6 +235,12 @@ protected: circle_pad_x = 0; circle_pad_y = 0; touch_pressed = false; + accel_x = 0; + accel_y = -512; + accel_z = 0; + gyro_x = 0; + gyro_y = 0; + gyro_z = 0; } virtual ~EmuWindow() {} @@ -281,6 +306,14 @@ private: s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156) s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156) + s16 accel_x; ///< Accelerometer X-axis value in native 3DS units + s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units + s16 accel_z; ///< Accelerometer Z-axis value in native 3DS units + + s16 gyro_x; ///< Gyroscope X-axis value in native 3DS units + s16 gyro_y; ///< Gyroscope Y-axis value in native 3DS units + s16 gyro_z; ///< Gyroscope Z-axis value in native 3DS units + /** * Clip the provided coordinates to be inside the touchscreen area. */ diff --git a/src/core/frontend/motion_emu.cpp b/src/core/frontend/motion_emu.cpp new file mode 100644 index 000000000..9a5b3185d --- /dev/null +++ b/src/core/frontend/motion_emu.cpp @@ -0,0 +1,89 @@ +// Copyright 2016 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 "core/frontend/emu_window.h" +#include "core/frontend/motion_emu.h" + +namespace Motion { + +static constexpr int update_millisecond = 100; +static constexpr auto update_duration = + std::chrono::duration_cast( + std::chrono::milliseconds(update_millisecond)); + +MotionEmu::MotionEmu(EmuWindow& emu_window) + : motion_emu_thread(&MotionEmu::MotionEmuThread, this, std::ref(emu_window)) {} + +MotionEmu::~MotionEmu() { + if (motion_emu_thread.joinable()) { + shutdown_event.Set(); + motion_emu_thread.join(); + } +} + +void MotionEmu::MotionEmuThread(EmuWindow& emu_window) { + auto update_time = std::chrono::steady_clock::now(); + Math::Quaternion q = MakeQuaternion(Math::Vec3(), 0); + Math::Quaternion 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 = 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 + emu_window.AccelerometerChanged(gravity.x, gravity.y, gravity.z); + emu_window.GyroscopeChanged(angular_rate.x, angular_rate.y, angular_rate.z); + } +} + +void MotionEmu::BeginTilt(int x, int y) { + mouse_origin = Math::MakeVec(x, y); + is_tilting = true; +} + +void MotionEmu::Tilt(int x, int y) { + constexpr float SENSITIVITY = 0.01f; + auto mouse_move = Math::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(); + tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * SENSITIVITY, 0.0f, + MathUtil::PI * 0.5f); + } + } +} + +void MotionEmu::EndTilt() { + std::lock_guard guard(tilt_mutex); + tilt_angle = 0; + is_tilting = false; +} + +} // namespace Motion diff --git a/src/core/frontend/motion_emu.h b/src/core/frontend/motion_emu.h new file mode 100644 index 000000000..99d41a726 --- /dev/null +++ b/src/core/frontend/motion_emu.h @@ -0,0 +1,52 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include "common/thread.h" +#include "common/vector_math.h" + +class EmuWindow; + +namespace Motion { + +class MotionEmu final { +public: + MotionEmu(EmuWindow& emu_window); + ~MotionEmu(); + + /** + * Signals that a motion sensor tilt has begun. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + */ + void BeginTilt(int x, int y); + + /** + * Signals that a motion sensor tilt is occurring. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + */ + void Tilt(int x, int y); + + /** + * Signals that a motion sensor tilt has ended. + */ + void EndTilt(); + +private: + Math::Vec2 mouse_origin; + + std::mutex tilt_mutex; + Math::Vec2 tilt_direction; + float tilt_angle = 0; + + bool is_tilting = false; + + Common::Event shutdown_event; + std::thread motion_emu_thread; + + void MotionEmuThread(EmuWindow& emu_window); +}; + +} // namespace Motion From d7d40b3c56df8e31d018477a5bd2abe3a6e4e550 Mon Sep 17 00:00:00 2001 From: wwylele Date: Thu, 29 Dec 2016 21:18:36 +0200 Subject: [PATCH 6/6] Frontend: make motion sensor interfaced thread-safe --- src/core/frontend/emu_window.cpp | 3 +++ src/core/frontend/emu_window.h | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 13c7f3de2..1541cc39d 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -93,6 +93,8 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { void EmuWindow::AccelerometerChanged(float x, float y, float z) { constexpr float coef = 512; + std::lock_guard lock(accel_mutex); + // TODO(wwylele): do a time stretch as it in GyroscopeChanged // The time stretch formula should be like // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity @@ -106,6 +108,7 @@ void EmuWindow::GyroscopeChanged(float x, float y, float z) { float coef = GetGyroscopeRawToDpsCoefficient(); float stretch = FULL_FPS / Common::Profiling::GetTimingResultsAggregator()->GetAggregatedResults().fps; + std::lock_guard lock(gyro_mutex); gyro_x = x * coef * stretch; gyro_y = y * coef * stretch; gyro_z = z * coef * stretch; diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 2cdbf1742..1ba64c92b 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include "common/common_types.h" @@ -155,10 +156,10 @@ public: * 1 unit of return value = 1/512 g (measured by hw test), * where g is the gravitational acceleration (9.8 m/sec2). * @note This should be called by the core emu thread to get a state set by the window thread. - * @todo Fix this function to be thread-safe. * @return std::tuple of (x, y, z) */ std::tuple GetAccelerometerState() { + std::lock_guard lock(accel_mutex); return std::make_tuple(accel_x, accel_y, accel_z); } @@ -173,10 +174,10 @@ public: * 1 unit of return value = (1/coef) deg/sec, * where coef is the return value of GetGyroscopeRawToDpsCoefficient(). * @note This should be called by the core emu thread to get a state set by the window thread. - * @todo Fix this function to be thread-safe. * @return std::tuple of (x, y, z) */ std::tuple GetGyroscopeState() { + std::lock_guard lock(gyro_mutex); return std::make_tuple(gyro_x, gyro_y, gyro_z); } @@ -306,10 +307,12 @@ private: s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156) s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156) + std::mutex accel_mutex; s16 accel_x; ///< Accelerometer X-axis value in native 3DS units s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units s16 accel_z; ///< Accelerometer Z-axis value in native 3DS units + std::mutex gyro_mutex; s16 gyro_x; ///< Gyroscope X-axis value in native 3DS units s16 gyro_y; ///< Gyroscope Y-axis value in native 3DS units s16 gyro_z; ///< Gyroscope Z-axis value in native 3DS units