mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
rawinput: distinguish between circular and linear analog input (#665)
## Link to GitHub Issue or related Pull Request, if one exists n/a ## Description of change Currently, all analogs are treated as circular values that wrap around. This works for knobs and turntables, but can get weird for linear values (like tilt sensors and sliders) especially once analog options are enabled, such as sensitivity. This PR groups analogs into 3 buckets: circular, linear (centered), and linear (positive). Linear types get different algorithms applied to it when options are set (e.g., sensitivity caps out at 0, 1 instead of wrapping around). In linear mode: * Sensitivity value applies an exponential response curve (power curve) to the original value. * Multiplier/divisor serves as a linear cut-off. * Relative mode works as you expect, but of course, values don't wrap around. * Invert and deadzone work as you expect (since they applied to the controller input anyway) * Smoothing is removed from the UI and has no effect. * Delay is unaffected. Also, fix a small bug with -/+ button not working for analog `Delay` option. ## Testing WIP
This commit is contained in:
@@ -207,6 +207,7 @@ float Analog::applyMultiplier(float value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float Analog::normalizeAnalogValue(float value) {
|
float Analog::normalizeAnalogValue(float value) {
|
||||||
|
if (getType() == GameAPI::Analogs::AnalogType::Circular) {
|
||||||
// effectively the same as fmodf(value, 1.f)
|
// effectively the same as fmodf(value, 1.f)
|
||||||
// for small values, this is MUCH faster than fmodf.
|
// for small values, this is MUCH faster than fmodf.
|
||||||
float new_value = value;
|
float new_value = value;
|
||||||
@@ -217,12 +218,27 @@ float Analog::normalizeAnalogValue(float value) {
|
|||||||
new_value += 1.f;
|
new_value += 1.f;
|
||||||
}
|
}
|
||||||
return new_value;
|
return new_value;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// clamp to [0, 1] range
|
||||||
|
return std::clamp(value, 0.f, 1.f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float Analog::applyDeadzone(float raw_value) {
|
float Analog::applyDeadzone(float raw_value) {
|
||||||
float value = raw_value;
|
float value = raw_value;
|
||||||
const auto deadzone = this->getDeadzone();
|
auto deadzone = this->getDeadzone();
|
||||||
if (deadzone > 0) {
|
|
||||||
|
// in the past, positive deadzone applied in the center, negative deadzone applied to 0
|
||||||
|
// after each analog value received a type (circular/linear) this has been simpliifed to
|
||||||
|
// positive values only since we can figure out where the rest value is
|
||||||
|
// for back compat, treat negative value as positive
|
||||||
|
if (deadzone < 0.f) {
|
||||||
|
deadzone = -deadzone;
|
||||||
|
}
|
||||||
|
|
||||||
|
// relative mode assumes that user is using a stick, so center is neutral regardless of analog type
|
||||||
|
if (getType() != GameAPI::Analogs::AnalogType::LinearPositive || isRelativeMode()) {
|
||||||
|
|
||||||
// calculate values
|
// calculate values
|
||||||
const auto delta = value - 0.5f;
|
const auto delta = value - 0.5f;
|
||||||
@@ -255,7 +271,7 @@ float Analog::applyDeadzone(float raw_value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (deadzone < 0) {
|
} else {
|
||||||
|
|
||||||
// invert for mirror
|
// invert for mirror
|
||||||
if (this->getDeadzoneMirror()) {
|
if (this->getDeadzoneMirror()) {
|
||||||
@@ -263,8 +279,8 @@ float Analog::applyDeadzone(float raw_value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// deadzone from minimum value
|
// deadzone from minimum value
|
||||||
if (deadzone > -1 && value > -deadzone) {
|
if (deadzone < 1.f && deadzone < value) {
|
||||||
value = std::min(1.f, (value + deadzone) / (1.f + deadzone));
|
value = std::max(0.f, (value - deadzone) / (1.f - deadzone));
|
||||||
} else {
|
} else {
|
||||||
value = 0.f;
|
value = 0.f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,21 @@ namespace rawinput {
|
|||||||
class RawInputManager;
|
class RawInputManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace GameAPI::Analogs {
|
||||||
|
enum class AnalogType {
|
||||||
|
// default; values wrap around (below 0 turns into 1, over 1 is 0)
|
||||||
|
// knobs, turntables
|
||||||
|
Circular = 0,
|
||||||
|
|
||||||
|
// typical joystick that rests at the center and caps at [0, 1]
|
||||||
|
LinearCentered = 1,
|
||||||
|
|
||||||
|
// one-directional value (sliders and instruments like piano/drum velocity)
|
||||||
|
// starts at 0 and goes up to 1
|
||||||
|
LinearPositive = 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
struct AnalogMovingAverage {
|
struct AnalogMovingAverage {
|
||||||
double time_in_ms;
|
double time_in_ms;
|
||||||
float sine;
|
float sine;
|
||||||
@@ -31,10 +46,11 @@ private:
|
|||||||
float last_state = 0.5f;
|
float last_state = 0.5f;
|
||||||
bool sensitivity_set = false;
|
bool sensitivity_set = false;
|
||||||
bool deadzone_set = false;
|
bool deadzone_set = false;
|
||||||
|
GameAPI::Analogs::AnalogType type = GameAPI::Analogs::AnalogType::Circular;
|
||||||
|
|
||||||
// smoothing function
|
// smoothing function
|
||||||
bool smoothing = false;
|
bool smoothing = false;
|
||||||
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history;
|
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history = {};
|
||||||
int vector_history_index = 0;
|
int vector_history_index = 0;
|
||||||
float smoothed_last_state = 0.f;
|
float smoothed_last_state = 0.f;
|
||||||
|
|
||||||
@@ -66,7 +82,8 @@ public:
|
|||||||
float override_state = 0.5f;
|
float override_state = 0.5f;
|
||||||
|
|
||||||
explicit Analog(std::string name) : name(std::move(name)) {
|
explicit Analog(std::string name) : name(std::move(name)) {
|
||||||
vector_history.fill({0.0, 0.f, 0.f});
|
};
|
||||||
|
explicit Analog(std::string name, GameAPI::Analogs::AnalogType type) : name(std::move(name)), type(type) {
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string getDisplayString(rawinput::RawInputManager* manager);
|
std::string getDisplayString(rawinput::RawInputManager* manager);
|
||||||
@@ -214,4 +231,12 @@ public:
|
|||||||
inline std::queue<float> &getDelayBuffer() {
|
inline std::queue<float> &getDelayBuffer() {
|
||||||
return this->delay_buffer;
|
return this->delay_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline GameAPI::Analogs::AnalogType getType() const {
|
||||||
|
return this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void setType(GameAPI::Analogs::AnalogType type) {
|
||||||
|
this->type = type;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+87
-2
@@ -679,7 +679,9 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
|
|||||||
}
|
}
|
||||||
|
|
||||||
// deadzone
|
// deadzone
|
||||||
if (analog.isDeadzoneSet()) {
|
// do not apply deadzone to circular analogs since it doesn't make sense (except in relative mode)
|
||||||
|
if (analog.isDeadzoneSet() &&
|
||||||
|
(analog.getType() != AnalogType::Circular || analog.isRelativeMode())) {
|
||||||
value = analog.applyDeadzone(value);
|
value = analog.applyDeadzone(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,7 +706,7 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
|
|||||||
// translate relative movement to absolute value
|
// translate relative movement to absolute value
|
||||||
value = analog.getAbsoluteValue(relative_delta);
|
value = analog.getAbsoluteValue(relative_delta);
|
||||||
|
|
||||||
} else {
|
} else if (analog.getType() == AnalogType::Circular) {
|
||||||
// integer multiplier
|
// integer multiplier
|
||||||
value = analog.applyMultiplier(value);
|
value = analog.applyMultiplier(value);
|
||||||
|
|
||||||
@@ -732,6 +734,53 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
|
|||||||
// apply to value
|
// apply to value
|
||||||
value = rads * (float) M_1_TAU;
|
value = rads * (float) M_1_TAU;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// sensitivity
|
||||||
|
if (analog.isSensitivitySet()) {
|
||||||
|
// adjust curve
|
||||||
|
// values < 1.f : less sensitive around neutral
|
||||||
|
// values > 1.f : more sensitive around neutral
|
||||||
|
float curve = analog.getSensitivity();
|
||||||
|
if (curve <= 0.f) {
|
||||||
|
curve = 0.01f;
|
||||||
|
}
|
||||||
|
curve = 1.f / curve;
|
||||||
|
|
||||||
|
if (analog.getType() == AnalogType::LinearCentered) {
|
||||||
|
// convert 0.0..1.0 to -1.0..+1.0
|
||||||
|
float signed_raw = (value - 0.5f) * 2.0f;
|
||||||
|
// apply curve
|
||||||
|
float sign = signed_raw < 0.0f ? -1.0f : 1.0f;
|
||||||
|
float magnitude = fabsf(signed_raw);
|
||||||
|
float curved = sign * powf(magnitude, curve);
|
||||||
|
// convert back to 0.0..1.0
|
||||||
|
value = curved * 0.5f + 0.5f;
|
||||||
|
} else {
|
||||||
|
value = powf(value, curve);
|
||||||
|
}
|
||||||
|
|
||||||
|
value = std::clamp(value, 0.f, 1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiplier / divisor
|
||||||
|
if (analog.getMultiplier() < -1) {
|
||||||
|
if (analog.getType() == AnalogType::LinearCentered) {
|
||||||
|
value = (value - 0.5f) / (-analog.getMultiplier()) + 0.5f;
|
||||||
|
} else {
|
||||||
|
value /= -analog.getMultiplier();
|
||||||
|
}
|
||||||
|
|
||||||
|
value = std::clamp(value, 0.f, 1.f);
|
||||||
|
|
||||||
|
} else if (analog.getMultiplier() > 1) {
|
||||||
|
if (analog.getType() == AnalogType::LinearCentered) {
|
||||||
|
value = (value - 0.5f) * analog.getMultiplier() + 0.5f;
|
||||||
|
} else {
|
||||||
|
value *= analog.getMultiplier();
|
||||||
|
}
|
||||||
|
|
||||||
|
value = std::clamp(value, 0.f, 1.f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// delay
|
// delay
|
||||||
@@ -839,6 +888,42 @@ std::vector<Analog> GameAPI::Analogs::sortAnalogs(
|
|||||||
return sorted;
|
return sorted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<Analog> sortAnalogsWithTypeInternal(
|
||||||
|
std::vector<Analog> &analogs,
|
||||||
|
const std::initializer_list<GameAPI::Analogs::AnalogWithType> list) {
|
||||||
|
|
||||||
|
std::vector<Analog> sorted;
|
||||||
|
|
||||||
|
bool analog_found;
|
||||||
|
for (auto &a : list) {
|
||||||
|
analog_found = false;
|
||||||
|
|
||||||
|
for (auto &analog : analogs) {
|
||||||
|
if (a.name == analog.getName()) {
|
||||||
|
analog_found = true;
|
||||||
|
analog.setType(a.type);
|
||||||
|
sorted.push_back(analog);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!analog_found) {
|
||||||
|
sorted.emplace_back(a.name, a.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameAPI::Analogs::sortAnalogsWithType(
|
||||||
|
std::vector<Analog> *analogs,
|
||||||
|
const std::initializer_list<AnalogWithType> list) {
|
||||||
|
|
||||||
|
if (analogs) {
|
||||||
|
*analogs = sortAnalogsWithTypeInternal(*analogs, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &analog) {
|
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &analog) {
|
||||||
|
|
||||||
// check override
|
// check override
|
||||||
|
|||||||
@@ -85,14 +85,15 @@ namespace GameAPI {
|
|||||||
const std::vector<Analog> &analogs,
|
const std::vector<Analog> &analogs,
|
||||||
const std::vector<std::string> &analog_names);
|
const std::vector<std::string> &analog_names);
|
||||||
|
|
||||||
template<typename T>
|
struct AnalogWithType {
|
||||||
void sortAnalogs(std::vector<Analog> *analogs, T t) {
|
const std::string name;
|
||||||
const std::vector<std::string> analog_names { t };
|
const AnalogType type;
|
||||||
|
|
||||||
if (analogs) {
|
AnalogWithType(std::string name, AnalogType type) :
|
||||||
*analogs = GameAPI::Analogs::sortAnalogs(*analogs, analog_names);
|
name(std::move(name)), type(type) {}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
void sortAnalogsWithType(std::vector<Analog> *analogs, const std::initializer_list<AnalogWithType> list);
|
||||||
|
|
||||||
template<typename T, typename... Rest>
|
template<typename T, typename... Rest>
|
||||||
void sortAnalogs(std::vector<Analog> *analogs, T t, Rest... rest) {
|
void sortAnalogs(std::vector<Analog> *analogs, T t, Rest... rest) {
|
||||||
|
|||||||
@@ -38,11 +38,12 @@ std::vector<Analog> &games::bc::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Busou Shinki: Armored Princess Battle Conductor");
|
analogs = GameAPI::Analogs::getAnalogs("Busou Shinki: Armored Princess Battle Conductor");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using GameAPI::Analogs::AnalogType;
|
||||||
&analogs,
|
|
||||||
"Stick X",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Stick Y"
|
{ "Stick X", AnalogType::LinearCentered },
|
||||||
);
|
{ "Stick Y", AnalogType::LinearCentered }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -45,13 +45,14 @@ std::vector<Analog> &games::ccj::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Chase Chase Jokers");
|
analogs = GameAPI::Analogs::getAnalogs("Chase Chase Jokers");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using GameAPI::Analogs::AnalogType;
|
||||||
&analogs,
|
|
||||||
"Joystick X",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Joystick Y",
|
{ "Joystick X", AnalogType::LinearCentered },
|
||||||
"Trackball DX",
|
{ "Joystick Y", AnalogType::LinearCentered },
|
||||||
"Trackball DY"
|
{ "Trackball DX", AnalogType::LinearCentered },
|
||||||
);
|
{ "Trackball DY", AnalogType::LinearCentered }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -55,13 +55,14 @@ std::vector<Analog> &games::ddr::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Dance Dance Revolution");
|
analogs = GameAPI::Analogs::getAnalogs("Dance Dance Revolution");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using namespace GameAPI::Analogs;
|
||||||
&analogs,
|
|
||||||
"P1 Left-Right (Axis Fix)",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"P1 Up-Down (Axis Fix)",
|
{ "P1 Left-Right (Axis Fix)", AnalogType::LinearCentered },
|
||||||
"P2 Left-Right (Axis Fix)",
|
{ "P1 Up-Down (Axis Fix)", AnalogType::LinearCentered },
|
||||||
"P2 Up-Down (Axis Fix)"
|
{ "P2 Left-Right (Axis Fix)", AnalogType::LinearCentered },
|
||||||
);
|
{ "P2 Up-Down (Axis Fix)", AnalogType::LinearCentered }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return analogs;
|
return analogs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,13 +35,14 @@ std::vector<Analog> &games::ftt::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("FutureTomTom");
|
analogs = GameAPI::Analogs::getAnalogs("FutureTomTom");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using GameAPI::Analogs::AnalogType;
|
||||||
&analogs,
|
|
||||||
"Pad 1",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Pad 2",
|
{ "Pad 1", AnalogType::LinearPositive },
|
||||||
"Pad 3",
|
{ "Pad 2", AnalogType::LinearPositive },
|
||||||
"Pad 4"
|
{ "Pad 3", AnalogType::LinearPositive },
|
||||||
);
|
{ "Pad 4", AnalogType::LinearPositive }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -86,19 +86,21 @@ std::vector<Button> &games::gitadora::get_buttons() {
|
|||||||
std::vector<Analog> &games::gitadora::get_analogs() {
|
std::vector<Analog> &games::gitadora::get_analogs() {
|
||||||
static std::vector<Analog> analogs;
|
static std::vector<Analog> analogs;
|
||||||
|
|
||||||
|
using namespace GameAPI::Analogs;
|
||||||
|
|
||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("GitaDora");
|
analogs = GameAPI::Analogs::getAnalogs("GitaDora");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(&analogs,
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Guitar P1 Wail X",
|
{"Guitar P1 Wail X", AnalogType::LinearCentered},
|
||||||
"Guitar P1 Wail Y",
|
{"Guitar P1 Wail Y", AnalogType::LinearCentered},
|
||||||
"Guitar P1 Wail Z",
|
{"Guitar P1 Wail Z", AnalogType::LinearCentered},
|
||||||
"Guitar P1 Knob",
|
{"Guitar P1 Knob", AnalogType::Circular},
|
||||||
"Guitar P2 Wail X",
|
{"Guitar P2 Wail X", AnalogType::LinearCentered},
|
||||||
"Guitar P2 Wail Y",
|
{"Guitar P2 Wail Y", AnalogType::LinearCentered},
|
||||||
"Guitar P2 Wail Z",
|
{"Guitar P2 Wail Z", AnalogType::LinearCentered},
|
||||||
"Guitar P2 Knob"
|
{"Guitar P2 Knob", AnalogType::Circular}
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -63,16 +63,17 @@ std::vector<Analog> &games::iidx::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX");
|
analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using namespace GameAPI::Analogs;
|
||||||
&analogs,
|
|
||||||
"Turntable P1",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Turntable P2",
|
{ "Turntable P1", AnalogType::Circular },
|
||||||
"VEFX",
|
{ "Turntable P2", AnalogType::Circular },
|
||||||
"Low-EQ",
|
{ "VEFX", AnalogType::LinearPositive },
|
||||||
"Hi-EQ",
|
{ "Low-EQ", AnalogType::LinearPositive },
|
||||||
"Filter",
|
{ "Hi-EQ", AnalogType::LinearPositive },
|
||||||
"Play Volume"
|
{ "Filter", AnalogType::LinearPositive },
|
||||||
);
|
{ "Play Volume", AnalogType::LinearPositive }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return analogs;
|
return analogs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ std::vector<Analog> &games::mga::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Metal Gear");
|
analogs = GameAPI::Analogs::getAnalogs("Metal Gear");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using namespace GameAPI::Analogs;
|
||||||
&analogs,
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Joy X",
|
{ "Joy X", AnalogType::LinearCentered },
|
||||||
"Joy Y"
|
{ "Joy Y", AnalogType::LinearCentered }
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -165,37 +165,38 @@ std::vector<Analog> &games::nost::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Nostalgia");
|
analogs = GameAPI::Analogs::getAnalogs("Nostalgia");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using namespace GameAPI::Analogs;
|
||||||
&analogs,
|
|
||||||
"Key 1",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Key 2",
|
{ "Key 1", AnalogType::LinearPositive },
|
||||||
"Key 3",
|
{ "Key 2", AnalogType::LinearPositive },
|
||||||
"Key 4",
|
{ "Key 3", AnalogType::LinearPositive },
|
||||||
"Key 5",
|
{ "Key 4", AnalogType::LinearPositive },
|
||||||
"Key 6",
|
{ "Key 5", AnalogType::LinearPositive },
|
||||||
"Key 7",
|
{ "Key 6", AnalogType::LinearPositive },
|
||||||
"Key 8",
|
{ "Key 7", AnalogType::LinearPositive },
|
||||||
"Key 9",
|
{ "Key 8", AnalogType::LinearPositive },
|
||||||
"Key 10",
|
{ "Key 9", AnalogType::LinearPositive },
|
||||||
"Key 11",
|
{ "Key 10", AnalogType::LinearPositive },
|
||||||
"Key 12",
|
{ "Key 11", AnalogType::LinearPositive },
|
||||||
"Key 13",
|
{ "Key 12", AnalogType::LinearPositive },
|
||||||
"Key 14",
|
{ "Key 13", AnalogType::LinearPositive },
|
||||||
"Key 15",
|
{ "Key 14", AnalogType::LinearPositive },
|
||||||
"Key 16",
|
{ "Key 15", AnalogType::LinearPositive },
|
||||||
"Key 17",
|
{ "Key 16", AnalogType::LinearPositive },
|
||||||
"Key 18",
|
{ "Key 17", AnalogType::LinearPositive },
|
||||||
"Key 19",
|
{ "Key 18", AnalogType::LinearPositive },
|
||||||
"Key 20",
|
{ "Key 19", AnalogType::LinearPositive },
|
||||||
"Key 21",
|
{ "Key 20", AnalogType::LinearPositive },
|
||||||
"Key 22",
|
{ "Key 21", AnalogType::LinearPositive },
|
||||||
"Key 23",
|
{ "Key 22", AnalogType::LinearPositive },
|
||||||
"Key 24",
|
{ "Key 23", AnalogType::LinearPositive },
|
||||||
"Key 25",
|
{ "Key 24", AnalogType::LinearPositive },
|
||||||
"Key 26",
|
{ "Key 25", AnalogType::LinearPositive },
|
||||||
"Key 27",
|
{ "Key 26", AnalogType::LinearPositive },
|
||||||
"Key 28"
|
{ "Key 27", AnalogType::LinearPositive },
|
||||||
);
|
{ "Key 28", AnalogType::LinearPositive }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return analogs;
|
return analogs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,11 +51,12 @@ std::vector<Analog> &games::pc::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Polaris Chord");
|
analogs = GameAPI::Analogs::getAnalogs("Polaris Chord");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using GameAPI::Analogs::AnalogType;
|
||||||
&analogs,
|
|
||||||
"Fader-L",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Fader-R"
|
{ "Fader-L", AnalogType::LinearCentered },
|
||||||
);
|
{ "Fader-R", AnalogType::LinearCentered }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -35,12 +35,13 @@ std::vector<Analog> &games::rf3d::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Road Fighters 3D");
|
analogs = GameAPI::Analogs::getAnalogs("Road Fighters 3D");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using GameAPI::Analogs::AnalogType;
|
||||||
&analogs,
|
|
||||||
"Wheel",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Accelerate",
|
{ "Wheel", AnalogType::LinearCentered },
|
||||||
"Brake"
|
{ "Accelerate", AnalogType::LinearPositive },
|
||||||
);
|
{ "Brake", AnalogType::LinearPositive }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -31,13 +31,14 @@ std::vector<Analog> &games::sc::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Steel Chronicle");
|
analogs = GameAPI::Analogs::getAnalogs("Steel Chronicle");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using GameAPI::Analogs::AnalogType;
|
||||||
&analogs,
|
|
||||||
"Left Stick X",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Left Stick Y",
|
{ "Left Stick X", AnalogType::LinearCentered },
|
||||||
"Right Stick X",
|
{ "Left Stick Y", AnalogType::LinearCentered },
|
||||||
"Right Stick Y"
|
{ "Right Stick X", AnalogType::LinearCentered },
|
||||||
);
|
{ "Right Stick Y", AnalogType::LinearCentered }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -31,11 +31,12 @@ std::vector<Analog> &games::silentscope::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Silent Scope: Bone Eater");
|
analogs = GameAPI::Analogs::getAnalogs("Silent Scope: Bone Eater");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using GameAPI::Analogs::AnalogType;
|
||||||
&analogs,
|
|
||||||
"Gun X",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Gun Y"
|
{ "Gun X", AnalogType::LinearCentered },
|
||||||
);
|
{ "Gun Y", AnalogType::LinearCentered }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -50,13 +50,14 @@ std::vector<Analog> &games::we::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Winning Eleven");
|
analogs = GameAPI::Analogs::getAnalogs("Winning Eleven");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using GameAPI::Analogs::AnalogType;
|
||||||
&analogs,
|
|
||||||
"Pad Stick Left X",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Pad Stick Left Y",
|
{ "Pad Stick Left X", AnalogType::LinearCentered },
|
||||||
"Pad Stick Right X",
|
{ "Pad Stick Left Y", AnalogType::LinearCentered },
|
||||||
"Pad Stick Right Y"
|
{ "Pad Stick Right X", AnalogType::LinearCentered },
|
||||||
);
|
{ "Pad Stick Right Y", AnalogType::LinearCentered }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -2506,31 +2506,45 @@ namespace overlay::windows {
|
|||||||
const bool value_changed =
|
const bool value_changed =
|
||||||
ImGui::SliderFloat("Sensitivity", &sensitivity, 0.f, 2.f, "%.3f");
|
ImGui::SliderFloat("Sensitivity", &sensitivity, 0.f, 2.f, "%.3f");
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
if (device->type == rawinput::HID && analog.getType() != GameAPI::Analogs::AnalogType::Circular) {
|
||||||
|
ImGui::HelpMarker(
|
||||||
|
"Adjust the analog sensitivity curve; "
|
||||||
|
"values <1.0 are less sensitive around neutral, "
|
||||||
|
"values >1.0 are more sensitive.");
|
||||||
|
} else {
|
||||||
ImGui::HelpMarker(
|
ImGui::HelpMarker(
|
||||||
"Adjust floating point multiplier to relative movement.\n\n"
|
"Adjust floating point multiplier to relative movement.\n\n"
|
||||||
"Value is squared before being multiplied (e.g., 1.44 is 2x sensitivity, 2.00 is 4x).\n\n"
|
"Value is squared before being multiplied (e.g., 1.44 is 2x sensitivity, 2.00 is 4x).");
|
||||||
"Dependent on how often the game polls for input. Intended for angular input (knobs, turntables)");
|
}
|
||||||
if (value_changed) {
|
if (value_changed) {
|
||||||
analog.setSensitivity(sensitivity * sensitivity);
|
analog.setSensitivity(sensitivity * sensitivity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (device->type == rawinput::HID || device->type == rawinput::MIDI) {
|
|
||||||
|
// hide deadzone for circular analog since it doesn't make any sense (unless in relative mode)
|
||||||
|
if ((device->type == rawinput::HID || device->type == rawinput::MIDI) &&
|
||||||
|
((analog.getType() != GameAPI::Analogs::AnalogType::Circular) || analog.isRelativeMode())) {
|
||||||
auto deadzone = analog.getDeadzone();
|
auto deadzone = analog.getDeadzone();
|
||||||
|
|
||||||
|
// for back compat (before each analog had a type)
|
||||||
|
if (deadzone < 0.f) {
|
||||||
|
deadzone = -deadzone;
|
||||||
|
analog.setDeadzone(deadzone);
|
||||||
|
}
|
||||||
|
|
||||||
const bool value_changed =
|
const bool value_changed =
|
||||||
ImGui::SliderFloat("Deadzone", &deadzone, -0.999f, 0.999f, "%.3f");
|
ImGui::SliderFloat("Deadzone", &deadzone, 0.f, 0.999f, "%.3f");
|
||||||
if (value_changed) {
|
if (value_changed) {
|
||||||
analog.setDeadzone(deadzone);
|
analog.setDeadzone(deadzone);
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::HelpMarker("Positive values specify a deadzone around the middle.\n"
|
ImGui::HelpMarker("Specify the deadzone that gets applied to at-rest (neutral) value.");
|
||||||
"Negative values specify a deadzone from the minimum value.");
|
|
||||||
|
|
||||||
// deadzone mirror
|
// deadzone mirror
|
||||||
bool deadzone_mirror = analog.getDeadzoneMirror();
|
bool deadzone_mirror = analog.getDeadzoneMirror();
|
||||||
ImGui::Checkbox("Deadzone Mirror", &deadzone_mirror);
|
ImGui::Checkbox("Deadzone Mirror", &deadzone_mirror);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::HelpMarker("Positive deadzone values cut off at edges instead.\n"
|
ImGui::HelpMarker("Apply deadzone to extreme value(s) instead of neutral.");
|
||||||
"Negative deadzone values cut off at maximum value instead.");
|
|
||||||
if (deadzone_mirror != analog.getDeadzoneMirror()) {
|
if (deadzone_mirror != analog.getDeadzoneMirror()) {
|
||||||
analog.setDeadzoneMirror(deadzone_mirror);
|
analog.setDeadzoneMirror(deadzone_mirror);
|
||||||
}
|
}
|
||||||
@@ -2549,6 +2563,8 @@ namespace overlay::windows {
|
|||||||
if (this->analogs_devices_selected >= 0) {
|
if (this->analogs_devices_selected >= 0) {
|
||||||
const auto device = this->analogs_devices.at(this->analogs_devices_selected);
|
const auto device = this->analogs_devices.at(this->analogs_devices_selected);
|
||||||
if (device->type == rawinput::HID) {
|
if (device->type == rawinput::HID) {
|
||||||
|
|
||||||
|
if (analog.getType() == GameAPI::Analogs::AnalogType::Circular) {
|
||||||
// smoothing
|
// smoothing
|
||||||
bool smoothing = analog.getSmoothing();
|
bool smoothing = analog.getSmoothing();
|
||||||
ImGui::BeginDisabled(analog.isRelativeMode());
|
ImGui::BeginDisabled(analog.isRelativeMode());
|
||||||
@@ -2562,10 +2578,11 @@ namespace overlay::windows {
|
|||||||
if (smoothing != analog.getSmoothing()) {
|
if (smoothing != analog.getSmoothing()) {
|
||||||
analog.setSmoothing(smoothing);
|
analog.setSmoothing(smoothing);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// relative input mode
|
// relative input mode
|
||||||
bool relative_analog = analog.isRelativeMode();
|
bool relative_analog = analog.isRelativeMode();
|
||||||
ImGui::Checkbox("Relative Axis (experimental)", &relative_analog);
|
ImGui::Checkbox("Relative Axis", &relative_analog);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::HelpMarker(
|
ImGui::HelpMarker(
|
||||||
"Use relative directional input instead of positional values.\n\n"
|
"Use relative directional input instead of positional values.\n\n"
|
||||||
@@ -2579,8 +2596,7 @@ namespace overlay::windows {
|
|||||||
|
|
||||||
// delay buffer
|
// delay buffer
|
||||||
int delay = analog.getDelayBufferDepth();
|
int delay = analog.getDelayBufferDepth();
|
||||||
ImGui::InputInt("Delay (experimental)", &delay, 1, 10);
|
if (ImGui::InputInt("Delay", &delay, 1, 10)) {
|
||||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
|
||||||
delay = CLAMP(delay, 0, 256);
|
delay = CLAMP(delay, 0, 256);
|
||||||
analog.setDelayBufferDepth(delay);
|
analog.setDelayBufferDepth(delay);
|
||||||
}
|
}
|
||||||
@@ -2600,10 +2616,12 @@ namespace overlay::windows {
|
|||||||
ImGui::ProgressBar(value);
|
ImGui::ProgressBar(value);
|
||||||
|
|
||||||
// centered knob preview
|
// centered knob preview
|
||||||
|
if (analog.getType() == GameAPI::Analogs::AnalogType::Circular) {
|
||||||
const float knob_size = 64.f;
|
const float knob_size = 64.f;
|
||||||
auto width = ImGui::GetContentRegionAvail().x - knob_size;
|
auto width = ImGui::GetContentRegionAvail().x - knob_size;
|
||||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (width / 2));
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (width / 2));
|
||||||
ImGui::Knob(value, knob_size);
|
ImGui::Knob(value, knob_size);
|
||||||
|
}
|
||||||
|
|
||||||
// update analog
|
// update analog
|
||||||
if (analogs_devices_selected >= 0 && analogs_devices_selected < (int) analogs_devices.size()) {
|
if (analogs_devices_selected >= 0 && analogs_devices_selected < (int) analogs_devices.size()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user