#include "sensormanager.hpp" #include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" 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) , 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(); } void SensorManager::init() { correctGyroscopeAxes(); updateSensors(); } SensorManager::~SensorManager() { if (mGyroscope != nullptr) { SDL_SensorClose(mGyroscope); mGyroscope = nullptr; } } 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")) return; // 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")); SDL_DisplayOrientation currentOrientation = SDL_GetDisplayOrientation(Settings::Manager::getInt("screen", "Video")); switch (currentOrientation) { case SDL_ORIENTATION_UNKNOWN: return; case SDL_ORIENTATION_LANDSCAPE: break; case SDL_ORIENTATION_LANDSCAPE_FLIPPED: { mGyroHAxis = GyroscopeAxis(-mGyroHAxis); mGyroVAxis = GyroscopeAxis(-mGyroVAxis); break; } case SDL_ORIENTATION_PORTRAIT: { GyroscopeAxis oldVAxis = mGyroVAxis; mGyroVAxis = mGyroHAxis; mGyroHAxis = GyroscopeAxis(-oldVAxis); break; } case SDL_ORIENTATION_PORTRAIT_FLIPPED: { GyroscopeAxis oldVAxis = mGyroVAxis; mGyroVAxis = GyroscopeAxis(-mGyroHAxis); mGyroHAxis = oldVAxis; break; } } } void SensorManager::updateSensors() { if (Settings::Manager::getBool("enable gyroscope", "Input")) { int numSensors = SDL_NumSensors(); for (int i = 0; i < numSensors; ++i) { if (SDL_SensorGetDeviceType(i) == SDL_SENSOR_GYRO) { // It is unclear how to handle several enabled gyroscopes, so use the first one. // Note: Android registers some gyroscope as two separate sensors, for non-wake-up mode and for wake-up mode. if (mGyroscope != nullptr) { SDL_SensorClose(mGyroscope); mGyroscope = nullptr; mGyroXSpeed = mGyroYSpeed = 0.f; mGyroUpdateTimer = 0.f; } // FIXME: SDL2 does not provide a way to configure a sensor update frequency so far. SDL_Sensor *sensor = SDL_SensorOpen(i); if (sensor == nullptr) Log(Debug::Error) << "Couldn't open sensor " << SDL_SensorGetDeviceName(i) << ": " << SDL_GetError(); else { mGyroscope = sensor; break; } } } } else { if (mGyroscope != nullptr) { SDL_SensorClose(mGyroscope); mGyroscope = nullptr; mGyroXSpeed = mGyroYSpeed = 0.f; mGyroUpdateTimer = 0.f; } } } void SensorManager::processChangedSettings(const Settings::CategorySettingVector& changed) { 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; } } void SensorManager::displayOrientationChanged() { correctGyroscopeAxes(); } void SensorManager::sensorUpdated(const SDL_SensorEvent &arg) { if (!Settings::Manager::getBool("enable gyroscope", "Input")) return; SDL_Sensor *sensor = SDL_SensorFromInstanceID(arg.which); if (!sensor) { Log(Debug::Info) << "Couldn't get sensor for sensor event"; return; } switch (SDL_SensorGetType(sensor)) { case SDL_SENSOR_ACCEL: break; case SDL_SENSOR_GYRO: { mGyroXSpeed = getGyroAxisSpeed(mGyroHAxis, arg); mGyroYSpeed = getGyroAxisSpeed(mGyroVAxis, arg); mGyroUpdateTimer = 0.f; break; } default: break; } } void SensorManager::update(float dt) { if (mGyroXSpeed == 0.f && mGyroYSpeed == 0.f) return; 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; 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(); } } }