InputCommon: Add "Dead Zone" setting to raw gyro inputs.
This commit is contained in:
parent
2d6a72e941
commit
82a3aa5ff6
|
@ -754,33 +754,34 @@ void AccelerometerMappingIndicator::paintEvent(QPaintEvent*)
|
|||
p.setBrush(Qt::NoBrush);
|
||||
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE);
|
||||
|
||||
// Red dot upright target.
|
||||
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
|
||||
p.drawEllipse(QPointF{0, SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
||||
// Red dot.
|
||||
const auto point = rotation * Common::Vec3{0, 0, SPHERE_INDICATOR_DIST};
|
||||
if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE)
|
||||
{
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(GetAdjustedInputColor());
|
||||
p.drawEllipse(QPointF(point.x, point.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||
}
|
||||
|
||||
// Blue dot target.
|
||||
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2));
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||
|
||||
// Blue dot.
|
||||
const auto point2 = -point;
|
||||
if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE)
|
||||
{
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(Qt::blue);
|
||||
p.drawEllipse(QPointF(point2.x, point2.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||
}
|
||||
|
||||
p.setBrush(Qt::NoBrush);
|
||||
|
||||
// Red dot upright target.
|
||||
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
|
||||
p.drawEllipse(QPointF{0, SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||
|
||||
// Blue dot target.
|
||||
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2));
|
||||
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||
|
||||
// Only draw g-force text if acceleration data is present.
|
||||
if (!accel_state.has_value())
|
||||
return;
|
||||
|
@ -802,6 +803,7 @@ GyroMappingIndicator::GyroMappingIndicator(ControllerEmu::IMUGyroscope* group)
|
|||
void GyroMappingIndicator::paintEvent(QPaintEvent*)
|
||||
{
|
||||
const auto gyro_state = m_gyro_group.GetState();
|
||||
const auto raw_gyro_state = m_gyro_group.GetRawState();
|
||||
const auto angular_velocity = gyro_state.value_or(Common::Vec3{});
|
||||
|
||||
m_state *= Common::Matrix33::FromQuaternion(angular_velocity.x / -INDICATOR_UPDATE_FREQ / 2,
|
||||
|
@ -810,8 +812,8 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*)
|
|||
|
||||
// Reset orientation when stable for a bit:
|
||||
constexpr u32 STABLE_RESET_STEPS = INDICATOR_UPDATE_FREQ;
|
||||
// This works well with my DS4 but a potentially noisy device might not behave.
|
||||
const bool is_stable = angular_velocity.Length() < MathUtil::TAU / 30;
|
||||
// Consider device stable when data (with deadzone applied) is zero.
|
||||
const bool is_stable = !angular_velocity.LengthSquared();
|
||||
|
||||
if (!is_stable)
|
||||
m_stable_steps = 0;
|
||||
|
@ -839,10 +841,39 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*)
|
|||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
|
||||
// Deadzone.
|
||||
if (const auto deadzone_value = m_gyro_group.GetDeadzone(); deadzone_value)
|
||||
{
|
||||
static constexpr auto DEADZONE_DRAW_SIZE = 1 - SPHERE_SIZE;
|
||||
static constexpr auto DEADZONE_DRAW_BOTTOM = 1;
|
||||
|
||||
p.setPen(GetDeadZonePen());
|
||||
p.setBrush(GetDeadZoneBrush());
|
||||
p.scale(-1.0, 1.0);
|
||||
p.drawRect(-scale, DEADZONE_DRAW_BOTTOM * scale, scale * 2, -scale * DEADZONE_DRAW_SIZE);
|
||||
p.scale(-1.0, 1.0);
|
||||
|
||||
if (gyro_state.has_value())
|
||||
{
|
||||
const auto max_velocity = std::max(
|
||||
{std::abs(raw_gyro_state.x), std::abs(raw_gyro_state.y), std::abs(raw_gyro_state.z)});
|
||||
const auto max_velocity_line_y =
|
||||
std::min(max_velocity / deadzone_value * DEADZONE_DRAW_SIZE - DEADZONE_DRAW_BOTTOM, 1.0);
|
||||
p.setPen(QPen(GetRawInputColor(), INPUT_DOT_RADIUS));
|
||||
p.drawLine(-scale, max_velocity_line_y * -scale, scale, max_velocity_line_y * -scale);
|
||||
|
||||
// Sphere background.
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(GetBBoxBrush());
|
||||
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
// Sphere dots.
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(GetRawInputColor());
|
||||
|
||||
GenerateFibonacciSphere(SPHERE_POINT_COUNT, [&, this](const Common::Vec3& point) {
|
||||
GenerateFibonacciSphere(SPHERE_POINT_COUNT, [&](const Common::Vec3& point) {
|
||||
const auto pt = rotation * point;
|
||||
|
||||
if (pt.y > 0)
|
||||
|
@ -850,49 +881,36 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*)
|
|||
});
|
||||
|
||||
// Sphere outline.
|
||||
p.setPen(GetRawInputColor());
|
||||
p.setPen(is_stable ? GetRawInputColor() : GetAdjustedInputColor());
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE);
|
||||
|
||||
// Red dot upright target.
|
||||
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
|
||||
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
||||
// Red dot.
|
||||
const auto point = rotation * Common::Vec3{0, 0, -SPHERE_INDICATOR_DIST};
|
||||
if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE)
|
||||
{
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(GetAdjustedInputColor());
|
||||
p.drawEllipse(QPointF(point.x, point.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||
}
|
||||
|
||||
// Blue dot target.
|
||||
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2));
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawEllipse(QPointF{}, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||
|
||||
// Blue dot.
|
||||
const auto point2 = rotation * Common::Vec3{0, SPHERE_INDICATOR_DIST, 0};
|
||||
if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE)
|
||||
{
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(Qt::blue);
|
||||
p.drawEllipse(QPointF(point2.x, point2.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||
}
|
||||
|
||||
// Only draw text if data is present.
|
||||
if (!gyro_state.has_value())
|
||||
return;
|
||||
p.setBrush(Qt::NoBrush);
|
||||
|
||||
// Angle of red dot from starting position.
|
||||
const auto angle = std::acos(point.Normalized().Dot({0, 0, -1}));
|
||||
// Red dot upright target.
|
||||
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
|
||||
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||
|
||||
// Angle text:
|
||||
p.setPen(GetTextColor());
|
||||
p.drawText(QRectF(-2, 0, scale, scale), Qt::AlignBottom | Qt::AlignRight,
|
||||
// i18n: "°" is the symbol for degrees (angular measurement).
|
||||
QString::fromStdString(fmt::format("{:.2f} °", angle / MathUtil::TAU * 360)));
|
||||
// Blue dot target.
|
||||
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2));
|
||||
p.drawEllipse(QPointF{}, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||
}
|
||||
|
||||
void MappingIndicator::DrawCalibration(QPainter& p, Common::DVec2 point)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <memory>
|
||||
|
||||
#include "Common/Common.h"
|
||||
#include "Common/MathUtil.h"
|
||||
|
||||
#include "InputCommon/ControlReference/ControlReference.h"
|
||||
#include "InputCommon/ControllerEmu/Control/Control.h"
|
||||
|
@ -23,6 +24,21 @@ IMUGyroscope::IMUGyroscope(std::string name, std::string ui_name)
|
|||
AddInput(Translate, _trans("Roll Right"));
|
||||
AddInput(Translate, _trans("Yaw Left"));
|
||||
AddInput(Translate, _trans("Yaw Right"));
|
||||
|
||||
AddSetting(&m_deadzone_setting,
|
||||
{_trans("Dead Zone"),
|
||||
// i18n: "°/s" is the symbol for degrees (angular measurement) divided by seconds.
|
||||
_trans("°/s"),
|
||||
// i18n: Refers to the dead-zone setting of gyroscope input.
|
||||
_trans("Angular velocity to ignore.")},
|
||||
1, 0, 180);
|
||||
}
|
||||
|
||||
auto IMUGyroscope::GetRawState() const -> StateData
|
||||
{
|
||||
return StateData(controls[1]->GetState() - controls[0]->GetState(),
|
||||
controls[2]->GetState() - controls[3]->GetState(),
|
||||
controls[4]->GetState() - controls[5]->GetState());
|
||||
}
|
||||
|
||||
std::optional<IMUGyroscope::StateData> IMUGyroscope::GetState() const
|
||||
|
@ -30,11 +46,18 @@ std::optional<IMUGyroscope::StateData> IMUGyroscope::GetState() const
|
|||
if (controls[0]->control_ref->BoundCount() == 0)
|
||||
return std::nullopt;
|
||||
|
||||
StateData state;
|
||||
state.x = (controls[1]->GetState() - controls[0]->GetState());
|
||||
state.y = (controls[2]->GetState() - controls[3]->GetState());
|
||||
state.z = (controls[4]->GetState() - controls[5]->GetState());
|
||||
auto state = GetRawState();
|
||||
|
||||
// Apply "deadzone".
|
||||
for (auto& c : state.data)
|
||||
c *= std::abs(c) > GetDeadzone();
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
ControlState IMUGyroscope::GetDeadzone() const
|
||||
{
|
||||
return m_deadzone_setting.GetValue() / 360 * MathUtil::TAU;
|
||||
}
|
||||
|
||||
} // namespace ControllerEmu
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "Common/Matrix.h"
|
||||
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
|
||||
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
|
||||
|
||||
namespace ControllerEmu
|
||||
{
|
||||
|
@ -19,6 +20,13 @@ public:
|
|||
|
||||
IMUGyroscope(std::string name, std::string ui_name);
|
||||
|
||||
StateData GetRawState() const;
|
||||
std::optional<StateData> GetState() const;
|
||||
|
||||
// Value is in rad/s.
|
||||
ControlState GetDeadzone() const;
|
||||
|
||||
private:
|
||||
SettingValue<double> m_deadzone_setting;
|
||||
};
|
||||
} // namespace ControllerEmu
|
||||
|
|
Loading…
Reference in New Issue