idz, idac, swdc: Added separate pedals config, better XInput controls

- Configure separate pedals which are not part of the steering wheel (base)
 - It is now possible to have a different steering wheel, pedal and shifter brand all connected to 3 different USB ports
- XInput does not have a deadzone at the end of the max steering anymore and the `restrict` setting works as intended
This commit is contained in:
2023-10-05 00:46:54 +02:00
parent 6c45d0995b
commit d521eeb43e
18 changed files with 450 additions and 142 deletions

View File

@ -24,6 +24,14 @@ void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename)
_countof(cfg->device_name),
filename);
GetPrivateProfileStringW(
L"dinput",
L"pedalsName",
L"",
cfg->pedals_name,
_countof(cfg->pedals_name),
filename);
GetPrivateProfileStringW(
L"dinput",
L"brakeAxis",

View File

@ -6,6 +6,7 @@
struct swdc_di_config {
wchar_t device_name[64];
wchar_t pedals_name[64];
wchar_t brake_axis[16];
wchar_t accel_axis[16];
uint8_t start;

View File

@ -25,6 +25,9 @@ static const struct swdc_di_axis *swdc_di_get_axis(const wchar_t *name);
static BOOL CALLBACK swdc_di_enum_callback(
const DIDEVICEINSTANCEW *dev,
void *ctx);
static BOOL CALLBACK swdc_di_enum_callback_pedals(
const DIDEVICEINSTANCEW *dev,
void *ctx);
static BOOL CALLBACK swdc_di_enum_callback_shifter(
const DIDEVICEINSTANCEW *dev,
void *ctx);
@ -52,6 +55,7 @@ static const struct swdc_io_backend swdc_di_backend = {
static HWND swdc_di_wnd;
static IDirectInput8W *swdc_di_api;
static IDirectInputDevice8W *swdc_di_dev;
static IDirectInputDevice8W *swdc_di_pedals;
static IDirectInputEffect *swdc_di_fx;
static size_t swdc_di_off_brake;
static size_t swdc_di_off_accel;
@ -63,6 +67,7 @@ static uint8_t swdc_di_wheel_green;
static uint8_t swdc_di_wheel_red;
static uint8_t swdc_di_wheel_blue;
static uint8_t swdc_di_wheel_yellow;
static bool swdc_di_use_pedals;
static bool swdc_di_reverse_brake_axis;
static bool swdc_di_reverse_accel_axis;
@ -165,6 +170,37 @@ HRESULT swdc_di_init(
swdc_di_dev_start_fx(swdc_di_dev, &swdc_di_fx);
if (cfg->pedals_name[0] != L'\0') {
hr = IDirectInput8_EnumDevices(
swdc_di_api,
DI8DEVCLASS_GAMECTRL,
swdc_di_enum_callback_pedals,
(void *) cfg,
DIEDFL_ATTACHEDONLY);
if (FAILED(hr)) {
dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr);
return hr;
}
if (swdc_di_dev == NULL) {
dprintf("Pedals: Controller not found\n");
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
hr = swdc_di_dev_start(swdc_di_pedals, swdc_di_wnd);
if (FAILED(hr)) {
return hr;
}
swdc_di_use_pedals = true;
} else {
swdc_di_use_pedals = false;
}
dprintf("DirectInput: Controller initialized\n");
*backend = &swdc_di_backend;
@ -246,8 +282,10 @@ static HRESULT swdc_di_config_apply(const struct swdc_di_config *cfg)
dprintf("Wheel: --- Begin configuration ---\n");
dprintf("Wheel: Device name . . . . : Contains \"%S\"\n",
cfg->device_name);
dprintf("Wheel: Brake axis . . . . . . : %S\n", brake_axis->name);
dprintf("Wheel: Accel axis . . . . . . : %S\n", accel_axis->name);
if (cfg->pedals_name[0] == L'\0') {
dprintf("Wheel: Brake axis . . . . : %S\n", brake_axis->name);
dprintf("Wheel: Accel axis . . . . : %S\n", accel_axis->name);
}
dprintf("Wheel: Start button . . . . . : %i\n", cfg->start);
dprintf("Wheel: View Change button . . : %i\n", cfg->view_chg);
dprintf("Wheel: Paddle Left button . . : %i\n", cfg->paddle_left);
@ -260,6 +298,15 @@ static HRESULT swdc_di_config_apply(const struct swdc_di_config *cfg)
dprintf("Wheel: Reverse Accel Axis . . : %i\n", cfg->reverse_accel_axis);
dprintf("Wheel: --- End configuration ---\n");
if (cfg->pedals_name[0] != L'\0') {
dprintf("Pedals: --- Begin configuration ---\n");
dprintf("Pedals: Device name . . . : Contains \"%S\"\n",
cfg->pedals_name);
dprintf("Pedals: Brake axis . . . . : %S\n", brake_axis->name);
dprintf("Pedals: Accel axis . . . . : %S\n", accel_axis->name);
dprintf("Pedals: --- End configuration ---\n");
}
swdc_di_off_brake = brake_axis->off;
swdc_di_off_accel = accel_axis->off;
swdc_di_start = cfg->start;
@ -320,6 +367,34 @@ static BOOL CALLBACK swdc_di_enum_callback(
return DIENUM_STOP;
}
static BOOL CALLBACK swdc_di_enum_callback_pedals(
const DIDEVICEINSTANCEW *dev,
void *ctx)
{
const struct swdc_di_config *cfg;
HRESULT hr;
cfg = ctx;
if (wcsstr(dev->tszProductName, cfg->pedals_name) == NULL) {
return DIENUM_CONTINUE;
}
dprintf("Pedals: Using DirectInput device \"%S\"\n", dev->tszProductName);
hr = IDirectInput8_CreateDevice(
swdc_di_api,
&dev->guidInstance,
&swdc_di_pedals,
NULL);
if (FAILED(hr)) {
dprintf("Pedals: CreateDevice failed: %08x\n", (int) hr);
}
return DIENUM_STOP;
}
static void swdc_di_get_buttons(uint16_t *gamebtn_out)
{
union swdc_di_state state;
@ -389,6 +464,7 @@ static uint8_t swdc_di_decode_pov(DWORD pov)
static void swdc_di_get_analogs(struct swdc_io_analog_state *out)
{
union swdc_di_state state;
union swdc_di_state pedals_state;
const LONG *brake;
const LONG *accel;
HRESULT hr;
@ -401,8 +477,19 @@ static void swdc_di_get_analogs(struct swdc_io_analog_state *out)
return;
}
brake = (LONG *) &state.bytes[swdc_di_off_brake];
accel = (LONG *) &state.bytes[swdc_di_off_accel];
if (swdc_di_use_pedals) {
hr = swdc_di_dev_poll(swdc_di_pedals, swdc_di_wnd, &pedals_state);
if (FAILED(hr)) {
return;
}
brake = (LONG *) &pedals_state.bytes[swdc_di_off_brake];
accel = (LONG *) &pedals_state.bytes[swdc_di_off_accel];
} else {
brake = (LONG *) &state.bytes[swdc_di_off_brake];
accel = (LONG *) &state.bytes[swdc_di_off_accel];
}
out->wheel = state.st.lX - 32768;

View File

@ -28,6 +28,11 @@ static bool swdc_xi_linear_steering;
static uint16_t swdc_xi_left_stick_deadzone;
static uint16_t swdc_xi_right_stick_deadzone;
const uint16_t max_stick_value = 32767;
/* Apply steering wheel restriction. Real cabs only report about 76% of
the output value when the wheel is turned to either of its maximum positions. */
const uint16_t max_wheel_value = 24831;
HRESULT swdc_xi_init(const struct swdc_xi_config *cfg, const struct swdc_io_backend **backend)
{
HRESULT hr;
@ -143,29 +148,39 @@ static void swdc_xi_get_gamebtns(uint16_t *gamebtn_out)
*gamebtn_out = gamebtn;
}
static int apply_non_linear_transform(int value, int deadzone_center) {
const int max_input = 32767;
const double power_factor = 3.0;
static int16_t calculate_norm_steering(int16_t axis, uint16_t deadzone, bool linear_steering) {
// determine how far the controller is pushed
float magnitude = sqrt(axis*axis);
// Apply deadzone only after passing the center threshold
if (abs(value) < deadzone_center) {
return 0;
// determine the direction the controller is pushed
float norm_axis = axis / magnitude;
float norm_magnitude = 0.0;
// check if the controller is outside a circular dead zone
if (magnitude > deadzone)
{
// clip the magnitude at its expected maximum value
if (magnitude > max_stick_value) magnitude = max_stick_value;
// adjust magnitude relative to the end of the dead zone
magnitude -= deadzone;
// optionally normalize the magnitude with respect to its expected range
// giving a magnitude value of 0.0 to 1.0
norm_magnitude = magnitude / (max_stick_value - deadzone);
} else // if the controller is in the deadzone zero out the magnitude
{
magnitude = 0.0;
norm_magnitude = 0.0;
}
// Scale the value to the range [-1.0, 1.0]
double scaled_value = (abs(value) - deadzone_center) / (double)(max_input - deadzone_center);
// apply non-linear transform to the axis
if (!linear_steering) {
return norm_axis * pow(norm_magnitude, 3.0) * max_wheel_value;
}
// Apply a non-linear transform (cubing in this case) and preserve the sign
double signed_value = copysign(pow(scaled_value, power_factor), value);
// Scale the value back to the range [-32770, 32767]
int transformed_value = (int)(signed_value * max_input);
// Clamp the value to the range [-32767, 32767]
transformed_value = (transformed_value > max_input) ? max_input : transformed_value;
transformed_value = (transformed_value < -max_input) ? -max_input : transformed_value;
return transformed_value;
return norm_axis * norm_magnitude * max_wheel_value;
}
static void swdc_xi_get_analogs(struct swdc_io_analog_state *out)
@ -182,28 +197,10 @@ static void swdc_xi_get_analogs(struct swdc_io_analog_state *out)
left = xi.Gamepad.sThumbLX;
right = xi.Gamepad.sThumbRX;
if (!swdc_xi_linear_steering) {
// Apply non-linear transform for both sticks
left = apply_non_linear_transform(left, swdc_xi_left_stick_deadzone);
right = apply_non_linear_transform(right, swdc_xi_right_stick_deadzone);
} else {
if (left < -swdc_xi_left_stick_deadzone) {
left += swdc_xi_left_stick_deadzone;
} else if (left > swdc_xi_left_stick_deadzone) {
left -= swdc_xi_left_stick_deadzone;
} else {
left = 0;
}
if (right < -swdc_xi_right_stick_deadzone) {
right += swdc_xi_right_stick_deadzone;
} else if (right > swdc_xi_right_stick_deadzone) {
right -= swdc_xi_right_stick_deadzone;
} else {
right = 0;
}
}
// normalize the steering axis
left = calculate_norm_steering(left, swdc_xi_left_stick_deadzone, swdc_xi_linear_steering);
right = calculate_norm_steering(right, swdc_xi_right_stick_deadzone, swdc_xi_linear_steering);
if (swdc_xi_single_stick_steering) {
out->wheel = left;
} else {