summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
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 @@
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<Motion::MotionEmu>(*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 <memory>
#include <utility>
#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::MotionEmu> motion_emu;
};
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() {
}
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<unsigned>(pos.x() * pixelRatio),
static_cast<unsigned>(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<unsigned>(pos.x() * pixelRatio), 0u),
std::max(static_cast<unsigned>(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<Motion::MotionEmu>(*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 <QThread>
#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::MotionEmu> motion_emu;
+
protected:
void showEvent(QShowEvent* event) override;
};
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/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));
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 <typename T>
+class Quaternion {
+public:
+ Math::Vec3<T> xyz;
+ T w;
+
+ Quaternion<decltype(-T{})> Inverse() const {
+ return {-xyz, w};
+ }
+
+ Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const {
+ return {xyz + other.xyz, w + other.w};
+ }
+
+ Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const {
+ return {xyz - other.xyz, w - other.w};
+ }
+
+ Quaternion<decltype(T{} * T{} - T{} * T{})> operator*(const Quaternion& other) const {
+ return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz),
+ w * other.w - Dot(xyz, other.xyz)};
+ }
+};
+
+template <typename T>
+auto QuaternionRotate(const Quaternion<T>& q, const Math::Vec3<T>& v) {
+ return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w);
+}
+
+inline Quaternion<float> MakeQuaternion(const Math::Vec3<float>& axis, float angle) {
+ return {axis * std::sin(angle / 2), std::cos(angle / 2)};
+}
+
+} // 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 @@
#pragma once
+#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <mutex>
@@ -54,6 +55,15 @@ public:
is_set = false;
}
+ template <class Clock, class Duration>
+ bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
+ std::unique_lock<std::mutex> lk(mutex);
+ if (!condvar.wait_until(lk, time, [this] { return is_set; }))
+ return false;
+ is_set = false;
+ return true;
+ }
+
void Reset() {
std::unique_lock<std::mutex> lk(mutex);
// 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) {
typedef Vec2<float> Vec2f;
+template <>
+inline float Vec2<float>::Length() const {
+ return std::sqrt(x * x + y * y);
+}
+
+template <>
+inline float Vec2<float>::Normalize() {
+ float length = Length();
+ *this /= length;
+ return length;
+}
+
template <typename T>
class Vec3 {
public:
@@ -388,6 +400,13 @@ inline Vec3<float> Vec3<float>::Normalized() const {
return *this / Length();
}
+template <>
+inline float Vec3<float>::Normalize() {
+ float length = Length();
+ *this /= length;
+ return length;
+}
+
typedef Vec3<float> Vec3f;
template <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
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..1541cc39d 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -5,6 +5,7 @@
#include <algorithm>
#include <cmath>
#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,30 @@ 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;
+
+ std::lock_guard<std::mutex> 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
+ 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;
+ std::lock_guard<std::mutex> lock(gyro_mutex);
+ 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..1ba64c92b 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -4,6 +4,7 @@
#pragma once
+#include <mutex>
#include <tuple>
#include <utility>
#include "common/common_types.h"
@@ -93,6 +94,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.
* @note This doesn't include analog input like circle pad direction
@@ -134,12 +156,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.
* @return std::tuple of (x, y, z)
*/
- std::tuple<s16, s16, s16> GetAccelerometerState() const {
- // stubbed
- return std::make_tuple(0, -512, 0);
+ std::tuple<s16, s16, s16> GetAccelerometerState() {
+ std::lock_guard<std::mutex> lock(accel_mutex);
+ return std::make_tuple(accel_x, accel_y, accel_z);
}
/**
@@ -153,12 +174,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.
* @return std::tuple of (x, y, z)
*/
- std::tuple<s16, s16, s16> GetGyroscopeState() const {
- // stubbed
- return std::make_tuple(0, 0, 0);
+ std::tuple<s16, s16, s16> GetGyroscopeState() {
+ std::lock_guard<std::mutex> lock(gyro_mutex);
+ return std::make_tuple(gyro_x, gyro_y, gyro_z);
}
/**
@@ -216,6 +236,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 +307,16 @@ 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
+
/**
* 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::steady_clock::duration>(
+ 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<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
+ 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<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 MotionEmu::EndTilt() {
+ std::lock_guard<std::mutex> 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<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;
+
+ void MotionEmuThread(EmuWindow& emu_window);
+};
+
+} // namespace Motion