summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar bunnei2017-01-07 12:39:20 -0500
committerGravatar GitHub2017-01-07 12:39:20 -0500
commit7cfe3ef0463ace034b1e5786c9581cfa5f2e810c (patch)
tree6b06cb03d276b29070ca29599fc4c5fcf77edb39 /src
parentMerge pull request #2410 from Subv/sleepthread (diff)
parentFrontend: make motion sensor interfaced thread-safe (diff)
downloadyuzu-7cfe3ef0463ace034b1e5786c9581cfa5f2e810c.tar.gz
yuzu-7cfe3ef0463ace034b1e5786c9581cfa5f2e810c.tar.xz
yuzu-7cfe3ef0463ace034b1e5786c9581cfa5f2e810c.zip
Merge pull request #1951 from wwylele/motion-sensor
Emulate motion sensor in frontend
Diffstat (limited to 'src')
-rw-r--r--src/citra/emu_window/emu_window_sdl2.cpp22
-rw-r--r--src/citra/emu_window/emu_window_sdl2.h5
-rw-r--r--src/citra_qt/bootmanager.cpp10
-rw-r--r--src/citra_qt/bootmanager.h4
-rw-r--r--src/common/CMakeLists.txt1
-rw-r--r--src/common/math_util.h2
-rw-r--r--src/common/quaternion.h44
-rw-r--r--src/common/thread.h10
-rw-r--r--src/common/vector_math.h19
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/frontend/emu_window.cpp25
-rw-r--r--src/core/frontend/emu_window.h52
-rw-r--r--src/core/frontend/motion_emu.cpp89
-rw-r--r--src/core/frontend/motion_emu.h52
14 files changed, 321 insertions, 16 deletions
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 @@
19 19
20void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { 20void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
21 TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); 21 TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
22 motion_emu->Tilt(x, y);
22} 23}
23 24
24void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { 25void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
25 if (button != SDL_BUTTON_LEFT) 26 if (button == SDL_BUTTON_LEFT) {
26 return; 27 if (state == SDL_PRESSED) {
27 28 TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
28 if (state == SDL_PRESSED) { 29 } else {
29 TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); 30 TouchReleased();
30 } else { 31 }
31 TouchReleased(); 32 } else if (button == SDL_BUTTON_RIGHT) {
33 if (state == SDL_PRESSED) {
34 motion_emu->BeginTilt(x, y);
35 } else {
36 motion_emu->EndTilt();
37 }
32 } 38 }
33} 39}
34 40
@@ -54,6 +60,7 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
54 keyboard_id = KeyMap::NewDeviceId(); 60 keyboard_id = KeyMap::NewDeviceId();
55 61
56 ReloadSetKeymaps(); 62 ReloadSetKeymaps();
63 motion_emu = std::make_unique<Motion::MotionEmu>(*this);
57 64
58 SDL_SetMainReady(); 65 SDL_SetMainReady();
59 66
@@ -109,6 +116,7 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
109EmuWindow_SDL2::~EmuWindow_SDL2() { 116EmuWindow_SDL2::~EmuWindow_SDL2() {
110 SDL_GL_DeleteContext(gl_context); 117 SDL_GL_DeleteContext(gl_context);
111 SDL_Quit(); 118 SDL_Quit();
119 motion_emu = nullptr;
112} 120}
113 121
114void EmuWindow_SDL2::SwapBuffers() { 122void 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 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <memory>
7#include <utility> 8#include <utility>
8#include "core/frontend/emu_window.h" 9#include "core/frontend/emu_window.h"
10#include "core/frontend/motion_emu.h"
9 11
10struct SDL_Window; 12struct SDL_Window;
11 13
@@ -61,4 +63,7 @@ private:
61 63
62 /// Device id of keyboard for use with KeyMap 64 /// Device id of keyboard for use with KeyMap
63 int keyboard_id; 65 int keyboard_id;
66
67 /// Motion sensors emulation
68 std::unique_ptr<Motion::MotionEmu> motion_emu;
64}; 69};
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index 59cb1b1bc..948db384d 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -191,6 +191,7 @@ qreal GRenderWindow::windowPixelRatio() {
191} 191}
192 192
193void GRenderWindow::closeEvent(QCloseEvent* event) { 193void GRenderWindow::closeEvent(QCloseEvent* event) {
194 motion_emu = nullptr;
194 emit Closed(); 195 emit Closed();
195 QWidget::closeEvent(event); 196 QWidget::closeEvent(event);
196} 197}
@@ -204,11 +205,13 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
204} 205}
205 206
206void GRenderWindow::mousePressEvent(QMouseEvent* event) { 207void GRenderWindow::mousePressEvent(QMouseEvent* event) {
208 auto pos = event->pos();
207 if (event->button() == Qt::LeftButton) { 209 if (event->button() == Qt::LeftButton) {
208 auto pos = event->pos();
209 qreal pixelRatio = windowPixelRatio(); 210 qreal pixelRatio = windowPixelRatio();
210 this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio), 211 this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
211 static_cast<unsigned>(pos.y() * pixelRatio)); 212 static_cast<unsigned>(pos.y() * pixelRatio));
213 } else if (event->button() == Qt::RightButton) {
214 motion_emu->BeginTilt(pos.x(), pos.y());
212 } 215 }
213} 216}
214 217
@@ -217,11 +220,14 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
217 qreal pixelRatio = windowPixelRatio(); 220 qreal pixelRatio = windowPixelRatio();
218 this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u), 221 this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
219 std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u)); 222 std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
223 motion_emu->Tilt(pos.x(), pos.y());
220} 224}
221 225
222void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { 226void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
223 if (event->button() == Qt::LeftButton) 227 if (event->button() == Qt::LeftButton)
224 this->TouchReleased(); 228 this->TouchReleased();
229 else if (event->button() == Qt::RightButton)
230 motion_emu->EndTilt();
225} 231}
226 232
227void GRenderWindow::ReloadSetKeymaps() { 233void GRenderWindow::ReloadSetKeymaps() {
@@ -279,11 +285,13 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(
279} 285}
280 286
281void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { 287void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
288 motion_emu = std::make_unique<Motion::MotionEmu>(*this);
282 this->emu_thread = emu_thread; 289 this->emu_thread = emu_thread;
283 child->DisablePainting(); 290 child->DisablePainting();
284} 291}
285 292
286void GRenderWindow::OnEmulationStopping() { 293void GRenderWindow::OnEmulationStopping() {
294 motion_emu = nullptr;
287 emu_thread = nullptr; 295 emu_thread = nullptr;
288 child->EnablePainting(); 296 child->EnablePainting();
289} 297}
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 @@
11#include <QThread> 11#include <QThread>
12#include "common/thread.h" 12#include "common/thread.h"
13#include "core/frontend/emu_window.h" 13#include "core/frontend/emu_window.h"
14#include "core/frontend/motion_emu.h"
14 15
15class QKeyEvent; 16class QKeyEvent;
16class QScreen; 17class QScreen;
@@ -156,6 +157,9 @@ private:
156 157
157 EmuThread* emu_thread; 158 EmuThread* emu_thread;
158 159
160 /// Motion sensors emulation
161 std::unique_ptr<Motion::MotionEmu> motion_emu;
162
159protected: 163protected:
160 void showEvent(QShowEvent* event) override; 164 void showEvent(QShowEvent* event) override;
161}; 165};
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
46 microprofileui.h 46 microprofileui.h
47 platform.h 47 platform.h
48 profiler_reporting.h 48 profiler_reporting.h
49 quaternion.h
49 scm_rev.h 50 scm_rev.h
50 scope_exit.h 51 scope_exit.h
51 string_util.h 52 string_util.h
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 @@
10 10
11namespace MathUtil { 11namespace MathUtil {
12 12
13static constexpr float PI = 3.14159265f;
14
13inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start1, 15inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start1,
14 unsigned length1) { 16 unsigned length1) {
15 return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1)); 17 return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1));
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 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "common/vector_math.h"
8
9namespace Math {
10
11template <typename T>
12class Quaternion {
13public:
14 Math::Vec3<T> xyz;
15 T w;
16
17 Quaternion<decltype(-T{})> Inverse() const {
18 return {-xyz, w};
19 }
20
21 Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const {
22 return {xyz + other.xyz, w + other.w};
23 }
24
25 Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const {
26 return {xyz - other.xyz, w - other.w};
27 }
28
29 Quaternion<decltype(T{} * T{} - T{} * T{})> operator*(const Quaternion& other) const {
30 return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz),
31 w * other.w - Dot(xyz, other.xyz)};
32 }
33};
34
35template <typename T>
36auto QuaternionRotate(const Quaternion<T>& q, const Math::Vec3<T>& v) {
37 return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w);
38}
39
40inline Quaternion<float> MakeQuaternion(const Math::Vec3<float>& axis, float angle) {
41 return {axis * std::sin(angle / 2), std::cos(angle / 2)};
42}
43
44} // namspace Math
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 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <chrono>
7#include <condition_variable> 8#include <condition_variable>
8#include <cstddef> 9#include <cstddef>
9#include <mutex> 10#include <mutex>
@@ -54,6 +55,15 @@ public:
54 is_set = false; 55 is_set = false;
55 } 56 }
56 57
58 template <class Clock, class Duration>
59 bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
60 std::unique_lock<std::mutex> lk(mutex);
61 if (!condvar.wait_until(lk, time, [this] { return is_set; }))
62 return false;
63 is_set = false;
64 return true;
65 }
66
57 void Reset() { 67 void Reset() {
58 std::unique_lock<std::mutex> lk(mutex); 68 std::unique_lock<std::mutex> lk(mutex);
59 // no other action required, since wait loops on the predicate and any lingering signal will 69 // no other action required, since wait loops on the predicate and any lingering signal will
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<T> operator*(const V& f, const Vec2<T>& vec) {
186 186
187typedef Vec2<float> Vec2f; 187typedef Vec2<float> Vec2f;
188 188
189template <>
190inline float Vec2<float>::Length() const {
191 return std::sqrt(x * x + y * y);
192}
193
194template <>
195inline float Vec2<float>::Normalize() {
196 float length = Length();
197 *this /= length;
198 return length;
199}
200
189template <typename T> 201template <typename T>
190class Vec3 { 202class Vec3 {
191public: 203public:
@@ -388,6 +400,13 @@ inline Vec3<float> Vec3<float>::Normalized() const {
388 return *this / Length(); 400 return *this / Length();
389} 401}
390 402
403template <>
404inline float Vec3<float>::Normalize() {
405 float length = Length();
406 *this /= length;
407 return length;
408}
409
391typedef Vec3<float> Vec3f; 410typedef Vec3<float> Vec3f;
392 411
393template <typename T> 412template <typename T>
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
31 file_sys/savedata_archive.cpp 31 file_sys/savedata_archive.cpp
32 frontend/emu_window.cpp 32 frontend/emu_window.cpp
33 frontend/key_map.cpp 33 frontend/key_map.cpp
34 frontend/motion_emu.cpp
34 gdbstub/gdbstub.cpp 35 gdbstub/gdbstub.cpp
35 hle/config_mem.cpp 36 hle/config_mem.cpp
36 hle/applets/applet.cpp 37 hle/applets/applet.cpp
@@ -202,6 +203,7 @@ set(HEADERS
202 file_sys/savedata_archive.h 203 file_sys/savedata_archive.h
203 frontend/emu_window.h 204 frontend/emu_window.h
204 frontend/key_map.h 205 frontend/key_map.h
206 frontend/motion_emu.h
205 gdbstub/gdbstub.h 207 gdbstub/gdbstub.h
206 hle/config_mem.h 208 hle/config_mem.h
207 hle/function_wrappers.h 209 hle/function_wrappers.h
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index f6f90f9e1..1541cc39d 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -5,6 +5,7 @@
5#include <algorithm> 5#include <algorithm>
6#include <cmath> 6#include <cmath>
7#include "common/assert.h" 7#include "common/assert.h"
8#include "common/profiler_reporting.h"
8#include "core/frontend/emu_window.h" 9#include "core/frontend/emu_window.h"
9#include "core/frontend/key_map.h" 10#include "core/frontend/key_map.h"
10#include "video_core/video_core.h" 11#include "video_core/video_core.h"
@@ -89,6 +90,30 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
89 TouchPressed(framebuffer_x, framebuffer_y); 90 TouchPressed(framebuffer_x, framebuffer_y);
90} 91}
91 92
93void EmuWindow::AccelerometerChanged(float x, float y, float z) {
94 constexpr float coef = 512;
95
96 std::lock_guard<std::mutex> lock(accel_mutex);
97
98 // TODO(wwylele): do a time stretch as it in GyroscopeChanged
99 // The time stretch formula should be like
100 // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
101 accel_x = x * coef;
102 accel_y = y * coef;
103 accel_z = z * coef;
104}
105
106void EmuWindow::GyroscopeChanged(float x, float y, float z) {
107 constexpr float FULL_FPS = 60;
108 float coef = GetGyroscopeRawToDpsCoefficient();
109 float stretch =
110 FULL_FPS / Common::Profiling::GetTimingResultsAggregator()->GetAggregatedResults().fps;
111 std::lock_guard<std::mutex> lock(gyro_mutex);
112 gyro_x = x * coef * stretch;
113 gyro_y = y * coef * stretch;
114 gyro_z = z * coef * stretch;
115}
116
92void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) { 117void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {
93 Layout::FramebufferLayout layout; 118 Layout::FramebufferLayout layout;
94 switch (Settings::values.layout_option) { 119 switch (Settings::values.layout_option) {
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 835c4d500..1ba64c92b 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <mutex>
7#include <tuple> 8#include <tuple>
8#include <utility> 9#include <utility>
9#include "common/common_types.h" 10#include "common/common_types.h"
@@ -93,6 +94,27 @@ public:
93 void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y); 94 void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
94 95
95 /** 96 /**
97 * Signal accelerometer state has changed.
98 * @param x X-axis accelerometer value
99 * @param y Y-axis accelerometer value
100 * @param z Z-axis accelerometer value
101 * @note all values are in unit of g (gravitational acceleration).
102 * e.g. x = 1.0 means 9.8m/s^2 in x direction.
103 * @see GetAccelerometerState for axis explanation.
104 */
105 void AccelerometerChanged(float x, float y, float z);
106
107 /**
108 * Signal gyroscope state has changed.
109 * @param x X-axis accelerometer value
110 * @param y Y-axis accelerometer value
111 * @param z Z-axis accelerometer value
112 * @note all values are in deg/sec.
113 * @see GetGyroscopeState for axis explanation.
114 */
115 void GyroscopeChanged(float x, float y, float z);
116
117 /**
96 * Gets the current pad state (which buttons are pressed). 118 * Gets the current pad state (which buttons are pressed).
97 * @note This should be called by the core emu thread to get a state set by the window thread. 119 * @note This should be called by the core emu thread to get a state set by the window thread.
98 * @note This doesn't include analog input like circle pad direction 120 * @note This doesn't include analog input like circle pad direction
@@ -134,12 +156,11 @@ public:
134 * 1 unit of return value = 1/512 g (measured by hw test), 156 * 1 unit of return value = 1/512 g (measured by hw test),
135 * where g is the gravitational acceleration (9.8 m/sec2). 157 * where g is the gravitational acceleration (9.8 m/sec2).
136 * @note This should be called by the core emu thread to get a state set by the window thread. 158 * @note This should be called by the core emu thread to get a state set by the window thread.
137 * @todo Implement accelerometer input in front-end.
138 * @return std::tuple of (x, y, z) 159 * @return std::tuple of (x, y, z)
139 */ 160 */
140 std::tuple<s16, s16, s16> GetAccelerometerState() const { 161 std::tuple<s16, s16, s16> GetAccelerometerState() {
141 // stubbed 162 std::lock_guard<std::mutex> lock(accel_mutex);
142 return std::make_tuple(0, -512, 0); 163 return std::make_tuple(accel_x, accel_y, accel_z);
143 } 164 }
144 165
145 /** 166 /**
@@ -153,12 +174,11 @@ public:
153 * 1 unit of return value = (1/coef) deg/sec, 174 * 1 unit of return value = (1/coef) deg/sec,
154 * where coef is the return value of GetGyroscopeRawToDpsCoefficient(). 175 * where coef is the return value of GetGyroscopeRawToDpsCoefficient().
155 * @note This should be called by the core emu thread to get a state set by the window thread. 176 * @note This should be called by the core emu thread to get a state set by the window thread.
156 * @todo Implement gyroscope input in front-end.
157 * @return std::tuple of (x, y, z) 177 * @return std::tuple of (x, y, z)
158 */ 178 */
159 std::tuple<s16, s16, s16> GetGyroscopeState() const { 179 std::tuple<s16, s16, s16> GetGyroscopeState() {
160 // stubbed 180 std::lock_guard<std::mutex> lock(gyro_mutex);
161 return std::make_tuple(0, 0, 0); 181 return std::make_tuple(gyro_x, gyro_y, gyro_z);
162 } 182 }
163 183
164 /** 184 /**
@@ -216,6 +236,12 @@ protected:
216 circle_pad_x = 0; 236 circle_pad_x = 0;
217 circle_pad_y = 0; 237 circle_pad_y = 0;
218 touch_pressed = false; 238 touch_pressed = false;
239 accel_x = 0;
240 accel_y = -512;
241 accel_z = 0;
242 gyro_x = 0;
243 gyro_y = 0;
244 gyro_z = 0;
219 } 245 }
220 virtual ~EmuWindow() {} 246 virtual ~EmuWindow() {}
221 247
@@ -281,6 +307,16 @@ private:
281 s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156) 307 s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156)
282 s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156) 308 s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156)
283 309
310 std::mutex accel_mutex;
311 s16 accel_x; ///< Accelerometer X-axis value in native 3DS units
312 s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units
313 s16 accel_z; ///< Accelerometer Z-axis value in native 3DS units
314
315 std::mutex gyro_mutex;
316 s16 gyro_x; ///< Gyroscope X-axis value in native 3DS units
317 s16 gyro_y; ///< Gyroscope Y-axis value in native 3DS units
318 s16 gyro_z; ///< Gyroscope Z-axis value in native 3DS units
319
284 /** 320 /**
285 * Clip the provided coordinates to be inside the touchscreen area. 321 * Clip the provided coordinates to be inside the touchscreen area.
286 */ 322 */
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 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/math_util.h"
6#include "common/quaternion.h"
7#include "core/frontend/emu_window.h"
8#include "core/frontend/motion_emu.h"
9
10namespace Motion {
11
12static constexpr int update_millisecond = 100;
13static constexpr auto update_duration =
14 std::chrono::duration_cast<std::chrono::steady_clock::duration>(
15 std::chrono::milliseconds(update_millisecond));
16
17MotionEmu::MotionEmu(EmuWindow& emu_window)
18 : motion_emu_thread(&MotionEmu::MotionEmuThread, this, std::ref(emu_window)) {}
19
20MotionEmu::~MotionEmu() {
21 if (motion_emu_thread.joinable()) {
22 shutdown_event.Set();
23 motion_emu_thread.join();
24 }
25}
26
27void MotionEmu::MotionEmuThread(EmuWindow& emu_window) {
28 auto update_time = std::chrono::steady_clock::now();
29 Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0);
30 Math::Quaternion<float> old_q;
31
32 while (!shutdown_event.WaitUntil(update_time)) {
33 update_time += update_duration;
34 old_q = q;
35
36 {
37 std::lock_guard<std::mutex> guard(tilt_mutex);
38
39 // Find the quaternion describing current 3DS tilting
40 q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x),
41 tilt_angle);
42 }
43
44 auto inv_q = q.Inverse();
45
46 // Set the gravity vector in world space
47 auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f);
48
49 // Find the angular rate vector in world space
50 auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
51 angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180;
52
53 // Transform the two vectors from world space to 3DS space
54 gravity = QuaternionRotate(inv_q, gravity);
55 angular_rate = QuaternionRotate(inv_q, angular_rate);
56
57 // Update the sensor state
58 emu_window.AccelerometerChanged(gravity.x, gravity.y, gravity.z);
59 emu_window.GyroscopeChanged(angular_rate.x, angular_rate.y, angular_rate.z);
60 }
61}
62
63void MotionEmu::BeginTilt(int x, int y) {
64 mouse_origin = Math::MakeVec(x, y);
65 is_tilting = true;
66}
67
68void MotionEmu::Tilt(int x, int y) {
69 constexpr float SENSITIVITY = 0.01f;
70 auto mouse_move = Math::MakeVec(x, y) - mouse_origin;
71 if (is_tilting) {
72 std::lock_guard<std::mutex> guard(tilt_mutex);
73 if (mouse_move.x == 0 && mouse_move.y == 0) {
74 tilt_angle = 0;
75 } else {
76 tilt_direction = mouse_move.Cast<float>();
77 tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * SENSITIVITY, 0.0f,
78 MathUtil::PI * 0.5f);
79 }
80 }
81}
82
83void MotionEmu::EndTilt() {
84 std::lock_guard<std::mutex> guard(tilt_mutex);
85 tilt_angle = 0;
86 is_tilting = false;
87}
88
89} // 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 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6#include "common/thread.h"
7#include "common/vector_math.h"
8
9class EmuWindow;
10
11namespace Motion {
12
13class MotionEmu final {
14public:
15 MotionEmu(EmuWindow& emu_window);
16 ~MotionEmu();
17
18 /**
19 * Signals that a motion sensor tilt has begun.
20 * @param x the x-coordinate of the cursor
21 * @param y the y-coordinate of the cursor
22 */
23 void BeginTilt(int x, int y);
24
25 /**
26 * Signals that a motion sensor tilt is occurring.
27 * @param x the x-coordinate of the cursor
28 * @param y the y-coordinate of the cursor
29 */
30 void Tilt(int x, int y);
31
32 /**
33 * Signals that a motion sensor tilt has ended.
34 */
35 void EndTilt();
36
37private:
38 Math::Vec2<int> mouse_origin;
39
40 std::mutex tilt_mutex;
41 Math::Vec2<float> tilt_direction;
42 float tilt_angle = 0;
43
44 bool is_tilting = false;
45
46 Common::Event shutdown_event;
47 std::thread motion_emu_thread;
48
49 void MotionEmuThread(EmuWindow& emu_window);
50};
51
52} // namespace Motion