1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-26 09:35:28 +00:00

Merge branch 'support_controller_gyro' into 'master'

Support controller gyro

Closes #6557

See merge request OpenMW/openmw!1571
This commit is contained in:
psi29a 2022-01-25 19:20:36 +00:00
commit fac47ce0b5
11 changed files with 254 additions and 145 deletions

View File

@ -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

View File

@ -4,6 +4,8 @@
#include <MyGUI_InputManager.h>
#include <MyGUI_Widget.h>
#include <SDL.h>
#include <components/debug/debuglog.hpp>
#include <components/sdlutil/sdlmappings.hpp>
@ -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<float, 3> 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<float, 3>({gyro[0], gyro[1], gyro[2]});
}
}

View File

@ -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<float, 3> 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;

View File

@ -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<float, 3> 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<float, 3> 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;
}
}

View File

@ -0,0 +1,47 @@
#ifndef MWINPUT_GYROMANAGER
#define MWINPUT_GYROMANAGER
#include <components/settings/settings.hpp>
namespace MWInput
{
class GyroManager
{
public:
GyroManager();
bool isEnabled() const { return mEnabled; }
void update(float dt, std::array<float, 3> 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<float, 3> values) const;
};
}
#endif // !MWINPUT_GYROMANAGER

View File

@ -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)

View File

@ -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

View File

@ -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<float, 3> SensorManager::getGyroValues() const
{
return { mGyroValues.x(), mGyroValues.y(), mGyroValues.z() };
}
}

View File

@ -3,6 +3,9 @@
#include <SDL_sensor.h>
#include <osg/Matrixf>
#include <osg/Vec3f>
#include <components/settings/settings.hpp>
#include <components/sdlutil/events.hpp>
@ -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<float, 3> 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

View File

@ -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.

View File

@ -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