input: new saturation setting for full analog axes. Gamepad settings UI
Saturation reduces or increases the range of analog axes. Issue #675 Axes values are re-scaled with dead zone to allow small values. New Gamepad Settings dialog box to set up dead zone, saturation and rumble/haptic power.
This commit is contained in:
parent
d7c28a4805
commit
4aecdbbb25
|
@ -179,6 +179,26 @@ bool GamepadDevice::gamepad_btn_input(u32 code, bool pressed)
|
|||
return rc;
|
||||
}
|
||||
|
||||
static DreamcastKey getOppositeAxis(DreamcastKey key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case DC_AXIS_RIGHT: return DC_AXIS_LEFT;
|
||||
case DC_AXIS_LEFT: return DC_AXIS_RIGHT;
|
||||
case DC_AXIS_UP: return DC_AXIS_DOWN;
|
||||
case DC_AXIS_DOWN: return DC_AXIS_UP;
|
||||
case DC_AXIS2_RIGHT: return DC_AXIS2_LEFT;
|
||||
case DC_AXIS2_LEFT: return DC_AXIS2_RIGHT;
|
||||
case DC_AXIS2_UP: return DC_AXIS2_DOWN;
|
||||
case DC_AXIS2_DOWN: return DC_AXIS2_UP;
|
||||
case DC_AXIS3_RIGHT: return DC_AXIS3_LEFT;
|
||||
case DC_AXIS3_LEFT: return DC_AXIS3_RIGHT;
|
||||
case DC_AXIS3_UP: return DC_AXIS3_DOWN;
|
||||
case DC_AXIS3_DOWN: return DC_AXIS3_UP;
|
||||
default: return key;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// value must be >= -32768 and <= 32767 for full axes
|
||||
// and 0 to 32767 for half axes/triggers
|
||||
|
@ -216,7 +236,7 @@ bool GamepadDevice::gamepad_axis_input(u32 code, int value)
|
|||
{
|
||||
//printf("AXIS %d Mapped to %d -> %d\n", key, value, v);
|
||||
s16 *this_axis;
|
||||
s16 *other_axis;
|
||||
int otherAxisValue;
|
||||
int axisDirection = -1;
|
||||
switch (key)
|
||||
{
|
||||
|
@ -225,7 +245,7 @@ bool GamepadDevice::gamepad_axis_input(u32 code, int value)
|
|||
[[fallthrough]];
|
||||
case DC_AXIS_LEFT:
|
||||
this_axis = &joyx[port];
|
||||
other_axis = &joyy[port];
|
||||
otherAxisValue = lastAxisValue[port][DC_AXIS_UP];
|
||||
break;
|
||||
|
||||
case DC_AXIS_DOWN:
|
||||
|
@ -233,7 +253,7 @@ bool GamepadDevice::gamepad_axis_input(u32 code, int value)
|
|||
[[fallthrough]];
|
||||
case DC_AXIS_UP:
|
||||
this_axis = &joyy[port];
|
||||
other_axis = &joyx[port];
|
||||
otherAxisValue = lastAxisValue[port][DC_AXIS_LEFT];
|
||||
break;
|
||||
|
||||
case DC_AXIS2_RIGHT:
|
||||
|
@ -241,7 +261,7 @@ bool GamepadDevice::gamepad_axis_input(u32 code, int value)
|
|||
[[fallthrough]];
|
||||
case DC_AXIS2_LEFT:
|
||||
this_axis = &joyrx[port];
|
||||
other_axis = &joyry[port];
|
||||
otherAxisValue = lastAxisValue[port][DC_AXIS2_UP];
|
||||
break;
|
||||
|
||||
case DC_AXIS2_DOWN:
|
||||
|
@ -249,7 +269,7 @@ bool GamepadDevice::gamepad_axis_input(u32 code, int value)
|
|||
[[fallthrough]];
|
||||
case DC_AXIS2_UP:
|
||||
this_axis = &joyry[port];
|
||||
other_axis = &joyrx[port];
|
||||
otherAxisValue = lastAxisValue[port][DC_AXIS2_LEFT];
|
||||
break;
|
||||
|
||||
case DC_AXIS3_RIGHT:
|
||||
|
@ -257,7 +277,7 @@ bool GamepadDevice::gamepad_axis_input(u32 code, int value)
|
|||
[[fallthrough]];
|
||||
case DC_AXIS3_LEFT:
|
||||
this_axis = &joy3x[port];
|
||||
other_axis = &joy3y[port];
|
||||
otherAxisValue = lastAxisValue[port][DC_AXIS3_UP];
|
||||
break;
|
||||
|
||||
case DC_AXIS3_DOWN:
|
||||
|
@ -265,17 +285,18 @@ bool GamepadDevice::gamepad_axis_input(u32 code, int value)
|
|||
[[fallthrough]];
|
||||
case DC_AXIS3_UP:
|
||||
this_axis = &joy3y[port];
|
||||
other_axis = &joy3x[port];
|
||||
otherAxisValue = lastAxisValue[port][DC_AXIS3_LEFT];
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
// Lightgun with left analog stick
|
||||
int& lastValue = lastAxisValue[port][key];
|
||||
if (lastValue != v)
|
||||
int& lastOpValue = lastAxisValue[port][getOppositeAxis(key)];
|
||||
if (lastValue != v || lastOpValue != v)
|
||||
{
|
||||
lastValue = v;
|
||||
lastValue = lastOpValue = v;
|
||||
// Lightgun with left analog stick
|
||||
if (key == DC_AXIS_RIGHT || key == DC_AXIS_LEFT)
|
||||
mo_x_abs[port] = (std::abs(v) * axisDirection + 32768) * 639 / 65535;
|
||||
else if (key == DC_AXIS_UP || key == DC_AXIS_DOWN)
|
||||
|
@ -283,14 +304,19 @@ bool GamepadDevice::gamepad_axis_input(u32 code, int value)
|
|||
}
|
||||
// Radial dead zone
|
||||
// FIXME compute both axes at the same time
|
||||
v = std::min(32767, std::abs(v));
|
||||
if ((float)(v * v + *other_axis * *other_axis) < input_mapper->dead_zone * input_mapper->dead_zone * 32768.f * 32768.f)
|
||||
const float nv = std::abs(v) / 32768.f;
|
||||
const float r2 = nv * nv + otherAxisValue * otherAxisValue / 32768.f / 32768.f;
|
||||
if (r2 < input_mapper->dead_zone * input_mapper->dead_zone || r2 == 0.f)
|
||||
{
|
||||
*this_axis = 0;
|
||||
*other_axis = 0;
|
||||
}
|
||||
else
|
||||
*this_axis = v * axisDirection;
|
||||
{
|
||||
float pdz = nv * input_mapper->dead_zone / std::sqrt(r2);
|
||||
// there's a dead angular zone at 45° with saturation > 1 (both axes are saturated)
|
||||
v = std::round((nv - pdz) / (1 - pdz) * 32768.f * input_mapper->saturation);
|
||||
*this_axis = std::clamp(v * axisDirection, -32768, 32767);
|
||||
}
|
||||
}
|
||||
else if (key != EMU_BTN_NONE && key <= DC_BTN_BITMAPPED_LAST) // Map triggers to digital buttons
|
||||
{
|
||||
|
|
|
@ -80,6 +80,16 @@ public:
|
|||
save_mapping();
|
||||
}
|
||||
}
|
||||
float get_saturation() const { return input_mapper->saturation; }
|
||||
void set_saturation(float saturation)
|
||||
{
|
||||
if (saturation != input_mapper->saturation)
|
||||
{
|
||||
input_mapper->saturation = saturation;
|
||||
input_mapper->set_dirty();
|
||||
save_mapping();
|
||||
}
|
||||
}
|
||||
|
||||
static void Register(const std::shared_ptr<GamepadDevice>& gamepad);
|
||||
|
||||
|
|
|
@ -196,6 +196,8 @@ void InputMapping::load(FILE* fp)
|
|||
dz = std::min(dz, 100);
|
||||
dz = std::max(dz, 0);
|
||||
this->dead_zone = (float)dz / 100.f;
|
||||
int sat = std::clamp(mf.get_int("emulator", "saturation", 100), 50, 200);
|
||||
this->saturation = (float)sat / 100.f;
|
||||
this->rumblePower = mf.get_int("emulator", "rumble_power", this->rumblePower);
|
||||
|
||||
version = mf.get_int("emulator", "version", 1);
|
||||
|
@ -428,6 +430,7 @@ bool InputMapping::save(const std::string& name)
|
|||
|
||||
mf.set("emulator", "mapping_name", this->name);
|
||||
mf.set_int("emulator", "dead_zone", (int)std::round(this->dead_zone * 100.f));
|
||||
mf.set_int("emulator", "saturation", (int)std::round(this->saturation * 100.f));
|
||||
mf.set_int("emulator", "rumble_power", this->rumblePower);
|
||||
mf.set_int("emulator", "version", 3);
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ public:
|
|||
InputMapping(const InputMapping& other) {
|
||||
name = other.name;
|
||||
dead_zone = other.dead_zone;
|
||||
saturation = other.saturation;
|
||||
for (int port = 0; port < 4; port++)
|
||||
{
|
||||
buttons[port] = other.buttons[port];
|
||||
|
@ -44,6 +45,7 @@ public:
|
|||
|
||||
std::string name;
|
||||
float dead_zone = 0.1f;
|
||||
float saturation = 1.0f;
|
||||
int rumblePower = 100;
|
||||
int version = 3;
|
||||
|
||||
|
|
|
@ -1257,6 +1257,61 @@ static void controller_mapping_popup(const std::shared_ptr<GamepadDevice>& gamep
|
|||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
static void gamepadSettingsPopup(const std::shared_ptr<GamepadDevice>& gamepad)
|
||||
{
|
||||
centerNextWindow();
|
||||
ImGui::SetNextWindowSize(min(ImGui::GetIO().DisplaySize, ScaledVec2(450.f, 300.f)));
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
|
||||
if (ImGui::BeginPopupModal("Gamepad Settings", NULL, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove))
|
||||
{
|
||||
if (ImGui::Button("Done", ScaledVec2(100, 30)))
|
||||
{
|
||||
ImGui::CloseCurrentPopup();
|
||||
gamepad->save_mapping(map_system);
|
||||
ImGui::EndPopup();
|
||||
ImGui::PopStyleVar();
|
||||
return;
|
||||
}
|
||||
ImGui::NewLine();
|
||||
if (gamepad->is_virtual_gamepad())
|
||||
{
|
||||
header("Haptic");
|
||||
OptionSlider("Power", config::VirtualGamepadVibration, 0, 60, "Haptic feedback power");
|
||||
}
|
||||
else if (gamepad->is_rumble_enabled())
|
||||
{
|
||||
header("Rumble");
|
||||
int power = gamepad->get_rumble_power();
|
||||
ImGui::SetNextItemWidth(300 * settings.display.uiScale);
|
||||
if (ImGui::SliderInt("Power", &power, 0, 100, "%d%%"))
|
||||
gamepad->set_rumble_power(power);
|
||||
ImGui::SameLine();
|
||||
ShowHelpMarker("Rumble power");
|
||||
}
|
||||
if (gamepad->has_analog_stick())
|
||||
{
|
||||
header("Thumbsticks");
|
||||
int deadzone = std::round(gamepad->get_dead_zone() * 100.f);
|
||||
ImGui::SetNextItemWidth(300 * settings.display.uiScale);
|
||||
if (ImGui::SliderInt("Dead zone", &deadzone, 0, 100, "%d%%"))
|
||||
gamepad->set_dead_zone(deadzone / 100.f);
|
||||
ImGui::SameLine();
|
||||
ShowHelpMarker("Minimum deflection to register as input");
|
||||
int saturation = std::round(gamepad->get_saturation() * 100.f);
|
||||
ImGui::SetNextItemWidth(300 * settings.display.uiScale);
|
||||
if (ImGui::SliderInt("Saturation", &saturation, 50, 200, "%d%%"))
|
||||
gamepad->set_saturation(saturation / 100.f);
|
||||
ImGui::SameLine();
|
||||
ShowHelpMarker("Value sent to the game at 100% thumbstick deflection. "
|
||||
"Values greater than 100% will saturate before full deflection of the thumbstick.");
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
void error_popup()
|
||||
{
|
||||
if (!error_msg_shown && !error_msg.empty())
|
||||
|
@ -1690,31 +1745,23 @@ static void gui_display_settings()
|
|||
#ifdef __ANDROID__
|
||||
if (gamepad->is_virtual_gamepad())
|
||||
{
|
||||
if (ImGui::Button("Edit"))
|
||||
if (ImGui::Button("Edit Layout"))
|
||||
{
|
||||
vjoy_start_editing();
|
||||
gui_setState(GuiState::VJoyEdit);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
OptionSlider("Haptic", config::VirtualGamepadVibration, 0, 60);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (gamepad->is_rumble_enabled())
|
||||
if (gamepad->is_rumble_enabled() || gamepad->has_analog_stick()
|
||||
#ifdef __ANDROID__
|
||||
|| gamepad->is_virtual_gamepad()
|
||||
#endif
|
||||
)
|
||||
{
|
||||
ImGui::SameLine(0, 16 * settings.display.uiScale);
|
||||
int power = gamepad->get_rumble_power();
|
||||
ImGui::SetNextItemWidth(150 * settings.display.uiScale);
|
||||
if (ImGui::SliderInt("Rumble", &power, 0, 100, "%d%%"))
|
||||
gamepad->set_rumble_power(power);
|
||||
}
|
||||
if (gamepad->has_analog_stick())
|
||||
{
|
||||
ImGui::SameLine(0, 16 * settings.display.uiScale);
|
||||
int deadzone = std::round(gamepad->get_dead_zone() * 100.f);
|
||||
ImGui::SetNextItemWidth(150 * settings.display.uiScale);
|
||||
if (ImGui::SliderInt("Dead zone", &deadzone, 0, 100, "%d%%"))
|
||||
gamepad->set_dead_zone(deadzone / 100.f);
|
||||
if (ImGui::Button("Settings"))
|
||||
ImGui::OpenPopup("Gamepad Settings");
|
||||
gamepadSettingsPopup(gamepad);
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
ImGui::PopID();
|
||||
|
|
Loading…
Reference in New Issue