Virtual Notch settings and UI for octagonal stick
This commit is contained in:
parent
ab8a128588
commit
55dd3d7337
|
@ -169,6 +169,45 @@ QPolygonF GetPolygonFromRadiusGetter(F&& radius_getter)
|
||||||
return shape;
|
return shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constructs a polygon by querying a radius at varying angles:
|
||||||
|
template <typename F>
|
||||||
|
QPolygonF GetPolygonSegmentFromRadiusGetter(F&& radius_getter, double direction,
|
||||||
|
double segment_size, double segment_depth)
|
||||||
|
{
|
||||||
|
constexpr int shape_point_count = 6;
|
||||||
|
QPolygonF shape{shape_point_count};
|
||||||
|
|
||||||
|
// We subtract from the provided direction angle so it's better
|
||||||
|
// to add Tau here to prevent a negative value instead of
|
||||||
|
// expecting the function call to be aware of this internal logic
|
||||||
|
const double center_angle = direction + MathUtil::TAU;
|
||||||
|
const double center_radius_outer = radius_getter(center_angle);
|
||||||
|
const double center_radius_inner = center_radius_outer - segment_depth;
|
||||||
|
|
||||||
|
const double lower_angle = center_angle - segment_size / 2;
|
||||||
|
const double lower_radius_outer = radius_getter(lower_angle);
|
||||||
|
const double lower_radius_inner = lower_radius_outer - segment_depth;
|
||||||
|
|
||||||
|
const double upper_angle = center_angle + segment_size / 2;
|
||||||
|
const double upper_radius_outer = radius_getter(upper_angle);
|
||||||
|
const double upper_radius_inner = upper_radius_outer - segment_depth;
|
||||||
|
|
||||||
|
shape[0] = {std::cos(lower_angle) * (lower_radius_inner),
|
||||||
|
std::sin(lower_angle) * (lower_radius_inner)};
|
||||||
|
shape[1] = {std::cos(center_angle) * (center_radius_inner),
|
||||||
|
std::sin(center_angle) * (center_radius_inner)};
|
||||||
|
shape[2] = {std::cos(upper_angle) * (upper_radius_inner),
|
||||||
|
std::sin(upper_angle) * (upper_radius_inner)};
|
||||||
|
shape[3] = {std::cos(upper_angle) * upper_radius_outer,
|
||||||
|
std::sin(upper_angle) * upper_radius_outer};
|
||||||
|
shape[4] = {std::cos(center_angle) * center_radius_outer,
|
||||||
|
std::sin(center_angle) * center_radius_outer};
|
||||||
|
shape[5] = {std::cos(lower_angle) * lower_radius_outer,
|
||||||
|
std::sin(lower_angle) * lower_radius_outer};
|
||||||
|
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
|
||||||
// Used to check if the user seems to have attempted proper calibration.
|
// Used to check if the user seems to have attempted proper calibration.
|
||||||
bool IsCalibrationDataSensible(const ControllerEmu::ReshapableInput::CalibrationData& data)
|
bool IsCalibrationDataSensible(const ControllerEmu::ReshapableInput::CalibrationData& data)
|
||||||
{
|
{
|
||||||
|
@ -210,6 +249,24 @@ bool IsPointOutsideCalibration(Common::DVec2 point, ControllerEmu::ReshapableInp
|
||||||
return current_radius > input_radius * ALLOWED_ERROR;
|
return current_radius > input_radius * ALLOWED_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DrawVirtualNotches(QPainter& p, ControllerEmu::ReshapableInput& stick, QColor notch_color)
|
||||||
|
{
|
||||||
|
const double segment_size = stick.GetVirtualNotchSize();
|
||||||
|
if (segment_size <= 0.0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
p.setBrush(notch_color);
|
||||||
|
for (int i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
const double segment_depth = 1.0 - ControllerEmu::MINIMUM_NOTCH_DISTANCE;
|
||||||
|
const double segment_gap = MathUtil::TAU / 8.0;
|
||||||
|
const double direction = segment_gap * i;
|
||||||
|
p.drawPolygon(GetPolygonSegmentFromRadiusGetter(
|
||||||
|
[&stick](double ang) { return stick.GetGateRadiusAtAngle(ang); }, direction, segment_size,
|
||||||
|
segment_depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <typename F>
|
template <typename F>
|
||||||
void GenerateFibonacciSphere(int point_count, F&& callback)
|
void GenerateFibonacciSphere(int point_count, F&& callback)
|
||||||
{
|
{
|
||||||
|
@ -301,6 +358,8 @@ void ReshapableInputIndicator::DrawReshapableInput(
|
||||||
p.drawPolygon(
|
p.drawPolygon(
|
||||||
GetPolygonFromRadiusGetter([&stick](double ang) { return stick.GetGateRadiusAtAngle(ang); }));
|
GetPolygonFromRadiusGetter([&stick](double ang) { return stick.GetGateRadiusAtAngle(ang); }));
|
||||||
|
|
||||||
|
DrawVirtualNotches(p, stick, gate_pen_color);
|
||||||
|
|
||||||
const auto center = stick.GetCenter();
|
const auto center = stick.GetCenter();
|
||||||
|
|
||||||
p.save();
|
p.save();
|
||||||
|
|
|
@ -65,6 +65,12 @@ OctagonAnalogStick::OctagonAnalogStick(const char* name_, const char* ui_name_,
|
||||||
ControlState gate_radius)
|
ControlState gate_radius)
|
||||||
: AnalogStick(name_, ui_name_, std::make_unique<ControllerEmu::OctagonStickGate>(gate_radius))
|
: AnalogStick(name_, ui_name_, std::make_unique<ControllerEmu::OctagonStickGate>(gate_radius))
|
||||||
{
|
{
|
||||||
|
AddVirtualNotchSetting(&m_virtual_notch_setting, 45);
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlState OctagonAnalogStick::GetVirtualNotchSize() const
|
||||||
|
{
|
||||||
|
return m_virtual_notch_setting.GetValue() * MathUtil::TAU / 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ControllerEmu
|
} // namespace ControllerEmu
|
||||||
|
|
|
@ -33,6 +33,11 @@ class OctagonAnalogStick : public AnalogStick
|
||||||
public:
|
public:
|
||||||
OctagonAnalogStick(const char* name, ControlState gate_radius);
|
OctagonAnalogStick(const char* name, ControlState gate_radius);
|
||||||
OctagonAnalogStick(const char* name, const char* ui_name, ControlState gate_radius);
|
OctagonAnalogStick(const char* name, const char* ui_name, ControlState gate_radius);
|
||||||
|
|
||||||
|
ControlState GetVirtualNotchSize() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SettingValue<double> m_virtual_notch_setting;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ControllerEmu
|
} // namespace ControllerEmu
|
||||||
|
|
|
@ -28,6 +28,17 @@ ControlGroup::ControlGroup(std::string name_, std::string ui_name_, const GroupT
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ControlGroup::AddVirtualNotchSetting(SettingValue<double>* value, double max_virtual_notch_deg)
|
||||||
|
{
|
||||||
|
AddSetting(value,
|
||||||
|
{_trans("Virtual Notches"),
|
||||||
|
// i18n: The degrees symbol.
|
||||||
|
_trans("°"),
|
||||||
|
// i18n: Snap the thumbstick position to the nearest octagonal axis.
|
||||||
|
_trans("Snap the thumbstick position to the nearest octagonal axis.")},
|
||||||
|
0, 0, max_virtual_notch_deg);
|
||||||
|
}
|
||||||
|
|
||||||
void ControlGroup::AddDeadzoneSetting(SettingValue<double>* value, double maximum_deadzone)
|
void ControlGroup::AddDeadzoneSetting(SettingValue<double>* value, double maximum_deadzone)
|
||||||
{
|
{
|
||||||
AddSetting(value,
|
AddSetting(value,
|
||||||
|
|
|
@ -82,6 +82,8 @@ public:
|
||||||
std::make_unique<NumericSetting<T>>(value, details, default_value_, min_value, max_value));
|
std::make_unique<NumericSetting<T>>(value, details, default_value_, min_value, max_value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AddVirtualNotchSetting(SettingValue<double>* value, double max_virtual_notch_deg);
|
||||||
|
|
||||||
void AddDeadzoneSetting(SettingValue<double>* value, double maximum_deadzone);
|
void AddDeadzoneSetting(SettingValue<double>* value, double maximum_deadzone);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
|
@ -48,6 +48,16 @@ std::optional<double> GetRayLineIntersection(Common::DVec2 ray, Common::DVec2 po
|
||||||
return diff.Cross(-point1) / dot;
|
return diff.Cross(-point1) / dot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double GetNearestNotch(double angle, double virtual_notch_angle)
|
||||||
|
{
|
||||||
|
constexpr auto sides = 8;
|
||||||
|
constexpr auto rounding = MathUtil::TAU / sides;
|
||||||
|
const auto closest_notch = std::round(angle / rounding) * rounding;
|
||||||
|
const auto angle_diff =
|
||||||
|
std::fmod(angle - closest_notch + MathUtil::PI, MathUtil::TAU) - MathUtil::PI;
|
||||||
|
return std::abs(angle_diff) < virtual_notch_angle / 2 ? closest_notch : angle;
|
||||||
|
}
|
||||||
|
|
||||||
Common::DVec2 GetPointFromAngleAndLength(double angle, double length)
|
Common::DVec2 GetPointFromAngleAndLength(double angle, double length)
|
||||||
{
|
{
|
||||||
return Common::DVec2{std::cos(angle), std::sin(angle)} * length;
|
return Common::DVec2{std::cos(angle), std::sin(angle)} * length;
|
||||||
|
@ -276,16 +286,24 @@ ReshapableInput::ReshapeData ReshapableInput::Reshape(ControlState x, ControlSta
|
||||||
y -= m_center.y;
|
y -= m_center.y;
|
||||||
|
|
||||||
// TODO: make the AtAngle functions work with negative angles:
|
// TODO: make the AtAngle functions work with negative angles:
|
||||||
const ControlState angle = std::atan2(y, x) + MathUtil::TAU;
|
ControlState angle = std::atan2(y, x) + MathUtil::TAU;
|
||||||
|
|
||||||
const ControlState gate_max_dist = GetGateRadiusAtAngle(angle);
|
|
||||||
const ControlState input_max_dist = GetInputRadiusAtAngle(angle);
|
const ControlState input_max_dist = GetInputRadiusAtAngle(angle);
|
||||||
|
ControlState gate_max_dist = GetGateRadiusAtAngle(angle);
|
||||||
|
|
||||||
// If input radius (from calibration) is zero apply no scaling to prevent division by zero.
|
// If input radius (from calibration) is zero apply no scaling to prevent division by zero.
|
||||||
const ControlState max_dist = input_max_dist ? input_max_dist : gate_max_dist;
|
const ControlState max_dist = input_max_dist ? input_max_dist : gate_max_dist;
|
||||||
|
|
||||||
ControlState dist = Common::DVec2{x, y}.Length() / max_dist;
|
ControlState dist = Common::DVec2{x, y}.Length() / max_dist;
|
||||||
|
|
||||||
|
const double virtual_notch_size = GetVirtualNotchSize();
|
||||||
|
|
||||||
|
if (virtual_notch_size > 0.0 && dist >= MINIMUM_NOTCH_DISTANCE)
|
||||||
|
{
|
||||||
|
angle = GetNearestNotch(angle, virtual_notch_size);
|
||||||
|
gate_max_dist = GetGateRadiusAtAngle(angle);
|
||||||
|
}
|
||||||
|
|
||||||
// If the modifier is pressed, scale the distance by the modifier's value.
|
// If the modifier is pressed, scale the distance by the modifier's value.
|
||||||
// This is affected by the modifier's "range" setting which defaults to 50%.
|
// This is affected by the modifier's "range" setting which defaults to 50%.
|
||||||
if (modifier)
|
if (modifier)
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
|
|
||||||
namespace ControllerEmu
|
namespace ControllerEmu
|
||||||
{
|
{
|
||||||
|
// Minimum stick distance from the center before virtual notches are applied.
|
||||||
|
constexpr ControlState MINIMUM_NOTCH_DISTANCE = 0.9;
|
||||||
|
|
||||||
// An abstract class representing the plastic shell that limits an analog stick's movement.
|
// An abstract class representing the plastic shell that limits an analog stick's movement.
|
||||||
class StickGate
|
class StickGate
|
||||||
{
|
{
|
||||||
|
@ -85,6 +88,8 @@ public:
|
||||||
|
|
||||||
ControlState GetDeadzonePercentage() const;
|
ControlState GetDeadzonePercentage() const;
|
||||||
|
|
||||||
|
virtual ControlState GetVirtualNotchSize() const { return 0.0; };
|
||||||
|
|
||||||
virtual ControlState GetGateRadiusAtAngle(double angle) const = 0;
|
virtual ControlState GetGateRadiusAtAngle(double angle) const = 0;
|
||||||
virtual ReshapeData GetReshapableState(bool adjusted) = 0;
|
virtual ReshapeData GetReshapableState(bool adjusted) = 0;
|
||||||
virtual ControlState GetDefaultInputRadiusAtAngle(double ang) const;
|
virtual ControlState GetDefaultInputRadiusAtAngle(double ang) const;
|
||||||
|
|
Loading…
Reference in New Issue