diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index ea1d48885c..3c8a0a4d9a 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -27,7 +27,7 @@ add_openmw_dir (mwrender add_openmw_dir (mwinput actions actionmanager bindingsmanager controllermanager controlswitch - inputmanagerimp mousemanager keyboardmanager sensormanager + inputmanagerimp mousemanager keyboardmanager sensormanager gyromanager ) add_openmw_dir (mwgui diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 1e72a1c95b..6c6549face 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include @@ -32,6 +34,7 @@ namespace MWInput , mActionManager(actionManager) , mMouseManager(mouseManager) , mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input")) + , mGyroAvailable(false) , mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input")) , mSneakToggleShortcutTimer(0.f) , mGamepadGuiCursorEnabled(true) @@ -287,6 +290,7 @@ namespace MWInput void ControllerManager::controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) { mBindingsManager->controllerAdded(deviceID, arg); + enableGyroSensor(); } void ControllerManager::controllerRemoved(const SDL_ControllerDeviceEvent &arg) @@ -399,4 +403,34 @@ namespace MWInput return false; } + void ControllerManager::enableGyroSensor() + { + mGyroAvailable = false; + #if SDL_VERSION_ATLEAST(2, 0, 14) + SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); + if (!cntrl) + return; + if (!SDL_GameControllerHasSensor(cntrl, SDL_SENSOR_GYRO)) + return; + if (SDL_GameControllerSetSensorEnabled(cntrl, SDL_SENSOR_GYRO, SDL_TRUE) < 0) + return; + mGyroAvailable = true; + #endif + } + + bool ControllerManager::isGyroAvailable() const + { + return mGyroAvailable; + } + + std::array ControllerManager::getGyroValues() const + { + float gyro[3] = { 0.f }; + #if SDL_VERSION_ATLEAST(2, 0, 14) + SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); + if (cntrl && mGyroAvailable) + SDL_GameControllerGetSensorData(cntrl, SDL_SENSOR_GYRO, gyro, 3); + #endif + return std::array({gyro[0], gyro[1], gyro[2]}); + } } diff --git a/apps/openmw/mwinput/controllermanager.hpp b/apps/openmw/mwinput/controllermanager.hpp index 948b48d538..8df6ee5c9f 100644 --- a/apps/openmw/mwinput/controllermanager.hpp +++ b/apps/openmw/mwinput/controllermanager.hpp @@ -44,16 +44,22 @@ namespace MWInput float getAxisValue(SDL_GameControllerAxis axis) const; // returns value in range [-1, 1] bool isButtonPressed(SDL_GameControllerButton button) const; + bool isGyroAvailable() const; + std::array getGyroValues() const; + private: // Return true if GUI consumes input. bool gamepadToGuiControl(const SDL_ControllerButtonEvent &arg); bool gamepadToGuiControl(const SDL_ControllerAxisEvent &arg); + void enableGyroSensor(); + BindingsManager* mBindingsManager; ActionManager* mActionManager; MouseManager* mMouseManager; bool mJoystickEnabled; + bool mGyroAvailable; float mGamepadCursorSpeed; float mSneakToggleShortcutTimer; bool mGamepadGuiCursorEnabled; diff --git a/apps/openmw/mwinput/gyromanager.cpp b/apps/openmw/mwinput/gyromanager.cpp new file mode 100644 index 0000000000..b0c6f121c5 --- /dev/null +++ b/apps/openmw/mwinput/gyromanager.cpp @@ -0,0 +1,101 @@ +#include "gyromanager.hpp" + +#include "../mwbase/inputmanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/player.hpp" + +namespace MWInput +{ + GyroManager::GyroscopeAxis GyroManager::gyroscopeAxisFromString(std::string_view s) + { + if (s == "x") + return GyroscopeAxis::X; + else if (s == "y") + return GyroscopeAxis::Y; + else if (s == "z") + return GyroscopeAxis::Z; + else if (s == "-x") + return GyroscopeAxis::Minus_X; + else if (s == "-y") + return GyroscopeAxis::Minus_Y; + else if (s == "-z") + return GyroscopeAxis::Minus_Z; + + return GyroscopeAxis::Unknown; + } + + GyroManager::GyroManager() + : mEnabled(Settings::Manager::getBool("enable gyroscope", "Input")) + , mGuiCursorEnabled(true) + , mSensitivityH(Settings::Manager::getFloat("gyro horizontal sensitivity", "Input")) + , mSensitivityV(Settings::Manager::getFloat("gyro vertical sensitivity", "Input")) + , mInputThreshold(Settings::Manager::getFloat("gyro input threshold", "Input")) + , mAxisH(gyroscopeAxisFromString(Settings::Manager::getString("gyro horizontal axis", "Input"))) + , mAxisV(gyroscopeAxisFromString(Settings::Manager::getString("gyro vertical axis", "Input"))) + {} + + void GyroManager::update(float dt, std::array values) const + { + if (!mGuiCursorEnabled) + { + float gyroH = getAxisValue(mAxisH, values); + float gyroV = getAxisValue(mAxisV, values); + + if (gyroH == 0.f && gyroV == 0.f) + return; + + float rot[3]; + rot[0] = -gyroV * dt * mSensitivityV; + rot[1] = 0.0f; + rot[2] = -gyroH * dt * mSensitivityH; + + // Only actually turn player when we're not in vanity mode + bool playerLooking = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); + if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && playerLooking) + { + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + player.yaw(-rot[2]); + player.pitch(-rot[0]); + } + else if (!playerLooking) + MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); + + MWBase::Environment::get().getInputManager()->resetIdleTime(); + } + } + + void GyroManager::processChangedSettings(const Settings::CategorySettingVector& changed) + { + for (const auto& setting : changed) + { + if (setting.first != "Input") + continue; + + if (setting.second == "enable gyroscope") + mEnabled = Settings::Manager::getBool("enable gyroscope", "Input"); + else if (setting.second == "gyro horizontal sensitivity") + mSensitivityH = Settings::Manager::getFloat("gyro horizontal sensitivity", "Input"); + else if (setting.second == "gyro vertical sensitivity") + mSensitivityV = Settings::Manager::getFloat("gyro vertical sensitivity", "Input"); + else if (setting.second == "gyro input threshold") + mInputThreshold = Settings::Manager::getFloat("gyro input threshold", "Input"); + else if (setting.second == "gyro horizontal axis") + mAxisH = gyroscopeAxisFromString(Settings::Manager::getString("gyro horizontal axis", "Input")); + else if (setting.second == "gyro vertical axis") + mAxisV = gyroscopeAxisFromString(Settings::Manager::getString("gyro vertical axis", "Input")); + } + } + + float GyroManager::getAxisValue(GyroscopeAxis axis, std::array values) const + { + if (axis == GyroscopeAxis::Unknown) + return 0; + float value = values[std::abs(axis) - 1]; + if (axis < 0) + value *= -1; + if (std::abs(value) <= mInputThreshold) + value = 0; + return value; + } +} diff --git a/apps/openmw/mwinput/gyromanager.hpp b/apps/openmw/mwinput/gyromanager.hpp new file mode 100644 index 0000000000..bcd3b88f49 --- /dev/null +++ b/apps/openmw/mwinput/gyromanager.hpp @@ -0,0 +1,47 @@ +#ifndef MWINPUT_GYROMANAGER +#define MWINPUT_GYROMANAGER + +#include + +namespace MWInput +{ + class GyroManager + { + public: + GyroManager(); + + bool isEnabled() const { return mEnabled; } + + void update(float dt, std::array values) const; + + void processChangedSettings(const Settings::CategorySettingVector& changed); + + void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } + + private: + enum GyroscopeAxis + { + Unknown = 0, + X = 1, + Y = 2, + Z = 3, + Minus_X = -1, + Minus_Y = -2, + Minus_Z = -3 + }; + + static GyroscopeAxis gyroscopeAxisFromString(std::string_view s); + + bool mEnabled; + bool mGuiCursorEnabled; + float mSensitivityH; + float mSensitivityV; + float mInputThreshold; + GyroscopeAxis mAxisH; + GyroscopeAxis mAxisV; + + float getAxisValue(GyroscopeAxis axis, std::array values) const; + }; +} + +#endif // !MWINPUT_GYROMANAGER diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 4ebe56bf94..40de2b45fb 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -19,6 +19,7 @@ #include "keyboardmanager.hpp" #include "mousemanager.hpp" #include "sensormanager.hpp" +#include "gyromanager.hpp" namespace MWInput { @@ -51,6 +52,8 @@ namespace MWInput mSensorManager = new SensorManager(); mInputWrapper->setSensorEventCallback(mSensorManager); + + mGyroManager = new GyroManager(); } void InputManager::clear() @@ -72,6 +75,8 @@ namespace MWInput delete mBindingsManager; delete mInputWrapper; + + delete mGyroManager; } void InputManager::setAttemptJump(bool jumping) @@ -100,6 +105,17 @@ namespace MWInput mMouseManager->update(dt); mSensorManager->update(dt); mActionManager->update(dt, controllerMove); + + if (mGyroManager->isEnabled()) + { + bool controllerAvailable = mControllerManager->isGyroAvailable(); + bool sensorAvailable = mSensorManager->isGyroAvailable(); + if (controllerAvailable || sensorAvailable) + { + mGyroManager->update(dt, + controllerAvailable ? mControllerManager->getGyroValues() : mSensorManager->getGyroValues()); + } + } } void InputManager::setDragDrop(bool dragDrop) @@ -116,7 +132,7 @@ namespace MWInput { mControllerManager->setGuiCursorEnabled(guiMode); mMouseManager->setGuiCursorEnabled(guiMode); - mSensorManager->setGuiCursorEnabled(guiMode); + mGyroManager->setGuiCursorEnabled(guiMode); mMouseManager->setMouseLookEnabled(!guiMode); if (guiMode) MWBase::Environment::get().getWindowManager()->showCrosshair(false); @@ -130,6 +146,7 @@ namespace MWInput { mMouseManager->processChangedSettings(changed); mSensorManager->processChangedSettings(changed); + mGyroManager->processChangedSettings(changed); } bool InputManager::getControlSwitch(std::string_view sw) diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 41478d5dcb..3f9f1b3be1 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -39,6 +39,7 @@ namespace MWInput class KeyboardManager; class MouseManager; class SensorManager; + class GyroManager; /** * @brief Class that provides a high-level API for game input @@ -128,6 +129,7 @@ namespace MWInput KeyboardManager* mKeyboardManager; MouseManager* mMouseManager; SensorManager* mSensorManager; + GyroManager* mGyroManager; }; } #endif diff --git a/apps/openmw/mwinput/sensormanager.cpp b/apps/openmw/mwinput/sensormanager.cpp index 3e8e70aefe..f3cee579e5 100644 --- a/apps/openmw/mwinput/sensormanager.cpp +++ b/apps/openmw/mwinput/sensormanager.cpp @@ -11,18 +11,10 @@ namespace MWInput { SensorManager::SensorManager() - : mInvertX(Settings::Manager::getBool("invert x axis", "Input")) - , mInvertY(Settings::Manager::getBool("invert y axis", "Input")) - , mGyroXSpeed(0.f) - , mGyroYSpeed(0.f) + : mRotation() + , mGyroValues() , mGyroUpdateTimer(0.f) - , mGyroHSensitivity(Settings::Manager::getFloat("gyro horizontal sensitivity", "Input")) - , mGyroVSensitivity(Settings::Manager::getFloat("gyro vertical sensitivity", "Input")) - , mGyroHAxis(GyroscopeAxis::Minus_X) - , mGyroVAxis(GyroscopeAxis::Y) - , mGyroInputThreshold(Settings::Manager::getFloat("gyro input threshold", "Input")) , mGyroscope(nullptr) - , mGuiCursorEnabled(true) { init(); } @@ -42,24 +34,6 @@ namespace MWInput } } - SensorManager::GyroscopeAxis SensorManager::mapGyroscopeAxis(const std::string& axis) - { - if (axis == "x") - return GyroscopeAxis::X; - else if (axis == "y") - return GyroscopeAxis::Y; - else if (axis == "z") - return GyroscopeAxis::Z; - else if (axis == "-x") - return GyroscopeAxis::Minus_X; - else if (axis == "-y") - return GyroscopeAxis::Minus_Y; - else if (axis == "-z") - return GyroscopeAxis::Minus_Z; - - return GyroscopeAxis::Unknown; - } - void SensorManager::correctGyroscopeAxes() { if (!Settings::Manager::getBool("enable gyroscope", "Input")) @@ -68,40 +42,36 @@ namespace MWInput // Treat setting from config as axes for landscape mode. // If the device does not support orientation change, do nothing. // Note: in is unclear how to correct axes for devices with non-standart Z axis direction. - mGyroHAxis = mapGyroscopeAxis(Settings::Manager::getString("gyro horizontal axis", "Input")); - mGyroVAxis = mapGyroscopeAxis(Settings::Manager::getString("gyro vertical axis", "Input")); + + mRotation = osg::Matrixf::identity(); + + float angle = 0; SDL_DisplayOrientation currentOrientation = SDL_GetDisplayOrientation(Settings::Manager::getInt("screen", "Video")); switch (currentOrientation) { case SDL_ORIENTATION_UNKNOWN: - return; + break; case SDL_ORIENTATION_LANDSCAPE: break; case SDL_ORIENTATION_LANDSCAPE_FLIPPED: { - mGyroHAxis = GyroscopeAxis(-mGyroHAxis); - mGyroVAxis = GyroscopeAxis(-mGyroVAxis); - + angle = osg::PIf; break; } case SDL_ORIENTATION_PORTRAIT: { - GyroscopeAxis oldVAxis = mGyroVAxis; - mGyroVAxis = mGyroHAxis; - mGyroHAxis = GyroscopeAxis(-oldVAxis); - + angle = -0.5 * osg::PIf; break; } case SDL_ORIENTATION_PORTRAIT_FLIPPED: { - GyroscopeAxis oldVAxis = mGyroVAxis; - mGyroVAxis = GyroscopeAxis(-mGyroHAxis); - mGyroHAxis = oldVAxis; - + angle = 0.5 * osg::PIf; break; } } + + mRotation.makeRotate(angle, osg::Vec3f(0, 0, 1)); } void SensorManager::updateSensors() @@ -119,7 +89,6 @@ namespace MWInput { SDL_SensorClose(mGyroscope); mGyroscope = nullptr; - mGyroXSpeed = mGyroYSpeed = 0.f; mGyroUpdateTimer = 0.f; } @@ -141,7 +110,6 @@ namespace MWInput { SDL_SensorClose(mGyroscope); mGyroscope = nullptr; - mGyroXSpeed = mGyroYSpeed = 0.f; mGyroUpdateTimer = 0.f; } } @@ -151,46 +119,8 @@ namespace MWInput { for (const auto& setting : changed) { - if (setting.first == "Input" && setting.second == "invert x axis") - mInvertX = Settings::Manager::getBool("invert x axis", "Input"); - - if (setting.first == "Input" && setting.second == "invert y axis") - mInvertY = Settings::Manager::getBool("invert y axis", "Input"); - - if (setting.first == "Input" && setting.second == "gyro horizontal sensitivity") - mGyroHSensitivity = Settings::Manager::getFloat("gyro horizontal sensitivity", "Input"); - - if (setting.first == "Input" && setting.second == "gyro vertical sensitivity") - mGyroVSensitivity = Settings::Manager::getFloat("gyro vertical sensitivity", "Input"); - if (setting.first == "Input" && setting.second == "enable gyroscope") init(); - - if (setting.first == "Input" && setting.second == "gyro horizontal axis") - correctGyroscopeAxes(); - - if (setting.first == "Input" && setting.second == "gyro vertical axis") - correctGyroscopeAxes(); - - if (setting.first == "Input" && setting.second == "gyro input threshold") - mGyroInputThreshold = Settings::Manager::getFloat("gyro input threshold", "Input"); - } - } - - float SensorManager::getGyroAxisSpeed(GyroscopeAxis axis, const SDL_SensorEvent &arg) const - { - switch (axis) - { - case GyroscopeAxis::X: - case GyroscopeAxis::Y: - case GyroscopeAxis::Z: - return std::abs(arg.data[0]) >= mGyroInputThreshold ? arg.data[axis-1] : 0.f; - case GyroscopeAxis::Minus_X: - case GyroscopeAxis::Minus_Y: - case GyroscopeAxis::Minus_Z: - return std::abs(arg.data[0]) >= mGyroInputThreshold ? -arg.data[std::abs(axis)-1] : 0.f; - default: - return 0.f; } } @@ -217,10 +147,9 @@ namespace MWInput break; case SDL_SENSOR_GYRO: { - mGyroXSpeed = getGyroAxisSpeed(mGyroHAxis, arg); - mGyroYSpeed = getGyroAxisSpeed(mGyroVAxis, arg); + osg::Vec3f gyro(arg.data[0], arg.data[1], arg.data[2]); + mGyroValues = mRotation * gyro; mGyroUpdateTimer = 0.f; - break; } default: @@ -230,41 +159,24 @@ namespace MWInput void SensorManager::update(float dt) { - if (mGyroXSpeed == 0.f && mGyroYSpeed == 0.f) - return; - + mGyroUpdateTimer += dt; if (mGyroUpdateTimer > 0.5f) { // More than half of second passed since the last gyroscope update. // A device more likely was disconnected or switched to the sleep mode. // Reset current rotation speed and wait for update. - mGyroXSpeed = 0.f; - mGyroYSpeed = 0.f; + mGyroValues = osg::Vec3f(); mGyroUpdateTimer = 0.f; - return; - } - - mGyroUpdateTimer += dt; - - if (!mGuiCursorEnabled) - { - float rot[3]; - rot[0] = -mGyroYSpeed * dt * mGyroVSensitivity * 4 * (mInvertY ? -1 : 1); - rot[1] = 0.0f; - rot[2] = -mGyroXSpeed * dt * mGyroHSensitivity * 4 * (mInvertX ? -1 : 1); - - // Only actually turn player when we're not in vanity mode - bool playerLooking = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); - if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && playerLooking) - { - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - player.yaw(-rot[2]); - player.pitch(-rot[0]); - } - else if (!playerLooking) - MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); - - MWBase::Environment::get().getInputManager()->resetIdleTime(); } } + + bool SensorManager::isGyroAvailable() const + { + return mGyroscope != nullptr; + } + + std::array SensorManager::getGyroValues() const + { + return { mGyroValues.x(), mGyroValues.y(), mGyroValues.z() }; + } } diff --git a/apps/openmw/mwinput/sensormanager.hpp b/apps/openmw/mwinput/sensormanager.hpp index 75472d43b4..8f72b99de9 100644 --- a/apps/openmw/mwinput/sensormanager.hpp +++ b/apps/openmw/mwinput/sensormanager.hpp @@ -3,6 +3,9 @@ #include +#include +#include + #include #include @@ -33,41 +36,19 @@ namespace MWInput void displayOrientationChanged() override; void processChangedSettings(const Settings::CategorySettingVector& changed); - void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; } + bool isGyroAvailable() const; + std::array getGyroValues() const; private: - enum GyroscopeAxis - { - Unknown = 0, - X = 1, - Y = 2, - Z = 3, - Minus_X = -1, - Minus_Y = -2, - Minus_Z = -3 - }; void updateSensors(); void correctGyroscopeAxes(); - GyroscopeAxis mapGyroscopeAxis(const std::string& axis); - float getGyroAxisSpeed(GyroscopeAxis axis, const SDL_SensorEvent &arg) const; - bool mInvertX; - bool mInvertY; - - float mGyroXSpeed; - float mGyroYSpeed; + osg::Matrixf mRotation; + osg::Vec3f mGyroValues; float mGyroUpdateTimer; - float mGyroHSensitivity; - float mGyroVSensitivity; - GyroscopeAxis mGyroHAxis; - GyroscopeAxis mGyroVAxis; - float mGyroInputThreshold; - SDL_Sensor* mGyroscope; - - bool mGuiCursorEnabled; }; } #endif diff --git a/docs/source/reference/modding/settings/input.rst b/docs/source/reference/modding/settings/input.rst index d04c267a76..7aedf1bc21 100644 --- a/docs/source/reference/modding/settings/input.rst +++ b/docs/source/reference/modding/settings/input.rst @@ -158,6 +158,11 @@ Enable the support of camera rotation based on the information supplied from the This setting can only be configured by editing the settings configuration file. +Built-in (e. g. in a phone or tablet) and controller gyroscopes are supported. If both are present, controller gyroscope takes priority. + +Note: controller gyroscopes are only supported when OpenMW is built with SDL 2.0.14 or higher, +and were tested only on Windows. + gyro horizontal axis -------------------- @@ -190,8 +195,8 @@ gyro input threshold -------------------- :Type: floating point -:Range: > 0 -:Default: 0.01 +:Range: >=0 +:Default: 0.0 This setting determines the minimum value of the rotation that will be accepted. It allows to avoid crosshair oscillation due to gyroscope "noise". @@ -209,6 +214,8 @@ This setting controls the overall gyroscope horizontal sensitivity. The smaller this sensitivity is, the less visible effect the device rotation will have on the horizontal camera rotation, and vice versa. +Value of X means that rotating the device by 1 degree will cause the player to rotate by X degrees. + This setting can only be configured by editing the settings configuration file. gyro vertical sensitivity @@ -222,4 +229,6 @@ This setting controls the overall gyroscope vertical sensitivity. The smaller this sensitivity is, the less visible effect the device rotation will have on the vertical camera rotation, and vice versa. +Value of X means that rotating the device by 1 degree will cause the player to rotate by X degrees. + This setting can only be configured by editing the settings configuration file. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f1f3206661..cec1dc3432 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -540,7 +540,7 @@ gyro horizontal axis = -x gyro vertical axis = y # The minimum gyroscope movement that is able to rotate the camera. -gyro input threshold = 0.01 +gyro input threshold = 0 # Horizontal camera axis sensitivity to gyroscope movement. gyro horizontal sensitivity = 1.0