summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar wwylele2016-12-11 23:32:41 +0200
committerGravatar wwylele2016-12-26 10:52:16 +0200
commitbcf9d20d5713e7003792bb8fa740b19a7204920e (patch)
treea5fd21a9dd1587d8dedddc33a19b09490ceb164d /src
parentCommon: add Quaternion (diff)
downloadyuzu-bcf9d20d5713e7003792bb8fa740b19a7204920e.tar.gz
yuzu-bcf9d20d5713e7003792bb8fa740b19a7204920e.tar.xz
yuzu-bcf9d20d5713e7003792bb8fa740b19a7204920e.zip
Frontend: emulate motion sensor
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/core/CMakeLists.txt2
-rw-r--r--src/core/frontend/emu_window.cpp22
-rw-r--r--src/core/frontend/emu_window.h49
-rw-r--r--src/core/frontend/motion_emu.cpp89
-rw-r--r--src/core/frontend/motion_emu.h52
9 files changed, 239 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 57fde6caa..4ea332a9f 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/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..13c7f3de2 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,27 @@ 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 // TODO(wwylele): do a time stretch as it in GyroscopeChanged
97 // The time stretch formula should be like
98 // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
99 accel_x = x * coef;
100 accel_y = y * coef;
101 accel_z = z * coef;
102}
103
104void EmuWindow::GyroscopeChanged(float x, float y, float z) {
105 constexpr float FULL_FPS = 60;
106 float coef = GetGyroscopeRawToDpsCoefficient();
107 float stretch =
108 FULL_FPS / Common::Profiling::GetTimingResultsAggregator()->GetAggregatedResults().fps;
109 gyro_x = x * coef * stretch;
110 gyro_y = y * coef * stretch;
111 gyro_z = z * coef * stretch;
112}
113
92void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) { 114void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {
93 Layout::FramebufferLayout layout; 115 Layout::FramebufferLayout layout;
94 switch (Settings::values.layout_option) { 116 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
@@ -93,6 +93,27 @@ public:
93 void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y); 93 void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
94 94
95 /** 95 /**
96 * Signal accelerometer state has changed.
97 * @param x X-axis accelerometer value
98 * @param y Y-axis accelerometer value
99 * @param z Z-axis accelerometer value
100 * @note all values are in unit of g (gravitational acceleration).
101 * e.g. x = 1.0 means 9.8m/s^2 in x direction.
102 * @see GetAccelerometerState for axis explanation.
103 */
104 void AccelerometerChanged(float x, float y, float z);
105
106 /**
107 * Signal gyroscope state has changed.
108 * @param x X-axis accelerometer value
109 * @param y Y-axis accelerometer value
110 * @param z Z-axis accelerometer value
111 * @note all values are in deg/sec.
112 * @see GetGyroscopeState for axis explanation.
113 */
114 void GyroscopeChanged(float x, float y, float z);
115
116 /**
96 * Gets the current pad state (which buttons are pressed). 117 * 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. 118 * @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 119 * @note This doesn't include analog input like circle pad direction
@@ -134,12 +155,11 @@ public:
134 * 1 unit of return value = 1/512 g (measured by hw test), 155 * 1 unit of return value = 1/512 g (measured by hw test),
135 * where g is the gravitational acceleration (9.8 m/sec2). 156 * 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. 157 * @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. 158 * @todo Fix this function to be thread-safe.
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 return std::make_tuple(accel_x, accel_y, accel_z);
142 return std::make_tuple(0, -512, 0);
143 } 163 }
144 164
145 /** 165 /**
@@ -153,12 +173,11 @@ public:
153 * 1 unit of return value = (1/coef) deg/sec, 173 * 1 unit of return value = (1/coef) deg/sec,
154 * where coef is the return value of GetGyroscopeRawToDpsCoefficient(). 174 * 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. 175 * @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. 176 * @todo Fix this function to be thread-safe.
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 return std::make_tuple(gyro_x, gyro_y, gyro_z);
161 return std::make_tuple(0, 0, 0);
162 } 181 }
163 182
164 /** 183 /**
@@ -216,6 +235,12 @@ protected:
216 circle_pad_x = 0; 235 circle_pad_x = 0;
217 circle_pad_y = 0; 236 circle_pad_y = 0;
218 touch_pressed = false; 237 touch_pressed = false;
238 accel_x = 0;
239 accel_y = -512;
240 accel_z = 0;
241 gyro_x = 0;
242 gyro_y = 0;
243 gyro_z = 0;
219 } 244 }
220 virtual ~EmuWindow() {} 245 virtual ~EmuWindow() {}
221 246
@@ -281,6 +306,14 @@ private:
281 s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156) 306 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) 307 s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156)
283 308
309 s16 accel_x; ///< Accelerometer X-axis value in native 3DS units
310 s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units
311 s16 accel_z; ///< Accelerometer Z-axis value in native 3DS units
312
313 s16 gyro_x; ///< Gyroscope X-axis value in native 3DS units
314 s16 gyro_y; ///< Gyroscope Y-axis value in native 3DS units
315 s16 gyro_z; ///< Gyroscope Z-axis value in native 3DS units
316
284 /** 317 /**
285 * Clip the provided coordinates to be inside the touchscreen area. 318 * Clip the provided coordinates to be inside the touchscreen area.
286 */ 319 */
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