summaryrefslogtreecommitdiff
path: root/Source/Core/InputCommon/ControllerEmu/ControlGroup
diff options
context:
space:
mode:
authorJordan Woyak <jordan.woyak@gmail.com>2020-02-11 17:30:16 -0600
committerJordan Woyak <jordan.woyak@gmail.com>2020-02-17 15:57:43 -0600
commit1e652d7d3434a612118a276de6ff7dcec16bd97b (patch)
treeac9a940659f17c3645de6678a7c84106ba3e562b /Source/Core/InputCommon/ControllerEmu/ControlGroup
parent9efcd08ea355dc5b37e90e6fcb7c43029a5232ef (diff)
InputCommon: Add calibration functionality to IMUGyroscope.
Diffstat (limited to 'Source/Core/InputCommon/ControllerEmu/ControlGroup')
-rw-r--r--Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp101
-rw-r--r--Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h14
2 files changed, 114 insertions, 1 deletions
diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp
index 6109f20b8c..8837c67f13 100644
--- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp
+++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.cpp
@@ -15,6 +15,15 @@
namespace ControllerEmu
{
+// Maximum period for calculating an average stable value.
+// Just to prevent failures due to timer overflow.
+static constexpr auto MAXIMUM_CALIBRATION_DURATION = std::chrono::hours(1);
+
+// If calibration updates do not happen at this rate, restart calibration period.
+// This prevents calibration across periods of no regular updates. (e.g. between game sessions)
+// This is made slightly lower than the UI update frequency of 30.
+static constexpr auto WORST_ACCEPTABLE_CALIBRATION_UPDATE_FREQUENCY = 25;
+
IMUGyroscope::IMUGyroscope(std::string name, std::string ui_name)
: ControlGroup(std::move(name), std::move(ui_name), GroupType::IMUGyroscope)
{
@@ -31,7 +40,79 @@ IMUGyroscope::IMUGyroscope(std::string name, std::string ui_name)
_trans("°/s"),
// i18n: Refers to the dead-zone setting of gyroscope input.
_trans("Angular velocity to ignore.")},
- 1, 0, 180);
+ 2, 0, 180);
+
+ AddSetting(&m_calibration_period_setting,
+ {_trans("Calibration Period"),
+ // i18n: "s" is the symbol for seconds.
+ _trans("s"),
+ // i18n: Refers to the "Calibration" setting of gyroscope input.
+ _trans("Time period of stable input to trigger calibration. (zero to disable)")},
+ 3, 0, 30);
+}
+
+void IMUGyroscope::RestartCalibration() const
+{
+ m_calibration_period_start = Clock::now();
+ m_running_calibration.Clear();
+}
+
+void IMUGyroscope::UpdateCalibration(const StateData& state) const
+{
+ const auto now = Clock::now();
+ const auto calibration_period = m_calibration_period_setting.GetValue();
+
+ // If calibration time is zero. User is choosing to not calibrate.
+ if (!calibration_period)
+ {
+ // Set calibration to zero.
+ m_calibration = {};
+ RestartCalibration();
+ return;
+ }
+
+ // If there is no running calibration a new gyro was just mapped or calibration was just enabled,
+ // apply the current state as calibration, it's often better than zeros.
+ if (!m_running_calibration.Count())
+ {
+ m_calibration = state;
+ }
+ else
+ {
+ const auto calibration_freq =
+ m_running_calibration.Count() /
+ std::chrono::duration_cast<std::chrono::duration<double>>(now - m_calibration_period_start)
+ .count();
+
+ const auto potential_calibration = m_running_calibration.Mean();
+ const auto current_difference = state - potential_calibration;
+ const auto deadzone = GetDeadzone();
+
+ // Check for required calibration update frequency
+ // and if current data is within deadzone distance of mean stable value.
+ if (calibration_freq < WORST_ACCEPTABLE_CALIBRATION_UPDATE_FREQUENCY ||
+ std::any_of(current_difference.data.begin(), current_difference.data.end(),
+ [&](auto c) { return std::abs(c) > deadzone; }))
+ {
+ RestartCalibration();
+ }
+ }
+
+ // Update running mean stable value.
+ m_running_calibration.Push(state);
+
+ // Apply calibration after configured time.
+ const auto calibration_duration = now - m_calibration_period_start;
+ if (calibration_duration >= std::chrono::duration<double>(calibration_period))
+ {
+ m_calibration = m_running_calibration.Mean();
+
+ if (calibration_duration >= MAXIMUM_CALIBRATION_DURATION)
+ {
+ RestartCalibration();
+ m_running_calibration.Push(m_calibration);
+ }
+ }
}
auto IMUGyroscope::GetRawState() const -> StateData
@@ -44,10 +125,21 @@ auto IMUGyroscope::GetRawState() const -> StateData
std::optional<IMUGyroscope::StateData> IMUGyroscope::GetState() const
{
if (controls[0]->control_ref->BoundCount() == 0)
+ {
+ // Set calibration to zero.
+ m_calibration = {};
+ RestartCalibration();
return std::nullopt;
+ }
auto state = GetRawState();
+ // If the input gate is disabled, miscalibration to zero values would occur.
+ if (ControlReference::GetInputGate())
+ UpdateCalibration(state);
+
+ state -= m_calibration;
+
// Apply "deadzone".
for (auto& c : state.data)
c *= std::abs(c) > GetDeadzone();
@@ -60,4 +152,11 @@ ControlState IMUGyroscope::GetDeadzone() const
return m_deadzone_setting.GetValue() / 360 * MathUtil::TAU;
}
+bool IMUGyroscope::IsCalibrating() const
+{
+ const auto calibration_period = m_calibration_period_setting.GetValue();
+ return calibration_period && (Clock::now() - m_calibration_period_start) >=
+ std::chrono::duration<double>(calibration_period);
+}
+
} // namespace ControllerEmu
diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h
index 1258a3eb76..459f885882 100644
--- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h
+++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h
@@ -4,9 +4,11 @@
#pragma once
+#include <chrono>
#include <optional>
#include <string>
+#include "Common/MathUtil.h"
#include "Common/Matrix.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
@@ -26,7 +28,19 @@ public:
// Value is in rad/s.
ControlState GetDeadzone() const;
+ bool IsCalibrating() const;
+
private:
+ using Clock = std::chrono::steady_clock;
+
+ void RestartCalibration() const;
+ void UpdateCalibration(const StateData&) const;
+
SettingValue<double> m_deadzone_setting;
+ SettingValue<double> m_calibration_period_setting;
+
+ mutable StateData m_calibration = {};
+ mutable MathUtil::RunningMean<StateData> m_running_calibration;
+ mutable Clock::time_point m_calibration_period_start = Clock::now();
};
} // namespace ControllerEmu