Merge pull request #17960 from hrydgard/control-mapping-fixes
Controls: Make the analog/digital mapping clash resolution more gentle.
This commit is contained in:
commit
ca40de852a
5 changed files with 68 additions and 38 deletions
|
@ -36,6 +36,7 @@ enum InputDeviceID {
|
||||||
DEVICE_ID_XR_CONTROLLER_LEFT = 40,
|
DEVICE_ID_XR_CONTROLLER_LEFT = 40,
|
||||||
DEVICE_ID_XR_CONTROLLER_RIGHT = 41,
|
DEVICE_ID_XR_CONTROLLER_RIGHT = 41,
|
||||||
DEVICE_ID_TOUCH = 42,
|
DEVICE_ID_TOUCH = 42,
|
||||||
|
DEVICE_ID_COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
inline InputDeviceID operator +(InputDeviceID deviceID, int addend) {
|
inline InputDeviceID operator +(InputDeviceID deviceID, int addend) {
|
||||||
|
@ -121,6 +122,9 @@ public:
|
||||||
if (keyCode != other.keyCode) return false;
|
if (keyCode != other.keyCode) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
bool operator != (const InputMapping &other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
void FormatDebug(char *buffer, size_t bufSize) const;
|
void FormatDebug(char *buffer, size_t bufSize) const;
|
||||||
};
|
};
|
||||||
|
|
|
@ -156,11 +156,12 @@ void ControlMapper::UpdateAnalogOutput(int stick) {
|
||||||
void ControlMapper::ForceReleaseVKey(int vkey) {
|
void ControlMapper::ForceReleaseVKey(int vkey) {
|
||||||
std::vector<KeyMap::MultiInputMapping> multiMappings;
|
std::vector<KeyMap::MultiInputMapping> multiMappings;
|
||||||
if (KeyMap::InputMappingsFromPspButton(vkey, &multiMappings, true)) {
|
if (KeyMap::InputMappingsFromPspButton(vkey, &multiMappings, true)) {
|
||||||
|
double now = time_now_d();
|
||||||
for (const auto &entry : multiMappings) {
|
for (const auto &entry : multiMappings) {
|
||||||
for (const auto &mapping : entry.mappings) {
|
for (const auto &mapping : entry.mappings) {
|
||||||
curInput_[mapping] = 0.0f;
|
curInput_[mapping] = { 0.0f, now };
|
||||||
// Different logic for signed axes?
|
// Different logic for signed axes?
|
||||||
UpdatePSPState(mapping);
|
UpdatePSPState(mapping, now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,12 +179,15 @@ static int RotatePSPKeyCode(int x) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to decay analog values when clashing with digital ones.
|
// Used to decay analog values when clashing with digital ones.
|
||||||
static float ReduceMagnitude(float value) {
|
static ControlMapper::InputSample ReduceMagnitude(ControlMapper::InputSample sample, double now) {
|
||||||
value *= 0.75f;
|
float reduction = std::min(std::max(0.0f, (float)(now - sample.timestamp) - 2.0f), 1.0f);
|
||||||
if ((value > 0.0f && value < 0.05f) || (value < 0.0f && value > -0.05f)) {
|
if (reduction > 0.0f) {
|
||||||
value = 0.0f;
|
sample.value *= (1.0f - reduction);
|
||||||
}
|
}
|
||||||
return value;
|
if ((sample.value > 0.0f && sample.value < 0.05f) || (sample.value < 0.0f && sample.value > -0.05f)) {
|
||||||
|
sample.value = 0.0f;
|
||||||
|
}
|
||||||
|
return sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
float ControlMapper::MapAxisValue(float value, int vkId, const InputMapping &mapping, const InputMapping &changedMapping, bool *oppositeTouched) {
|
float ControlMapper::MapAxisValue(float value, int vkId, const InputMapping &mapping, const InputMapping &changedMapping, bool *oppositeTouched) {
|
||||||
|
@ -199,7 +203,7 @@ float ControlMapper::MapAxisValue(float value, int vkId, const InputMapping &map
|
||||||
if (other == changedMapping) {
|
if (other == changedMapping) {
|
||||||
*oppositeTouched = true;
|
*oppositeTouched = true;
|
||||||
}
|
}
|
||||||
float valueOther = curInput_[other];
|
float valueOther = curInput_[other].value;
|
||||||
float signedValue = value - valueOther;
|
float signedValue = value - valueOther;
|
||||||
float ranged = (signedValue + 1.0f) * 0.5f;
|
float ranged = (signedValue + 1.0f) * 0.5f;
|
||||||
if (direction == -1) {
|
if (direction == -1) {
|
||||||
|
@ -247,7 +251,8 @@ void ControlMapper::SwapMappingIfEnabled(uint32_t *vkey) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can only be called from Key or Axis.
|
// Can only be called from Key or Axis.
|
||||||
bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) {
|
// TODO: We should probably make a batched version of this.
|
||||||
|
bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping, double now) {
|
||||||
// Instead of taking an input key and finding what it outputs, we loop through the OUTPUTS and
|
// Instead of taking an input key and finding what it outputs, we loop through the OUTPUTS and
|
||||||
// see if the input that corresponds to it has a value. That way we can easily implement all sorts
|
// see if the input that corresponds to it has a value. That way we can easily implement all sorts
|
||||||
// of crazy input combos if needed.
|
// of crazy input combos if needed.
|
||||||
|
@ -290,7 +295,7 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) {
|
||||||
bool all = true;
|
bool all = true;
|
||||||
for (auto mapping : multiMapping.mappings) {
|
for (auto mapping : multiMapping.mappings) {
|
||||||
auto iter = curInput_.find(mapping);
|
auto iter = curInput_.find(mapping);
|
||||||
bool down = iter != curInput_.end() && iter->second > GetDeviceAxisThreshold(iter->first.deviceId);
|
bool down = iter != curInput_.end() && iter->second.value > GetDeviceAxisThreshold(iter->first.deviceId);
|
||||||
if (!down)
|
if (!down)
|
||||||
all = false;
|
all = false;
|
||||||
}
|
}
|
||||||
|
@ -307,6 +312,7 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) {
|
||||||
bool updateAnalogSticks = false;
|
bool updateAnalogSticks = false;
|
||||||
|
|
||||||
// OK, handle all the virtual keys next. For these we need to do deltas here and send events.
|
// OK, handle all the virtual keys next. For these we need to do deltas here and send events.
|
||||||
|
// Note that virtual keys include the analog directions, as they are driven by them.
|
||||||
for (int i = 0; i < VIRTKEY_COUNT; i++) {
|
for (int i = 0; i < VIRTKEY_COUNT; i++) {
|
||||||
int vkId = i + VIRTKEY_FIRST;
|
int vkId = i + VIRTKEY_FIRST;
|
||||||
std::vector<MultiInputMapping> inputMappings;
|
std::vector<MultiInputMapping> inputMappings;
|
||||||
|
@ -334,9 +340,9 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) {
|
||||||
if (iter != curInput_.end()) {
|
if (iter != curInput_.end()) {
|
||||||
if (mapping.IsAxis()) {
|
if (mapping.IsAxis()) {
|
||||||
threshold = GetDeviceAxisThreshold(iter->first.deviceId);
|
threshold = GetDeviceAxisThreshold(iter->first.deviceId);
|
||||||
product *= MapAxisValue(iter->second, idForMapping, mapping, changedMapping, &touchedByMapping);
|
product *= MapAxisValue(iter->second.value, idForMapping, mapping, changedMapping, &touchedByMapping);
|
||||||
} else {
|
} else {
|
||||||
product *= iter->second;
|
product *= iter->second.value;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
product = 0.0f;
|
product = 0.0f;
|
||||||
|
@ -361,7 +367,11 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) {
|
||||||
if (!changedMapping.IsAxis()) {
|
if (!changedMapping.IsAxis()) {
|
||||||
for (auto &multiMapping : inputMappings) {
|
for (auto &multiMapping : inputMappings) {
|
||||||
for (auto &mapping : multiMapping.mappings) {
|
for (auto &mapping : multiMapping.mappings) {
|
||||||
curInput_[mapping] = ReduceMagnitude(curInput_[mapping]);
|
if (mapping != changedMapping && curInput_[mapping].value > 0.0f) {
|
||||||
|
// Note that this takes the time into account now - values will
|
||||||
|
// decay after a while, not immediately.
|
||||||
|
curInput_[mapping] = ReduceMagnitude(curInput_[mapping], now);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -412,15 +422,19 @@ bool ControlMapper::Key(const KeyInput &key, bool *pauseTrigger) {
|
||||||
// Claim that we handled this. Prevents volume key repeats from popping up the volume control on Android.
|
// Claim that we handled this. Prevents volume key repeats from popping up the volume control on Android.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
double now = time_now_d();
|
||||||
std::lock_guard<std::mutex> guard(mutex_);
|
if (key.deviceId < DEVICE_ID_COUNT) {
|
||||||
|
deviceTimestamps_[(int)key.deviceId] = now;
|
||||||
|
}
|
||||||
|
|
||||||
InputMapping mapping(key.deviceId, key.keyCode);
|
InputMapping mapping(key.deviceId, key.keyCode);
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> guard(mutex_);
|
||||||
|
|
||||||
if (key.flags & KEY_DOWN) {
|
if (key.flags & KEY_DOWN) {
|
||||||
curInput_[mapping] = 1.0f;
|
curInput_[mapping] = { 1.0f, now };
|
||||||
} else if (key.flags & KEY_UP) {
|
} else if (key.flags & KEY_UP) {
|
||||||
curInput_[mapping] = 0.0f;
|
curInput_[mapping] = { 0.0f, now};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: See if this can be simplified further somehow.
|
// TODO: See if this can be simplified further somehow.
|
||||||
|
@ -433,7 +447,7 @@ bool ControlMapper::Key(const KeyInput &key, bool *pauseTrigger) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return UpdatePSPState(mapping);
|
return UpdatePSPState(mapping, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlMapper::ToggleSwapAxes() {
|
void ControlMapper::ToggleSwapAxes() {
|
||||||
|
@ -462,34 +476,37 @@ void ControlMapper::ToggleSwapAxes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlMapper::Axis(const AxisInput &axis) {
|
void ControlMapper::Axis(const AxisInput &axis) {
|
||||||
|
double now = time_now_d();
|
||||||
|
|
||||||
std::lock_guard<std::mutex> guard(mutex_);
|
std::lock_guard<std::mutex> guard(mutex_);
|
||||||
|
if (axis.deviceId < DEVICE_ID_COUNT) {
|
||||||
|
deviceTimestamps_[(int)axis.deviceId] = now;
|
||||||
|
}
|
||||||
if (axis.value >= 0.0f) {
|
if (axis.value >= 0.0f) {
|
||||||
InputMapping mapping(axis.deviceId, axis.axisId, 1);
|
InputMapping mapping(axis.deviceId, axis.axisId, 1);
|
||||||
InputMapping opposite(axis.deviceId, axis.axisId, -1);
|
InputMapping opposite(axis.deviceId, axis.axisId, -1);
|
||||||
curInput_[mapping] = axis.value;
|
curInput_[mapping] = { axis.value, now };
|
||||||
curInput_[opposite] = 0.0f;
|
curInput_[opposite] = { 0.0f, now };
|
||||||
UpdatePSPState(mapping);
|
UpdatePSPState(mapping, now);
|
||||||
UpdatePSPState(opposite);
|
UpdatePSPState(opposite, now);
|
||||||
} else if (axis.value < 0.0f) {
|
} else if (axis.value < 0.0f) {
|
||||||
InputMapping mapping(axis.deviceId, axis.axisId, -1);
|
InputMapping mapping(axis.deviceId, axis.axisId, -1);
|
||||||
InputMapping opposite(axis.deviceId, axis.axisId, 1);
|
InputMapping opposite(axis.deviceId, axis.axisId, 1);
|
||||||
curInput_[mapping] = -axis.value;
|
curInput_[mapping] = { -axis.value, now };
|
||||||
curInput_[opposite] = 0.0f;
|
curInput_[opposite] = { 0.0f, now };
|
||||||
UpdatePSPState(mapping);
|
UpdatePSPState(mapping, now);
|
||||||
UpdatePSPState(opposite);
|
UpdatePSPState(opposite, now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlMapper::Update() {
|
void ControlMapper::Update(double now) {
|
||||||
if (autoRotatingAnalogCW_) {
|
if (autoRotatingAnalogCW_) {
|
||||||
const double now = time_now_d();
|
|
||||||
// Clamp to a square
|
// Clamp to a square
|
||||||
float x = std::min(1.0f, std::max(-1.0f, 1.42f * (float)cos(now * -g_Config.fAnalogAutoRotSpeed)));
|
float x = std::min(1.0f, std::max(-1.0f, 1.42f * (float)cos(now * -g_Config.fAnalogAutoRotSpeed)));
|
||||||
float y = std::min(1.0f, std::max(-1.0f, 1.42f * (float)sin(now * -g_Config.fAnalogAutoRotSpeed)));
|
float y = std::min(1.0f, std::max(-1.0f, 1.42f * (float)sin(now * -g_Config.fAnalogAutoRotSpeed)));
|
||||||
|
|
||||||
setPSPAnalog_(0, x, y);
|
setPSPAnalog_(0, x, y);
|
||||||
} else if (autoRotatingAnalogCCW_) {
|
} else if (autoRotatingAnalogCCW_) {
|
||||||
const double now = time_now_d();
|
|
||||||
float x = std::min(1.0f, std::max(-1.0f, 1.42f * (float)cos(now * g_Config.fAnalogAutoRotSpeed)));
|
float x = std::min(1.0f, std::max(-1.0f, 1.42f * (float)cos(now * g_Config.fAnalogAutoRotSpeed)));
|
||||||
float y = std::min(1.0f, std::max(-1.0f, 1.42f * (float)sin(now * g_Config.fAnalogAutoRotSpeed)));
|
float y = std::min(1.0f, std::max(-1.0f, 1.42f * (float)sin(now * g_Config.fAnalogAutoRotSpeed)));
|
||||||
|
|
||||||
|
@ -583,7 +600,7 @@ void ControlMapper::GetDebugString(char *buffer, size_t bufSize) const {
|
||||||
for (auto iter : curInput_) {
|
for (auto iter : curInput_) {
|
||||||
char temp[256];
|
char temp[256];
|
||||||
iter.first.FormatDebug(temp, sizeof(temp));
|
iter.first.FormatDebug(temp, sizeof(temp));
|
||||||
str << temp << ": " << iter.second << std::endl;
|
str << temp << ": " << iter.second.value << std::endl;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < ARRAY_SIZE(virtKeys_); i++) {
|
for (int i = 0; i < ARRAY_SIZE(virtKeys_); i++) {
|
||||||
int vkId = VIRTKEY_FIRST + i;
|
int vkId = VIRTKEY_FIRST + i;
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
// Main use is of course from EmuScreen.cpp, but also useful from control settings etc.
|
// Main use is of course from EmuScreen.cpp, but also useful from control settings etc.
|
||||||
class ControlMapper {
|
class ControlMapper {
|
||||||
public:
|
public:
|
||||||
void Update();
|
void Update(double now);
|
||||||
|
|
||||||
// Inputs to the table-based mapping
|
// Inputs to the table-based mapping
|
||||||
// These functions are free-threaded.
|
// These functions are free-threaded.
|
||||||
|
@ -42,8 +42,13 @@ public:
|
||||||
|
|
||||||
void GetDebugString(char *buffer, size_t bufSize) const;
|
void GetDebugString(char *buffer, size_t bufSize) const;
|
||||||
|
|
||||||
|
struct InputSample {
|
||||||
|
float value;
|
||||||
|
double timestamp;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool UpdatePSPState(const InputMapping &changedMapping);
|
bool UpdatePSPState(const InputMapping &changedMapping, double now);
|
||||||
float MapAxisValue(float value, int vkId, const InputMapping &mapping, const InputMapping &changedMapping, bool *oppositeTouched);
|
float MapAxisValue(float value, int vkId, const InputMapping &mapping, const InputMapping &changedMapping, bool *oppositeTouched);
|
||||||
void SwapMappingIfEnabled(uint32_t *vkey);
|
void SwapMappingIfEnabled(uint32_t *vkey);
|
||||||
|
|
||||||
|
@ -57,6 +62,8 @@ private:
|
||||||
float virtKeys_[VIRTKEY_COUNT]{};
|
float virtKeys_[VIRTKEY_COUNT]{};
|
||||||
bool virtKeyOn_[VIRTKEY_COUNT]{}; // Track boolean output separaately since thresholds may differ.
|
bool virtKeyOn_[VIRTKEY_COUNT]{}; // Track boolean output separaately since thresholds may differ.
|
||||||
|
|
||||||
|
double deviceTimestamps_[42]{};
|
||||||
|
|
||||||
int lastNonDeadzoneDeviceID_[2]{};
|
int lastNonDeadzoneDeviceID_[2]{};
|
||||||
|
|
||||||
float history_[2][2]{};
|
float history_[2][2]{};
|
||||||
|
@ -71,7 +78,7 @@ private:
|
||||||
// Protects basically all the state.
|
// Protects basically all the state.
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
|
|
||||||
std::map<InputMapping, float> curInput_;
|
std::map<InputMapping, InputSample> curInput_;
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
std::function<void(int, bool)> onVKey_;
|
std::function<void(int, bool)> onVKey_;
|
||||||
|
|
|
@ -512,7 +512,7 @@ AnalogSetupScreen::AnalogSetupScreen(const Path &gamePath) : UIDialogScreenWithG
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnalogSetupScreen::update() {
|
void AnalogSetupScreen::update() {
|
||||||
mapper_.Update();
|
mapper_.Update(time_now_d());
|
||||||
// We ignore the secondary stick for now and just use the two views
|
// We ignore the secondary stick for now and just use the two views
|
||||||
// for raw and psp input.
|
// for raw and psp input.
|
||||||
if (stickView_[0]) {
|
if (stickView_[0]) {
|
||||||
|
|
|
@ -1127,7 +1127,9 @@ void EmuScreen::update() {
|
||||||
if (invalid_)
|
if (invalid_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
controlMapper_.Update();
|
double now = time_now_d();
|
||||||
|
|
||||||
|
controlMapper_.Update(now);
|
||||||
|
|
||||||
if (pauseTrigger_) {
|
if (pauseTrigger_) {
|
||||||
pauseTrigger_ = false;
|
pauseTrigger_ = false;
|
||||||
|
@ -1147,7 +1149,7 @@ void EmuScreen::update() {
|
||||||
saveStatePreview_->SetFilename(fn);
|
saveStatePreview_->SetFilename(fn);
|
||||||
if (!fn.empty()) {
|
if (!fn.empty()) {
|
||||||
saveStatePreview_->SetVisibility(UI::V_VISIBLE);
|
saveStatePreview_->SetVisibility(UI::V_VISIBLE);
|
||||||
saveStatePreviewShownTime_ = time_now_d();
|
saveStatePreviewShownTime_ = now;
|
||||||
} else {
|
} else {
|
||||||
saveStatePreview_->SetVisibility(UI::V_GONE);
|
saveStatePreview_->SetVisibility(UI::V_GONE);
|
||||||
}
|
}
|
||||||
|
@ -1155,10 +1157,10 @@ void EmuScreen::update() {
|
||||||
|
|
||||||
if (saveStatePreview_->GetVisibility() == UI::V_VISIBLE) {
|
if (saveStatePreview_->GetVisibility() == UI::V_VISIBLE) {
|
||||||
double endTime = saveStatePreviewShownTime_ + 2.0;
|
double endTime = saveStatePreviewShownTime_ + 2.0;
|
||||||
float alpha = clamp_value((endTime - time_now_d()) * 4.0, 0.0, 1.0);
|
float alpha = clamp_value((endTime - now) * 4.0, 0.0, 1.0);
|
||||||
saveStatePreview_->SetColor(colorAlpha(0x00FFFFFF, alpha));
|
saveStatePreview_->SetColor(colorAlpha(0x00FFFFFF, alpha));
|
||||||
|
|
||||||
if (time_now_d() - saveStatePreviewShownTime_ > 2) {
|
if (now - saveStatePreviewShownTime_ > 2) {
|
||||||
saveStatePreview_->SetVisibility(UI::V_GONE);
|
saveStatePreview_->SetVisibility(UI::V_GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue