diff --git a/DS4Windows/DS4Control/Mapping.cs b/DS4Windows/DS4Control/Mapping.cs index 84ce6d8..c8f8da2 100644 --- a/DS4Windows/DS4Control/Mapping.cs +++ b/DS4Windows/DS4Control/Mapping.cs @@ -1650,8 +1650,10 @@ namespace DS4Windows } } - if (getSASteeringWheelEmulationAxis(device) != SASteeringWheelEmulationAxisType.None) + if (GetSASteeringWheelEmulationAxis(device) != SASteeringWheelEmulationAxisType.None) + { MappedState.SASteeringWheelEmulationUnit = Mapping.Scale360degreeGyroAxis(device, eState, ctrl); + } calculateFinalMouseMovement(ref tempMouseDeltaX, ref tempMouseDeltaY, out mouseDeltaX, out mouseDeltaY); @@ -3915,6 +3917,8 @@ namespace DS4Windows gyroAccelX = exposedState.getAccelX(); gyroAccelZ = exposedState.getAccelZ(); + //gyroAccelX = exposedState.OutputAccelX; + //gyroAccelZ = exposedState.OutputAccelZ; // State 0=Normal mode (ie. calibration process is not running), 1=Activating calibration, 2=Calibration process running, 3=Completing calibration, 4=Cancelling calibration if (controller.WheelRecalibrateActiveState == 1) @@ -4057,6 +4061,8 @@ namespace DS4Windows gyroAccelX = exposedState.getAccelX(); gyroAccelZ = exposedState.getAccelZ(); + //gyroAccelX = exposedState.OutputAccelX; + //gyroAccelZ = exposedState.OutputAccelZ; // If calibration values are missing then use "educated guesses" about good starting values if (controller.wheelCenterPoint.IsEmpty) @@ -4086,11 +4092,19 @@ namespace DS4Windows } - int maxRangeRight = Global.getSASteeringWheelEmulationRange(device) / 2 * C_WHEEL_ANGLE_PRECISION; + int maxRangeRight = Global.GetSASteeringWheelEmulationRange(device) / 2 * C_WHEEL_ANGLE_PRECISION; int maxRangeLeft = -maxRangeRight; result = CalculateControllerAngle(gyroAccelX, gyroAccelZ, controller); + // Apply deadzone (SA X-deadzone value). This code assumes that 20deg is the max deadzone anyone ever might wanna use (in practice effective deadzone + // is probably just few degrees by using SXDeadZone values 0.01...0.05) + double sxDead = getSXDeadzone(device); + if ( sxDead > 0 && result != 0 && Math.Abs(result) < (20.0 * C_WHEEL_ANGLE_PRECISION * sxDead) ) + { + result = 0; + } + // If wrapped around from +180 to -180 side (or vice versa) then SA steering wheel keeps on turning beyond 360 degrees (if range is >360) int wheelFullTurnCount = controller.wheelFullTurnCount; if (controller.wheelPrevPhysicalAngle < 0 && result > 0) @@ -4134,28 +4148,76 @@ namespace DS4Windows result = Mapping.ClampInt(maxRangeLeft, result, maxRangeRight); // Debug log output of SA sensor values - //LogToGuiSACalibrationDebugMsg($"DEBUG gyro=({gyroAccelX}, {gyroAccelZ}) gyroPitchRollYaw=({currentDeviceState.Motion.gyroPitch}, {currentDeviceState.Motion.gyroRoll}, {currentDeviceState.Motion.gyroYaw}) gyroPitchRollYaw=({currentDeviceState.Motion.angVelPitch}, {currentDeviceState.Motion.angVelRoll}, {currentDeviceState.Motion.angVelYaw}) angle={result / (1.0 * C_WHEEL_ANGLE_PRECISION)} fullTurns={controller.wheelFullTurnCount}", false); + //LogToGuiSACalibrationDebugMsg($"DBG gyro=({gyroAccelX}, {gyroAccelZ}) output=({exposedState.OutputAccelX}, {exposedState.OutputAccelZ}) PitRolYaw=({currentDeviceState.Motion.gyroPitch}, {currentDeviceState.Motion.gyroRoll}, {currentDeviceState.Motion.gyroYaw}) VelPitRolYaw=({currentDeviceState.Motion.angVelPitch}, {currentDeviceState.Motion.angVelRoll}, {currentDeviceState.Motion.angVelYaw}) angle={result / (1.0 * C_WHEEL_ANGLE_PRECISION)} fullTurns={controller.wheelFullTurnCount}", false); - // Scale input to a raw x360 16bit output scale, except if output axis of steering wheel emulation is L2+R2 trigger axis. - switch(Global.getSASteeringWheelEmulationAxis(device)) + // Apply anti-deadzone (SA X-antideadzone value) + double sxAntiDead = getSXAntiDeadzone(device); + + switch (Global.GetSASteeringWheelEmulationAxis(device)) { case SASteeringWheelEmulationAxisType.LX: case SASteeringWheelEmulationAxisType.LY: case SASteeringWheelEmulationAxisType.RX: case SASteeringWheelEmulationAxisType.RY: // DS4 thumbstick axis output (-32768..32767 raw value range) - return (((result - maxRangeLeft) * (32767 - (-32768))) / (maxRangeRight - maxRangeLeft)) + (-32768); + //return (((result - maxRangeLeft) * (32767 - (-32768))) / (maxRangeRight - maxRangeLeft)) + (-32768); + if (result == 0) return 0; + + if (sxAntiDead > 0) + { + sxAntiDead *= 32767; + if (result < 0) return (((result - maxRangeLeft) * (-Convert.ToInt32(sxAntiDead) - (-32768))) / (0 - maxRangeLeft)) + (-32768); + else return (((result - 0) * (32767 - (Convert.ToInt32(sxAntiDead)))) / (maxRangeRight - 0)) + (Convert.ToInt32(sxAntiDead)); + } + else + { + return (((result - maxRangeLeft) * (32767 - (-32768))) / (maxRangeRight - maxRangeLeft)) + (-32768); + } case SASteeringWheelEmulationAxisType.L2R2: // DS4 Trigger axis output. L2+R2 triggers share the same axis in x360 xInput/DInput controller, // so L2+R2 steering output supports only 360 turn range (-255..255 raw value range in the shared trigger axis) + // return (((result - (-180)) * (255 - (-255))) / (180 - (-180))) + (-255); + if (result == 0) return 0; + result = Convert.ToInt32(Math.Round(result / (1.0 * C_WHEEL_ANGLE_PRECISION))); if (result < 0) result = -181 - result; - return (((result - (-180)) * (255 - (-255))) / (180 - (-180))) + (-255); + + if (sxAntiDead > 0) + { + sxAntiDead *= 255; + if(result < 0) return (((result - (-180)) * (-Convert.ToInt32(sxAntiDead) - (-255))) / (0 - (-180))) + (-255); + else return (((result - (0)) * (255 - (Convert.ToInt32(sxAntiDead)))) / (180 - (0))) + (Convert.ToInt32(sxAntiDead)); + } + else + { + return (((result - (-180)) * (255 - (-255))) / (180 - (-180))) + (-255); + } + + case SASteeringWheelEmulationAxisType.VJoy1X: + case SASteeringWheelEmulationAxisType.VJoy1Y: + case SASteeringWheelEmulationAxisType.VJoy1Z: + case SASteeringWheelEmulationAxisType.VJoy2X: + case SASteeringWheelEmulationAxisType.VJoy2Y: + case SASteeringWheelEmulationAxisType.VJoy2Z: + // SASteeringWheelEmulationAxisType.VJoy1X/VJoy1Y/VJoy1Z/VJoy2X/VJoy2Y/VJoy2Z VJoy axis output (0..32767 raw value range by default) + // return (((result - maxRangeLeft) * (32767 - (-0))) / (maxRangeRight - maxRangeLeft)) + (-0); + if (result == 0) return 16384; + + if (sxAntiDead > 0) + { + sxAntiDead *= 16384; + if (result < 0) return (((result - maxRangeLeft) * (16384 - Convert.ToInt32(sxAntiDead) - (-0))) / (0 - maxRangeLeft)) + (-0); + else return (((result - 0) * (32767 - (16384 + Convert.ToInt32(sxAntiDead)))) / (maxRangeRight - 0)) + (16384 + Convert.ToInt32(sxAntiDead)); + } + else + { + return (((result - maxRangeLeft) * (32767 - (-0))) / (maxRangeRight - maxRangeLeft)) + (-0); + } default: - // SASteeringWheelEmulationAxisType.VJoy1X/VJoy1Y/VJoy1Z/VJoy2X/VJoy2Y/VJoy2Z VJoy axis output (0..32767 raw value range by default) - return (((result - maxRangeLeft) * (32767 - (-0))) / (maxRangeRight - maxRangeLeft)) + (-0); + // Should never come here, but C# case statement syntax requires DEFAULT handler + return 0; } } } diff --git a/DS4Windows/DS4Control/ScpUtil.cs b/DS4Windows/DS4Control/ScpUtil.cs index a5f7660..d6af7e1 100644 --- a/DS4Windows/DS4Control/ScpUtil.cs +++ b/DS4Windows/DS4Control/ScpUtil.cs @@ -721,13 +721,13 @@ namespace DS4Windows } public static SASteeringWheelEmulationAxisType[] SASteeringWheelEmulationAxis => m_Config.sASteeringWheelEmulationAxis; - public static SASteeringWheelEmulationAxisType getSASteeringWheelEmulationAxis(int index) + public static SASteeringWheelEmulationAxisType GetSASteeringWheelEmulationAxis(int index) { return m_Config.sASteeringWheelEmulationAxis[index]; } public static int[] SASteeringWheelEmulationRange => m_Config.sASteeringWheelEmulationRange; - public static int getSASteeringWheelEmulationRange(int index) + public static int GetSASteeringWheelEmulationRange(int index) { return m_Config.sASteeringWheelEmulationRange[index]; } diff --git a/DS4Windows/DS4Control/X360Device.cs b/DS4Windows/DS4Control/X360Device.cs index a8244ee..e0873e2 100644 --- a/DS4Windows/DS4Control/X360Device.cs +++ b/DS4Windows/DS4Control/X360Device.cs @@ -129,7 +129,7 @@ namespace DS4Windows if (state.PS) Output[11] |= (Byte)(1 << 2); // Guide - SASteeringWheelEmulationAxisType steeringWheelMappedAxis = Global.getSASteeringWheelEmulationAxis(device); + SASteeringWheelEmulationAxisType steeringWheelMappedAxis = Global.GetSASteeringWheelEmulationAxis(device); Int32 ThumbLX; Int32 ThumbLY; Int32 ThumbRX; @@ -140,6 +140,13 @@ namespace DS4Windows switch(steeringWheelMappedAxis) { + case SASteeringWheelEmulationAxisType.None: + ThumbLX = Scale(state.LX, false); + ThumbLY = Scale(state.LY, true); + ThumbRX = Scale(state.RX, false); + ThumbRY = Scale(state.RY, true); + break; + case SASteeringWheelEmulationAxisType.LX: ThumbLX = state.SASteeringWheelEmulationUnit; ThumbLY = Scale(state.LY, true); @@ -172,29 +179,26 @@ namespace DS4Windows Output[12] = Output[13] = 0; if (state.SASteeringWheelEmulationUnit >= 0) Output[12] = (Byte)state.SASteeringWheelEmulationUnit; else Output[13] = (Byte)state.SASteeringWheelEmulationUnit; - goto default; // Usually GOTO is not a good idea but in switch-case statements it is sometimes pretty handy and acceptable way to fall through case options + goto case SASteeringWheelEmulationAxisType.None; case SASteeringWheelEmulationAxisType.VJoy1X: case SASteeringWheelEmulationAxisType.VJoy2X: DS4Windows.VJoyFeeder.vJoyFeeder.FeedAxisValue(state.SASteeringWheelEmulationUnit, ((((uint)steeringWheelMappedAxis) - ((uint)SASteeringWheelEmulationAxisType.VJoy1X)) / 3) + 1, DS4Windows.VJoyFeeder.HID_USAGES.HID_USAGE_X); - goto default; + goto case SASteeringWheelEmulationAxisType.None; case SASteeringWheelEmulationAxisType.VJoy1Y: case SASteeringWheelEmulationAxisType.VJoy2Y: DS4Windows.VJoyFeeder.vJoyFeeder.FeedAxisValue(state.SASteeringWheelEmulationUnit, ((((uint)steeringWheelMappedAxis) - ((uint)SASteeringWheelEmulationAxisType.VJoy1X)) / 3) + 1, DS4Windows.VJoyFeeder.HID_USAGES.HID_USAGE_Y); - goto default; + goto case SASteeringWheelEmulationAxisType.None; case SASteeringWheelEmulationAxisType.VJoy1Z: case SASteeringWheelEmulationAxisType.VJoy2Z: DS4Windows.VJoyFeeder.vJoyFeeder.FeedAxisValue(state.SASteeringWheelEmulationUnit, ((((uint)steeringWheelMappedAxis) - ((uint)SASteeringWheelEmulationAxisType.VJoy1X)) / 3) + 1, DS4Windows.VJoyFeeder.HID_USAGES.HID_USAGE_Z); - goto default; + goto case SASteeringWheelEmulationAxisType.None; default: - ThumbLX = Scale(state.LX, false); - ThumbLY = Scale(state.LY, true); - ThumbRX = Scale(state.RX, false); - ThumbRY = Scale(state.RY, true); - break; + // Should never come here but just in case use the NONE case as default handler.... + goto case SASteeringWheelEmulationAxisType.None; } Output[14] = (Byte)((ThumbLX >> 0) & 0xFF); // LX diff --git a/DS4Windows/DS4Forms/Options.cs b/DS4Windows/DS4Forms/Options.cs index 16f5001..a4d8b48 100644 --- a/DS4Windows/DS4Forms/Options.cs +++ b/DS4Windows/DS4Forms/Options.cs @@ -719,9 +719,9 @@ namespace DS4Windows cBGyroMouseXAxis.SelectedIndex = GyroMouseHorizontalAxis[device]; triggerCondAndCombo.SelectedIndex = SATriggerCond[device] ? 0 : 1; - cBSteeringWheelEmulationAxis.SelectedIndex = (int) getSASteeringWheelEmulationAxis(device); + cBSteeringWheelEmulationAxis.SelectedIndex = (int) GetSASteeringWheelEmulationAxis(device); - int idxSASteeringWheelEmulationRange = cBSteeringWheelEmulationRange.Items.IndexOf(getSASteeringWheelEmulationRange(device).ToString()); + int idxSASteeringWheelEmulationRange = cBSteeringWheelEmulationRange.Items.IndexOf(GetSASteeringWheelEmulationRange(device).ToString()); if (idxSASteeringWheelEmulationRange >= 0) cBSteeringWheelEmulationRange.SelectedIndex = idxSASteeringWheelEmulationRange; } else diff --git a/DS4Windows/VJoyFeeder/vJoyFeeder.cs b/DS4Windows/VJoyFeeder/vJoyFeeder.cs index 8304cbb..fcee9ff 100644 --- a/DS4Windows/VJoyFeeder/vJoyFeeder.cs +++ b/DS4Windows/VJoyFeeder/vJoyFeeder.cs @@ -4,7 +4,7 @@ // https://github.com/shauleiz/vJoy/tree/master/apps/common/vJoyInterfaceCS // // This module is a feeder for VJoy virtual joystick driver. DS4Windows can optionally re-map and feed buttons and analog axis values from DS4 Controller to VJoy device. -// At first this may seem silly because DS4Windows can already to re-mapping by using a virtual X360 Controller driver, so why feed VJoy virtual driver also? +// At first this may seem silly because DS4Windows can already do re-mapping by using a virtual X360 Controller driver, so why feed VJoy virtual driver also? // Sometimes X360 driver may run out of analog axis options, so for example "SA motion sensor steering wheel emulation" in DS4Windows would reserve a thumbstick X or Y // axis for SA steering wheel emulation usage. That thumbstick axis would be unavailable for "normal" thumbstick usage after this re-mapping. // The problem can be solved by configuring DS4Windows to re-map SA steering wheel emulation axis to VJoy axis, so all analog axies in DS4 controller are still available for normal usage. @@ -12,6 +12,7 @@ using System; using System.Runtime.InteropServices; +using System.Security; // SuppressUnmanagedCodeSecurity support to optimize for performance instead of code security namespace DS4Windows.VJoyFeeder { @@ -105,7 +106,8 @@ namespace DS4Windows.VJoyFeeder //namespace vJoyInterfaceWrap //{ - public class vJoy + [SuppressUnmanagedCodeSecurity] + public class vJoy { /***************************************************/ @@ -698,12 +700,17 @@ namespace DS4Windows.VJoyFeeder // Feed axis value to VJoy virtual joystic driver (DS4Windows sixaxis (SA) motion sensor steering wheel emulation feature can optionally feed VJoy analog axis instead of ScpVBus x360 axis public static void FeedAxisValue(int value, uint vJoyID, HID_USAGES axis) { - if (!vJoyInitialized) - InitializeVJoyDevice(vJoyID, axis); - if (vJoyAvailable) + { vJoyObj.SetAxis(value, vJoyID, axis); + } + else if (!vJoyInitialized) + { + // If this was the first call to this FeedAxisValue function and VJoy driver connection is not yet initialized + // then try to do it now. Subsequent calls will see the the vJoy as available (if connection succeeded) and + // there is no need to re-initialize the connection everytime the feeder is used. + InitializeVJoyDevice(vJoyID, axis); + } } - } }