mirror of
https://github.com/citra-emu/citra.git
synced 2024-12-19 17:11:06 +00:00
Merge pull request #1951 from wwylele/motion-sensor
Emulate motion sensor in frontend
This commit is contained in:
commit
7cfe3ef046
@ -19,16 +19,22 @@
|
|||||||
|
|
||||||
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
|
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
|
||||||
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
|
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) {
|
void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
|
||||||
if (button != SDL_BUTTON_LEFT)
|
if (button == SDL_BUTTON_LEFT) {
|
||||||
return;
|
if (state == SDL_PRESSED) {
|
||||||
|
TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
|
||||||
if (state == SDL_PRESSED) {
|
} else {
|
||||||
TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
|
TouchReleased();
|
||||||
} 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();
|
keyboard_id = KeyMap::NewDeviceId();
|
||||||
|
|
||||||
ReloadSetKeymaps();
|
ReloadSetKeymaps();
|
||||||
|
motion_emu = std::make_unique<Motion::MotionEmu>(*this);
|
||||||
|
|
||||||
SDL_SetMainReady();
|
SDL_SetMainReady();
|
||||||
|
|
||||||
@ -109,6 +116,7 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
|
|||||||
EmuWindow_SDL2::~EmuWindow_SDL2() {
|
EmuWindow_SDL2::~EmuWindow_SDL2() {
|
||||||
SDL_GL_DeleteContext(gl_context);
|
SDL_GL_DeleteContext(gl_context);
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
|
motion_emu = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuWindow_SDL2::SwapBuffers() {
|
void EmuWindow_SDL2::SwapBuffers() {
|
||||||
|
@ -4,8 +4,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
|
#include "core/frontend/motion_emu.h"
|
||||||
|
|
||||||
struct SDL_Window;
|
struct SDL_Window;
|
||||||
|
|
||||||
@ -61,4 +63,7 @@ private:
|
|||||||
|
|
||||||
/// Device id of keyboard for use with KeyMap
|
/// Device id of keyboard for use with KeyMap
|
||||||
int keyboard_id;
|
int keyboard_id;
|
||||||
|
|
||||||
|
/// Motion sensors emulation
|
||||||
|
std::unique_ptr<Motion::MotionEmu> motion_emu;
|
||||||
};
|
};
|
||||||
|
@ -191,6 +191,7 @@ qreal GRenderWindow::windowPixelRatio() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::closeEvent(QCloseEvent* event) {
|
void GRenderWindow::closeEvent(QCloseEvent* event) {
|
||||||
|
motion_emu = nullptr;
|
||||||
emit Closed();
|
emit Closed();
|
||||||
QWidget::closeEvent(event);
|
QWidget::closeEvent(event);
|
||||||
}
|
}
|
||||||
@ -204,11 +205,13 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
|
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
|
||||||
|
auto pos = event->pos();
|
||||||
if (event->button() == Qt::LeftButton) {
|
if (event->button() == Qt::LeftButton) {
|
||||||
auto pos = event->pos();
|
|
||||||
qreal pixelRatio = windowPixelRatio();
|
qreal pixelRatio = windowPixelRatio();
|
||||||
this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
|
this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
|
||||||
static_cast<unsigned>(pos.y() * 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();
|
qreal pixelRatio = windowPixelRatio();
|
||||||
this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
|
this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
|
||||||
std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
|
std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
|
||||||
|
motion_emu->Tilt(pos.x(), pos.y());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
|
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
|
||||||
if (event->button() == Qt::LeftButton)
|
if (event->button() == Qt::LeftButton)
|
||||||
this->TouchReleased();
|
this->TouchReleased();
|
||||||
|
else if (event->button() == Qt::RightButton)
|
||||||
|
motion_emu->EndTilt();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::ReloadSetKeymaps() {
|
void GRenderWindow::ReloadSetKeymaps() {
|
||||||
@ -279,11 +285,13 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
|
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
|
||||||
|
motion_emu = std::make_unique<Motion::MotionEmu>(*this);
|
||||||
this->emu_thread = emu_thread;
|
this->emu_thread = emu_thread;
|
||||||
child->DisablePainting();
|
child->DisablePainting();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::OnEmulationStopping() {
|
void GRenderWindow::OnEmulationStopping() {
|
||||||
|
motion_emu = nullptr;
|
||||||
emu_thread = nullptr;
|
emu_thread = nullptr;
|
||||||
child->EnablePainting();
|
child->EnablePainting();
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
|
#include "core/frontend/motion_emu.h"
|
||||||
|
|
||||||
class QKeyEvent;
|
class QKeyEvent;
|
||||||
class QScreen;
|
class QScreen;
|
||||||
@ -156,6 +157,9 @@ private:
|
|||||||
|
|
||||||
EmuThread* emu_thread;
|
EmuThread* emu_thread;
|
||||||
|
|
||||||
|
/// Motion sensors emulation
|
||||||
|
std::unique_ptr<Motion::MotionEmu> motion_emu;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void showEvent(QShowEvent* event) override;
|
void showEvent(QShowEvent* event) override;
|
||||||
};
|
};
|
||||||
|
@ -46,6 +46,7 @@ set(HEADERS
|
|||||||
microprofileui.h
|
microprofileui.h
|
||||||
platform.h
|
platform.h
|
||||||
profiler_reporting.h
|
profiler_reporting.h
|
||||||
|
quaternion.h
|
||||||
scm_rev.h
|
scm_rev.h
|
||||||
scope_exit.h
|
scope_exit.h
|
||||||
string_util.h
|
string_util.h
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
namespace MathUtil {
|
namespace MathUtil {
|
||||||
|
|
||||||
|
static constexpr float PI = 3.14159265f;
|
||||||
|
|
||||||
inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start1,
|
inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start1,
|
||||||
unsigned length1) {
|
unsigned length1) {
|
||||||
return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1));
|
return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1));
|
||||||
|
44
src/common/quaternion.h
Normal file
44
src/common/quaternion.h
Normal file
@ -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
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@ -54,6 +55,15 @@ public:
|
|||||||
is_set = false;
|
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() {
|
void Reset() {
|
||||||
std::unique_lock<std::mutex> lk(mutex);
|
std::unique_lock<std::mutex> lk(mutex);
|
||||||
// no other action required, since wait loops on the predicate and any lingering signal will
|
// no other action required, since wait loops on the predicate and any lingering signal will
|
||||||
|
@ -186,6 +186,18 @@ Vec2<T> operator*(const V& f, const Vec2<T>& vec) {
|
|||||||
|
|
||||||
typedef Vec2<float> Vec2f;
|
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>
|
template <typename T>
|
||||||
class Vec3 {
|
class Vec3 {
|
||||||
public:
|
public:
|
||||||
@ -388,6 +400,13 @@ inline Vec3<float> Vec3<float>::Normalized() const {
|
|||||||
return *this / Length();
|
return *this / Length();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline float Vec3<float>::Normalize() {
|
||||||
|
float length = Length();
|
||||||
|
*this /= length;
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
typedef Vec3<float> Vec3f;
|
typedef Vec3<float> Vec3f;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -31,6 +31,7 @@ set(SRCS
|
|||||||
file_sys/savedata_archive.cpp
|
file_sys/savedata_archive.cpp
|
||||||
frontend/emu_window.cpp
|
frontend/emu_window.cpp
|
||||||
frontend/key_map.cpp
|
frontend/key_map.cpp
|
||||||
|
frontend/motion_emu.cpp
|
||||||
gdbstub/gdbstub.cpp
|
gdbstub/gdbstub.cpp
|
||||||
hle/config_mem.cpp
|
hle/config_mem.cpp
|
||||||
hle/applets/applet.cpp
|
hle/applets/applet.cpp
|
||||||
@ -202,6 +203,7 @@ set(HEADERS
|
|||||||
file_sys/savedata_archive.h
|
file_sys/savedata_archive.h
|
||||||
frontend/emu_window.h
|
frontend/emu_window.h
|
||||||
frontend/key_map.h
|
frontend/key_map.h
|
||||||
|
frontend/motion_emu.h
|
||||||
gdbstub/gdbstub.h
|
gdbstub/gdbstub.h
|
||||||
hle/config_mem.h
|
hle/config_mem.h
|
||||||
hle/function_wrappers.h
|
hle/function_wrappers.h
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/profiler_reporting.h"
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
#include "core/frontend/key_map.h"
|
#include "core/frontend/key_map.h"
|
||||||
#include "video_core/video_core.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);
|
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) {
|
void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {
|
||||||
Layout::FramebufferLayout layout;
|
Layout::FramebufferLayout layout;
|
||||||
switch (Settings::values.layout_option) {
|
switch (Settings::values.layout_option) {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
@ -92,6 +93,27 @@ public:
|
|||||||
*/
|
*/
|
||||||
void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
|
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).
|
* 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 should be called by the core emu thread to get a state set by the window thread.
|
||||||
@ -134,12 +156,11 @@ public:
|
|||||||
* 1 unit of return value = 1/512 g (measured by hw test),
|
* 1 unit of return value = 1/512 g (measured by hw test),
|
||||||
* where g is the gravitational acceleration (9.8 m/sec2).
|
* 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.
|
* @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)
|
* @return std::tuple of (x, y, z)
|
||||||
*/
|
*/
|
||||||
std::tuple<s16, s16, s16> GetAccelerometerState() const {
|
std::tuple<s16, s16, s16> GetAccelerometerState() {
|
||||||
// stubbed
|
std::lock_guard<std::mutex> lock(accel_mutex);
|
||||||
return std::make_tuple(0, -512, 0);
|
return std::make_tuple(accel_x, accel_y, accel_z);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -153,12 +174,11 @@ public:
|
|||||||
* 1 unit of return value = (1/coef) deg/sec,
|
* 1 unit of return value = (1/coef) deg/sec,
|
||||||
* where coef is the return value of GetGyroscopeRawToDpsCoefficient().
|
* 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.
|
* @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)
|
* @return std::tuple of (x, y, z)
|
||||||
*/
|
*/
|
||||||
std::tuple<s16, s16, s16> GetGyroscopeState() const {
|
std::tuple<s16, s16, s16> GetGyroscopeState() {
|
||||||
// stubbed
|
std::lock_guard<std::mutex> lock(gyro_mutex);
|
||||||
return std::make_tuple(0, 0, 0);
|
return std::make_tuple(gyro_x, gyro_y, gyro_z);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -216,6 +236,12 @@ protected:
|
|||||||
circle_pad_x = 0;
|
circle_pad_x = 0;
|
||||||
circle_pad_y = 0;
|
circle_pad_y = 0;
|
||||||
touch_pressed = false;
|
touch_pressed = false;
|
||||||
|
accel_x = 0;
|
||||||
|
accel_y = -512;
|
||||||
|
accel_z = 0;
|
||||||
|
gyro_x = 0;
|
||||||
|
gyro_y = 0;
|
||||||
|
gyro_z = 0;
|
||||||
}
|
}
|
||||||
virtual ~EmuWindow() {}
|
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_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 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.
|
* Clip the provided coordinates to be inside the touchscreen area.
|
||||||
*/
|
*/
|
||||||
|
89
src/core/frontend/motion_emu.cpp
Normal file
89
src/core/frontend/motion_emu.cpp
Normal file
@ -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
|
52
src/core/frontend/motion_emu.h
Normal file
52
src/core/frontend/motion_emu.h
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user