mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-21 21:30:19 -06:00
ControllerInterface: Input detection improvements.
This commit is contained in:
@ -2,6 +2,10 @@
|
|||||||
// Licensed under GPLv2+
|
// Licensed under GPLv2+
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "InputCommon/ControlReference/ControlReference.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/Thread.h"
|
#include "Common/Thread.h"
|
||||||
// For InputGateOn()
|
// For InputGateOn()
|
||||||
// This is a bad layering violation, but it's the cleanest
|
// This is a bad layering violation, but it's the cleanest
|
||||||
@ -9,11 +13,13 @@
|
|||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
#include "Core/Host.h"
|
#include "Core/Host.h"
|
||||||
|
|
||||||
#include "InputCommon/ControlReference/ControlReference.h"
|
|
||||||
|
|
||||||
using namespace ciface::ExpressionParser;
|
using namespace ciface::ExpressionParser;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Compared to an input's current state (ideally 1.0) minus abs(initial_state) (ideally 0.0).
|
||||||
constexpr ControlState INPUT_DETECT_THRESHOLD = 0.55;
|
constexpr ControlState INPUT_DETECT_THRESHOLD = 0.55;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
bool ControlReference::InputGateOn()
|
bool ControlReference::InputGateOn()
|
||||||
{
|
{
|
||||||
@ -110,56 +116,53 @@ ControlState OutputReference::State(const ControlState state)
|
|||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
// Wait for input on a particular device.
|
||||||
// InputReference :: Detect
|
// Inputs are considered if they are first seen in a neutral state.
|
||||||
//
|
// This is useful for crazy flightsticks that have certain buttons that are always held down
|
||||||
// Wait for input on all binded devices
|
// and also properly handles detection when using "FullAnalogSurface" inputs.
|
||||||
// supports not detecting inputs that were held down at the time of Detect start,
|
// Upon input, return a pointer to the detected Control, else return nullptr.
|
||||||
// which is useful for those crazy flightsticks that have certain buttons that are always held down
|
|
||||||
// or some crazy axes or something
|
|
||||||
// upon input, return pointer to detected Control
|
|
||||||
// else return nullptr
|
|
||||||
//
|
|
||||||
ciface::Core::Device::Control* InputReference::Detect(const unsigned int ms,
|
ciface::Core::Device::Control* InputReference::Detect(const unsigned int ms,
|
||||||
ciface::Core::Device* const device)
|
ciface::Core::Device* const device)
|
||||||
{
|
{
|
||||||
unsigned int time = 0;
|
struct InputState
|
||||||
std::vector<bool> states(device->Inputs().size());
|
|
||||||
|
|
||||||
if (device->Inputs().empty())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
// get starting state of all inputs,
|
|
||||||
// so we can ignore those that were activated at time of Detect start
|
|
||||||
std::vector<ciface::Core::Device::Input*>::const_iterator i = device->Inputs().begin(),
|
|
||||||
e = device->Inputs().end();
|
|
||||||
for (std::vector<bool>::iterator state = states.begin(); i != e; ++i)
|
|
||||||
*state++ = ((*i)->GetState() > (1 - INPUT_DETECT_THRESHOLD));
|
|
||||||
|
|
||||||
while (time < ms)
|
|
||||||
{
|
{
|
||||||
device->UpdateInput();
|
ciface::Core::Device::Input& input;
|
||||||
i = device->Inputs().begin();
|
ControlState initial_state;
|
||||||
for (std::vector<bool>::iterator state = states.begin(); i != e; ++i, ++state)
|
};
|
||||||
{
|
|
||||||
// detected an input
|
std::vector<InputState> input_states;
|
||||||
if ((*i)->IsDetectable() && (*i)->GetState() > INPUT_DETECT_THRESHOLD)
|
for (auto* input : device->Inputs())
|
||||||
{
|
{
|
||||||
// input was released at some point during Detect call
|
// Don't detect things like absolute cursor position.
|
||||||
// return the detected input
|
if (!input->IsDetectable())
|
||||||
if (false == *state)
|
continue;
|
||||||
return *i;
|
|
||||||
}
|
// Undesirable axes will have negative values here when trying to map a "FullAnalogSurface".
|
||||||
else if ((*i)->GetState() < (1 - INPUT_DETECT_THRESHOLD))
|
input_states.push_back({*input, input->GetState()});
|
||||||
{
|
|
||||||
*state = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Common::SleepCurrentThread(10);
|
|
||||||
time += 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// no input was detected
|
if (input_states.empty())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
unsigned int time = 0;
|
||||||
|
while (time < ms)
|
||||||
|
{
|
||||||
|
Common::SleepCurrentThread(10);
|
||||||
|
time += 10;
|
||||||
|
|
||||||
|
device->UpdateInput();
|
||||||
|
for (auto& input_state : input_states)
|
||||||
|
{
|
||||||
|
// We want an input that was initially 0.0 and currently 1.0.
|
||||||
|
const auto detection_score =
|
||||||
|
(input_state.input.GetState() - std::abs(input_state.initial_state));
|
||||||
|
|
||||||
|
if (detection_score > INPUT_DETECT_THRESHOLD)
|
||||||
|
return &input_state.input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No input was detected. :'(
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +217,19 @@ public:
|
|||||||
std::shared_ptr<Device> m_device;
|
std::shared_ptr<Device> m_device;
|
||||||
|
|
||||||
explicit ControlExpression(ControlQualifier qualifier_) : qualifier(qualifier_) {}
|
explicit ControlExpression(ControlQualifier qualifier_) : qualifier(qualifier_) {}
|
||||||
ControlState GetValue() const override { return control ? control->ToInput()->GetState() : 0.0; }
|
ControlState GetValue() const override
|
||||||
|
{
|
||||||
|
if (!control)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
// Note: Inputs may return negative values in situations where opposing directions are
|
||||||
|
// activated. We clamp off the negative values here.
|
||||||
|
|
||||||
|
// FYI: Clamping values greater than 1.0 is purposely not done to support unbounded values in
|
||||||
|
// the future. (e.g. raw accelerometer/gyro data)
|
||||||
|
|
||||||
|
return std::max(0.0, control->ToInput()->GetState());
|
||||||
|
}
|
||||||
void SetValue(ControlState value) override
|
void SetValue(ControlState value) override
|
||||||
{
|
{
|
||||||
if (control)
|
if (control)
|
||||||
|
@ -259,7 +259,7 @@ std::string Joystick::Hat::GetName() const
|
|||||||
|
|
||||||
ControlState Joystick::Axis::GetState() const
|
ControlState Joystick::Axis::GetState() const
|
||||||
{
|
{
|
||||||
return std::max(0.0, ControlState(m_axis - m_base) / m_range);
|
return ControlState(m_axis - m_base) / m_range;
|
||||||
}
|
}
|
||||||
|
|
||||||
ControlState Joystick::Button::GetState() const
|
ControlState Joystick::Button::GetState() const
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "InputCommon/ControllerInterface/Device.h"
|
#include "InputCommon/ControllerInterface/Device.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -68,6 +69,11 @@ Device::Output* Device::FindOutput(const std::string& name) const
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ControlState Device::FullAnalogSurface::GetState() const
|
||||||
|
{
|
||||||
|
return (1 + std::max(0.0, m_high.GetState()) - std::max(0.0, m_low.GetState())) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// DeviceQualifier :: ToString
|
// DeviceQualifier :: ToString
|
||||||
//
|
//
|
||||||
@ -214,5 +220,5 @@ bool DeviceContainer::HasConnectedDevice(const DeviceQualifier& qualifier) const
|
|||||||
const auto device = FindDevice(qualifier);
|
const auto device = FindDevice(qualifier);
|
||||||
return device != nullptr && device->IsValid();
|
return device != nullptr && device->IsValid();
|
||||||
}
|
}
|
||||||
}
|
} // namespace Core
|
||||||
}
|
} // namespace ciface
|
||||||
|
@ -55,9 +55,22 @@ public:
|
|||||||
class Input : public Control
|
class Input : public Control
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// things like absolute axes/ absolute mouse position will override this
|
// Things like absolute axes/ absolute mouse position should override this to prevent
|
||||||
|
// undesirable behavior in our mapping logic.
|
||||||
virtual bool IsDetectable() { return true; }
|
virtual bool IsDetectable() { return true; }
|
||||||
|
|
||||||
|
// Implementations should return a value from 0.0 to 1.0 across their normal range.
|
||||||
|
// One input should be provided for each "direction". (e.g. 2 for each axis)
|
||||||
|
// If possible, negative values may be returned in situations where an opposing input is
|
||||||
|
// activated. (e.g. When an underlying axis, X, is currently negative, "Axis X-", will return a
|
||||||
|
// positive value and "Axis X+" may return a negative value.)
|
||||||
|
// Doing so is solely to allow our input detection logic to better detect false positives.
|
||||||
|
// This is necessary when making use of "FullAnalogSurface" as multiple inputs will be seen
|
||||||
|
// increasing from 0.0 to 1.0 as a user tries to map just one. The negative values provide a
|
||||||
|
// view of the underlying axis. (Negative values are clamped off before they reach
|
||||||
|
// expression-parser or controller-emu)
|
||||||
virtual ControlState GetState() const = 0;
|
virtual ControlState GetState() const = 0;
|
||||||
|
|
||||||
Input* ToInput() override { return this; }
|
Input* ToInput() override { return this; }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -96,11 +109,7 @@ protected:
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FullAnalogSurface(Input* low, Input* high) : m_low(*low), m_high(*high) {}
|
FullAnalogSurface(Input* low, Input* high) : m_low(*low), m_high(*high) {}
|
||||||
ControlState GetState() const override
|
ControlState GetState() const override;
|
||||||
{
|
|
||||||
return (1 + m_high.GetState() - m_low.GetState()) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GetName() const override { return m_low.GetName() + *m_high.GetName().rbegin(); }
|
std::string GetName() const override { return m_low.GetName() + *m_high.GetName().rbegin(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -228,7 +228,7 @@ ControlState Device::Trigger::GetState() const
|
|||||||
|
|
||||||
ControlState Device::Axis::GetState() const
|
ControlState Device::Axis::GetState() const
|
||||||
{
|
{
|
||||||
return std::max(0.0, ControlState(m_axis) / m_range);
|
return ControlState(m_axis) / m_range;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Device::Motor::SetState(ControlState state)
|
void Device::Motor::SetState(ControlState state)
|
||||||
@ -236,5 +236,5 @@ void Device::Motor::SetState(ControlState state)
|
|||||||
m_motor = (WORD)(state * m_range);
|
m_motor = (WORD)(state * m_range);
|
||||||
m_parent->UpdateMotors();
|
m_parent->UpdateMotors();
|
||||||
}
|
}
|
||||||
}
|
} // namespace XInput
|
||||||
}
|
} // namespace ciface
|
||||||
|
@ -343,7 +343,7 @@ ControlState evdevDevice::Axis::GetState() const
|
|||||||
int value = 0;
|
int value = 0;
|
||||||
libevdev_fetch_event_value(m_dev, EV_ABS, m_code, &value);
|
libevdev_fetch_event_value(m_dev, EV_ABS, m_code, &value);
|
||||||
|
|
||||||
return std::max(0.0, ControlState(value - m_base) / m_range);
|
return ControlState(value - m_base) / m_range;
|
||||||
}
|
}
|
||||||
|
|
||||||
evdevDevice::Effect::Effect(int fd) : m_fd(fd)
|
evdevDevice::Effect::Effect(int fd) : m_fd(fd)
|
||||||
|
Reference in New Issue
Block a user