From a933eae0a92b29078e71936e8d465cb5af3ffba9 Mon Sep 17 00:00:00 2001 From: mika-n Date: Sat, 17 Nov 2018 01:41:21 +0200 Subject: [PATCH 01/14] New "360 degree gyro steering wheel emulation" functionality. This works best if the DS4 controller is mounted on a "DoItYourself steering wheel rig" (ie. controller attached at a tip of a plastic or wooden pipe which acts as a "steering shaft". This way the controller turns around like a steering wheel and gyro sensor values are more consistent). At this point there is no GUI to enable this, so you should edit a profile XML file (fex default.xml profile) with Notepad and add LXPos entry. Accepted values are None, LXPos, LYPos, RXPos, RYPos) which indicates which X360 axis is used for steering wheel values (ie. gyro tilt converted as steering wheel turning range values). The normal behaviour of this axis should be set as "unmapped" to avoid conflicting values. If steering wheel axis is LX then LY axis is still available for other purposes. --- DS4Windows/DS4Control/Mapping.cs | 261 ++++++++++++++++++++++++++++ DS4Windows/DS4Control/ScpUtil.cs | 142 ++++++++++++++- DS4Windows/DS4Control/X360Device.cs | 25 ++- DS4Windows/DS4Library/DS4Device.cs | 28 +++ DS4Windows/DS4Library/DS4State.cs | 4 + 5 files changed, 455 insertions(+), 5 deletions(-) diff --git a/DS4Windows/DS4Control/Mapping.cs b/DS4Windows/DS4Control/Mapping.cs index 32671b7..cfad5a4 100644 --- a/DS4Windows/DS4Control/Mapping.cs +++ b/DS4Windows/DS4Control/Mapping.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using System.Diagnostics; using static DS4Windows.Global; +using System.Drawing; // Point struct + namespace DS4Windows { public class Mapping @@ -1648,6 +1650,9 @@ namespace DS4Windows } } + if (getSASteeringWheelEmulationAxis(device) != DS4Controls.None) + MappedState.SASteeringWheelEmulationUnit = Mapping.Scale360degreeGyroAxis(device, eState, ctrl); + calculateFinalMouseMovement(ref tempMouseDeltaX, ref tempMouseDeltaY, out mouseDeltaX, out mouseDeltaY); if (mouseDeltaX != 0 || mouseDeltaY != 0) @@ -3773,5 +3778,261 @@ namespace DS4Windows fieldMap.buttons[controlNum] = false; } } + + // BEGIN: SixAxis steering wheel emulation logic + + // "In-game" calibration process: + // TODO: Launching a calibration process should probably be a special action which allows multiple key bindings to launch a specific task. + // - Place controller at "steering wheel center" position and press DS4 Option button to start the calibration (Profile should have "SASteeringWheelEmulationAxis" option set to LXPos, LYPos, RXPos or RYPos value). + // - Hold the controller still for a while and wait until red lightbar turns to blinking yellow (center point calibrated) + // - Turn the controller at 90 degree left or right position and hold still for few seconds and wait until lightbar turns to blinking light blue (two points calibrated) + // - Turn the controller at 90 degree position on the opposite side (but do it by going through 0 degree center position. Don't go through 180deg mark). Wait until lighbar turns to green (three points calibrated) + // - Now you can check the calibratio by turning the wheel and see when the green lightbar starts to blink (it should blink at those three calibrated positions). + // - Press DS4 Options button to accept the calibration (result is saved to ControllerConfigs.xml xml file in AppData folder). + // + // 0 = None of the anchors calibrated yet. Hold controller still at "wheel center position" and wait until lightbar turns from constant red to flashing yellow color + // 1 = one anchor calibrated (The first calibration point should always be the center position) + // 2 = two anchors calibrated (center and 90Right or 90Left depending on which way user turn the wheel after the first center calibration point) + // 3 = all three anchor points calibrated (center, 90Right, 90Left). Good to go. User can check calibration by turning the wheel and checking when the green lightbar blinks. If happy then pressing Options btn accepts the calibration. + private static readonly DS4Color calibrationColor_0 = new DS4Color { red = 0xA0, green = 0x00, blue = 0x00 }; + private static readonly DS4Color calibrationColor_1 = new DS4Color { red = 0xFF, green = 0xFF, blue = 0x00 }; + private static readonly DS4Color calibrationColor_2 = new DS4Color { red = 0x00, green = 0x50, blue = 0x50 }; + private static readonly DS4Color calibrationColor_3 = new DS4Color { red = 0x00, green = 0xC0, blue = 0x00 }; + + private static DateTime latestDebugMsgTime; + private static void LogToGuiSACalibrationDebugMsg(string data) + { + // Print debug calibration log messages only once per 2 secs to avoid flooding the log receiver + DateTime curTime = DateTime.Now; + if (((TimeSpan)(curTime - latestDebugMsgTime)).TotalSeconds > 2) + { + latestDebugMsgTime = curTime; + AppLogger.LogToGui(data, false); + } + } + + // Return number of bits set in a value + protected static int CountNumOfSetBits(int bitValue) + { + int count = 0; + while (bitValue != 0) + { + count++; + bitValue &= (bitValue - 1); + } + return count; + } + + // Calculate and return the angle of the controller as -180...0...+180 value. + // TODO: Support >360 degree turn range by adding "lap counter" when wheel is rotated full rounds left or right.At the moment this logic supports only 360 degree turn range. + protected static Int32 CalculateControllerAngle(int gyroAccelX, int gyroAccelZ, DS4Device controller) + { + Int32 result; + + if (Math.Abs(gyroAccelX - controller.wheelCenterPoint.X) <= 1 && Math.Abs(gyroAccelZ - controller.wheelCenterPoint.Y) <= 1) + { + // When the current gyro position is "close enough" the wheel center point then no need to go through the hassle of calculating an angle + result = 0; + } + else + { + // Calculate two vectors based on "circle center" (ie. circle represents the 360 degree wheel turn and wheelCenterPoint and currentPosition vectors both start from circle center). + // To improve accuracy both left and right turns use a decicated calibration "circle" because DS4 gyro and DoItYourselfWheelRig may return slightly different SA sensor values depending on the tilt direction (well, only one or two degree difference so nothing major). + Point vectorAB; + Point vectorCD; + + if (gyroAccelX >= controller.wheelCenterPoint.X) + { + // "DS4 gyro wheel" tilted to right + vectorAB = new Point(controller.wheelCenterPoint.X - controller.wheelCircleCenterPointRight.X, controller.wheelCenterPoint.Y - controller.wheelCircleCenterPointRight.Y); + vectorCD = new Point(gyroAccelX - controller.wheelCircleCenterPointRight.X, gyroAccelZ - controller.wheelCircleCenterPointRight.Y); + } + else + { + // "DS4 gyro wheel" tilted to left + vectorAB = new Point(controller.wheelCenterPoint.X - controller.wheelCircleCenterPointLeft.X, controller.wheelCenterPoint.Y - controller.wheelCircleCenterPointLeft.Y); + vectorCD = new Point(gyroAccelX - controller.wheelCircleCenterPointLeft.X, gyroAccelZ - controller.wheelCircleCenterPointLeft.Y); + } + + // Calculate dot product and magnitude of vectors (center vector and the current tilt vector) + double dotProduct = vectorAB.X * vectorCD.X + vectorAB.Y * vectorCD.Y; + double magAB = Math.Sqrt(vectorAB.X * vectorAB.X + vectorAB.Y * vectorAB.Y); + double magCD = Math.Sqrt(vectorCD.X * vectorCD.X + vectorCD.Y * vectorCD.Y); + + // Calculate angle between vectors and convert radian to degrees + double angle = Math.Acos(dotProduct / (magAB * magCD)); + result = Convert.ToInt32(Math.Round(angle * (180.0 / Math.PI))); + + // Left turn is -180..0 and right turn 0..180 degrees + if (gyroAccelX < controller.wheelCenterPoint.X) result = -result; + + // Just to be sure.. Probably not needed. TODO: Add support for 360/720/900 turn ranges by counting "laps" how many times the steering wheel is turned around + result = ClampInt(-180, result, 180); + } + + return result; + } + + protected static Int32 Scale360degreeGyroAxis(int device, /*DS4State state,*/ DS4StateExposed exposedState, ControlService ctrl) + { + unchecked + { + DS4Device controller; + DS4State currentDeviceState; + + int gyroAccelX, gyroAccelZ; + int result; + + //controller = Program.rootHub.DS4Controllers[device]; + controller = ctrl.DS4Controllers[device]; + if (controller == null) return 0; + + currentDeviceState = controller.getCurrentStateRef(); + + gyroAccelX = exposedState.getAccelX(); + gyroAccelZ = exposedState.getAccelZ(); + + // If DS4 Options btn is pressed and the previous re-calibration was done more than 2 secs ago (avoids repeated "calibration" loops if user holds down the Option btn too long) then re-calibrate the gyro wheel emulation. + // TODO. Maybe here should be logic to enter calibration process only if Options btn is hold down minimum of 3 secs? This way Option btn can be re-mapped to do other things without kicking on re-calibration everytime it is pressed. + if (currentDeviceState.Options && ((TimeSpan)(DateTime.Now - controller.wheelPrevRecalibrateTime)).TotalSeconds > 2) + { + if (controller.WheelRecalibrateActive == false) + { + AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} activated re-calibration of motion steering wheel emulation", false); + + controller.WheelRecalibrateActive = true; + + // Clear existing calibration value and use current position as "center" point. + // This initial center value may be off-center because of shaking the controller while button was pressed. The value will be overriden with correct value once controller is stabilized and hold still few secs at the center point + controller.wheelCenterPoint.X = gyroAccelX; + controller.wheelCenterPoint.Y = gyroAccelZ; + controller.wheel90DegPointRight.X = gyroAccelX + 25; + controller.wheel90DegPointLeft.X = gyroAccelX - 25; + + // Clear bitmask for calibration points. All three calibration points need to be set before re-calibration process is valid + controller.wheelCalibratedAxisBitmask = DS4Device.WheelCalibrationPoint.None; + } + else + { + AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} completed steering wheel calibration. center=({controller.wheelCenterPoint.X}, {controller.wheelCenterPoint.Y}) 90L=({controller.wheel90DegPointLeft.X}, {controller.wheel90DegPointLeft.Y}) 90R=({controller.wheel90DegPointRight.X}, {controller.wheel90DegPointRight.Y})", false); + + controller.WheelRecalibrateActive = false; + + // If any of the calibration points (center, left 90deg, right 90deg) are missing then reset back to default calibration values + if (((controller.wheelCalibratedAxisBitmask & DS4Device.WheelCalibrationPoint.All) == DS4Device.WheelCalibrationPoint.All)) + Global.SaveControllerConfigs(controller); + else + controller.wheelCenterPoint.X = controller.wheelCenterPoint.Y = 0; + + // Reset lightbar back to normal color + DS4LightBar.forcelight[device] = false; + DS4LightBar.forcedFlash[device] = 0; + controller.LightBarColor = Global.getMainColor(device); + DS4LightBar.updateLightBar(controller, device); + } + + controller.wheelPrevRecalibrateTime = DateTime.Now; + } + + if (controller.WheelRecalibrateActive) + { + // Auto calibrate 90deg left/right positions (these values may change over time because gyro/accel sensor values are not "precise mathematics", so user can trigger calibration even in mid-game sessions by pressing DS4 Options btn), + // but make sure controller is stable enough to avoid misaligments because of hard shaking (check velocity of gyro axis) + if (Math.Abs(/*state*/ currentDeviceState.Motion.angVelPitch) < 0.5 && Math.Abs(currentDeviceState.Motion.angVelYaw) < 0.5 && Math.Abs(currentDeviceState.Motion.angVelRoll) < 0.5) + { + if (controller.wheelCalibratedAxisBitmask == DS4Device.WheelCalibrationPoint.None) + { + // Wait few secs after re-calibration button was pressed. Hold controller still at center position and don't shake it too much until red lightbar turns to yellow. + if (((TimeSpan)(DateTime.Now - controller.wheelPrevRecalibrateTime)).TotalSeconds >= 3) + { + controller.wheelCenterPoint.X = gyroAccelX; + controller.wheelCenterPoint.Y = gyroAccelZ; + + controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Center; + } + } + else if (controller.wheel90DegPointRight.X < gyroAccelX) + { + controller.wheel90DegPointRight.X = gyroAccelX; + controller.wheel90DegPointRight.Y = gyroAccelZ; + controller.wheelCircleCenterPointRight.X = controller.wheelCenterPoint.X; + controller.wheelCircleCenterPointRight.Y = controller.wheel90DegPointRight.Y; + + controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Right90; + } + else if (controller.wheel90DegPointLeft.X > gyroAccelX) + { + controller.wheel90DegPointLeft.X = gyroAccelX; + controller.wheel90DegPointLeft.Y = gyroAccelZ; + controller.wheelCircleCenterPointLeft.X = controller.wheelCenterPoint.X; + controller.wheelCircleCenterPointLeft.Y = controller.wheel90DegPointLeft.Y; + + controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Left90; + } + } + + // Show lightbar color feedback how the calibration process is proceeding. + // red / yellow / blue / green = No calibration anchors/one anchor/two anchors/three anchors calibrated (center, 90DegLeft, 90DegRight) + // Blinking led = Controller is tilted at the current calibration point (or calibration routine just set a new anchor point) + // TODO: device num to flash led idx (use red for center calibrated, yellow for one 90deg and green for both 90deg calibration and then reset back to normal led) + int bitsSet = CountNumOfSetBits((int)controller.wheelCalibratedAxisBitmask); + if (bitsSet >= 3) DS4LightBar.forcedColor[device] = calibrationColor_3; + else if (bitsSet == 2) DS4LightBar.forcedColor[device] = calibrationColor_2; + else if (bitsSet == 1) DS4LightBar.forcedColor[device] = calibrationColor_1; + else DS4LightBar.forcedColor[device] = calibrationColor_0; + + result = CalculateControllerAngle(gyroAccelX, gyroAccelZ, controller); + if (bitsSet >= 1 && (Math.Abs(result) <= 1 || (Math.Abs(result) >= 88 && Math.Abs(result) <= 92) || Math.Abs(result) >= 178)) DS4LightBar.forcedFlash[device] = 2; + else DS4LightBar.forcedFlash[device] = 0; + + DS4LightBar.forcelight[device] = true; + //DS4LightBar.updateLightBar(controller, device); + + LogToGuiSACalibrationDebugMsg($"Calibration values ({gyroAccelX}, {gyroAccelZ}) angle={result}\n"); + + // Return center wheel position while gyro is calibrated, not the calculated angle + return 0; + } + + + // If calibration values are missing then use "educated guesses" about good starting values. + // TODO. Use pre-calibrated default values from configuration file. + if (controller.wheelCenterPoint.IsEmpty) + { + if (!Global.LoadControllerConfigs(controller)) + { + AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} motion steering wheel calibration data missing. It is recommended to run steering wheel calibration process by pressing DS4 Options button. Using estimated values until the controller is calibrated at least once.", false); + + controller.wheelCenterPoint.X = gyroAccelX; + controller.wheelCenterPoint.Y = gyroAccelZ; + + controller.wheel90DegPointRight.X = controller.wheelCenterPoint.X + 113; // 113; + controller.wheel90DegPointRight.Y = controller.wheelCenterPoint.Y + 110; // 110; + + controller.wheel90DegPointLeft.X = controller.wheelCenterPoint.X - 127; // -127; + controller.wheel90DegPointLeft.Y = controller.wheel90DegPointRight.Y; // 2; + } + + controller.wheelCircleCenterPointRight.X = controller.wheelCenterPoint.X; + controller.wheelCircleCenterPointRight.Y = controller.wheel90DegPointRight.Y; + controller.wheelCircleCenterPointLeft.X = controller.wheelCenterPoint.X; + controller.wheelCircleCenterPointLeft.Y = controller.wheel90DegPointLeft.Y; + + AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} steering wheel emulation calibration values. center=({controller.wheelCenterPoint.X}, {controller.wheelCenterPoint.Y}) 90L=({controller.wheel90DegPointLeft.X}, {controller.wheel90DegPointLeft.Y}) 90R=({controller.wheel90DegPointRight.X}, {controller.wheel90DegPointRight.Y})", false); + controller.wheelPrevRecalibrateTime = DateTime.Now; + } + + result = CalculateControllerAngle(gyroAccelX, gyroAccelZ, controller); + + // Keep outputting debug data 30secs after the latest re-calibration event (user can check these values from the log screen of DS4Windows GUI) + //if (((TimeSpan)(DateTime.Now - prevRecalibrateTime)).TotalSeconds < 30) + // LogToGuiSACalibrationDebugMsg($"DEBUG gyro=({gyroAccelX}, {gyroAccelZ}) angle={result}"); + + // Scale input to a raw x360 thumbstick output scale + return (((result - (-180)) * (32767 - (-32768))) / (180 - (-180))) + (-32768); + } + } + // END: SixAxis steering wheel emulation logic + } } diff --git a/DS4Windows/DS4Control/ScpUtil.cs b/DS4Windows/DS4Control/ScpUtil.cs index 6959f53..7e3e3a1 100644 --- a/DS4Windows/DS4Control/ScpUtil.cs +++ b/DS4Windows/DS4Control/ScpUtil.cs @@ -270,6 +270,7 @@ namespace DS4Windows m_Config.m_Profile = appdatapath + "\\Profiles.xml"; m_Config.m_Actions = appdatapath + "\\Actions.xml"; m_Config.m_linkedProfiles = Global.appdatapath + "\\LinkedProfiles.xml"; + m_Config.m_controllerConfigs = Global.appdatapath + "\\ControllerConfigs.xml"; } /// @@ -717,6 +718,12 @@ namespace DS4Windows m_Config.SetSaTriggerCond(index, text); } + public static DS4Controls[] SASteeringWheelEmulationAxis => m_Config.sASteeringWheelEmulationAxis; + public static DS4Controls getSASteeringWheelEmulationAxis(int index) + { + return m_Config.sASteeringWheelEmulationAxis[index]; + } + public static int[][] TouchDisInvertTriggers => m_Config.touchDisInvertTriggers; public static int[] getTouchDisInvertTriggers(int index) { @@ -1284,6 +1291,30 @@ namespace DS4Windows return m_Config.LoadLinkedProfiles(); } + public static bool SaveControllerConfigs(DS4Device device = null) + { + if (device != null) + return m_Config.SaveControllerConfigsForDevice(device); + + for (int idx = 0; idx < ControlService.DS4_CONTROLLER_COUNT; idx++) + if (Program.rootHub.DS4Controllers[idx] != null) + m_Config.SaveControllerConfigsForDevice(Program.rootHub.DS4Controllers[idx]); + + return true; + } + + public static bool LoadControllerConfigs(DS4Device device = null) + { + if (device != null) + return m_Config.LoadControllerConfigsForDevice(device); + + for (int idx = 0; idx < ControlService.DS4_CONTROLLER_COUNT; idx++) + if (Program.rootHub.DS4Controllers[idx] != null) + m_Config.LoadControllerConfigsForDevice(Program.rootHub.DS4Controllers[idx]); + + return true; + } + private static byte applyRatio(byte b1, byte b2, double r) { if (r > 100.0) @@ -1382,6 +1413,7 @@ namespace DS4Windows public String m_Profile = Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName + "\\Profiles.xml"; public String m_Actions = Global.appdatapath + "\\Actions.xml"; public string m_linkedProfiles = Global.appdatapath + "\\LinkedProfiles.xml"; + public string m_controllerConfigs = Global.appdatapath + "\\ControllerConfigs.xml"; protected XmlDocument m_Xdoc = new XmlDocument(); // fifth value used for options, not fifth controller @@ -1481,6 +1513,7 @@ namespace DS4Windows public bool[] useSAforMouse = new bool[5] { false, false, false, false, false }; public string[] sATriggers = new string[5] { string.Empty, string.Empty, string.Empty, string.Empty, string.Empty }; public bool[] sATriggerCond = new bool[5] { true, true, true, true, true }; + public DS4Controls[] sASteeringWheelEmulationAxis = new DS4Controls[5] { DS4Controls.None, DS4Controls.None, DS4Controls.None, DS4Controls.None, DS4Controls.None }; public int[][] touchDisInvertTriggers = new int[5][] { new int[1] { -1 }, new int[1] { -1 }, new int[1] { -1 }, new int[1] { -1 }, new int[1] { -1 } }; public int[] lsCurve = new int[5] { 0, 0, 0, 0, 0 }; @@ -1727,6 +1760,7 @@ namespace DS4Windows XmlNode xmlUseSAforMouse = m_Xdoc.CreateNode(XmlNodeType.Element, "UseSAforMouse", null); xmlUseSAforMouse.InnerText = useSAforMouse[device].ToString(); Node.AppendChild(xmlUseSAforMouse); XmlNode xmlSATriggers = m_Xdoc.CreateNode(XmlNodeType.Element, "SATriggers", null); xmlSATriggers.InnerText = sATriggers[device].ToString(); Node.AppendChild(xmlSATriggers); XmlNode xmlSATriggerCond = m_Xdoc.CreateNode(XmlNodeType.Element, "SATriggerCond", null); xmlSATriggerCond.InnerText = SaTriggerCondString(sATriggerCond[device]); Node.AppendChild(xmlSATriggerCond); + XmlNode xmlSASteeringWheelEmulationAxis = m_Xdoc.CreateNode(XmlNodeType.Element, "SASteeringWheelEmulationAxis", null); xmlSASteeringWheelEmulationAxis.InnerText = sASteeringWheelEmulationAxis[device].ToString("G"); Node.AppendChild(xmlSASteeringWheelEmulationAxis); XmlNode xmlTouchDisInvTriggers = m_Xdoc.CreateNode(XmlNodeType.Element, "TouchDisInvTriggers", null); string tempTouchDisInv = string.Join(",", touchDisInvertTriggers[device]); @@ -2603,7 +2637,11 @@ namespace DS4Windows try { Item = m_Xdoc.SelectSingleNode("/" + rootname + "/SATriggerCond"); sATriggerCond[device] = SaTriggerCondValue(Item.InnerText); } catch { sATriggerCond[device] = true; missingSetting = true; } - try { + try { Item = m_Xdoc.SelectSingleNode("/" + rootname + "/SASteeringWheelEmulationAxis"); DS4Controls.TryParse(Item.InnerText, out sASteeringWheelEmulationAxis[device]); } + catch { sASteeringWheelEmulationAxis[device] = DS4Controls.None; missingSetting = true; } + + try + { Item = m_Xdoc.SelectSingleNode("/" + rootname + "/TouchDisInvTriggers"); string[] triggers = Item.InnerText.Split(','); int temp = -1; @@ -3467,6 +3505,107 @@ namespace DS4Windows return saved; } + public bool createControllerConfigs() + { + bool saved = true; + XmlDocument configXdoc = new XmlDocument(); + XmlNode Node; + + Node = configXdoc.CreateXmlDeclaration("1.0", "utf-8", string.Empty); + configXdoc.AppendChild(Node); + + Node = configXdoc.CreateComment(string.Format(" Controller config data. {0} ", DateTime.Now)); + configXdoc.AppendChild(Node); + + Node = configXdoc.CreateWhitespace("\r\n"); + configXdoc.AppendChild(Node); + + Node = configXdoc.CreateNode(XmlNodeType.Element, "Controllers", ""); + configXdoc.AppendChild(Node); + + try { configXdoc.Save(m_controllerConfigs); } + catch (UnauthorizedAccessException) { AppLogger.LogToGui("Unauthorized Access - Save failed to path: " + m_controllerConfigs, false); saved = false; } + + return saved; + } + + public bool LoadControllerConfigsForDevice(DS4Device device) + { + bool loaded = false; + + if (device == null) return false; + if (!File.Exists(m_controllerConfigs)) createControllerConfigs(); + + try + { + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.Load(m_controllerConfigs); + + XmlNode node = xmlDoc.SelectSingleNode("/Controllers/Controller[@Mac=\"" + device.getMacAddress() + "\"]"); + if (node != null) + { + Int32 intValue; + if (Int32.TryParse(node["wheelCenterPoint"].InnerText.Split(',')[0], out intValue)) device.wheelCenterPoint.X = intValue; + if (Int32.TryParse(node["wheelCenterPoint"].InnerText.Split(',')[1], out intValue)) device.wheelCenterPoint.Y = intValue; + if (Int32.TryParse(node["wheel90DegPointLeft"].InnerText.Split(',')[0], out intValue)) device.wheel90DegPointLeft.X = intValue; + if (Int32.TryParse(node["wheel90DegPointLeft"].InnerText.Split(',')[1], out intValue)) device.wheel90DegPointLeft.Y = intValue; + if (Int32.TryParse(node["wheel90DegPointRight"].InnerText.Split(',')[0], out intValue)) device.wheel90DegPointRight.X = intValue; + if (Int32.TryParse(node["wheel90DegPointRight"].InnerText.Split(',')[1], out intValue)) device.wheel90DegPointRight.Y = intValue; + + loaded = true; + } + } + catch + { + AppLogger.LogToGui("ControllerConfigs.xml can't be found.", false); + loaded = false; + } + + return loaded; + } + + public bool SaveControllerConfigsForDevice(DS4Device device) + { + bool saved = true; + + if (device == null) return false; + if (!File.Exists(m_controllerConfigs)) createControllerConfigs(); + + try + { + //XmlNode node = null; + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.Load(m_controllerConfigs); + + XmlNode node = xmlDoc.SelectSingleNode("/Controllers/Controller[@Mac=\"" + device.getMacAddress() + "\"]"); + if (node == null) + { + XmlNode xmlControllersNode = xmlDoc.SelectSingleNode("/Controllers"); + XmlElement el = xmlDoc.CreateElement("Controller"); + el.SetAttribute("Mac", device.getMacAddress()); + + el.AppendChild(xmlDoc.CreateElement("wheelCenterPoint")); + el.AppendChild(xmlDoc.CreateElement("wheel90DegPointLeft")); + el.AppendChild(xmlDoc.CreateElement("wheel90DegPointRight")); + + node = xmlControllersNode.AppendChild(el); + } + + node["wheelCenterPoint"].InnerText = $"{device.wheelCenterPoint.X},{device.wheelCenterPoint.Y}"; + node["wheel90DegPointLeft"].InnerText = $"{device.wheel90DegPointLeft.X},{device.wheel90DegPointLeft.Y}"; + node["wheel90DegPointRight"].InnerText = $"{device.wheel90DegPointRight.X},{device.wheel90DegPointRight.Y}"; + + xmlDoc.Save(m_controllerConfigs); + } + catch (UnauthorizedAccessException) + { + AppLogger.LogToGui("Unauthorized Access - Save failed to path: " + m_controllerConfigs, false); + saved = false; + } + + return saved; + } + public void UpdateDS4CSetting(int deviceNum, string buttonName, bool shift, object action, string exts, DS4KeyType kt, int trigger = 0) { DS4Controls dc; @@ -3780,6 +3919,7 @@ namespace DS4Windows useSAforMouse[device] = false; sATriggers[device] = string.Empty; sATriggerCond[device] = true; + sASteeringWheelEmulationAxis[device] = DS4Controls.None; touchDisInvertTriggers[device] = new int[1] { -1 }; lsCurve[device] = rsCurve[device] = 0; gyroSensitivity[device] = 100; diff --git a/DS4Windows/DS4Control/X360Device.cs b/DS4Windows/DS4Control/X360Device.cs index e3dd1fb..7ef8123 100644 --- a/DS4Windows/DS4Control/X360Device.cs +++ b/DS4Windows/DS4Control/X360Device.cs @@ -5,6 +5,8 @@ using System.ComponentModel; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; +using System.Drawing; // Point struct + namespace DS4Windows { public partial class X360Device : ScpDevice @@ -141,10 +143,25 @@ namespace DS4Windows Output[12] = state.L2; // Left Trigger Output[13] = state.R2; // Right Trigger - Int32 ThumbLX = Scale(state.LX, false); - Int32 ThumbLY = Scale(state.LY, true); - Int32 ThumbRX = Scale(state.RX, false); - Int32 ThumbRY = Scale(state.RY, true); + Int32 ThumbLX; + Int32 ThumbLY; + Int32 ThumbRX; + Int32 ThumbRY; + + DS4Controls steeringWheelMappedAxis = Global.getSASteeringWheelEmulationAxis(device); + + if (steeringWheelMappedAxis == DS4Controls.LXPos) ThumbLX = state.SASteeringWheelEmulationUnit; + else ThumbLX = Scale(state.LX, false); + + if (steeringWheelMappedAxis == DS4Controls.LYPos) ThumbLY = state.SASteeringWheelEmulationUnit; + else ThumbLY = Scale(state.LY, true); + + if (steeringWheelMappedAxis == DS4Controls.RXPos) ThumbRX = state.SASteeringWheelEmulationUnit; + else ThumbRX = Scale(state.RX, false); + + if (steeringWheelMappedAxis == DS4Controls.RYPos) ThumbRY = state.SASteeringWheelEmulationUnit; + else ThumbRY = Scale(state.RY, true); + Output[14] = (Byte)((ThumbLX >> 0) & 0xFF); // LX Output[15] = (Byte)((ThumbLX >> 8) & 0xFF); Output[16] = (Byte)((ThumbLY >> 0) & 0xFF); // LY diff --git a/DS4Windows/DS4Library/DS4Device.cs b/DS4Windows/DS4Library/DS4Device.cs index aba9179..dc18361 100644 --- a/DS4Windows/DS4Library/DS4Device.cs +++ b/DS4Windows/DS4Library/DS4Device.cs @@ -157,6 +157,34 @@ namespace DS4Windows return warnInterval; } + public Point wheelCenterPoint; + public Point wheel90DegPointLeft; + public Point wheelCircleCenterPointLeft; + public Point wheel90DegPointRight; + public Point wheelCircleCenterPointRight; + + public DateTime wheelPrevRecalibrateTime; + + private bool wheelRecalibrateActive = false; + public bool WheelRecalibrateActive + { + get { return wheelRecalibrateActive; } + set + { + wheelRecalibrateActive = value; + } + } + + public enum WheelCalibrationPoint + { + None = 0, + Center = 1, + Right90 = 2, + Left90 = 4, + All = Center | Right90 | Left90 + } + public WheelCalibrationPoint wheelCalibratedAxisBitmask; + private bool exitOutputThread = false; public bool ExitOutputThread => exitOutputThread; private bool exitInputThread = false; diff --git a/DS4Windows/DS4Library/DS4State.cs b/DS4Windows/DS4Library/DS4State.cs index 7400f15..1aae958 100644 --- a/DS4Windows/DS4Library/DS4State.cs +++ b/DS4Windows/DS4Library/DS4State.cs @@ -28,6 +28,7 @@ namespace DS4Windows public ulong totalMicroSec = 0; public SixAxis Motion = null; public static readonly int DEFAULT_AXISDIR_VALUE = 127; + public Int32 SASteeringWheelEmulationUnit; public struct TrackPadTouch { @@ -66,6 +67,7 @@ namespace DS4Windows Motion = new SixAxis(0, 0, 0, 0, 0, 0, 0.0); TrackPadTouch0.IsActive = false; TrackPadTouch1.IsActive = false; + SASteeringWheelEmulationUnit = 0; } public DS4State(DS4State state) @@ -120,6 +122,7 @@ namespace DS4Windows Motion = state.Motion; TrackPadTouch0 = state.TrackPadTouch0; TrackPadTouch1 = state.TrackPadTouch1; + SASteeringWheelEmulationUnit = state.SASteeringWheelEmulationUnit; } public DS4State Clone() @@ -179,6 +182,7 @@ namespace DS4Windows state.Motion = Motion; state.TrackPadTouch0 = TrackPadTouch0; state.TrackPadTouch1 = TrackPadTouch1; + state.SASteeringWheelEmulationUnit = SASteeringWheelEmulationUnit; } public void calculateStickAngles() From 1cb04d03adbdd4c0b7c75c600257b1a8c5f8f506 Mon Sep 17 00:00:00 2001 From: mika-n Date: Mon, 19 Nov 2018 13:32:48 +0200 Subject: [PATCH 02/14] Changed precision of angle values sent to x360 virtual controller from 1 degree to 1/10th degree precision. Also, minimized "center deadzone" gap (the previous optimization of angle calculation was too aggressive in wheel center position). --- DS4Windows/DS4Control/Mapping.cs | 39 +++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/DS4Windows/DS4Control/Mapping.cs b/DS4Windows/DS4Control/Mapping.cs index cfad5a4..ae4d6aa 100644 --- a/DS4Windows/DS4Control/Mapping.cs +++ b/DS4Windows/DS4Control/Mapping.cs @@ -3781,6 +3781,9 @@ namespace DS4Windows // BEGIN: SixAxis steering wheel emulation logic + private const int C_WHEEL_ANGLE_PRECISION = 10; // 1=precision of one degree, 10=precision of 1/10 of degree. Bigger number means fine graned precision + private const int C_WHEEL_ANGLE_PRECISION_DECIMALS = (C_WHEEL_ANGLE_PRECISION == 1 ? 0 : C_WHEEL_ANGLE_PRECISION/10); + // "In-game" calibration process: // TODO: Launching a calibration process should probably be a special action which allows multiple key bindings to launch a specific task. // - Place controller at "steering wheel center" position and press DS4 Option button to start the calibration (Profile should have "SASteeringWheelEmulationAxis" option set to LXPos, LYPos, RXPos or RYPos value). @@ -3829,7 +3832,7 @@ namespace DS4Windows { Int32 result; - if (Math.Abs(gyroAccelX - controller.wheelCenterPoint.X) <= 1 && Math.Abs(gyroAccelZ - controller.wheelCenterPoint.Y) <= 1) + if (gyroAccelX == controller.wheelCenterPoint.X && Math.Abs(gyroAccelZ - controller.wheelCenterPoint.Y) <= 1) { // When the current gyro position is "close enough" the wheel center point then no need to go through the hassle of calculating an angle result = 0; @@ -3860,14 +3863,24 @@ namespace DS4Windows double magCD = Math.Sqrt(vectorCD.X * vectorCD.X + vectorCD.Y * vectorCD.Y); // Calculate angle between vectors and convert radian to degrees - double angle = Math.Acos(dotProduct / (magAB * magCD)); - result = Convert.ToInt32(Math.Round(angle * (180.0 / Math.PI))); + if (magAB == 0 || magCD == 0) + { + result = 0; + } + else + { + double angle = Math.Acos(dotProduct / (magAB * magCD)); + result = Convert.ToInt32( Clamp(-180.0 * C_WHEEL_ANGLE_PRECISION, + Math.Round((angle * (180.0 / Math.PI)), C_WHEEL_ANGLE_PRECISION_DECIMALS) * C_WHEEL_ANGLE_PRECISION, + 180.0 * C_WHEEL_ANGLE_PRECISION) + ); + } // Left turn is -180..0 and right turn 0..180 degrees if (gyroAccelX < controller.wheelCenterPoint.X) result = -result; // Just to be sure.. Probably not needed. TODO: Add support for 360/720/900 turn ranges by counting "laps" how many times the steering wheel is turned around - result = ClampInt(-180, result, 180); + //result = ClampInt(-180, result, 180); } return result; @@ -3974,7 +3987,6 @@ namespace DS4Windows // Show lightbar color feedback how the calibration process is proceeding. // red / yellow / blue / green = No calibration anchors/one anchor/two anchors/three anchors calibrated (center, 90DegLeft, 90DegRight) // Blinking led = Controller is tilted at the current calibration point (or calibration routine just set a new anchor point) - // TODO: device num to flash led idx (use red for center calibrated, yellow for one 90deg and green for both 90deg calibration and then reset back to normal led) int bitsSet = CountNumOfSetBits((int)controller.wheelCalibratedAxisBitmask); if (bitsSet >= 3) DS4LightBar.forcedColor[device] = calibrationColor_3; else if (bitsSet == 2) DS4LightBar.forcedColor[device] = calibrationColor_2; @@ -3982,13 +3994,19 @@ namespace DS4Windows else DS4LightBar.forcedColor[device] = calibrationColor_0; result = CalculateControllerAngle(gyroAccelX, gyroAccelZ, controller); - if (bitsSet >= 1 && (Math.Abs(result) <= 1 || (Math.Abs(result) >= 88 && Math.Abs(result) <= 92) || Math.Abs(result) >= 178)) DS4LightBar.forcedFlash[device] = 2; - else DS4LightBar.forcedFlash[device] = 0; + if (bitsSet >= 1 && ( + Math.Abs(result) <= 1 * C_WHEEL_ANGLE_PRECISION + || (Math.Abs(result) >= 88 * C_WHEEL_ANGLE_PRECISION && Math.Abs(result) <= 92 * C_WHEEL_ANGLE_PRECISION) + || Math.Abs(result) >= 178 * C_WHEEL_ANGLE_PRECISION + )) + DS4LightBar.forcedFlash[device] = 2; + else + DS4LightBar.forcedFlash[device] = 0; DS4LightBar.forcelight[device] = true; //DS4LightBar.updateLightBar(controller, device); - LogToGuiSACalibrationDebugMsg($"Calibration values ({gyroAccelX}, {gyroAccelZ}) angle={result}\n"); + LogToGuiSACalibrationDebugMsg($"Calibration values ({gyroAccelX}, {gyroAccelZ}) angle={result / (1.0 * C_WHEEL_ANGLE_PRECISION)}\n"); // Return center wheel position while gyro is calibrated, not the calculated angle return 0; @@ -3996,7 +4014,6 @@ namespace DS4Windows // If calibration values are missing then use "educated guesses" about good starting values. - // TODO. Use pre-calibrated default values from configuration file. if (controller.wheelCenterPoint.IsEmpty) { if (!Global.LoadControllerConfigs(controller)) @@ -4026,10 +4043,10 @@ namespace DS4Windows // Keep outputting debug data 30secs after the latest re-calibration event (user can check these values from the log screen of DS4Windows GUI) //if (((TimeSpan)(DateTime.Now - prevRecalibrateTime)).TotalSeconds < 30) - // LogToGuiSACalibrationDebugMsg($"DEBUG gyro=({gyroAccelX}, {gyroAccelZ}) angle={result}"); + // LogToGuiSACalibrationDebugMsg($"DEBUG gyro=({gyroAccelX}, {gyroAccelZ}) angle={result / (1.0 * C_WHEEL_ANGLE_PRECISION)}"); // Scale input to a raw x360 thumbstick output scale - return (((result - (-180)) * (32767 - (-32768))) / (180 - (-180))) + (-32768); + return (((result - (-180 * C_WHEEL_ANGLE_PRECISION)) * (32767 - (-32768))) / (180 * C_WHEEL_ANGLE_PRECISION - (-180 * C_WHEEL_ANGLE_PRECISION))) + (-32768); } } // END: SixAxis steering wheel emulation logic From 1eed418022640fc8f77a22c844204b480b975100 Mon Sep 17 00:00:00 2001 From: mika-n Date: Tue, 4 Dec 2018 00:50:37 +0200 Subject: [PATCH 03/14] Added support for 180/360/720/900/1440 turn range (or any other turn range between 90-3240 degrees). New SASteeringWheelEmulationRange profile option with int32 value (default 360). --- DS4Windows/DS4Control/Mapping.cs | 319 ++++++++++------ DS4Windows/DS4Control/ScpUtil.cs | 30 +- DS4Windows/DS4Forms/Options.cs | 3 + DS4Windows/DS4Forms/SpecActions.Designer.cs | 11 +- DS4Windows/DS4Forms/SpecActions.cs | 13 +- DS4Windows/DS4Forms/SpecActions.resx | 393 ++++++-------------- DS4Windows/DS4Library/DS4Device.cs | 11 +- DS4Windows/DS4Windows.csproj | 2 + DS4Windows/Properties/Resources.Designer.cs | 9 + DS4Windows/Properties/Resources.resx | 3 + DS4Windows/Settings.cs | 28 ++ 11 files changed, 406 insertions(+), 416 deletions(-) create mode 100644 DS4Windows/Settings.cs diff --git a/DS4Windows/DS4Control/Mapping.cs b/DS4Windows/DS4Control/Mapping.cs index ae4d6aa..32e561a 100644 --- a/DS4Windows/DS4Control/Mapping.cs +++ b/DS4Windows/DS4Control/Mapping.cs @@ -1964,6 +1964,25 @@ namespace DS4Windows } actionDone[index].dev[device] = true; } + else if (action.typeID == SpecialAction.ActionTypeId.SASteeringWheelEmulationCalibrate) + { + actionFound = true; + + DS4Device d = ctrl.DS4Controllers[device]; + // If controller is not already in SASteeringWheelCalibration state then enable it now. If calibration is active then complete it (commit calibration values) + if (d.WheelRecalibrateActiveState == 0 && DateTime.UtcNow > (action.firstTap + TimeSpan.FromMilliseconds(3000))) + { + action.firstTap = DateTime.UtcNow; + d.WheelRecalibrateActiveState = 1; // Start calibration process + } + else if (d.WheelRecalibrateActiveState == 2 && DateTime.UtcNow > (action.firstTap + TimeSpan.FromMilliseconds(3000))) + { + action.firstTap = DateTime.UtcNow; + d.WheelRecalibrateActiveState = 3; // Complete calibration process + } + + actionDone[index].dev[device] = true; + } } else { @@ -3803,14 +3822,19 @@ namespace DS4Windows private static readonly DS4Color calibrationColor_3 = new DS4Color { red = 0x00, green = 0xC0, blue = 0x00 }; private static DateTime latestDebugMsgTime; - private static void LogToGuiSACalibrationDebugMsg(string data) + private static string latestDebugData; + private static void LogToGuiSACalibrationDebugMsg(string data, bool forceOutput = false) { // Print debug calibration log messages only once per 2 secs to avoid flooding the log receiver DateTime curTime = DateTime.Now; - if (((TimeSpan)(curTime - latestDebugMsgTime)).TotalSeconds > 2) + if (forceOutput || ((TimeSpan)(curTime - latestDebugMsgTime)).TotalSeconds > 2) { latestDebugMsgTime = curTime; - AppLogger.LogToGui(data, false); + if (data != latestDebugData) + { + AppLogger.LogToGui(data, false); + latestDebugData = data; + } } } @@ -3828,7 +3852,7 @@ namespace DS4Windows // Calculate and return the angle of the controller as -180...0...+180 value. // TODO: Support >360 degree turn range by adding "lap counter" when wheel is rotated full rounds left or right.At the moment this logic supports only 360 degree turn range. - protected static Int32 CalculateControllerAngle(int gyroAccelX, int gyroAccelZ, DS4Device controller) + private static Int32 CalculateControllerAngle(int gyroAccelX, int gyroAccelZ, DS4Device controller) { Int32 result; @@ -3870,7 +3894,8 @@ namespace DS4Windows else { double angle = Math.Acos(dotProduct / (magAB * magCD)); - result = Convert.ToInt32( Clamp(-180.0 * C_WHEEL_ANGLE_PRECISION, + result = Convert.ToInt32( Global.Clamp( + -180.0 * C_WHEEL_ANGLE_PRECISION, Math.Round((angle * (180.0 / Math.PI)), C_WHEEL_ANGLE_PRECISION_DECIMALS) * C_WHEEL_ANGLE_PRECISION, 180.0 * C_WHEEL_ANGLE_PRECISION) ); @@ -3878,15 +3903,130 @@ namespace DS4Windows // Left turn is -180..0 and right turn 0..180 degrees if (gyroAccelX < controller.wheelCenterPoint.X) result = -result; - - // Just to be sure.. Probably not needed. TODO: Add support for 360/720/900 turn ranges by counting "laps" how many times the steering wheel is turned around - //result = ClampInt(-180, result, 180); } return result; } - protected static Int32 Scale360degreeGyroAxis(int device, /*DS4State state,*/ DS4StateExposed exposedState, ControlService ctrl) + private static void SAWheelEmulationCalibration(int device, DS4StateExposed exposedState, ControlService ctrl, DS4State currentDeviceState, DS4Device controller) + { + int gyroAccelX, gyroAccelZ; + int result; + + gyroAccelX = exposedState.getAccelX(); + gyroAccelZ = exposedState.getAccelZ(); + + // "SASteeringWheelEmulaationCalibration" action key combination is used to run "in-game" calibration process (user can define the action key combination in DS4Windows GUI). + // State 0=Normal mode (ie. calibration process is not running), 1=Activating calibration, 2=Calibration process running, 3=Completing calibration + if (controller.WheelRecalibrateActiveState == 1) + { + AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} activated re-calibration of SA steering wheel emulation", false); + + controller.WheelRecalibrateActiveState = 2; + + controller.wheelPrevPhysicalAngle = 0; + controller.wheelFullTurnCount = 0; + + // Clear existing calibration value and use current position as "center" point. + // This initial center value may be off-center because of shaking the controller while button was pressed. The value will be overriden with correct value once controller is stabilized and hold still few secs at the center point + controller.wheelCenterPoint.X = gyroAccelX; + controller.wheelCenterPoint.Y = gyroAccelZ; + controller.wheel90DegPointRight.X = gyroAccelX + 25; + controller.wheel90DegPointLeft.X = gyroAccelX - 25; + + // Clear bitmask for calibration points. All three calibration points need to be set before re-calibration process is valid + controller.wheelCalibratedAxisBitmask = DS4Device.WheelCalibrationPoint.None; + + controller.wheelPrevRecalibrateTime = DateTime.Now; + } + else if (controller.WheelRecalibrateActiveState == 3 /*&& currentDeviceState.Options && ((TimeSpan)(DateTime.Now - controller.wheelPrevRecalibrateTime)).TotalSeconds > 3 */) + { + AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} completed the calibration of SA steering wheel emulation. center=({controller.wheelCenterPoint.X}, {controller.wheelCenterPoint.Y}) 90L=({controller.wheel90DegPointLeft.X}, {controller.wheel90DegPointLeft.Y}) 90R=({controller.wheel90DegPointRight.X}, {controller.wheel90DegPointRight.Y})", false); + + // If any of the calibration points (center, left 90deg, right 90deg) are missing then reset back to default calibration values + if (((controller.wheelCalibratedAxisBitmask & DS4Device.WheelCalibrationPoint.All) == DS4Device.WheelCalibrationPoint.All)) + Global.SaveControllerConfigs(controller); + else + controller.wheelCenterPoint.X = controller.wheelCenterPoint.Y = 0; + + // Reset lightbar back to normal color + // TODO: hmmm... Sometimes color is not reset back to normal "main color". Investigate why following code logic doesn't always restore it. + DS4LightBar.forcelight[device] = false; + DS4LightBar.forcedFlash[device] = 0; + DS4LightBar.updateLightBar(controller, device); + + controller.LightBarColor = Global.getMainColor(device); + DS4LightBar.updateLightBar(controller, device); + + controller.WheelRecalibrateActiveState = 0; + + controller.wheelPrevRecalibrateTime = DateTime.Now; + } + + if (controller.WheelRecalibrateActiveState > 0) + { + // Auto calibrate 90deg left/right positions (these values may change over time because gyro/accel sensor values are not "precise mathematics", so user can trigger calibration even in mid-game sessions by pressing DS4 Options btn), + // but make sure controller is stable enough to avoid misaligments because of hard shaking (check velocity of gyro axis) + if (Math.Abs(currentDeviceState.Motion.angVelPitch) < 0.5 && Math.Abs(currentDeviceState.Motion.angVelYaw) < 0.5 && Math.Abs(currentDeviceState.Motion.angVelRoll) < 0.5) + { + if (controller.wheelCalibratedAxisBitmask == DS4Device.WheelCalibrationPoint.None) + { + // Wait few secs after re-calibration button was pressed. Hold controller still at center position and don't shake it too much until red lightbar turns to yellow. + if (((TimeSpan)(DateTime.Now - controller.wheelPrevRecalibrateTime)).TotalSeconds >= 3) + { + controller.wheelCenterPoint.X = gyroAccelX; + controller.wheelCenterPoint.Y = gyroAccelZ; + + controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Center; + } + } + else if (controller.wheel90DegPointRight.X < gyroAccelX) + { + controller.wheel90DegPointRight.X = gyroAccelX; + controller.wheel90DegPointRight.Y = gyroAccelZ; + controller.wheelCircleCenterPointRight.X = controller.wheelCenterPoint.X; + controller.wheelCircleCenterPointRight.Y = controller.wheel90DegPointRight.Y; + + controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Right90; + } + else if (controller.wheel90DegPointLeft.X > gyroAccelX) + { + controller.wheel90DegPointLeft.X = gyroAccelX; + controller.wheel90DegPointLeft.Y = gyroAccelZ; + controller.wheelCircleCenterPointLeft.X = controller.wheelCenterPoint.X; + controller.wheelCircleCenterPointLeft.Y = controller.wheel90DegPointLeft.Y; + + controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Left90; + } + } + + // Show lightbar color feedback how the calibration process is proceeding. + // red / yellow / blue / green = No calibration anchors/one anchor/two anchors/three anchors calibrated (center, 90DegLeft, 90DegRight) + // Blinking led = Controller is tilted at the current calibration point (or calibration routine just set a new anchor point) + int bitsSet = CountNumOfSetBits((int)controller.wheelCalibratedAxisBitmask); + if (bitsSet >= 3) DS4LightBar.forcedColor[device] = calibrationColor_3; + else if (bitsSet == 2) DS4LightBar.forcedColor[device] = calibrationColor_2; + else if (bitsSet == 1) DS4LightBar.forcedColor[device] = calibrationColor_1; + else DS4LightBar.forcedColor[device] = calibrationColor_0; + + result = CalculateControllerAngle(gyroAccelX, gyroAccelZ, controller); + if (bitsSet >= 1 && ( + Math.Abs(result) <= 1 * C_WHEEL_ANGLE_PRECISION + || (Math.Abs(result) >= 88 * C_WHEEL_ANGLE_PRECISION && Math.Abs(result) <= 92 * C_WHEEL_ANGLE_PRECISION) + || Math.Abs(result) >= 178 * C_WHEEL_ANGLE_PRECISION + )) + DS4LightBar.forcedFlash[device] = 2; + else + DS4LightBar.forcedFlash[device] = 0; + + DS4LightBar.forcelight[device] = true; + //DS4LightBar.updateLightBar(controller, device); + + LogToGuiSACalibrationDebugMsg($"Calibration values ({gyroAccelX}, {gyroAccelZ}) angle={result / (1.0 * C_WHEEL_ANGLE_PRECISION)}\n"); + } + } + + protected static Int32 Scale360degreeGyroAxis(int device, DS4StateExposed exposedState, ControlService ctrl) { unchecked { @@ -3902,132 +4042,34 @@ namespace DS4Windows currentDeviceState = controller.getCurrentStateRef(); - gyroAccelX = exposedState.getAccelX(); - gyroAccelZ = exposedState.getAccelZ(); - - // If DS4 Options btn is pressed and the previous re-calibration was done more than 2 secs ago (avoids repeated "calibration" loops if user holds down the Option btn too long) then re-calibrate the gyro wheel emulation. - // TODO. Maybe here should be logic to enter calibration process only if Options btn is hold down minimum of 3 secs? This way Option btn can be re-mapped to do other things without kicking on re-calibration everytime it is pressed. - if (currentDeviceState.Options && ((TimeSpan)(DateTime.Now - controller.wheelPrevRecalibrateTime)).TotalSeconds > 2) + // If user has pressed "SASteeringWheelEmulationCalibration" special action key combination then run "in-game" calibration process + if (controller.WheelRecalibrateActiveState > 0) { - if (controller.WheelRecalibrateActive == false) - { - AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} activated re-calibration of motion steering wheel emulation", false); + SAWheelEmulationCalibration(device, exposedState, ctrl, currentDeviceState, controller); - controller.WheelRecalibrateActive = true; - - // Clear existing calibration value and use current position as "center" point. - // This initial center value may be off-center because of shaking the controller while button was pressed. The value will be overriden with correct value once controller is stabilized and hold still few secs at the center point - controller.wheelCenterPoint.X = gyroAccelX; - controller.wheelCenterPoint.Y = gyroAccelZ; - controller.wheel90DegPointRight.X = gyroAccelX + 25; - controller.wheel90DegPointLeft.X = gyroAccelX - 25; - - // Clear bitmask for calibration points. All three calibration points need to be set before re-calibration process is valid - controller.wheelCalibratedAxisBitmask = DS4Device.WheelCalibrationPoint.None; - } - else - { - AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} completed steering wheel calibration. center=({controller.wheelCenterPoint.X}, {controller.wheelCenterPoint.Y}) 90L=({controller.wheel90DegPointLeft.X}, {controller.wheel90DegPointLeft.Y}) 90R=({controller.wheel90DegPointRight.X}, {controller.wheel90DegPointRight.Y})", false); - - controller.WheelRecalibrateActive = false; - - // If any of the calibration points (center, left 90deg, right 90deg) are missing then reset back to default calibration values - if (((controller.wheelCalibratedAxisBitmask & DS4Device.WheelCalibrationPoint.All) == DS4Device.WheelCalibrationPoint.All)) - Global.SaveControllerConfigs(controller); - else - controller.wheelCenterPoint.X = controller.wheelCenterPoint.Y = 0; - - // Reset lightbar back to normal color - DS4LightBar.forcelight[device] = false; - DS4LightBar.forcedFlash[device] = 0; - controller.LightBarColor = Global.getMainColor(device); - DS4LightBar.updateLightBar(controller, device); - } - - controller.wheelPrevRecalibrateTime = DateTime.Now; - } - - if (controller.WheelRecalibrateActive) - { - // Auto calibrate 90deg left/right positions (these values may change over time because gyro/accel sensor values are not "precise mathematics", so user can trigger calibration even in mid-game sessions by pressing DS4 Options btn), - // but make sure controller is stable enough to avoid misaligments because of hard shaking (check velocity of gyro axis) - if (Math.Abs(/*state*/ currentDeviceState.Motion.angVelPitch) < 0.5 && Math.Abs(currentDeviceState.Motion.angVelYaw) < 0.5 && Math.Abs(currentDeviceState.Motion.angVelRoll) < 0.5) - { - if (controller.wheelCalibratedAxisBitmask == DS4Device.WheelCalibrationPoint.None) - { - // Wait few secs after re-calibration button was pressed. Hold controller still at center position and don't shake it too much until red lightbar turns to yellow. - if (((TimeSpan)(DateTime.Now - controller.wheelPrevRecalibrateTime)).TotalSeconds >= 3) - { - controller.wheelCenterPoint.X = gyroAccelX; - controller.wheelCenterPoint.Y = gyroAccelZ; - - controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Center; - } - } - else if (controller.wheel90DegPointRight.X < gyroAccelX) - { - controller.wheel90DegPointRight.X = gyroAccelX; - controller.wheel90DegPointRight.Y = gyroAccelZ; - controller.wheelCircleCenterPointRight.X = controller.wheelCenterPoint.X; - controller.wheelCircleCenterPointRight.Y = controller.wheel90DegPointRight.Y; - - controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Right90; - } - else if (controller.wheel90DegPointLeft.X > gyroAccelX) - { - controller.wheel90DegPointLeft.X = gyroAccelX; - controller.wheel90DegPointLeft.Y = gyroAccelZ; - controller.wheelCircleCenterPointLeft.X = controller.wheelCenterPoint.X; - controller.wheelCircleCenterPointLeft.Y = controller.wheel90DegPointLeft.Y; - - controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Left90; - } - } - - // Show lightbar color feedback how the calibration process is proceeding. - // red / yellow / blue / green = No calibration anchors/one anchor/two anchors/three anchors calibrated (center, 90DegLeft, 90DegRight) - // Blinking led = Controller is tilted at the current calibration point (or calibration routine just set a new anchor point) - int bitsSet = CountNumOfSetBits((int)controller.wheelCalibratedAxisBitmask); - if (bitsSet >= 3) DS4LightBar.forcedColor[device] = calibrationColor_3; - else if (bitsSet == 2) DS4LightBar.forcedColor[device] = calibrationColor_2; - else if (bitsSet == 1) DS4LightBar.forcedColor[device] = calibrationColor_1; - else DS4LightBar.forcedColor[device] = calibrationColor_0; - - result = CalculateControllerAngle(gyroAccelX, gyroAccelZ, controller); - if (bitsSet >= 1 && ( - Math.Abs(result) <= 1 * C_WHEEL_ANGLE_PRECISION - || (Math.Abs(result) >= 88 * C_WHEEL_ANGLE_PRECISION && Math.Abs(result) <= 92 * C_WHEEL_ANGLE_PRECISION) - || Math.Abs(result) >= 178 * C_WHEEL_ANGLE_PRECISION - )) - DS4LightBar.forcedFlash[device] = 2; - else - DS4LightBar.forcedFlash[device] = 0; - - DS4LightBar.forcelight[device] = true; - //DS4LightBar.updateLightBar(controller, device); - - LogToGuiSACalibrationDebugMsg($"Calibration values ({gyroAccelX}, {gyroAccelZ}) angle={result / (1.0 * C_WHEEL_ANGLE_PRECISION)}\n"); - - // Return center wheel position while gyro is calibrated, not the calculated angle + // Return center wheel position while SA wheel emuation is being calibrated return 0; } + gyroAccelX = exposedState.getAccelX(); + gyroAccelZ = exposedState.getAccelZ(); - // If calibration values are missing then use "educated guesses" about good starting values. + // If calibration values are missing then use "educated guesses" about good starting values if (controller.wheelCenterPoint.IsEmpty) { if (!Global.LoadControllerConfigs(controller)) { - AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} motion steering wheel calibration data missing. It is recommended to run steering wheel calibration process by pressing DS4 Options button. Using estimated values until the controller is calibrated at least once.", false); + AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} sixaxis steering wheel calibration data missing. It is recommended to run steering wheel calibration process by pressing SASteeringWheelEmulationCalibration special action key. Using estimated values until the controller is calibrated at least once.", false); - controller.wheelCenterPoint.X = gyroAccelX; + // Use current controller position as "center point". Assume DS4Windows was started while controller was hold in center position (yes, dangerous assumption but can't do much until controller is calibrated) + controller.wheelCenterPoint.X = gyroAccelX; controller.wheelCenterPoint.Y = gyroAccelZ; - controller.wheel90DegPointRight.X = controller.wheelCenterPoint.X + 113; // 113; - controller.wheel90DegPointRight.Y = controller.wheelCenterPoint.Y + 110; // 110; + controller.wheel90DegPointRight.X = controller.wheelCenterPoint.X + 113; + controller.wheel90DegPointRight.Y = controller.wheelCenterPoint.Y + 110; - controller.wheel90DegPointLeft.X = controller.wheelCenterPoint.X - 127; // -127; - controller.wheel90DegPointLeft.Y = controller.wheel90DegPointRight.Y; // 2; + controller.wheel90DegPointLeft.X = controller.wheelCenterPoint.X - 127; + controller.wheel90DegPointLeft.Y = controller.wheel90DegPointRight.Y; } controller.wheelCircleCenterPointRight.X = controller.wheelCenterPoint.X; @@ -4040,13 +4082,46 @@ namespace DS4Windows } result = CalculateControllerAngle(gyroAccelX, gyroAccelZ, controller); + + if (controller.wheelPrevPhysicalAngle < 0 && result > 0) + { + // If wrapped around from -180 to +180 side then SA steering wheel keeps on turning "left" (ie. beyond 360 degrees) + if ((result - controller.wheelPrevPhysicalAngle) > 180 * C_WHEEL_ANGLE_PRECISION) + controller.wheelFullTurnCount--; + } + else if (controller.wheelPrevPhysicalAngle > 0 && result < 0) + { + // If wrapped around from +180 to -180 side then SA steering wheel keeps on turning "right" (ie. beyond 360 degrees) + if ((controller.wheelPrevPhysicalAngle - result) > 180 * C_WHEEL_ANGLE_PRECISION) + controller.wheelFullTurnCount++; + } + controller.wheelPrevPhysicalAngle = result; + + if (controller.wheelFullTurnCount != 0) + { + if (controller.wheelFullTurnCount > 0) + result = (controller.wheelFullTurnCount * 180 * C_WHEEL_ANGLE_PRECISION) + ((controller.wheelFullTurnCount * 180 * C_WHEEL_ANGLE_PRECISION) + result); + else + result = (controller.wheelFullTurnCount * 180 * C_WHEEL_ANGLE_PRECISION) - ((controller.wheelFullTurnCount * -180 * C_WHEEL_ANGLE_PRECISION) - result); + } + + // TODO: How to prevent "too many laps" problem or reset it (DS4Win doesn't know when a game is running so it keeps counting laps)? + + int maxRangeRight = Global.getSASteeringWheelEmulationRange(device) / 2 * C_WHEEL_ANGLE_PRECISION; + int maxRangeLeft = -maxRangeRight; + + if (maxRangeRight != (360 / 2 * C_WHEEL_ANGLE_PRECISION) ) + result = Mapping.ClampInt(maxRangeLeft, result, maxRangeRight); // Keep outputting debug data 30secs after the latest re-calibration event (user can check these values from the log screen of DS4Windows GUI) //if (((TimeSpan)(DateTime.Now - prevRecalibrateTime)).TotalSeconds < 30) // LogToGuiSACalibrationDebugMsg($"DEBUG gyro=({gyroAccelX}, {gyroAccelZ}) angle={result / (1.0 * C_WHEEL_ANGLE_PRECISION)}"); + //LogToGuiSACalibrationDebugMsg($"DEBUG gyro=({gyroAccelX}, {gyroAccelZ}) angle={result / (1.0 * C_WHEEL_ANGLE_PRECISION)} fullTurns={controller.wheelFullTurnCount}", false); + // Scale input to a raw x360 thumbstick output scale - return (((result - (-180 * C_WHEEL_ANGLE_PRECISION)) * (32767 - (-32768))) / (180 * C_WHEEL_ANGLE_PRECISION - (-180 * C_WHEEL_ANGLE_PRECISION))) + (-32768); + //return (((result - (-180 * C_WHEEL_ANGLE_PRECISION)) * (32767 - (-32768))) / (180 * C_WHEEL_ANGLE_PRECISION - (-180 * C_WHEEL_ANGLE_PRECISION))) + (-32768); + return (( (result - maxRangeLeft) * (32767 - (-32768))) / (maxRangeRight - maxRangeLeft)) + (-32768); } } // END: SixAxis steering wheel emulation logic diff --git a/DS4Windows/DS4Control/ScpUtil.cs b/DS4Windows/DS4Control/ScpUtil.cs index 7e3e3a1..e526897 100644 --- a/DS4Windows/DS4Control/ScpUtil.cs +++ b/DS4Windows/DS4Control/ScpUtil.cs @@ -724,6 +724,12 @@ namespace DS4Windows return m_Config.sASteeringWheelEmulationAxis[index]; } + public static int[] SASteeringWheelEmulationRange => m_Config.sASteeringWheelEmulationRange; + public static int getSASteeringWheelEmulationRange(int index) + { + return m_Config.sASteeringWheelEmulationRange[index]; + } + public static int[][] TouchDisInvertTriggers => m_Config.touchDisInvertTriggers; public static int[] getTouchDisInvertTriggers(int index) { @@ -1514,6 +1520,7 @@ namespace DS4Windows public string[] sATriggers = new string[5] { string.Empty, string.Empty, string.Empty, string.Empty, string.Empty }; public bool[] sATriggerCond = new bool[5] { true, true, true, true, true }; public DS4Controls[] sASteeringWheelEmulationAxis = new DS4Controls[5] { DS4Controls.None, DS4Controls.None, DS4Controls.None, DS4Controls.None, DS4Controls.None }; + public int[] sASteeringWheelEmulationRange = new int[5] { 360, 360, 360, 360, 360 }; public int[][] touchDisInvertTriggers = new int[5][] { new int[1] { -1 }, new int[1] { -1 }, new int[1] { -1 }, new int[1] { -1 }, new int[1] { -1 } }; public int[] lsCurve = new int[5] { 0, 0, 0, 0, 0 }; @@ -1761,6 +1768,7 @@ namespace DS4Windows XmlNode xmlSATriggers = m_Xdoc.CreateNode(XmlNodeType.Element, "SATriggers", null); xmlSATriggers.InnerText = sATriggers[device].ToString(); Node.AppendChild(xmlSATriggers); XmlNode xmlSATriggerCond = m_Xdoc.CreateNode(XmlNodeType.Element, "SATriggerCond", null); xmlSATriggerCond.InnerText = SaTriggerCondString(sATriggerCond[device]); Node.AppendChild(xmlSATriggerCond); XmlNode xmlSASteeringWheelEmulationAxis = m_Xdoc.CreateNode(XmlNodeType.Element, "SASteeringWheelEmulationAxis", null); xmlSASteeringWheelEmulationAxis.InnerText = sASteeringWheelEmulationAxis[device].ToString("G"); Node.AppendChild(xmlSASteeringWheelEmulationAxis); + XmlNode xmlSASteeringWheelEmulationRange = m_Xdoc.CreateNode(XmlNodeType.Element, "SASteeringWheelEmulationRange", null); xmlSASteeringWheelEmulationRange.InnerText = sASteeringWheelEmulationRange[device].ToString(); Node.AppendChild(xmlSASteeringWheelEmulationRange); XmlNode xmlTouchDisInvTriggers = m_Xdoc.CreateNode(XmlNodeType.Element, "TouchDisInvTriggers", null); string tempTouchDisInv = string.Join(",", touchDisInvertTriggers[device]); @@ -2640,6 +2648,9 @@ namespace DS4Windows try { Item = m_Xdoc.SelectSingleNode("/" + rootname + "/SASteeringWheelEmulationAxis"); DS4Controls.TryParse(Item.InnerText, out sASteeringWheelEmulationAxis[device]); } catch { sASteeringWheelEmulationAxis[device] = DS4Controls.None; missingSetting = true; } + try { Item = m_Xdoc.SelectSingleNode("/" + rootname + "/SASteeringWheelEmulationRange"); int.TryParse(Item.InnerText, out sASteeringWheelEmulationRange[device]); } + catch { sASteeringWheelEmulationRange[device] = 360; missingSetting = true; } + try { Item = m_Xdoc.SelectSingleNode("/" + rootname + "/TouchDisInvTriggers"); @@ -3281,6 +3292,10 @@ namespace DS4Windows el.AppendChild(m_Xdoc.CreateElement("Type")).InnerText = "MultiAction"; el.AppendChild(m_Xdoc.CreateElement("Details")).InnerText = details; break; + case 8: + el.AppendChild(m_Xdoc.CreateElement("Type")).InnerText = "SASteeringWheelEmulationCalibrate"; + el.AppendChild(m_Xdoc.CreateElement("Details")).InnerText = details; + break; } if (edit) @@ -3400,6 +3415,14 @@ namespace DS4Windows { actions.Add(new SpecialAction(name, controls, type, details)); } + else if (type == "SASteeringWheelEmulationCalibrate") + { + double doub; + if (double.TryParse(details, out doub)) + actions.Add(new SpecialAction(name, controls, type, "", doub)); + else + actions.Add(new SpecialAction(name, controls, type, "")); + } } } catch { saved = false; } @@ -3920,6 +3943,7 @@ namespace DS4Windows sATriggers[device] = string.Empty; sATriggerCond[device] = true; sASteeringWheelEmulationAxis[device] = DS4Controls.None; + sASteeringWheelEmulationRange[device] = 360; touchDisInvertTriggers[device] = new int[1] { -1 }; lsCurve[device] = rsCurve[device] = 0; gyroSensitivity[device] = 100; @@ -3941,7 +3965,7 @@ namespace DS4Windows public class SpecialAction { - public enum ActionTypeId { None, Key, Program, Profile, Macro, DisconnectBT, BatteryCheck, MultiAction, XboxGameDVR } + public enum ActionTypeId { None, Key, Program, Profile, Macro, DisconnectBT, BatteryCheck, MultiAction, XboxGameDVR, SASteeringWheelEmulationCalibrate } public string name; public List trigger = new List(); @@ -4059,6 +4083,10 @@ namespace DS4Windows type = "MultiAction"; this.details = string.Join(",", macros); } + else if (type == "SASteeringWheelEmulationCalibrate") + { + typeID = ActionTypeId.SASteeringWheelEmulationCalibrate; + } else this.details = details; diff --git a/DS4Windows/DS4Forms/Options.cs b/DS4Windows/DS4Forms/Options.cs index 7c9aa91..bad6866 100644 --- a/DS4Windows/DS4Forms/Options.cs +++ b/DS4Windows/DS4Forms/Options.cs @@ -873,6 +873,9 @@ namespace DS4Windows case "MultiAction": lvi.SubItems.Add(Properties.Resources.MultiAction); break; + case "SASteeringWheelEmulationCalibrate": + lvi.SubItems.Add(Properties.Resources.SASteeringWheelEmulationCalibrate); + break; } if (newp) diff --git a/DS4Windows/DS4Forms/SpecActions.Designer.cs b/DS4Windows/DS4Forms/SpecActions.Designer.cs index 054a3a3..3283401 100644 --- a/DS4Windows/DS4Forms/SpecActions.Designer.cs +++ b/DS4Windows/DS4Forms/SpecActions.Designer.cs @@ -86,7 +86,6 @@ this.lbDTapDVR = new System.Windows.Forms.Label(); this.lbHoldDVR = new System.Windows.Forms.Label(); this.lbTapDVR = new System.Windows.Forms.Label(); - this.advColorDialog = new DS4Windows.AdvancedColorDialog(); ((System.ComponentModel.ISupportInitialize)(this.pBProgram)).BeginInit(); this.pnlProgram.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.nUDProg)).BeginInit(); @@ -221,7 +220,8 @@ resources.GetString("cBActions.Items4"), resources.GetString("cBActions.Items5"), resources.GetString("cBActions.Items6"), - resources.GetString("cBActions.Items7")}); + resources.GetString("cBActions.Items7"), + resources.GetString("cBActions.Items8")}); resources.ApplyResources(this.cBActions, "cBActions"); this.cBActions.Name = "cBActions"; this.cBActions.SelectedIndexChanged += new System.EventHandler(this.cBActions_SelectedIndexChanged); @@ -596,13 +596,6 @@ resources.ApplyResources(this.lbTapDVR, "lbTapDVR"); this.lbTapDVR.Name = "lbTapDVR"; // - // advColorDialog - // - this.advColorDialog.AnyColor = true; - this.advColorDialog.Color = System.Drawing.Color.Blue; - this.advColorDialog.FullOpen = true; - this.advColorDialog.OnUpdateColor += new DS4Windows.AdvancedColorDialog.ColorUpdateHandler(this.advColorDialog_OnUpdateColor); - // // SpecActions // resources.ApplyResources(this, "$this"); diff --git a/DS4Windows/DS4Forms/SpecActions.cs b/DS4Windows/DS4Forms/SpecActions.cs index 3f65003..d70aa69 100644 --- a/DS4Windows/DS4Forms/SpecActions.cs +++ b/DS4Windows/DS4Forms/SpecActions.cs @@ -177,6 +177,10 @@ namespace DS4Windows cBHoldDVR.SelectedIndex = int.Parse(dets[1]); cBDTapDVR.SelectedIndex = int.Parse(dets[2]);*/ break; + case "SASteeringWheelEmulationCalibrate": + cBActions.SelectedIndex = 8; + nUDDCBT.Value = (decimal)act.delayTime; + break; } } @@ -331,6 +335,13 @@ namespace DS4Windows } break; + case 8: + action = Properties.Resources.SASteeringWheelEmulationCalibrate; + actRe = true; + if (!string.IsNullOrEmpty(oldprofilename) && oldprofilename != tBName.Text) + Global.RemoveAction(oldprofilename); + Global.SaveAction(tBName.Text, String.Join("/", controls), cBActions.SelectedIndex, Math.Round(nUDDCBT.Value, 1).ToString(), edit); + break; } if (actRe) { @@ -368,7 +379,7 @@ namespace DS4Windows pnlProgram.Visible = i == 2; pnlProfile.Visible = i == 3; pnlKeys.Visible = i == 4; - pnlDisconnectBT.Visible = i == 5; + pnlDisconnectBT.Visible = i == 5 || i == 8; // SASteeringWheelEmulationCalibrate action #8 re-uses DisconnectBT panel ("hold for X secs" detail option) pnlBatteryCheck.Visible = i == 6; pnlGameDVR.Visible = i == 7; btnSave.Enabled = i > 0; diff --git a/DS4Windows/DS4Forms/SpecActions.resx b/DS4Windows/DS4Forms/SpecActions.resx index 1df5a54..13b7a3a 100644 --- a/DS4Windows/DS4Forms/SpecActions.resx +++ b/DS4Windows/DS4Forms/SpecActions.resx @@ -796,13 +796,10 @@ - 19, 41 - - - 4, 4, 4, 4 + 14, 33 - 237, 267 + 178, 217 14 @@ -825,11 +822,8 @@ 0, 0 - - 4, 4, 4, 4 - - 204, 28 + 153, 23 16 @@ -853,13 +847,10 @@ NoControl - 1, 33 - - - 4, 4, 4, 4 + 1, 27 - 204, 28 + 153, 23 16 @@ -882,11 +873,8 @@ 0, 0 - - 4, 4, 4, 4 - - 203, 24 + 153, 21 17 @@ -913,13 +901,10 @@ NoControl - 275, 282 - - - 4, 4, 4, 4 + 206, 229 - 105, 28 + 79, 23 16 @@ -946,13 +931,10 @@ NoControl - 388, 282 - - - 4, 4, 4, 4 + 291, 229 - 91, 28 + 68, 23 16 @@ -976,13 +958,10 @@ NoControl - 16, 10 - - - 4, 0, 4, 0 + 12, 8 - 72, 16 + 54, 13 15 @@ -1009,13 +988,10 @@ Top, Left, Right - 77, 6 - - - 4, 4, 4, 4 + 58, 5 - 431, 22 + 324, 20 18 @@ -1056,14 +1032,14 @@ Mutli Action Button - - 273, 38 + + Calibration of sixaxis wheel emulation - - 4, 4, 4, 4 + + 205, 31 - 203, 24 + 153, 21 17 @@ -1093,13 +1069,10 @@ NoControl - 173, 65 - - - 4, 4, 4, 4 + 130, 53 - 31, 28 + 23, 23 Zoom @@ -1123,13 +1096,10 @@ NoControl - 4, 65 - - - 4, 0, 4, 0 + 3, 53 - 163, 28 + 122, 23 15 @@ -1159,13 +1129,10 @@ NoControl - 16, 38 - - - 4, 4, 4, 4 + 12, 31 - 243, 272 + 182, 221 257 @@ -1186,13 +1153,10 @@ NoControl - 0, 33 - - - 4, 4, 4, 4 + 0, 27 - 204, 28 + 153, 23 258 @@ -1888,13 +1852,10 @@ - 19, 41 - - - 4, 4, 4, 4 + 14, 33 - 237, 267 + 178, 217 259 @@ -1912,13 +1873,10 @@ 7 - 5, 112 - - - 4, 4, 4, 4 + 4, 91 - 196, 22 + 148, 20 264 @@ -1936,13 +1894,10 @@ 0 - 75, 4 - - - 4, 4, 4, 4 + 56, 3 - 71, 22 + 53, 20 263 @@ -1966,13 +1921,10 @@ NoControl - 4, 94 - - - 4, 0, 4, 0 + 3, 76 - 76, 17 + 57, 13 261 @@ -1999,20 +1951,17 @@ NoControl - 4, 6 - - - 4, 0, 4, 0 + 3, 5 - 63, 22 + 47, 18 261 Hold for - @Invariant + TopCenter @@ -2032,20 +1981,17 @@ NoControl - 153, 6 - - - 4, 0, 4, 0 + 115, 5 - 44, 22 + 33, 18 262 secs - @Invariant + TopCenter @@ -2062,13 +2008,10 @@ 4 - 275, 71 - - - 4, 4, 4, 4 + 206, 58 - 216, 153 + 162, 124 260 @@ -2095,13 +2038,10 @@ NoControl - 5, 37 - - - 4, 4, 4, 4 + 4, 30 - 99, 21 + 79, 17 17 @@ -2125,13 +2065,10 @@ NoControl - 0, 62 - - - 4, 0, 4, 0 + 0, 50 - 204, 28 + 153, 23 15 @@ -2152,13 +2089,10 @@ 2 - 275, 71 - - - 4, 4, 4, 4 + 206, 58 - 217, 116 + 163, 94 261 @@ -2182,13 +2116,10 @@ NoControl - 0, 68 - - - 4, 0, 4, 0 + 0, 55 - 133, 28 + 100, 23 259 @@ -2206,13 +2137,10 @@ 0 - 275, 71 - - - 4, 4, 4, 4 + 206, 58 - 215, 116 + 161, 94 262 @@ -2233,13 +2161,10 @@ 11 - 75, 4 - - - 4, 4, 4, 4 + 56, 3 - 71, 22 + 53, 20 260 @@ -2260,13 +2185,10 @@ NoControl - 4, 6 - - - 4, 0, 4, 0 + 3, 5 - 63, 22 + 47, 18 259 @@ -2293,13 +2215,10 @@ NoControl - 153, 6 - - - 4, 0, 4, 0 + 115, 5 - 44, 22 + 33, 18 259 @@ -2323,13 +2242,10 @@ 2 - 275, 71 - - - 4, 4, 4, 4 + 206, 58 - 215, 73 + 161, 59 262 @@ -2353,13 +2269,10 @@ NoControl - 7, 4 - - - 4, 4, 4, 4 + 5, 3 - 204, 28 + 153, 23 260 @@ -2386,13 +2299,10 @@ releasing unload trigger - 7, 94 - - - 4, 4, 4, 4 + 5, 76 - 203, 24 + 153, 21 17 @@ -2416,13 +2326,10 @@ NoControl - 7, 36 - - - 4, 4, 4, 4 + 5, 29 - 204, 28 + 153, 23 258 @@ -2449,13 +2356,10 @@ NoControl - 7, 75 - - - 4, 0, 4, 0 + 5, 61 - 203, 27 + 152, 22 259 @@ -2482,13 +2386,10 @@ 3 - 267, 71 - - - 4, 4, 4, 4 + 200, 58 - 215, 132 + 161, 107 263 @@ -2512,13 +2413,10 @@ NoControl - 37, 87 - - - 4, 4, 4, 4 + 28, 71 - 145, 16 + 109, 13 264 @@ -2542,13 +2440,10 @@ NoControl - 24, 31 - - - 4, 4, 4, 4 + 18, 25 - 120, 21 + 94, 17 263 @@ -2575,13 +2470,10 @@ NoControl - 24, 54 - - - 4, 4, 4, 4 + 18, 44 - 99, 21 + 77, 17 262 @@ -2608,13 +2500,10 @@ NoControl - 191, 87 - - - 4, 4, 4, 4 + 143, 71 - 17, 16 + 13, 13 261 @@ -2638,13 +2527,10 @@ NoControl - 12, 87 - - - 4, 4, 4, 4 + 9, 71 - 17, 16 + 13, 13 261 @@ -2662,13 +2548,10 @@ 4 - 75, 4 - - - 4, 4, 4, 4 + 56, 3 - 71, 22 + 53, 20 260 @@ -2689,20 +2572,17 @@ NoControl - 4, 6 - - - 4, 0, 4, 0 + 3, 5 - 63, 22 + 47, 18 259 Hold for - @Invariant + TopCenter @@ -2722,20 +2602,17 @@ NoControl - 171, 114 - - - 4, 0, 4, 0 + 128, 93 - 44, 22 + 33, 18 259 100% - @Invariant + TopRight @@ -2755,20 +2632,17 @@ NoControl - 8, 114 - - - 4, 0, 4, 0 + 6, 93 - 44, 22 + 33, 18 259 0% - @Invariant + lbEmptyBatt @@ -2785,20 +2659,17 @@ NoControl - 153, 6 - - - 4, 0, 4, 0 + 115, 5 - 44, 22 + 33, 18 259 secs - @Invariant + TopCenter @@ -2815,13 +2686,10 @@ 9 - 271, 73 - - - 4, 4, 4, 4 + 203, 59 - 215, 151 + 161, 123 264 @@ -2845,13 +2713,10 @@ NoControl - 4, 123 - - - 4, 4, 4, 4 + 3, 100 - 217, 28 + 163, 23 267 @@ -2875,13 +2740,10 @@ NoControl - 6, 73 - - - 4, 4, 4, 4 + 4, 59 - 217, 28 + 163, 23 266 @@ -2905,13 +2767,10 @@ NoControl - 6, 25 - - - 4, 4, 4, 4 + 4, 20 - 217, 28 + 163, 23 265 @@ -2938,13 +2797,10 @@ NoControl - 5, 102 - - - 4, 0, 4, 0 + 4, 83 - 132, 17 + 99, 13 263 @@ -2971,13 +2827,10 @@ NoControl - 5, 54 - - - 4, 0, 4, 0 + 4, 44 - 87, 17 + 65, 13 261 @@ -3004,13 +2857,10 @@ NoControl - 4, 4 - - - 4, 0, 4, 0 + 3, 3 - 83, 17 + 62, 13 259 @@ -3031,13 +2881,10 @@ 5 - 267, 70 - - - 4, 4, 4, 4 + 200, 57 - 228, 154 + 171, 125 264 @@ -3057,24 +2904,18 @@ 0 - - 267, 17 - True - 8, 16 + 6, 13 - 536, 325 - - - 4, 4, 4, 4 + 402, 264 SpecActions - @Invariant + cHTrigger @@ -3099,12 +2940,6 @@ System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - advColorDialog - - - DS4Windows.AdvancedColorDialog, DS4Windows, Version=1.4.5.0, Culture=neutral, PublicKeyToken=null - SpecActions diff --git a/DS4Windows/DS4Library/DS4Device.cs b/DS4Windows/DS4Library/DS4Device.cs index dc18361..1dc2991 100644 --- a/DS4Windows/DS4Library/DS4Device.cs +++ b/DS4Windows/DS4Library/DS4Device.cs @@ -157,6 +157,9 @@ namespace DS4Windows return warnInterval; } + public Int32 wheelPrevPhysicalAngle = 0; + public Int32 wheelFullTurnCount = 0; + public Point wheelCenterPoint; public Point wheel90DegPointLeft; public Point wheelCircleCenterPointLeft; @@ -165,13 +168,13 @@ namespace DS4Windows public DateTime wheelPrevRecalibrateTime; - private bool wheelRecalibrateActive = false; - public bool WheelRecalibrateActive + private int wheelRecalibrateActiveState = 0; + public int WheelRecalibrateActiveState { - get { return wheelRecalibrateActive; } + get { return wheelRecalibrateActiveState; } set { - wheelRecalibrateActive = value; + wheelRecalibrateActiveState = value; } } diff --git a/DS4Windows/DS4Windows.csproj b/DS4Windows/DS4Windows.csproj index aabe5b0..3646923 100644 --- a/DS4Windows/DS4Windows.csproj +++ b/DS4Windows/DS4Windows.csproj @@ -250,6 +250,7 @@ + DS4Form.cs Designer @@ -874,6 +875,7 @@ SpecActions.cs + Designer SpecActions.cs diff --git a/DS4Windows/Properties/Resources.Designer.cs b/DS4Windows/Properties/Resources.Designer.cs index 2f233d7..3236d4e 100644 --- a/DS4Windows/Properties/Resources.Designer.cs +++ b/DS4Windows/Properties/Resources.Designer.cs @@ -1627,6 +1627,15 @@ namespace DS4Windows.Properties { } } + /// + /// Looks up a localized string similar to Calibration of sixaxis wheel emulation. + /// + internal static string SASteeringWheelEmulationCalibrate { + get { + return ResourceManager.GetString("SASteeringWheelEmulationCalibrate", resourceCulture); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/DS4Windows/Properties/Resources.resx b/DS4Windows/Properties/Resources.resx index f04a78a..d6b7155 100644 --- a/DS4Windows/Properties/Resources.resx +++ b/DS4Windows/Properties/Resources.resx @@ -637,6 +637,9 @@ Shortcuts + + Calibration of sixaxis wheel emulation + Click for advanced Sixaxis reading diff --git a/DS4Windows/Settings.cs b/DS4Windows/Settings.cs new file mode 100644 index 0000000..ac0570e --- /dev/null +++ b/DS4Windows/Settings.cs @@ -0,0 +1,28 @@ +namespace DS4Windows.Properties { + + + // This class allows you to handle specific events on the settings class: + // The SettingChanging event is raised before a setting's value is changed. + // The PropertyChanged event is raised after a setting's value is changed. + // The SettingsLoaded event is raised after the setting values are loaded. + // The SettingsSaving event is raised before the setting values are saved. + internal sealed partial class Settings { + + public Settings() { + // // To add event handlers for saving and changing settings, uncomment the lines below: + // + // this.SettingChanging += this.SettingChangingEventHandler; + // + // this.SettingsSaving += this.SettingsSavingEventHandler; + // + } + + private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) { + // Add code to handle the SettingChangingEvent event here. + } + + private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) { + // Add code to handle the SettingsSaving event here. + } + } +} From 94d5f83db10ab6450a876251e8aa331d93d01137 Mon Sep 17 00:00:00 2001 From: mika-n Date: Fri, 7 Dec 2018 00:09:09 +0200 Subject: [PATCH 04/14] Added config options of sixaxis steering wheel emulation and calibration btn to config GUI form (Options). If controller is calibrated via this config screen then there is no need to define "calibrate" special action key. Improved >360 turn range calculation. Added possibility to map SA steering values to L2+R2 trigger axis (Z-axis). For example in ETS2/ATS game controller settings it is possible to use that axis for steering if triggers are not used for throttling and braking (this would leave all axis of L and R thumbstick available for other purposes). --- DS4Windows/DS4Control/Mapping.cs | 182 ++++++++++--------- DS4Windows/DS4Control/X360Device.cs | 19 +- DS4Windows/DS4Forms/Options.Designer.cs | 102 +++++++++-- DS4Windows/DS4Forms/Options.cs | 95 ++++++++++ DS4Windows/DS4Forms/Options.resx | 191 +++++++++++++++++++- DS4Windows/DS4Library/DS4Device.cs | 1 + DS4Windows/Properties/Resources.Designer.cs | 54 ++++++ DS4Windows/Properties/Resources.resx | 18 ++ 8 files changed, 552 insertions(+), 110 deletions(-) diff --git a/DS4Windows/DS4Control/Mapping.cs b/DS4Windows/DS4Control/Mapping.cs index 32e561a..4b052de 100644 --- a/DS4Windows/DS4Control/Mapping.cs +++ b/DS4Windows/DS4Control/Mapping.cs @@ -3804,18 +3804,13 @@ namespace DS4Windows private const int C_WHEEL_ANGLE_PRECISION_DECIMALS = (C_WHEEL_ANGLE_PRECISION == 1 ? 0 : C_WHEEL_ANGLE_PRECISION/10); // "In-game" calibration process: - // TODO: Launching a calibration process should probably be a special action which allows multiple key bindings to launch a specific task. - // - Place controller at "steering wheel center" position and press DS4 Option button to start the calibration (Profile should have "SASteeringWheelEmulationAxis" option set to LXPos, LYPos, RXPos or RYPos value). - // - Hold the controller still for a while and wait until red lightbar turns to blinking yellow (center point calibrated) - // - Turn the controller at 90 degree left or right position and hold still for few seconds and wait until lightbar turns to blinking light blue (two points calibrated) - // - Turn the controller at 90 degree position on the opposite side (but do it by going through 0 degree center position. Don't go through 180deg mark). Wait until lighbar turns to green (three points calibrated) - // - Now you can check the calibratio by turning the wheel and see when the green lightbar starts to blink (it should blink at those three calibrated positions). - // - Press DS4 Options button to accept the calibration (result is saved to ControllerConfigs.xml xml file in AppData folder). + // - Place controller at "steering wheel center" position and press "Calibrate SA steering wheel" special action button to start the calibration + // - Hold the controller still for a while at center point and press "X" + // - Turn the controller at 90 degree left or right position and hold still for few seconds and press "X" + // - Turn the controller at 90 degree position on the opposite side and press "X" + // - Now you can check the calibratio by turning the wheel and see when the green lightbar starts to blink (it should blink at those three calibrated positions) + // - Press "Calibrate SA steering wheel" special action key to accept the calibration (result is saved to ControllerConfigs.xml xml file) // - // 0 = None of the anchors calibrated yet. Hold controller still at "wheel center position" and wait until lightbar turns from constant red to flashing yellow color - // 1 = one anchor calibrated (The first calibration point should always be the center position) - // 2 = two anchors calibrated (center and 90Right or 90Left depending on which way user turn the wheel after the first center calibration point) - // 3 = all three anchor points calibrated (center, 90Right, 90Left). Good to go. User can check calibration by turning the wheel and checking when the green lightbar blinks. If happy then pressing Options btn accepts the calibration. private static readonly DS4Color calibrationColor_0 = new DS4Color { red = 0xA0, green = 0x00, blue = 0x00 }; private static readonly DS4Color calibrationColor_1 = new DS4Color { red = 0xFF, green = 0xFF, blue = 0x00 }; private static readonly DS4Color calibrationColor_2 = new DS4Color { red = 0x00, green = 0x50, blue = 0x50 }; @@ -3851,7 +3846,6 @@ namespace DS4Windows } // Calculate and return the angle of the controller as -180...0...+180 value. - // TODO: Support >360 degree turn range by adding "lap counter" when wheel is rotated full rounds left or right.At the moment this logic supports only 360 degree turn range. private static Int32 CalculateControllerAngle(int gyroAccelX, int gyroAccelZ, DS4Device controller) { Int32 result; @@ -3908,6 +3902,7 @@ namespace DS4Windows return result; } + // Calibrate sixaxis steering wheel emulation. Use DS4Windows configuration screen to start a calibration or press a special action key (if defined) private static void SAWheelEmulationCalibration(int device, DS4StateExposed exposedState, ControlService ctrl, DS4State currentDeviceState, DS4Device controller) { int gyroAccelX, gyroAccelZ; @@ -3916,8 +3911,7 @@ namespace DS4Windows gyroAccelX = exposedState.getAccelX(); gyroAccelZ = exposedState.getAccelZ(); - // "SASteeringWheelEmulaationCalibration" action key combination is used to run "in-game" calibration process (user can define the action key combination in DS4Windows GUI). - // State 0=Normal mode (ie. calibration process is not running), 1=Activating calibration, 2=Calibration process running, 3=Completing calibration + // 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) { AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} activated re-calibration of SA steering wheel emulation", false); @@ -3925,21 +3919,22 @@ namespace DS4Windows controller.WheelRecalibrateActiveState = 2; controller.wheelPrevPhysicalAngle = 0; + controller.wheelPrevFullAngle = 0; controller.wheelFullTurnCount = 0; // Clear existing calibration value and use current position as "center" point. // This initial center value may be off-center because of shaking the controller while button was pressed. The value will be overriden with correct value once controller is stabilized and hold still few secs at the center point controller.wheelCenterPoint.X = gyroAccelX; controller.wheelCenterPoint.Y = gyroAccelZ; - controller.wheel90DegPointRight.X = gyroAccelX + 25; - controller.wheel90DegPointLeft.X = gyroAccelX - 25; + controller.wheel90DegPointRight.X = gyroAccelX + 20; + controller.wheel90DegPointLeft.X = gyroAccelX - 20; // Clear bitmask for calibration points. All three calibration points need to be set before re-calibration process is valid controller.wheelCalibratedAxisBitmask = DS4Device.WheelCalibrationPoint.None; - controller.wheelPrevRecalibrateTime = DateTime.Now; + controller.wheelPrevRecalibrateTime = new DateTime(2500, 1, 1); } - else if (controller.WheelRecalibrateActiveState == 3 /*&& currentDeviceState.Options && ((TimeSpan)(DateTime.Now - controller.wheelPrevRecalibrateTime)).TotalSeconds > 3 */) + else if (controller.WheelRecalibrateActiveState == 3) { AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} completed the calibration of SA steering wheel emulation. center=({controller.wheelCenterPoint.X}, {controller.wheelCenterPoint.Y}) 90L=({controller.wheel90DegPointLeft.X}, {controller.wheel90DegPointLeft.Y}) 90R=({controller.wheel90DegPointRight.X}, {controller.wheel90DegPointRight.Y})", false); @@ -3949,36 +3944,34 @@ namespace DS4Windows else controller.wheelCenterPoint.X = controller.wheelCenterPoint.Y = 0; - // Reset lightbar back to normal color - // TODO: hmmm... Sometimes color is not reset back to normal "main color". Investigate why following code logic doesn't always restore it. - DS4LightBar.forcelight[device] = false; - DS4LightBar.forcedFlash[device] = 0; - DS4LightBar.updateLightBar(controller, device); - - controller.LightBarColor = Global.getMainColor(device); - DS4LightBar.updateLightBar(controller, device); + controller.WheelRecalibrateActiveState = 0; + controller.wheelPrevRecalibrateTime = DateTime.Now; + } + else if (controller.WheelRecalibrateActiveState == 4) + { + AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} cancelled the calibration of SA steering wheel emulation.", false); controller.WheelRecalibrateActiveState = 0; - controller.wheelPrevRecalibrateTime = DateTime.Now; } if (controller.WheelRecalibrateActiveState > 0) { - // Auto calibrate 90deg left/right positions (these values may change over time because gyro/accel sensor values are not "precise mathematics", so user can trigger calibration even in mid-game sessions by pressing DS4 Options btn), - // but make sure controller is stable enough to avoid misaligments because of hard shaking (check velocity of gyro axis) - if (Math.Abs(currentDeviceState.Motion.angVelPitch) < 0.5 && Math.Abs(currentDeviceState.Motion.angVelYaw) < 0.5 && Math.Abs(currentDeviceState.Motion.angVelRoll) < 0.5) + // Cross "X" key pressed. Set calibration point when the key is released and controller hold steady for a few seconds + if(currentDeviceState.Cross == true) controller.wheelPrevRecalibrateTime = DateTime.Now; + + // Make sure controller is hold steady (velocity of gyro axis) to avoid misaligments and set calibration few secs after the "X" key was released + if (Math.Abs(currentDeviceState.Motion.angVelPitch) < 0.5 && Math.Abs(currentDeviceState.Motion.angVelYaw) < 0.5 && Math.Abs(currentDeviceState.Motion.angVelRoll) < 0.5 + && ((TimeSpan)(DateTime.Now - controller.wheelPrevRecalibrateTime)).TotalSeconds > 1) { + controller.wheelPrevRecalibrateTime = new DateTime(2500, 1, 1); + if (controller.wheelCalibratedAxisBitmask == DS4Device.WheelCalibrationPoint.None) { - // Wait few secs after re-calibration button was pressed. Hold controller still at center position and don't shake it too much until red lightbar turns to yellow. - if (((TimeSpan)(DateTime.Now - controller.wheelPrevRecalibrateTime)).TotalSeconds >= 3) - { - controller.wheelCenterPoint.X = gyroAccelX; - controller.wheelCenterPoint.Y = gyroAccelZ; + controller.wheelCenterPoint.X = gyroAccelX; + controller.wheelCenterPoint.Y = gyroAccelZ; - controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Center; - } + controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Center; } else if (controller.wheel90DegPointRight.X < gyroAccelX) { @@ -4001,8 +3994,7 @@ namespace DS4Windows } // Show lightbar color feedback how the calibration process is proceeding. - // red / yellow / blue / green = No calibration anchors/one anchor/two anchors/three anchors calibrated (center, 90DegLeft, 90DegRight) - // Blinking led = Controller is tilted at the current calibration point (or calibration routine just set a new anchor point) + // red / yellow / blue / green = No calibration anchors/one anchor/two anchors/all three anchors calibrated when color turns to green (center, 90DegLeft, 90DegRight). int bitsSet = CountNumOfSetBits((int)controller.wheelCalibratedAxisBitmask); if (bitsSet >= 3) DS4LightBar.forcedColor[device] = calibrationColor_3; else if (bitsSet == 2) DS4LightBar.forcedColor[device] = calibrationColor_2; @@ -4010,20 +4002,28 @@ namespace DS4Windows else DS4LightBar.forcedColor[device] = calibrationColor_0; result = CalculateControllerAngle(gyroAccelX, gyroAccelZ, controller); - if (bitsSet >= 1 && ( - Math.Abs(result) <= 1 * C_WHEEL_ANGLE_PRECISION - || (Math.Abs(result) >= 88 * C_WHEEL_ANGLE_PRECISION && Math.Abs(result) <= 92 * C_WHEEL_ANGLE_PRECISION) - || Math.Abs(result) >= 178 * C_WHEEL_ANGLE_PRECISION - )) + + // Force lightbar flashing when controller is currently at calibration point (user can verify the calibration before accepting it by looking at flashing lightbar) + if( ((controller.wheelCalibratedAxisBitmask & DS4Device.WheelCalibrationPoint.Center) != 0 && Math.Abs(result) <= 1 * C_WHEEL_ANGLE_PRECISION) + || ((controller.wheelCalibratedAxisBitmask & DS4Device.WheelCalibrationPoint.Left90) != 0 && result <= -89 * C_WHEEL_ANGLE_PRECISION && result >= -91 * C_WHEEL_ANGLE_PRECISION) + || ((controller.wheelCalibratedAxisBitmask & DS4Device.WheelCalibrationPoint.Right90) != 0 && result >= 89 * C_WHEEL_ANGLE_PRECISION && result <= 91 * C_WHEEL_ANGLE_PRECISION) + || ((controller.wheelCalibratedAxisBitmask & DS4Device.WheelCalibrationPoint.Left90) != 0 && Math.Abs(result) >= 179 * C_WHEEL_ANGLE_PRECISION) ) DS4LightBar.forcedFlash[device] = 2; else DS4LightBar.forcedFlash[device] = 0; DS4LightBar.forcelight[device] = true; - //DS4LightBar.updateLightBar(controller, device); LogToGuiSACalibrationDebugMsg($"Calibration values ({gyroAccelX}, {gyroAccelZ}) angle={result / (1.0 * C_WHEEL_ANGLE_PRECISION)}\n"); } + else + { + // Re-calibration completed or cancelled. Set lightbar color back to normal color + DS4LightBar.forcedFlash[device] = 0; + DS4LightBar.forcedColor[device] = Global.getMainColor(device); + DS4LightBar.forcelight[device] = false; + DS4LightBar.updateLightBar(controller, device); + } } protected static Int32 Scale360degreeGyroAxis(int device, DS4StateExposed exposedState, ControlService ctrl) @@ -4036,13 +4036,12 @@ namespace DS4Windows int gyroAccelX, gyroAccelZ; int result; - //controller = Program.rootHub.DS4Controllers[device]; controller = ctrl.DS4Controllers[device]; if (controller == null) return 0; currentDeviceState = controller.getCurrentStateRef(); - // If user has pressed "SASteeringWheelEmulationCalibration" special action key combination then run "in-game" calibration process + // If calibration is active then do the calibration process instead of the normal "angle calculation" if (controller.WheelRecalibrateActiveState > 0) { SAWheelEmulationCalibration(device, exposedState, ctrl, currentDeviceState, controller); @@ -4081,47 +4080,68 @@ namespace DS4Windows controller.wheelPrevRecalibrateTime = DateTime.Now; } - result = CalculateControllerAngle(gyroAccelX, gyroAccelZ, controller); - - if (controller.wheelPrevPhysicalAngle < 0 && result > 0) - { - // If wrapped around from -180 to +180 side then SA steering wheel keeps on turning "left" (ie. beyond 360 degrees) - if ((result - controller.wheelPrevPhysicalAngle) > 180 * C_WHEEL_ANGLE_PRECISION) - controller.wheelFullTurnCount--; - } - else if (controller.wheelPrevPhysicalAngle > 0 && result < 0) - { - // If wrapped around from +180 to -180 side then SA steering wheel keeps on turning "right" (ie. beyond 360 degrees) - if ((controller.wheelPrevPhysicalAngle - result) > 180 * C_WHEEL_ANGLE_PRECISION) - controller.wheelFullTurnCount++; - } - controller.wheelPrevPhysicalAngle = result; - - if (controller.wheelFullTurnCount != 0) - { - if (controller.wheelFullTurnCount > 0) - result = (controller.wheelFullTurnCount * 180 * C_WHEEL_ANGLE_PRECISION) + ((controller.wheelFullTurnCount * 180 * C_WHEEL_ANGLE_PRECISION) + result); - else - result = (controller.wheelFullTurnCount * 180 * C_WHEEL_ANGLE_PRECISION) - ((controller.wheelFullTurnCount * -180 * C_WHEEL_ANGLE_PRECISION) - result); - } - - // TODO: How to prevent "too many laps" problem or reset it (DS4Win doesn't know when a game is running so it keeps counting laps)? int maxRangeRight = Global.getSASteeringWheelEmulationRange(device) / 2 * C_WHEEL_ANGLE_PRECISION; int maxRangeLeft = -maxRangeRight; - if (maxRangeRight != (360 / 2 * C_WHEEL_ANGLE_PRECISION) ) - result = Mapping.ClampInt(maxRangeLeft, result, maxRangeRight); + result = CalculateControllerAngle(gyroAccelX, gyroAccelZ, controller); - // Keep outputting debug data 30secs after the latest re-calibration event (user can check these values from the log screen of DS4Windows GUI) - //if (((TimeSpan)(DateTime.Now - prevRecalibrateTime)).TotalSeconds < 30) - // LogToGuiSACalibrationDebugMsg($"DEBUG gyro=({gyroAccelX}, {gyroAccelZ}) angle={result / (1.0 * C_WHEEL_ANGLE_PRECISION)}"); + // 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) + { + if ((result - controller.wheelPrevPhysicalAngle) > 180 * C_WHEEL_ANGLE_PRECISION) + if (maxRangeRight > 360 * C_WHEEL_ANGLE_PRECISION) + wheelFullTurnCount--; + else + result = maxRangeLeft; + } + else if (controller.wheelPrevPhysicalAngle > 0 && result < 0) + { + if ((controller.wheelPrevPhysicalAngle - result) > 180 * C_WHEEL_ANGLE_PRECISION) + if (maxRangeRight > 360 * C_WHEEL_ANGLE_PRECISION) + wheelFullTurnCount++; + else + result = maxRangeRight; + } + controller.wheelPrevPhysicalAngle = result; - //LogToGuiSACalibrationDebugMsg($"DEBUG gyro=({gyroAccelX}, {gyroAccelZ}) angle={result / (1.0 * C_WHEEL_ANGLE_PRECISION)} fullTurns={controller.wheelFullTurnCount}", false); + if (wheelFullTurnCount != 0) + { + if (wheelFullTurnCount > 0) + result = (wheelFullTurnCount * 180 * C_WHEEL_ANGLE_PRECISION) + ((wheelFullTurnCount * 180 * C_WHEEL_ANGLE_PRECISION) + result); + else + result = (wheelFullTurnCount * 180 * C_WHEEL_ANGLE_PRECISION) - ((wheelFullTurnCount * -180 * C_WHEEL_ANGLE_PRECISION) - result); + } - // Scale input to a raw x360 thumbstick output scale - //return (((result - (-180 * C_WHEEL_ANGLE_PRECISION)) * (32767 - (-32768))) / (180 * C_WHEEL_ANGLE_PRECISION - (-180 * C_WHEEL_ANGLE_PRECISION))) + (-32768); - return (( (result - maxRangeLeft) * (32767 - (-32768))) / (maxRangeRight - maxRangeLeft)) + (-32768); + // If the new angle is more than 180 degrees further away then this is probably bogus value (controller shaking too much and gyro and velocity sensors went crazy). + // Accept the new angle only when the new angle is within a "stability threshold", otherwise use the previous full angle value. + if (Math.Abs(result - controller.wheelPrevFullAngle) <= 180 * C_WHEEL_ANGLE_PRECISION) + { + controller.wheelPrevFullAngle = result; + controller.wheelFullTurnCount = wheelFullTurnCount; + } + else + { + result = controller.wheelPrevFullAngle; + } + + result = Mapping.ClampInt(maxRangeLeft, result, maxRangeRight); + + //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); + + // Scale input to a raw x360 16bit output scale, except if output axis of steering wheel emulation is L2+R2 trigger axis. + // L2+R2 triggers use independent 8bit values, so use -255..0..+255 scaled values (therefore L2+R2 Trigger axis supports only 360 turn range) + if (Global.getSASteeringWheelEmulationAxis(device) != DS4Controls.L2) + { + return (((result - maxRangeLeft) * (32767 - (-32768))) / (maxRangeRight - maxRangeLeft)) + (-32768); + } + else + { + 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); + } } } // END: SixAxis steering wheel emulation logic diff --git a/DS4Windows/DS4Control/X360Device.cs b/DS4Windows/DS4Control/X360Device.cs index 7ef8123..a19fc51 100644 --- a/DS4Windows/DS4Control/X360Device.cs +++ b/DS4Windows/DS4Control/X360Device.cs @@ -140,16 +140,27 @@ namespace DS4Windows if (state.PS) Output[11] |= (Byte)(1 << 2); // Guide - Output[12] = state.L2; // Left Trigger - Output[13] = state.R2; // Right Trigger + DS4Controls steeringWheelMappedAxis = Global.getSASteeringWheelEmulationAxis(device); + + if (steeringWheelMappedAxis == DS4Controls.L2) + { + Output[12] = Output[13] = 0; + if (state.SASteeringWheelEmulationUnit >= 0) + Output[12] = (Byte)state.SASteeringWheelEmulationUnit; + else + Output[13] = (Byte)state.SASteeringWheelEmulationUnit; + } + else + { + Output[12] = state.L2; // Left Trigger + Output[13] = state.R2; // Right Trigger + } Int32 ThumbLX; Int32 ThumbLY; Int32 ThumbRX; Int32 ThumbRY; - DS4Controls steeringWheelMappedAxis = Global.getSASteeringWheelEmulationAxis(device); - if (steeringWheelMappedAxis == DS4Controls.LXPos) ThumbLX = state.SASteeringWheelEmulationUnit; else ThumbLX = Scale(state.LX, false); diff --git a/DS4Windows/DS4Forms/Options.Designer.cs b/DS4Windows/DS4Forms/Options.Designer.cs index cd9742a..7bc6cfb 100644 --- a/DS4Windows/DS4Forms/Options.Designer.cs +++ b/DS4Windows/DS4Forms/Options.Designer.cs @@ -188,6 +188,11 @@ this.lbGyroXP = new System.Windows.Forms.Label(); this.bnGyroXN = new System.Windows.Forms.Button(); this.lbGyroXN = new System.Windows.Forms.Label(); + this.lblSteeringWheelEmulationAxis = new System.Windows.Forms.Label(); + this.cBSteeringWheelEmulationAxis = new System.Windows.Forms.ComboBox(); + this.lblSteeringWheelEmulationRange = new System.Windows.Forms.Label(); + this.cBSteeringWheelEmulationRange = new System.Windows.Forms.ComboBox(); + this.btnSteeringWheelEmulationCalibrate = new System.Windows.Forms.Button(); this.tCControls = new System.Windows.Forms.TabControl(); this.tPControls = new System.Windows.Forms.TabPage(); this.lBControls = new System.Windows.Forms.ListBox(); @@ -315,6 +320,8 @@ this.rBSAControls = new System.Windows.Forms.RadioButton(); this.rBSAMouse = new System.Windows.Forms.RadioButton(); this.pnlSAMouse = new System.Windows.Forms.Panel(); + this.label26 = new System.Windows.Forms.Label(); + this.triggerCondAndCombo = new System.Windows.Forms.ComboBox(); this.cBGyroMouseXAxis = new System.Windows.Forms.ComboBox(); this.label16 = new System.Windows.Forms.Label(); this.lbGyroSmooth = new System.Windows.Forms.Label(); @@ -386,8 +393,6 @@ this.optionsTouchInvStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.shareTouchInvStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.psTouchInvStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.triggerCondAndCombo = new System.Windows.Forms.ComboBox(); - this.label26 = new System.Windows.Forms.Label(); this.advColorDialog = new DS4Windows.AdvancedColorDialog(); ((System.ComponentModel.ISupportInitialize)(this.nUDRainbow)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.tBBlueBar)).BeginInit(); @@ -1856,6 +1861,11 @@ this.fLPTiltControls.Controls.Add(this.lbGyroXP); this.fLPTiltControls.Controls.Add(this.bnGyroXN); this.fLPTiltControls.Controls.Add(this.lbGyroXN); + this.fLPTiltControls.Controls.Add(this.lblSteeringWheelEmulationAxis); + this.fLPTiltControls.Controls.Add(this.cBSteeringWheelEmulationAxis); + this.fLPTiltControls.Controls.Add(this.lblSteeringWheelEmulationRange); + this.fLPTiltControls.Controls.Add(this.cBSteeringWheelEmulationRange); + this.fLPTiltControls.Controls.Add(this.btnSteeringWheelEmulationCalibrate); resources.ApplyResources(this.fLPTiltControls, "fLPTiltControls"); this.fLPTiltControls.Name = "fLPTiltControls"; // @@ -1911,6 +1921,56 @@ resources.ApplyResources(this.lbGyroXN, "lbGyroXN"); this.lbGyroXN.Name = "lbGyroXN"; // + // lblSteeringWheelEmulationAxis + // + resources.ApplyResources(this.lblSteeringWheelEmulationAxis, "lblSteeringWheelEmulationAxis"); + this.lblSteeringWheelEmulationAxis.Name = "lblSteeringWheelEmulationAxis"; + // + // cBSteeringWheelEmulationAxis + // + this.cBSteeringWheelEmulationAxis.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cBSteeringWheelEmulationAxis.FormattingEnabled = true; + this.cBSteeringWheelEmulationAxis.Items.AddRange(new object[] { + resources.GetString("cBSteeringWheelEmulationAxis.Items"), + resources.GetString("cBSteeringWheelEmulationAxis.Items1"), + resources.GetString("cBSteeringWheelEmulationAxis.Items2"), + resources.GetString("cBSteeringWheelEmulationAxis.Items3"), + resources.GetString("cBSteeringWheelEmulationAxis.Items4"), + resources.GetString("cBSteeringWheelEmulationAxis.Items5")}); + resources.ApplyResources(this.cBSteeringWheelEmulationAxis, "cBSteeringWheelEmulationAxis"); + this.cBSteeringWheelEmulationAxis.Name = "cBSteeringWheelEmulationAxis"; + this.cBSteeringWheelEmulationAxis.SelectedIndexChanged += new System.EventHandler(this.cBSteeringWheelEmulationAxis_SelectedIndexChanged); + // + // lblSteeringWheelEmulationRange + // + resources.ApplyResources(this.lblSteeringWheelEmulationRange, "lblSteeringWheelEmulationRange"); + this.lblSteeringWheelEmulationRange.Name = "lblSteeringWheelEmulationRange"; + // + // cBSteeringWheelEmulationRange + // + this.cBSteeringWheelEmulationRange.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cBSteeringWheelEmulationRange.FormattingEnabled = true; + this.cBSteeringWheelEmulationRange.Items.AddRange(new object[] { + resources.GetString("cBSteeringWheelEmulationRange.Items"), + resources.GetString("cBSteeringWheelEmulationRange.Items1"), + resources.GetString("cBSteeringWheelEmulationRange.Items2"), + resources.GetString("cBSteeringWheelEmulationRange.Items3"), + resources.GetString("cBSteeringWheelEmulationRange.Items4"), + resources.GetString("cBSteeringWheelEmulationRange.Items5"), + resources.GetString("cBSteeringWheelEmulationRange.Items6"), + resources.GetString("cBSteeringWheelEmulationRange.Items7"), + resources.GetString("cBSteeringWheelEmulationRange.Items8")}); + resources.ApplyResources(this.cBSteeringWheelEmulationRange, "cBSteeringWheelEmulationRange"); + this.cBSteeringWheelEmulationRange.Name = "cBSteeringWheelEmulationRange"; + this.cBSteeringWheelEmulationRange.SelectedIndexChanged += new System.EventHandler(this.cBSteeringWheelEmulationRange_SelectedIndexChanged); + // + // btnSteeringWheelEmulationCalibrate + // + resources.ApplyResources(this.btnSteeringWheelEmulationCalibrate, "btnSteeringWheelEmulationCalibrate"); + this.btnSteeringWheelEmulationCalibrate.Name = "btnSteeringWheelEmulationCalibrate"; + this.btnSteeringWheelEmulationCalibrate.UseVisualStyleBackColor = true; + this.btnSteeringWheelEmulationCalibrate.Click += new System.EventHandler(this.btnSteeringWheelEmulationCalibrate_Click); + // // tCControls // this.tCControls.Controls.Add(this.tPControls); @@ -3362,8 +3422,8 @@ this.gBGyro.BackColor = System.Drawing.SystemColors.Control; this.gBGyro.Controls.Add(this.rBSAControls); this.gBGyro.Controls.Add(this.rBSAMouse); - this.gBGyro.Controls.Add(this.pnlSAMouse); this.gBGyro.Controls.Add(this.fLPTiltControls); + this.gBGyro.Controls.Add(this.pnlSAMouse); resources.ApplyResources(this.gBGyro, "gBGyro"); this.gBGyro.Name = "gBGyro"; this.gBGyro.TabStop = false; @@ -3408,6 +3468,21 @@ resources.ApplyResources(this.pnlSAMouse, "pnlSAMouse"); this.pnlSAMouse.Name = "pnlSAMouse"; // + // label26 + // + resources.ApplyResources(this.label26, "label26"); + this.label26.Name = "label26"; + // + // triggerCondAndCombo + // + this.triggerCondAndCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.triggerCondAndCombo.FormattingEnabled = true; + this.triggerCondAndCombo.Items.AddRange(new object[] { + resources.GetString("triggerCondAndCombo.Items"), + resources.GetString("triggerCondAndCombo.Items1")}); + resources.ApplyResources(this.triggerCondAndCombo, "triggerCondAndCombo"); + this.triggerCondAndCombo.Name = "triggerCondAndCombo"; + // // cBGyroMouseXAxis // this.cBGyroMouseXAxis.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; @@ -4110,21 +4185,6 @@ resources.ApplyResources(this.psTouchInvStripMenuItem, "psTouchInvStripMenuItem"); this.psTouchInvStripMenuItem.CheckedChanged += new System.EventHandler(this.TouchDisableInvert_CheckedChanged); // - // triggerCondAndCombo - // - this.triggerCondAndCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.triggerCondAndCombo.FormattingEnabled = true; - this.triggerCondAndCombo.Items.AddRange(new object[] { - resources.GetString("triggerCondAndCombo.Items"), - resources.GetString("triggerCondAndCombo.Items1")}); - resources.ApplyResources(this.triggerCondAndCombo, "triggerCondAndCombo"); - this.triggerCondAndCombo.Name = "triggerCondAndCombo"; - // - // label26 - // - resources.ApplyResources(this.label26, "label26"); - this.label26.Name = "label26"; - // // Options // resources.ApplyResources(this, "$this"); @@ -4192,6 +4252,7 @@ this.pnlLSTrack.ResumeLayout(false); this.pnlRSTrack.ResumeLayout(false); this.fLPTiltControls.ResumeLayout(false); + this.fLPTiltControls.PerformLayout(); this.tCControls.ResumeLayout(false); this.tPControls.ResumeLayout(false); this.pnlController.ResumeLayout(false); @@ -4614,5 +4675,10 @@ private System.Windows.Forms.CheckBox trackballCk; private System.Windows.Forms.Label label26; private System.Windows.Forms.ComboBox triggerCondAndCombo; + private System.Windows.Forms.Label lblSteeringWheelEmulationAxis; + private System.Windows.Forms.ComboBox cBSteeringWheelEmulationAxis; + private System.Windows.Forms.Label lblSteeringWheelEmulationRange; + private System.Windows.Forms.ComboBox cBSteeringWheelEmulationRange; + private System.Windows.Forms.Button btnSteeringWheelEmulationCalibrate; } } \ No newline at end of file diff --git a/DS4Windows/DS4Forms/Options.cs b/DS4Windows/DS4Forms/Options.cs index bad6866..b10b94c 100644 --- a/DS4Windows/DS4Forms/Options.cs +++ b/DS4Windows/DS4Forms/Options.cs @@ -718,6 +718,20 @@ namespace DS4Windows nUDGyroSmoothWeight.Value = (decimal)(GyroSmoothingWeight[device]); cBGyroMouseXAxis.SelectedIndex = GyroMouseHorizontalAxis[device]; triggerCondAndCombo.SelectedIndex = SATriggerCond[device] ? 0 : 1; + + switch (getSASteeringWheelEmulationAxis(device)) + { + case DS4Controls.None: cBSteeringWheelEmulationAxis.SelectedIndex = 0; break; + case DS4Controls.LXPos: cBSteeringWheelEmulationAxis.SelectedIndex = 1; break; + case DS4Controls.LYPos: cBSteeringWheelEmulationAxis.SelectedIndex = 2; break; + case DS4Controls.RXPos: cBSteeringWheelEmulationAxis.SelectedIndex = 3; break; + case DS4Controls.RYPos: cBSteeringWheelEmulationAxis.SelectedIndex = 4; break; + case DS4Controls.L2: + case DS4Controls.R2: cBSteeringWheelEmulationAxis.SelectedIndex = 5; break; + } + + int idxSASteeringWheelEmulationRange = cBSteeringWheelEmulationRange.Items.IndexOf(getSASteeringWheelEmulationRange(device).ToString()); + if (idxSASteeringWheelEmulationRange >= 0) cBSteeringWheelEmulationRange.SelectedIndex = idxSASteeringWheelEmulationRange; } else { @@ -836,6 +850,8 @@ namespace DS4Windows nUDGyroSmoothWeight.Value = 0.5m; cBGyroMouseXAxis.SelectedIndex = 0; triggerCondAndCombo.SelectedIndex = 0; + cBSteeringWheelEmulationAxis.SelectedIndex = 0; + cBSteeringWheelEmulationRange.SelectedIndex = cBSteeringWheelEmulationRange.Items.IndexOf("360"); Set(); } @@ -2992,6 +3008,85 @@ namespace DS4Windows } } + private void cBSteeringWheelEmulationRange_SelectedIndexChanged(object sender, EventArgs e) + { + if (loading == false) + { + SASteeringWheelEmulationRange[device] = Convert.ToInt32(cBSteeringWheelEmulationRange.Items[cBSteeringWheelEmulationRange.SelectedIndex].ToString()); + } + } + + private void cBSteeringWheelEmulationAxis_SelectedIndexChanged(object sender, EventArgs e) + { + if (loading == false) + { + switch (cBSteeringWheelEmulationAxis.SelectedIndex) + { + case 0: SASteeringWheelEmulationAxis[device] = DS4Controls.None; break; // Gyro SA steering wheel emulation disabled + case 1: SASteeringWheelEmulationAxis[device] = DS4Controls.LXPos; break; // Left stick X axis + case 2: SASteeringWheelEmulationAxis[device] = DS4Controls.LYPos; break; // Left stick Y axis + case 3: SASteeringWheelEmulationAxis[device] = DS4Controls.RXPos; break; // Right stick X axis + case 4: SASteeringWheelEmulationAxis[device] = DS4Controls.RYPos; break; // Right stick Y axis + case 5: SASteeringWheelEmulationAxis[device] = DS4Controls.L2; break; // Left+Right trigger axis (max range -255..0 as left trigger and 0..+255 as right trigger) + } + } + } + + private void btnSteeringWheelEmulationCalibrate_Click(object sender, EventArgs e) + { + if(cBSteeringWheelEmulationAxis.SelectedIndex > 0) + { + DS4Device d; + int tempDeviceNum = (int)nUDSixaxis.Value - 1; + + d = Program.rootHub.DS4Controllers[tempDeviceNum]; + if (d != null) + { + Point origWheelCenterPoint = new Point(d.wheelCenterPoint.X, d.wheelCenterPoint.Y); + Point origWheel90DegPointLeft = new Point(d.wheel90DegPointLeft.X, d.wheel90DegPointLeft.Y); + Point origWheel90DegPointRight = new Point(d.wheel90DegPointRight.X, d.wheel90DegPointRight.Y); + + d.WheelRecalibrateActiveState = 1; + + DialogResult msgBoxResult = MessageBox.Show($"{Properties.Resources.SASteeringWheelEmulationCalibrate}.\n\n" + + $"{Properties.Resources.SASteeringWheelEmulationCalibrateInstruction1}.\n" + + $"{Properties.Resources.SASteeringWheelEmulationCalibrateInstruction2}.\n" + + $"{Properties.Resources.SASteeringWheelEmulationCalibrateInstruction3}.\n\n" + + $"{Properties.Resources.SASteeringWheelEmulationCalibrateInstruction}.\n", + Properties.Resources.SASteeringWheelEmulationCalibrate, + MessageBoxButtons.OKCancel, + MessageBoxIcon.Information, + MessageBoxDefaultButton.Button1, + 0, + false); + + if (msgBoxResult == DialogResult.OK) + { + // Accept new calibration values (State 3 is "Complete calibration" state) + d.WheelRecalibrateActiveState = 3; + } + else + { + // Cancel calibration and reset back to original calibration values + d.WheelRecalibrateActiveState = 4; + + d.wheelFullTurnCount = 0; + d.wheelCenterPoint = origWheelCenterPoint; + d.wheel90DegPointLeft = origWheel90DegPointLeft; + d.wheel90DegPointRight = origWheel90DegPointRight; + } + } + else + { + MessageBox.Show($"{Properties.Resources.SASteeringWheelEmulationCalibrateNoControllerError}."); + } + } + else + { + MessageBox.Show($"{Properties.Resources.SASteeringWheelEmulationCalibrateNoneAxisError}."); + } + } + private void trackFrictionNUD_ValueChanged(object sender, EventArgs e) { if (loading == false) diff --git a/DS4Windows/DS4Forms/Options.resx b/DS4Windows/DS4Forms/Options.resx index dc50f18..4a5c38d 100644 --- a/DS4Windows/DS4Forms/Options.resx +++ b/DS4Windows/DS4Forms/Options.resx @@ -4057,11 +4057,191 @@ with profile 7 + + True + + + NoControl + + + 3, 116 + + + 0, 5, 0, 0 + + + 98, 18 + + + 290 + + + Steering wheel axis + + + lblSteeringWheelEmulationAxis + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + fLPTiltControls + + + 8 + + + None + + + Left X-Axis + + + Left Y-Axis + + + Right X-Axis + + + Right Y-Axis + + + Trigger L+R Axis + + + 107, 119 + + + 110, 21 + + + 291 + + + cBSteeringWheelEmulationAxis + + + System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + fLPTiltControls + + + 9 + + + True + + + NoControl + + + 3, 143 + + + 0, 5, 0, 0 + + + 107, 18 + + + 292 + + + Steering wheel range + + + lblSteeringWheelEmulationRange + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + fLPTiltControls + + + 10 + + + 90 + + + 180 + + + 270 + + + 360 + + + 450 + + + 720 + + + 900 + + + 1080 + + + 1440 + + + 116, 146 + + + 60, 21 + + + 293 + + + cBSteeringWheelEmulationRange + + + System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + fLPTiltControls + + + 11 + + + NoControl + + + 182, 146 + + + 75, 23 + + + 294 + + + Calibrate... + + + btnSteeringWheelEmulationCalibrate + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + fLPTiltControls + + + 12 + 4, 43 - 271, 167 + 271, 170 254 @@ -4076,7 +4256,7 @@ with profile gBGyro - 3 + 2 True @@ -8297,7 +8477,7 @@ with profile gBGyro - 2 + 3 3, 253 @@ -8971,9 +9151,6 @@ with profile 1011, 481 - - NoControl - 4, 4, 4, 4 @@ -9392,7 +9569,7 @@ with profile advColorDialog - DS4Windows.AdvancedColorDialog, DS4Windows, Version=1.5.8.0, Culture=neutral, PublicKeyToken=null + DS4Windows.AdvancedColorDialog, DS4Windows, Version=1.5.17.0, Culture=neutral, PublicKeyToken=null Options diff --git a/DS4Windows/DS4Library/DS4Device.cs b/DS4Windows/DS4Library/DS4Device.cs index 1dc2991..283e582 100644 --- a/DS4Windows/DS4Library/DS4Device.cs +++ b/DS4Windows/DS4Library/DS4Device.cs @@ -158,6 +158,7 @@ namespace DS4Windows } public Int32 wheelPrevPhysicalAngle = 0; + public Int32 wheelPrevFullAngle = 0; public Int32 wheelFullTurnCount = 0; public Point wheelCenterPoint; diff --git a/DS4Windows/Properties/Resources.Designer.cs b/DS4Windows/Properties/Resources.Designer.cs index 2145fc9..1d7eb76 100644 --- a/DS4Windows/Properties/Resources.Designer.cs +++ b/DS4Windows/Properties/Resources.Designer.cs @@ -1636,6 +1636,60 @@ namespace DS4Windows.Properties { } } + /// + /// Looks up a localized string similar to All calibraton points are set when lightbar color turns to green. While turning the controller the lightbar color flashes when the controller is at calibration point. Accept calibration with OK button. + /// + internal static string SASteeringWheelEmulationCalibrateInstruction { + get { + return ResourceManager.GetString("SASteeringWheelEmulationCalibrateInstruction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to (1) Center the controller, hold it steady and press "X". + /// + internal static string SASteeringWheelEmulationCalibrateInstruction1 { + get { + return ResourceManager.GetString("SASteeringWheelEmulationCalibrateInstruction1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to (2) Turn to 90° left (or right) position and press "X". + /// + internal static string SASteeringWheelEmulationCalibrateInstruction2 { + get { + return ResourceManager.GetString("SASteeringWheelEmulationCalibrateInstruction2", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to (3) Turn to 90° right (or left) position and press "X". + /// + internal static string SASteeringWheelEmulationCalibrateInstruction3 { + get { + return ResourceManager.GetString("SASteeringWheelEmulationCalibrateInstruction3", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot calibrate gyro (sixaxis) steering wheel emulation values without a controller. Connect a controller via bluetooth or usb. + /// + internal static string SASteeringWheelEmulationCalibrateNoControllerError { + get { + return ResourceManager.GetString("SASteeringWheelEmulationCalibrateNoControllerError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Gyro steering wheel emulation axis option is set to NONE (emulation is not used). Please select an axis option before calibrating the sixaxis gyro steering wheel emulation. + /// + internal static string SASteeringWheelEmulationCalibrateNoneAxisError { + get { + return ResourceManager.GetString("SASteeringWheelEmulationCalibrateNoneAxisError", resourceCulture); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/DS4Windows/Properties/Resources.resx b/DS4Windows/Properties/Resources.resx index dbc3d2c..af66c79 100644 --- a/DS4Windows/Properties/Resources.resx +++ b/DS4Windows/Properties/Resources.resx @@ -640,6 +640,24 @@ Calibration of sixaxis wheel emulation + + (1) Center the controller, hold it steady and press "X" + + + (2) Turn to 90° left (or right) position and press "X" + + + (3) Turn to 90° right (or left) position and press "X" + + + All calibraton points are set when lightbar color turns to green. While turning the controller the lightbar color flashes when the controller is at calibration point. Accept calibration with OK button + + + Cannot calibrate gyro (sixaxis) steering wheel emulation values without a controller. Connect a controller via bluetooth or usb + + + Gyro steering wheel emulation axis option is set to NONE (emulation is not used). Please select an axis option before calibrating the sixaxis gyro steering wheel emulation + Click for advanced Sixaxis reading From 5bd49065213a069a74ea330fcc01508f14047ab2 Mon Sep 17 00:00:00 2001 From: mika-n Date: Mon, 10 Dec 2018 20:29:49 +0200 Subject: [PATCH 05/14] Fine tunes. synced with upstream changes --- DS4Windows/DS4Control/Mapping.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DS4Windows/DS4Control/Mapping.cs b/DS4Windows/DS4Control/Mapping.cs index 585ca7d..a6daa98 100644 --- a/DS4Windows/DS4Control/Mapping.cs +++ b/DS4Windows/DS4Control/Mapping.cs @@ -4134,11 +4134,11 @@ namespace DS4Windows // L2+R2 triggers use independent 8bit values, so use -255..0..+255 scaled values (therefore L2+R2 Trigger axis supports only 360 turn range) if (Global.getSASteeringWheelEmulationAxis(device) != DS4Controls.L2) { - return (((result - maxRangeLeft) * (32767 - (-32768))) / (maxRangeRight - maxRangeLeft)) + (-32768); + return (((result - maxRangeLeft) * (32767 - (-32768))) / (maxRangeRight - maxRangeLeft)) + (-32768); // Stick axis with configurable range } else { - result = Convert.ToInt32(Math.Round(result / (1.0 * C_WHEEL_ANGLE_PRECISION))); + result = Convert.ToInt32(Math.Round(result / (1.0 * C_WHEEL_ANGLE_PRECISION))); // Trigger axis with fixed 360 range if (result < 0) result = -181 - result; return (((result - (-180)) * (255 - (-255))) / (180 - (-180))) + (-255); } From 5b49a8ed4742ad2557e3759fcfb76537464fd0fa Mon Sep 17 00:00:00 2001 From: mika-n Date: Wed, 2 Jan 2019 21:44:15 +0200 Subject: [PATCH 06/14] SA steering wheel emulation can now feed VJoy virtual joystick device (analog axies there). This leaves all original X360/DS4 analog axies for other purposes. Useful in games which support mapping controls to more than one controller (for example EurotruckSimulator/AmericalTruckSimulator). --- DS4Windows/DS4Control/Mapping.cs | 27 +- DS4Windows/DS4Control/ScpUtil.cs | 14 +- DS4Windows/DS4Control/X360Device.cs | 83 ++- DS4Windows/DS4Forms/Options.Designer.cs | 8 +- DS4Windows/DS4Forms/Options.cs | 22 +- DS4Windows/DS4Forms/Options.resx | 22 +- DS4Windows/DS4Windows.csproj | 7 +- DS4Windows/Program.cs | 2 +- DS4Windows/VJoyFeeder/vJoyFeeder.cs | 701 ++++++++++++++++++++++++ 9 files changed, 823 insertions(+), 63 deletions(-) create mode 100644 DS4Windows/VJoyFeeder/vJoyFeeder.cs diff --git a/DS4Windows/DS4Control/Mapping.cs b/DS4Windows/DS4Control/Mapping.cs index db5766e..15538d7 100644 --- a/DS4Windows/DS4Control/Mapping.cs +++ b/DS4Windows/DS4Control/Mapping.cs @@ -1650,7 +1650,7 @@ namespace DS4Windows } } - if (getSASteeringWheelEmulationAxis(device) != DS4Controls.None) + if (getSASteeringWheelEmulationAxis(device) != SASteeringWheelEmulationAxisType.None) MappedState.SASteeringWheelEmulationUnit = Mapping.Scale360degreeGyroAxis(device, eState, ctrl); calculateFinalMouseMovement(ref tempMouseDeltaX, ref tempMouseDeltaY, @@ -4132,15 +4132,24 @@ namespace DS4Windows // Scale input to a raw x360 16bit output scale, except if output axis of steering wheel emulation is L2+R2 trigger axis. // L2+R2 triggers use independent 8bit values, so use -255..0..+255 scaled values (therefore L2+R2 Trigger axis supports only 360 turn range) - if (Global.getSASteeringWheelEmulationAxis(device) != DS4Controls.L2) + switch(Global.getSASteeringWheelEmulationAxis(device)) { - return (((result - maxRangeLeft) * (32767 - (-32768))) / (maxRangeRight - maxRangeLeft)) + (-32768); // Stick axis with configurable range - } - else - { - result = Convert.ToInt32(Math.Round(result / (1.0 * C_WHEEL_ANGLE_PRECISION))); // Trigger axis with fixed 360 range - if (result < 0) result = -181 - result; - return (((result - (-180)) * (255 - (-255))) / (180 - (-180))) + (-255); + case SASteeringWheelEmulationAxisType.LX: + case SASteeringWheelEmulationAxisType.LY: + case SASteeringWheelEmulationAxisType.RX: + case SASteeringWheelEmulationAxisType.RY: + // DS4 Stick axis values with configurable range + return (((result - maxRangeLeft) * (32767 - (-32768))) / (maxRangeRight - maxRangeLeft)) + (-32768); + + case SASteeringWheelEmulationAxisType.L2R2: + // DS4 Trigger axis values with fixed 360 range + 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); + + default: + // VJoy axis values with configurable range + return (((result - maxRangeLeft) * (32767 - (-0))) / (maxRangeRight - maxRangeLeft)) + (-0); } } } diff --git a/DS4Windows/DS4Control/ScpUtil.cs b/DS4Windows/DS4Control/ScpUtil.cs index e526897..a5f7660 100644 --- a/DS4Windows/DS4Control/ScpUtil.cs +++ b/DS4Windows/DS4Control/ScpUtil.cs @@ -20,6 +20,8 @@ namespace DS4Windows public enum DS4Controls : byte { None, LXNeg, LXPos, LYNeg, LYPos, RXNeg, RXPos, RYNeg, RYPos, L1, L2, L3, R1, R2, R3, Square, Triangle, Circle, Cross, DpadUp, DpadRight, DpadDown, DpadLeft, PS, TouchLeft, TouchUpper, TouchMulti, TouchRight, Share, Options, GyroXPos, GyroXNeg, GyroZPos, GyroZNeg, SwipeLeft, SwipeRight, SwipeUp, SwipeDown }; public enum X360Controls : byte { None, LXNeg, LXPos, LYNeg, LYPos, RXNeg, RXPos, RYNeg, RYPos, LB, LT, LS, RB, RT, RS, X, Y, B, A, DpadUp, DpadRight, DpadDown, DpadLeft, Guide, Back, Start, LeftMouse, RightMouse, MiddleMouse, FourthMouse, FifthMouse, WUP, WDOWN, MouseUp, MouseDown, MouseLeft, MouseRight, Unbound }; + public enum SASteeringWheelEmulationAxisType: byte { None = 0, LX, LY, RX, RY, L2R2, VJoy1X, VJoy1Y, VJoy1Z, VJoy2X, VJoy2Y, VJoy2Z }; + public class DS4ControlSettings { public DS4Controls control; @@ -718,8 +720,8 @@ namespace DS4Windows m_Config.SetSaTriggerCond(index, text); } - public static DS4Controls[] SASteeringWheelEmulationAxis => m_Config.sASteeringWheelEmulationAxis; - public static DS4Controls getSASteeringWheelEmulationAxis(int index) + public static SASteeringWheelEmulationAxisType[] SASteeringWheelEmulationAxis => m_Config.sASteeringWheelEmulationAxis; + public static SASteeringWheelEmulationAxisType getSASteeringWheelEmulationAxis(int index) { return m_Config.sASteeringWheelEmulationAxis[index]; } @@ -1519,7 +1521,7 @@ namespace DS4Windows public bool[] useSAforMouse = new bool[5] { false, false, false, false, false }; public string[] sATriggers = new string[5] { string.Empty, string.Empty, string.Empty, string.Empty, string.Empty }; public bool[] sATriggerCond = new bool[5] { true, true, true, true, true }; - public DS4Controls[] sASteeringWheelEmulationAxis = new DS4Controls[5] { DS4Controls.None, DS4Controls.None, DS4Controls.None, DS4Controls.None, DS4Controls.None }; + public SASteeringWheelEmulationAxisType[] sASteeringWheelEmulationAxis = new SASteeringWheelEmulationAxisType[5] { SASteeringWheelEmulationAxisType.None, SASteeringWheelEmulationAxisType.None, SASteeringWheelEmulationAxisType.None, SASteeringWheelEmulationAxisType.None, SASteeringWheelEmulationAxisType.None }; public int[] sASteeringWheelEmulationRange = new int[5] { 360, 360, 360, 360, 360 }; public int[][] touchDisInvertTriggers = new int[5][] { new int[1] { -1 }, new int[1] { -1 }, new int[1] { -1 }, new int[1] { -1 }, new int[1] { -1 } }; @@ -2645,8 +2647,8 @@ namespace DS4Windows try { Item = m_Xdoc.SelectSingleNode("/" + rootname + "/SATriggerCond"); sATriggerCond[device] = SaTriggerCondValue(Item.InnerText); } catch { sATriggerCond[device] = true; missingSetting = true; } - try { Item = m_Xdoc.SelectSingleNode("/" + rootname + "/SASteeringWheelEmulationAxis"); DS4Controls.TryParse(Item.InnerText, out sASteeringWheelEmulationAxis[device]); } - catch { sASteeringWheelEmulationAxis[device] = DS4Controls.None; missingSetting = true; } + try { Item = m_Xdoc.SelectSingleNode("/" + rootname + "/SASteeringWheelEmulationAxis"); SASteeringWheelEmulationAxisType.TryParse(Item.InnerText, out sASteeringWheelEmulationAxis[device]); } + catch { sASteeringWheelEmulationAxis[device] = SASteeringWheelEmulationAxisType.None; missingSetting = true; } try { Item = m_Xdoc.SelectSingleNode("/" + rootname + "/SASteeringWheelEmulationRange"); int.TryParse(Item.InnerText, out sASteeringWheelEmulationRange[device]); } catch { sASteeringWheelEmulationRange[device] = 360; missingSetting = true; } @@ -3942,7 +3944,7 @@ namespace DS4Windows useSAforMouse[device] = false; sATriggers[device] = string.Empty; sATriggerCond[device] = true; - sASteeringWheelEmulationAxis[device] = DS4Controls.None; + sASteeringWheelEmulationAxis[device] = SASteeringWheelEmulationAxisType.None; sASteeringWheelEmulationRange[device] = 360; touchDisInvertTriggers[device] = new int[1] { -1 }; lsCurve[device] = rsCurve[device] = 0; diff --git a/DS4Windows/DS4Control/X360Device.cs b/DS4Windows/DS4Control/X360Device.cs index 84b9ed5..a8244ee 100644 --- a/DS4Windows/DS4Control/X360Device.cs +++ b/DS4Windows/DS4Control/X360Device.cs @@ -129,38 +129,73 @@ namespace DS4Windows if (state.PS) Output[11] |= (Byte)(1 << 2); // Guide - DS4Controls steeringWheelMappedAxis = Global.getSASteeringWheelEmulationAxis(device); - - if (steeringWheelMappedAxis == DS4Controls.L2) - { - Output[12] = Output[13] = 0; - if (state.SASteeringWheelEmulationUnit >= 0) - Output[12] = (Byte)state.SASteeringWheelEmulationUnit; - else - Output[13] = (Byte)state.SASteeringWheelEmulationUnit; - } - else - { - Output[12] = state.L2; // Left Trigger - Output[13] = state.R2; // Right Trigger - } - + SASteeringWheelEmulationAxisType steeringWheelMappedAxis = Global.getSASteeringWheelEmulationAxis(device); Int32 ThumbLX; Int32 ThumbLY; Int32 ThumbRX; Int32 ThumbRY; - if (steeringWheelMappedAxis == DS4Controls.LXPos) ThumbLX = state.SASteeringWheelEmulationUnit; - else ThumbLX = Scale(state.LX, false); + Output[12] = state.L2; // Left Trigger + Output[13] = state.R2; // Right Trigger - if (steeringWheelMappedAxis == DS4Controls.LYPos) ThumbLY = state.SASteeringWheelEmulationUnit; - else ThumbLY = Scale(state.LY, true); + switch(steeringWheelMappedAxis) + { + case SASteeringWheelEmulationAxisType.LX: + ThumbLX = state.SASteeringWheelEmulationUnit; + ThumbLY = Scale(state.LY, true); + ThumbRX = Scale(state.RX, false); + ThumbRY = Scale(state.RY, true); + break; - if (steeringWheelMappedAxis == DS4Controls.RXPos) ThumbRX = state.SASteeringWheelEmulationUnit; - else ThumbRX = Scale(state.RX, false); + case SASteeringWheelEmulationAxisType.LY: + ThumbLX = Scale(state.LX, false); + ThumbLY = state.SASteeringWheelEmulationUnit; + ThumbRX = Scale(state.RX, false); + ThumbRY = Scale(state.RY, true); + break; - if (steeringWheelMappedAxis == DS4Controls.RYPos) ThumbRY = state.SASteeringWheelEmulationUnit; - else ThumbRY = Scale(state.RY, true); + case SASteeringWheelEmulationAxisType.RX: + ThumbLX = Scale(state.LX, false); + ThumbLY = Scale(state.LY, true); + ThumbRX = state.SASteeringWheelEmulationUnit; + ThumbRY = Scale(state.RY, true); + break; + + case SASteeringWheelEmulationAxisType.RY: + ThumbLX = Scale(state.LX, false); + ThumbLY = Scale(state.LY, true); + ThumbRX = Scale(state.RX, false); + ThumbRY = state.SASteeringWheelEmulationUnit; + break; + + case SASteeringWheelEmulationAxisType.L2R2: + 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 + + 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; + + 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; + + 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; + + default: + ThumbLX = Scale(state.LX, false); + ThumbLY = Scale(state.LY, true); + ThumbRX = Scale(state.RX, false); + ThumbRY = Scale(state.RY, true); + break; + } Output[14] = (Byte)((ThumbLX >> 0) & 0xFF); // LX Output[15] = (Byte)((ThumbLX >> 8) & 0xFF); diff --git a/DS4Windows/DS4Forms/Options.Designer.cs b/DS4Windows/DS4Forms/Options.Designer.cs index 7bc6cfb..180e074 100644 --- a/DS4Windows/DS4Forms/Options.Designer.cs +++ b/DS4Windows/DS4Forms/Options.Designer.cs @@ -1936,7 +1936,13 @@ resources.GetString("cBSteeringWheelEmulationAxis.Items2"), resources.GetString("cBSteeringWheelEmulationAxis.Items3"), resources.GetString("cBSteeringWheelEmulationAxis.Items4"), - resources.GetString("cBSteeringWheelEmulationAxis.Items5")}); + resources.GetString("cBSteeringWheelEmulationAxis.Items5"), + resources.GetString("cBSteeringWheelEmulationAxis.Items6"), + resources.GetString("cBSteeringWheelEmulationAxis.Items7"), + resources.GetString("cBSteeringWheelEmulationAxis.Items8"), + resources.GetString("cBSteeringWheelEmulationAxis.Items9"), + resources.GetString("cBSteeringWheelEmulationAxis.Items10"), + resources.GetString("cBSteeringWheelEmulationAxis.Items11")}); resources.ApplyResources(this.cBSteeringWheelEmulationAxis, "cBSteeringWheelEmulationAxis"); this.cBSteeringWheelEmulationAxis.Name = "cBSteeringWheelEmulationAxis"; this.cBSteeringWheelEmulationAxis.SelectedIndexChanged += new System.EventHandler(this.cBSteeringWheelEmulationAxis_SelectedIndexChanged); diff --git a/DS4Windows/DS4Forms/Options.cs b/DS4Windows/DS4Forms/Options.cs index b10b94c..16f5001 100644 --- a/DS4Windows/DS4Forms/Options.cs +++ b/DS4Windows/DS4Forms/Options.cs @@ -719,16 +719,7 @@ namespace DS4Windows cBGyroMouseXAxis.SelectedIndex = GyroMouseHorizontalAxis[device]; triggerCondAndCombo.SelectedIndex = SATriggerCond[device] ? 0 : 1; - switch (getSASteeringWheelEmulationAxis(device)) - { - case DS4Controls.None: cBSteeringWheelEmulationAxis.SelectedIndex = 0; break; - case DS4Controls.LXPos: cBSteeringWheelEmulationAxis.SelectedIndex = 1; break; - case DS4Controls.LYPos: cBSteeringWheelEmulationAxis.SelectedIndex = 2; break; - case DS4Controls.RXPos: cBSteeringWheelEmulationAxis.SelectedIndex = 3; break; - case DS4Controls.RYPos: cBSteeringWheelEmulationAxis.SelectedIndex = 4; break; - case DS4Controls.L2: - case DS4Controls.R2: cBSteeringWheelEmulationAxis.SelectedIndex = 5; break; - } + cBSteeringWheelEmulationAxis.SelectedIndex = (int) getSASteeringWheelEmulationAxis(device); int idxSASteeringWheelEmulationRange = cBSteeringWheelEmulationRange.Items.IndexOf(getSASteeringWheelEmulationRange(device).ToString()); if (idxSASteeringWheelEmulationRange >= 0) cBSteeringWheelEmulationRange.SelectedIndex = idxSASteeringWheelEmulationRange; @@ -3020,15 +3011,8 @@ namespace DS4Windows { if (loading == false) { - switch (cBSteeringWheelEmulationAxis.SelectedIndex) - { - case 0: SASteeringWheelEmulationAxis[device] = DS4Controls.None; break; // Gyro SA steering wheel emulation disabled - case 1: SASteeringWheelEmulationAxis[device] = DS4Controls.LXPos; break; // Left stick X axis - case 2: SASteeringWheelEmulationAxis[device] = DS4Controls.LYPos; break; // Left stick Y axis - case 3: SASteeringWheelEmulationAxis[device] = DS4Controls.RXPos; break; // Right stick X axis - case 4: SASteeringWheelEmulationAxis[device] = DS4Controls.RYPos; break; // Right stick Y axis - case 5: SASteeringWheelEmulationAxis[device] = DS4Controls.L2; break; // Left+Right trigger axis (max range -255..0 as left trigger and 0..+255 as right trigger) - } + if (cBSteeringWheelEmulationAxis.SelectedIndex >= 0) SASteeringWheelEmulationAxis[device] = (SASteeringWheelEmulationAxisType) ((byte) cBSteeringWheelEmulationAxis.SelectedIndex); + else SASteeringWheelEmulationAxis[device] = SASteeringWheelEmulationAxisType.None; } } diff --git a/DS4Windows/DS4Forms/Options.resx b/DS4Windows/DS4Forms/Options.resx index 4a5c38d..7996876 100644 --- a/DS4Windows/DS4Forms/Options.resx +++ b/DS4Windows/DS4Forms/Options.resx @@ -4108,6 +4108,24 @@ with profile Trigger L+R Axis + + VJoy1 X-Axis + + + VJoy1 Y-Axis + + + VJoy1 Z-Axis + + + VJoy2 X-Axis + + + VJoy2 Y-Axis + + + VJoy2 Z-Axis + 107, 119 @@ -9140,7 +9158,7 @@ with profile True - 102 + 25 96, 96 @@ -9569,7 +9587,7 @@ with profile advColorDialog - DS4Windows.AdvancedColorDialog, DS4Windows, Version=1.5.17.0, Culture=neutral, PublicKeyToken=null + DS4Windows.AdvancedColorDialog, DS4Windows, Version=1.6.0.0, Culture=neutral, PublicKeyToken=null Options diff --git a/DS4Windows/DS4Windows.csproj b/DS4Windows/DS4Windows.csproj index 94900f6..70fae34 100644 --- a/DS4Windows/DS4Windows.csproj +++ b/DS4Windows/DS4Windows.csproj @@ -231,6 +231,7 @@ + DS4Form.cs Designer @@ -621,6 +622,7 @@ Options.cs + Designer Options.cs @@ -1044,7 +1046,9 @@ - + + Designer + @@ -1172,6 +1176,7 @@ true + rem Copy compiled l18n assemblies to alt folder diff --git a/DS4Windows/Program.cs b/DS4Windows/Program.cs index 06406da..838d22b 100644 --- a/DS4Windows/Program.cs +++ b/DS4Windows/Program.cs @@ -99,7 +99,7 @@ namespace DS4Windows //if (mutex.WaitOne(TimeSpan.Zero, true)) //{ - createControlService(); + createControlService(); //rootHub = new ControlService(); Application.EnableVisualStyles(); ds4form = new DS4Form(args); diff --git a/DS4Windows/VJoyFeeder/vJoyFeeder.cs b/DS4Windows/VJoyFeeder/vJoyFeeder.cs new file mode 100644 index 0000000..f44a37c --- /dev/null +++ b/DS4Windows/VJoyFeeder/vJoyFeeder.cs @@ -0,0 +1,701 @@ +// VJoy C# interface file taken from an excellent Shaul's virtual joystick driver project. +// Licensed to public domain as is (http://vjoystick.sourceforge.net/site/index.php/forum/5-Discussion/104-what-is-the-usage-license-for-this-software). +// http://vjoystick.sourceforge.net/site/ +// 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? +// 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. +// + +using System; +using System.Runtime.InteropServices; + +namespace DS4Windows.VJoyFeeder +{ + [Flags] + public enum HID_USAGES + { + HID_USAGE_X = 0x30, + HID_USAGE_Y = 0x31, + HID_USAGE_Z = 0x32, + HID_USAGE_RX = 0x33, + HID_USAGE_RY = 0x34, + HID_USAGE_RZ = 0x35, + HID_USAGE_SL0 = 0x36, + HID_USAGE_SL1 = 0x37, + HID_USAGE_WHL = 0x38, + HID_USAGE_POV = 0x39, + } + + public enum VjdStat /* Declares an enumeration data type called BOOLEAN */ + { + VJD_STAT_OWN, // The vJoy Device is owned by this application. + VJD_STAT_FREE, // The vJoy Device is NOT owned by any application (including this one). + VJD_STAT_BUSY, // The vJoy Device is owned by another application. It cannot be acquired by this application. + VJD_STAT_MISS, // The vJoy Device is missing. It either does not exist or the driver is down. + VJD_STAT_UNKN // Unknown + }; + + + // FFB Declarations + + // HID Descriptor definitions - FFB Report IDs + + public enum FFBPType // FFB Packet Type + { + // Write + PT_EFFREP = 0x01, // Usage Set Effect Report + PT_ENVREP = 0x02, // Usage Set Envelope Report + PT_CONDREP = 0x03, // Usage Set Condition Report + PT_PRIDREP = 0x04, // Usage Set Periodic Report + PT_CONSTREP = 0x05, // Usage Set Constant Force Report + PT_RAMPREP = 0x06, // Usage Set Ramp Force Report + PT_CSTMREP = 0x07, // Usage Custom Force Data Report + PT_SMPLREP = 0x08, // Usage Download Force Sample + PT_EFOPREP = 0x0A, // Usage Effect Operation Report + PT_BLKFRREP = 0x0B, // Usage PID Block Free Report + PT_CTRLREP = 0x0C, // Usage PID Device Control + PT_GAINREP = 0x0D, // Usage Device Gain Report + PT_SETCREP = 0x0E, // Usage Set Custom Force Report + + // Feature + PT_NEWEFREP = 0x01 + 0x10, // Usage Create New Effect Report + PT_BLKLDREP = 0x02 + 0x10, // Usage Block Load Report + PT_POOLREP = 0x03 + 0x10, // Usage PID Pool Report + }; + + public enum FFBEType // FFB Effect Type + { + + // Effect Type + ET_NONE = 0, // No Force + ET_CONST = 1, // Constant Force + ET_RAMP = 2, // Ramp + ET_SQR = 3, // Square + ET_SINE = 4, // Sine + ET_TRNGL = 5, // Triangle + ET_STUP = 6, // Sawtooth Up + ET_STDN = 7, // Sawtooth Down + ET_SPRNG = 8, // Spring + ET_DMPR = 9, // Damper + ET_INRT = 10, // Inertia + ET_FRCTN = 11, // Friction + ET_CSTM = 12, // Custom Force Data + }; + + public enum FFB_CTRL + { + CTRL_ENACT = 1, // Enable all device actuators. + CTRL_DISACT = 2, // Disable all the device actuators. + CTRL_STOPALL = 3, // Stop All Effects­ Issues a stop on every running effect. + CTRL_DEVRST = 4, // Device Reset– Clears any device paused condition, enables all actuators and clears all effects from memory. + CTRL_DEVPAUSE = 5, // Device Pause– The all effects on the device are paused at the current time step. + CTRL_DEVCONT = 6, // Device Continue– The all effects that running when the device was paused are restarted from their last time step. + }; + + public enum FFBOP + { + EFF_START = 1, // EFFECT START + EFF_SOLO = 2, // EFFECT SOLO START + EFF_STOP = 3, // EFFECT STOP + }; + + //namespace vJoyInterfaceWrap + //{ + public class vJoy + { + + /***************************************************/ + /*********** Various declarations ******************/ + /***************************************************/ + private static RemovalCbFunc UserRemCB; + private static WrapRemovalCbFunc wrf; + private static GCHandle hRemUserData; + + + private static FfbCbFunc UserFfbCB; + private static WrapFfbCbFunc wf; + private static GCHandle hFfbUserData; + + [StructLayout(LayoutKind.Sequential)] + public struct JoystickState + { + public byte bDevice; + public Int32 Throttle; + public Int32 Rudder; + public Int32 Aileron; + public Int32 AxisX; + public Int32 AxisY; + public Int32 AxisZ; + public Int32 AxisXRot; + public Int32 AxisYRot; + public Int32 AxisZRot; + public Int32 Slider; + public Int32 Dial; + public Int32 Wheel; + public Int32 AxisVX; + public Int32 AxisVY; + public Int32 AxisVZ; + public Int32 AxisVBRX; + public Int32 AxisVBRY; + public Int32 AxisVBRZ; + public UInt32 Buttons; + public UInt32 bHats; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch + public UInt32 bHatsEx1; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch + public UInt32 bHatsEx2; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch + public UInt32 bHatsEx3; // Lower 4 bits: HAT switch or 16-bit of continuous HAT switch + public UInt32 ButtonsEx1; + public UInt32 ButtonsEx2; + public UInt32 ButtonsEx3; + }; + + [StructLayout(LayoutKind.Sequential)] + private struct FFB_DATA + { + private UInt32 size; + private UInt32 cmd; + private IntPtr data; + } + + [StructLayout(LayoutKind.Explicit)] + public struct FFB_EFF_CONSTANT + { + [FieldOffset(0)] + public Byte EffectBlockIndex; + [FieldOffset(4)] + public Int16 Magnitude; + } + + [System.Obsolete("use FFB_EFF_REPORT")] + [StructLayout(LayoutKind.Explicit)] + public struct FFB_EFF_CONST + { + [FieldOffset(0)] + public Byte EffectBlockIndex; + [FieldOffset(4)] + public FFBEType EffectType; + [FieldOffset(8)] + public UInt16 Duration;// Value in milliseconds. 0xFFFF means infinite + [FieldOffset(10)] + public UInt16 TrigerRpt; + [FieldOffset(12)] + public UInt16 SamplePrd; + [FieldOffset(14)] + public Byte Gain; + [FieldOffset(15)] + public Byte TrigerBtn; + [FieldOffset(16)] + public bool Polar; // How to interpret force direction Polar (0-360°) or Cartesian (X,Y) + [FieldOffset(20)] + public Byte Direction; // Polar direction: (0x00-0xFF correspond to 0-360°) + [FieldOffset(20)] + public Byte DirX; // X direction: Positive values are To the right of the center (X); Negative are Two's complement + [FieldOffset(21)] + public Byte DirY; // Y direction: Positive values are below the center (Y); Negative are Two's complement + } + + [StructLayout(LayoutKind.Explicit)] + public struct FFB_EFF_REPORT + { + [FieldOffset(0)] + public Byte EffectBlockIndex; + [FieldOffset(4)] + public FFBEType EffectType; + [FieldOffset(8)] + public UInt16 Duration;// Value in milliseconds. 0xFFFF means infinite + [FieldOffset(10)] + public UInt16 TrigerRpt; + [FieldOffset(12)] + public UInt16 SamplePrd; + [FieldOffset(14)] + public Byte Gain; + [FieldOffset(15)] + public Byte TrigerBtn; + [FieldOffset(16)] + public bool Polar; // How to interpret force direction Polar (0-360°) or Cartesian (X,Y) + [FieldOffset(20)] + public Byte Direction; // Polar direction: (0x00-0xFF correspond to 0-360°) + [FieldOffset(20)] + public Byte DirX; // X direction: Positive values are To the right of the center (X); Negative are Two's complement + [FieldOffset(21)] + public Byte DirY; // Y direction: Positive values are below the center (Y); Negative are Two's complement + } + + [StructLayout(LayoutKind.Explicit)] + public struct FFB_EFF_OP + { + [FieldOffset(0)] + public Byte EffectBlockIndex; + [FieldOffset(4)] + public FFBOP EffectOp; + [FieldOffset(8)] + public Byte LoopCount; + } + + [StructLayout(LayoutKind.Explicit)] + public struct FFB_EFF_COND + { + [FieldOffset(0)] + public Byte EffectBlockIndex; + [FieldOffset(4)] + public bool isY; + [FieldOffset(8)] + public Int16 CenterPointOffset; // CP Offset: Range 0x80­0x7F (­10000 ­ 10000) + [FieldOffset(12)] + public Int16 PosCoeff; // Positive Coefficient: Range 0x80­0x7F (­10000 ­ 10000) + [FieldOffset(16)] + public Int16 NegCoeff; // Negative Coefficient: Range 0x80­0x7F (­10000 ­ 10000) + [FieldOffset(20)] + public UInt32 PosSatur; // Positive Saturation: Range 0x00­0xFF (0 – 10000) + [FieldOffset(24)] + public UInt32 NegSatur; // Negative Saturation: Range 0x00­0xFF (0 – 10000) + [FieldOffset(28)] + public Int32 DeadBand; // Dead Band: : Range 0x00­0xFF (0 – 10000) + } + + [StructLayout(LayoutKind.Explicit)] + public struct FFB_EFF_ENVLP + { + [FieldOffset(0)] + public Byte EffectBlockIndex; + [FieldOffset(4)] + public UInt16 AttackLevel; + [FieldOffset(8)] + public UInt16 FadeLevel; + [FieldOffset(12)] + public UInt32 AttackTime; + [FieldOffset(16)] + public UInt32 FadeTime; + } + + [StructLayout(LayoutKind.Explicit)] + public struct FFB_EFF_PERIOD + { + [FieldOffset(0)] + public Byte EffectBlockIndex; + [FieldOffset(4)] + public UInt32 Magnitude; + [FieldOffset(8)] + public Int16 Offset; + [FieldOffset(12)] + public UInt32 Phase; + [FieldOffset(16)] + public UInt32 Period; + } + + [StructLayout(LayoutKind.Explicit)] + public struct FFB_EFF_RAMP + { + [FieldOffset(0)] + public Byte EffectBlockIndex; + [FieldOffset(4)] + public Int16 Start; // The Normalized magnitude at the start of the effect + [FieldOffset(8)] + public Int16 End; // The Normalized magnitude at the end of the effect + } + + + /***************************************************/ + /***** Import from file vJoyInterface.dll (C) ******/ + /***************************************************/ + + ///// General driver data + [DllImport("vJoyInterface.dll", EntryPoint = "GetvJoyVersion")] + private static extern short _GetvJoyVersion(); + + [DllImport("vJoyInterface.dll", EntryPoint = "vJoyEnabled")] + private static extern bool _vJoyEnabled(); + + [DllImport("vJoyInterface.dll", EntryPoint = "GetvJoyProductString")] + private static extern IntPtr _GetvJoyProductString(); + + [DllImport("vJoyInterface.dll", EntryPoint = "GetvJoyManufacturerString")] + private static extern IntPtr _GetvJoyManufacturerString(); + + [DllImport("vJoyInterface.dll", EntryPoint = "GetvJoySerialNumberString")] + private static extern IntPtr _GetvJoySerialNumberString(); + + [DllImport("vJoyInterface.dll", EntryPoint = "DriverMatch")] + private static extern bool _DriverMatch(ref UInt32 DllVer, ref UInt32 DrvVer); + + ///// vJoy Device properties + [DllImport("vJoyInterface.dll", EntryPoint = "GetVJDButtonNumber")] + private static extern int _GetVJDButtonNumber(UInt32 rID); + + [DllImport("vJoyInterface.dll", EntryPoint = "GetVJDDiscPovNumber")] + private static extern int _GetVJDDiscPovNumber(UInt32 rID); + + [DllImport("vJoyInterface.dll", EntryPoint = "GetVJDContPovNumber")] + private static extern int _GetVJDContPovNumber(UInt32 rID); + + [DllImport("vJoyInterface.dll", EntryPoint = "GetVJDAxisExist")] + private static extern UInt32 _GetVJDAxisExist(UInt32 rID, UInt32 Axis); + + [DllImport("vJoyInterface.dll", EntryPoint = "GetVJDAxisMax")] + private static extern bool _GetVJDAxisMax(UInt32 rID, UInt32 Axis, ref long Max); + + [DllImport("vJoyInterface.dll", EntryPoint = "GetVJDAxisMin")] + private static extern bool _GetVJDAxisMin(UInt32 rID, UInt32 Axis, ref long Min); + + [DllImport("vJoyInterface.dll", EntryPoint = "isVJDExists")] + private static extern bool _isVJDExists(UInt32 rID); + + [DllImport("vJoyInterface.dll", EntryPoint = "GetOwnerPid")] + private static extern int _GetOwnerPid(UInt32 rID); + + ///// Write access to vJoy Device - Basic + [DllImport("vJoyInterface.dll", EntryPoint = "AcquireVJD")] + private static extern bool _AcquireVJD(UInt32 rID); + + [DllImport("vJoyInterface.dll", EntryPoint = "RelinquishVJD")] + private static extern void _RelinquishVJD(UInt32 rID); + + [DllImport("vJoyInterface.dll", EntryPoint = "UpdateVJD")] + private static extern bool _UpdateVJD(UInt32 rID, ref JoystickState pData); + + [DllImport("vJoyInterface.dll", EntryPoint = "GetVJDStatus")] + private static extern int _GetVJDStatus(UInt32 rID); + + + //// Reset functions + [DllImport("vJoyInterface.dll", EntryPoint = "ResetVJD")] + private static extern bool _ResetVJD(UInt32 rID); + + [DllImport("vJoyInterface.dll", EntryPoint = "ResetAll")] + private static extern bool _ResetAll(); + + [DllImport("vJoyInterface.dll", EntryPoint = "ResetButtons")] + private static extern bool _ResetButtons(UInt32 rID); + + [DllImport("vJoyInterface.dll", EntryPoint = "ResetPovs")] + private static extern bool _ResetPovs(UInt32 rID); + + ////// Write data + [DllImport("vJoyInterface.dll", EntryPoint = "SetAxis")] + private static extern bool _SetAxis(Int32 Value, UInt32 rID, HID_USAGES Axis); + + [DllImport("vJoyInterface.dll", EntryPoint = "SetBtn")] + private static extern bool _SetBtn(bool Value, UInt32 rID, Byte nBtn); + + [DllImport("vJoyInterface.dll", EntryPoint = "SetDiscPov")] + private static extern bool _SetDiscPov(Int32 Value, UInt32 rID, uint nPov); + + [DllImport("vJoyInterface.dll", EntryPoint = "SetContPov")] + private static extern bool _SetContPov(Int32 Value, UInt32 rID, uint nPov); + + [DllImport("vJoyInterface.dll", EntryPoint = "RegisterRemovalCB", CallingConvention = CallingConvention.Cdecl)] + private extern static void _RegisterRemovalCB(WrapRemovalCbFunc cb, IntPtr data); + + public delegate void RemovalCbFunc(bool complete, bool First, object userData); + public delegate void WrapRemovalCbFunc(bool complete, bool First, IntPtr userData); + + public static void WrapperRemCB(bool complete, bool First, IntPtr userData) + { + + object obj = null; + + if (userData != IntPtr.Zero) + { + // Convert userData from pointer to object + GCHandle handle2 = (GCHandle)userData; + obj = handle2.Target as object; + } + + // Call user-defined CB function + UserRemCB(complete, First, obj); + } + + // Force Feedback (FFB) + [DllImport("vJoyInterface.dll", EntryPoint = "FfbRegisterGenCB", CallingConvention = CallingConvention.Cdecl)] + private extern static void _FfbRegisterGenCB(WrapFfbCbFunc cb, IntPtr data); + + public delegate void FfbCbFunc(IntPtr data, object userData); + public delegate void WrapFfbCbFunc(IntPtr data, IntPtr userData); + + public static void WrapperFfbCB(IntPtr data, IntPtr userData) + { + + object obj = null; + + if (userData != IntPtr.Zero) + { + // Convert userData from pointer to object + GCHandle handle2 = (GCHandle)userData; + obj = handle2.Target as object; + } + + // Call user-defined CB function + UserFfbCB(data, obj); + } + + [DllImport("vJoyInterface.dll", EntryPoint = "FfbStart")] + private static extern bool _FfbStart(UInt32 rID); + + [DllImport("vJoyInterface.dll", EntryPoint = "FfbStop")] + private static extern bool _FfbStop(UInt32 rID); + + [DllImport("vJoyInterface.dll", EntryPoint = "IsDeviceFfb")] + private static extern bool _IsDeviceFfb(UInt32 rID); + + [DllImport("vJoyInterface.dll", EntryPoint = "IsDeviceFfbEffect")] + private static extern bool _IsDeviceFfbEffect(UInt32 rID, UInt32 Effect); + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_DeviceID")] + private static extern UInt32 _Ffb_h_DeviceID(IntPtr Packet, ref int DeviceID); + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_Type")] + private static extern UInt32 _Ffb_h_Type(IntPtr Packet, ref FFBPType Type); + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_Packet")] + private static extern UInt32 _Ffb_h_Packet(IntPtr Packet, ref UInt32 Type, ref Int32 DataSize, ref IntPtr Data); + + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_EBI")] + private static extern UInt32 _Ffb_h_EBI(IntPtr Packet, ref Int32 Index); + +#pragma warning disable 618 + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_Eff_Const")] + private static extern UInt32 _Ffb_h_Eff_Const(IntPtr Packet, ref FFB_EFF_CONST Effect); +#pragma warning restore 618 + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_Eff_Report")] + private static extern UInt32 _Ffb_h_Eff_Report(IntPtr Packet, ref FFB_EFF_REPORT Effect); + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_DevCtrl")] + private static extern UInt32 _Ffb_h_DevCtrl(IntPtr Packet, ref FFB_CTRL Control); + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_EffOp")] + private static extern UInt32 _Ffb_h_EffOp(IntPtr Packet, ref FFB_EFF_OP Operation); + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_DevGain")] + private static extern UInt32 _Ffb_h_DevGain(IntPtr Packet, ref Byte Gain); + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_Eff_Cond")] + private static extern UInt32 _Ffb_h_Eff_Cond(IntPtr Packet, ref FFB_EFF_COND Condition); + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_Eff_Envlp")] + private static extern UInt32 _Ffb_h_Eff_Envlp(IntPtr Packet, ref FFB_EFF_ENVLP Envelope); + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_Eff_Period")] + private static extern UInt32 _Ffb_h_Eff_Period(IntPtr Packet, ref FFB_EFF_PERIOD Effect); + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_EffNew")] + private static extern UInt32 _Ffb_h_EffNew(IntPtr Packet, ref FFBEType Effect); + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_Eff_Ramp")] + private static extern UInt32 _Ffb_h_Eff_Ramp(IntPtr Packet, ref FFB_EFF_RAMP RampEffect); + + [DllImport("vJoyInterface.dll", EntryPoint = "Ffb_h_Eff_Constant")] + private static extern UInt32 _Ffb_h_Eff_Constant(IntPtr Packet, ref FFB_EFF_CONSTANT ConstantEffect); + + /***************************************************/ + /********** Export functions (C#) ******************/ + /***************************************************/ + + ///// General driver data + public short GetvJoyVersion() { return _GetvJoyVersion(); } + public bool vJoyEnabled() { return _vJoyEnabled(); } + public string GetvJoyProductString() { return Marshal.PtrToStringAuto(_GetvJoyProductString()); } + public string GetvJoyManufacturerString() { return Marshal.PtrToStringAuto(_GetvJoyManufacturerString()); } + public string GetvJoySerialNumberString() { return Marshal.PtrToStringAuto(_GetvJoySerialNumberString()); } + public bool DriverMatch(ref UInt32 DllVer, ref UInt32 DrvVer) { return _DriverMatch(ref DllVer, ref DrvVer); } + + ///// vJoy Device properties + public int GetVJDButtonNumber(uint rID) { return _GetVJDButtonNumber(rID); } + public int GetVJDDiscPovNumber(uint rID) { return _GetVJDDiscPovNumber(rID); } + public int GetVJDContPovNumber(uint rID) { return _GetVJDContPovNumber(rID); } + public bool GetVJDAxisExist(UInt32 rID, HID_USAGES Axis) + { + UInt32 res = _GetVJDAxisExist(rID, (uint)Axis); + if (res == 1) + return true; + else + return false; + } + public bool GetVJDAxisMax(UInt32 rID, HID_USAGES Axis, ref long Max) { return _GetVJDAxisMax(rID, (uint)Axis, ref Max); } + public bool GetVJDAxisMin(UInt32 rID, HID_USAGES Axis, ref long Min) { return _GetVJDAxisMin(rID, (uint)Axis, ref Min); } + public bool isVJDExists(UInt32 rID) { return _isVJDExists(rID); } + public int GetOwnerPid(UInt32 rID) { return _GetOwnerPid(rID); } + + ///// Write access to vJoy Device - Basic + public bool AcquireVJD(UInt32 rID) { return _AcquireVJD(rID); } + public void RelinquishVJD(uint rID) { _RelinquishVJD(rID); } + public bool UpdateVJD(UInt32 rID, ref JoystickState pData) { return _UpdateVJD(rID, ref pData); } + public VjdStat GetVJDStatus(UInt32 rID) { return (VjdStat)_GetVJDStatus(rID); } + + //// Reset functions + public bool ResetVJD(UInt32 rID) { return _ResetVJD(rID); } + public bool ResetAll() { return _ResetAll(); } + public bool ResetButtons(UInt32 rID) { return _ResetButtons(rID); } + public bool ResetPovs(UInt32 rID) { return _ResetPovs(rID); } + + ////// Write data + public bool SetAxis(Int32 Value, UInt32 rID, HID_USAGES Axis) { return _SetAxis(Value, rID, Axis); } + public bool SetBtn(bool Value, UInt32 rID, uint nBtn) { return _SetBtn(Value, rID, (Byte)nBtn); } + public bool SetDiscPov(Int32 Value, UInt32 rID, uint nPov) { return _SetDiscPov(Value, rID, nPov); } + public bool SetContPov(Int32 Value, UInt32 rID, uint nPov) { return _SetContPov(Value, rID, nPov); } + + // Register CB function that takes a C# object as userdata + public void RegisterRemovalCB(RemovalCbFunc cb, object data) + { + // Free existing GCHandle (if exists) + if (hRemUserData.IsAllocated && hRemUserData.Target != null) + hRemUserData.Free(); + + // Convert object to pointer + hRemUserData = GCHandle.Alloc(data); + + // Apply the user-defined CB function + UserRemCB = new RemovalCbFunc(cb); + wrf = new WrapRemovalCbFunc(WrapperRemCB); + + _RegisterRemovalCB(wrf, (IntPtr)hRemUserData); + } + + // Register CB function that takes a pointer as userdata + public void RegisterRemovalCB(WrapRemovalCbFunc cb, IntPtr data) + { + wrf = new WrapRemovalCbFunc(cb); + _RegisterRemovalCB(wrf, data); + } + + + ///////////////////////////////////////////////////////////////////////////////////////////// + //// Force Feedback (FFB) + + // Register CB function that takes a C# object as userdata + public void FfbRegisterGenCB(FfbCbFunc cb, object data) + { + // Free existing GCHandle (if exists) + if (hFfbUserData.IsAllocated && hFfbUserData.Target != null) + hFfbUserData.Free(); + + // Convert object to pointer + hFfbUserData = GCHandle.Alloc(data); + + // Apply the user-defined CB function + UserFfbCB = new FfbCbFunc(cb); + wf = new WrapFfbCbFunc(WrapperFfbCB); + + _FfbRegisterGenCB(wf, (IntPtr)hFfbUserData); + } + + // Register CB function that takes a pointer as userdata + public void FfbRegisterGenCB(WrapFfbCbFunc cb, IntPtr data) + { + wf = new WrapFfbCbFunc(cb); + _FfbRegisterGenCB(wf, data); + } + + [Obsolete("you can remove the function from your code")] + public bool FfbStart(UInt32 rID) { return _FfbStart(rID); } + [Obsolete("you can remove the function from your code")] + public bool FfbStop(UInt32 rID) { return _FfbStop(rID); } + public bool IsDeviceFfb(UInt32 rID) { return _IsDeviceFfb(rID); } + public bool IsDeviceFfbEffect(UInt32 rID, UInt32 Effect) { return _IsDeviceFfbEffect(rID, Effect); } + public UInt32 Ffb_h_DeviceID(IntPtr Packet, ref int DeviceID) { return _Ffb_h_DeviceID(Packet, ref DeviceID); } + public UInt32 Ffb_h_Type(IntPtr Packet, ref FFBPType Type) { return _Ffb_h_Type(Packet, ref Type); } + public UInt32 Ffb_h_Packet(IntPtr Packet, ref UInt32 Type, ref Int32 DataSize, ref Byte[] Data) + { + IntPtr buf = IntPtr.Zero; + UInt32 res = _Ffb_h_Packet(Packet, ref Type, ref DataSize, ref buf); + if (res != 0) + return res; + + DataSize -= 8; + Data = new byte[DataSize]; + Marshal.Copy(buf, Data, 0, DataSize); + return res; + } + public UInt32 Ffb_h_EBI(IntPtr Packet, ref Int32 Index) { return _Ffb_h_EBI(Packet, ref Index); } + [Obsolete("use Ffb_h_Eff_Report instead")] + public UInt32 Ffb_h_Eff_Const(IntPtr Packet, ref FFB_EFF_CONST Effect) { return _Ffb_h_Eff_Const(Packet, ref Effect); } + public UInt32 Ffb_h_Eff_Report(IntPtr Packet, ref FFB_EFF_REPORT Effect) { return _Ffb_h_Eff_Report(Packet, ref Effect); } + public UInt32 Ffb_h_DevCtrl(IntPtr Packet, ref FFB_CTRL Control) { return _Ffb_h_DevCtrl(Packet, ref Control); } + public UInt32 Ffb_h_EffOp(IntPtr Packet, ref FFB_EFF_OP Operation) { return _Ffb_h_EffOp(Packet, ref Operation); } + public UInt32 Ffb_h_DevGain(IntPtr Packet, ref Byte Gain) { return _Ffb_h_DevGain(Packet, ref Gain); } + public UInt32 Ffb_h_Eff_Cond(IntPtr Packet, ref FFB_EFF_COND Condition) { return _Ffb_h_Eff_Cond(Packet, ref Condition); } + public UInt32 Ffb_h_Eff_Envlp(IntPtr Packet, ref FFB_EFF_ENVLP Envelope) { return _Ffb_h_Eff_Envlp(Packet, ref Envelope); } + public UInt32 Ffb_h_Eff_Period(IntPtr Packet, ref FFB_EFF_PERIOD Effect) { return _Ffb_h_Eff_Period(Packet, ref Effect); } + public UInt32 Ffb_h_EffNew(IntPtr Packet, ref FFBEType Effect) { return _Ffb_h_EffNew(Packet, ref Effect); } + public UInt32 Ffb_h_Eff_Ramp(IntPtr Packet, ref FFB_EFF_RAMP RampEffect) { return _Ffb_h_Eff_Ramp(Packet, ref RampEffect); } + public UInt32 Ffb_h_Eff_Constant(IntPtr Packet, ref FFB_EFF_CONSTANT ConstantEffect) { return _Ffb_h_Eff_Constant(Packet, ref ConstantEffect); } + } + //} + + public class vJoyFeeder + { + private static readonly object vJoyLocker = new object(); + + static bool vJoyInitialized = false; + static bool vJoyAvailable = false; + static vJoy vJoyObj = null; + + vJoyFeeder() + { + // Do nothing + } + + ~vJoyFeeder() + { + // Do nothing + } + + public static void InitializeVJoyDevice(uint vJoyID, HID_USAGES axis) + { + lock (vJoyLocker) + { + if (vJoyInitialized) return; + + vJoyInitialized = true; + AppLogger.LogToGui("Initializing VJoy virtual joystick driver via vJoyInterface.dll interface", false); + + if (vJoyObj == null) vJoyObj = new vJoy(); + + if (vJoyObj.vJoyEnabled() && vJoyObj.GetVJDAxisExist(vJoyID, axis)) + { + AppLogger.LogToGui("Connection to VJoy virtual joystick established", false); + AppLogger.LogToGui($"VJoy driver. Vendor={vJoyObj.GetvJoyManufacturerString()} Product={vJoyObj.GetvJoyProductString()} Version={vJoyObj.GetvJoySerialNumberString()} Device#={vJoyID} Axis={axis}", false); + + // Test if DLL matches the driver + UInt32 DllVer = 0, DrvVer = 0; + if (!vJoyObj.DriverMatch(ref DllVer, ref DrvVer)) + AppLogger.LogToGui("WARNING. VJoy version of Driver {DrvVer}) does not match interface DLL Version {DllVer}. This may lead to unexpected problems or crashes. Update VJoy driver and vJoyInterface.dll", false); + + VjdStat status = vJoyObj.GetVJDStatus(vJoyID); + if ((status == VjdStat.VJD_STAT_OWN) || ((status == VjdStat.VJD_STAT_FREE) && (!vJoyObj.AcquireVJD(vJoyID)))) + { + vJoyAvailable = false; + AppLogger.LogToGui("ERROR. Failed to acquire vJoy device# {vJoyID}. Use another VJoy device or make sure there are no other VJoy feeder apps using the same device", false); + } + else + { + //vJoyObj.GetVJDAxisMax(vJoyID, axis, ref vJoyAxisMaxValue); + //AppLogger.LogToGui($"VJoy axis {axis} max value={vJoyAxisMaxValue}", false); + vJoyObj.ResetVJD(vJoyID); + vJoyAvailable = true; + } + } + else + { + vJoyAvailable = false; + AppLogger.LogToGui($"ERROR. VJoy device# {vJoyID} or {axis} axis not available. Check vJoy driver installation and configuration", false); + } + } + } + + // 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); + } + + } +} From abadfb137bc4e73a011d8312239b70544c5fe0cb Mon Sep 17 00:00:00 2001 From: mika-n Date: Sun, 13 Jan 2019 21:25:42 +0200 Subject: [PATCH 07/14] Fine tuning vJoy output feeder --- DS4Windows/DS4Control/Mapping.cs | 11 ++++--- DS4Windows/VJoyFeeder/vJoyFeeder.cs | 48 +++++++++++++++++------------ 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/DS4Windows/DS4Control/Mapping.cs b/DS4Windows/DS4Control/Mapping.cs index 18779dd..3673ab1 100644 --- a/DS4Windows/DS4Control/Mapping.cs +++ b/DS4Windows/DS4Control/Mapping.cs @@ -4128,28 +4128,29 @@ 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); // Scale input to a raw x360 16bit output scale, except if output axis of steering wheel emulation is L2+R2 trigger axis. - // L2+R2 triggers use independent 8bit values, so use -255..0..+255 scaled values (therefore L2+R2 Trigger axis supports only 360 turn range) switch(Global.getSASteeringWheelEmulationAxis(device)) { case SASteeringWheelEmulationAxisType.LX: case SASteeringWheelEmulationAxisType.LY: case SASteeringWheelEmulationAxisType.RX: case SASteeringWheelEmulationAxisType.RY: - // DS4 Stick axis values with configurable range + // DS4 thumbstick axis output (-32768..32767 raw value range) return (((result - maxRangeLeft) * (32767 - (-32768))) / (maxRangeRight - maxRangeLeft)) + (-32768); case SASteeringWheelEmulationAxisType.L2R2: - // DS4 Trigger axis values with fixed 360 range + // 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) 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); default: - // VJoy axis values with configurable range - return (((result - maxRangeLeft) * (32767 - (-0))) / (maxRangeRight - maxRangeLeft)) + (-0); + // 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); } } } diff --git a/DS4Windows/VJoyFeeder/vJoyFeeder.cs b/DS4Windows/VJoyFeeder/vJoyFeeder.cs index f44a37c..8304cbb 100644 --- a/DS4Windows/VJoyFeeder/vJoyFeeder.cs +++ b/DS4Windows/VJoyFeeder/vJoyFeeder.cs @@ -653,36 +653,44 @@ namespace DS4Windows.VJoyFeeder vJoyInitialized = true; AppLogger.LogToGui("Initializing VJoy virtual joystick driver via vJoyInterface.dll interface", false); - if (vJoyObj == null) vJoyObj = new vJoy(); - - if (vJoyObj.vJoyEnabled() && vJoyObj.GetVJDAxisExist(vJoyID, axis)) + try { - AppLogger.LogToGui("Connection to VJoy virtual joystick established", false); - AppLogger.LogToGui($"VJoy driver. Vendor={vJoyObj.GetvJoyManufacturerString()} Product={vJoyObj.GetvJoyProductString()} Version={vJoyObj.GetvJoySerialNumberString()} Device#={vJoyID} Axis={axis}", false); + if (vJoyObj == null) vJoyObj = new vJoy(); - // Test if DLL matches the driver - UInt32 DllVer = 0, DrvVer = 0; - if (!vJoyObj.DriverMatch(ref DllVer, ref DrvVer)) - AppLogger.LogToGui("WARNING. VJoy version of Driver {DrvVer}) does not match interface DLL Version {DllVer}. This may lead to unexpected problems or crashes. Update VJoy driver and vJoyInterface.dll", false); - - VjdStat status = vJoyObj.GetVJDStatus(vJoyID); - if ((status == VjdStat.VJD_STAT_OWN) || ((status == VjdStat.VJD_STAT_FREE) && (!vJoyObj.AcquireVJD(vJoyID)))) + if (vJoyObj.vJoyEnabled() && vJoyObj.GetVJDAxisExist(vJoyID, axis)) { - vJoyAvailable = false; - AppLogger.LogToGui("ERROR. Failed to acquire vJoy device# {vJoyID}. Use another VJoy device or make sure there are no other VJoy feeder apps using the same device", false); + AppLogger.LogToGui("Connection to VJoy virtual joystick established", false); + AppLogger.LogToGui($"VJoy driver. Vendor={vJoyObj.GetvJoyManufacturerString()} Product={vJoyObj.GetvJoyProductString()} Version={vJoyObj.GetvJoySerialNumberString()} Device#={vJoyID} Axis={axis}", false); + + // Test if DLL matches the driver + UInt32 DllVer = 0, DrvVer = 0; + if (!vJoyObj.DriverMatch(ref DllVer, ref DrvVer)) + AppLogger.LogToGui("WARNING. VJoy version of Driver {DrvVer}) does not match interface DLL Version {DllVer}. This may lead to unexpected problems or crashes. Update VJoy driver and vJoyInterface.dll", false); + + VjdStat status = vJoyObj.GetVJDStatus(vJoyID); + if ((status == VjdStat.VJD_STAT_OWN) || ((status == VjdStat.VJD_STAT_FREE) && (!vJoyObj.AcquireVJD(vJoyID)))) + { + vJoyAvailable = false; + AppLogger.LogToGui("ERROR. Failed to acquire vJoy device# {vJoyID}. Use another VJoy device or make sure there are no other VJoy feeder apps using the same device", false); + } + else + { + //vJoyObj.GetVJDAxisMax(vJoyID, axis, ref vJoyAxisMaxValue); + //AppLogger.LogToGui($"VJoy axis {axis} max value={vJoyAxisMaxValue}", false); + vJoyObj.ResetVJD(vJoyID); + vJoyAvailable = true; + } } else { - //vJoyObj.GetVJDAxisMax(vJoyID, axis, ref vJoyAxisMaxValue); - //AppLogger.LogToGui($"VJoy axis {axis} max value={vJoyAxisMaxValue}", false); - vJoyObj.ResetVJD(vJoyID); - vJoyAvailable = true; + vJoyAvailable = false; + AppLogger.LogToGui($"ERROR. VJoy device# {vJoyID} or {axis} axis not available. Check vJoy driver installation and configuration", false); } } - else + catch { vJoyAvailable = false; - AppLogger.LogToGui($"ERROR. VJoy device# {vJoyID} or {axis} axis not available. Check vJoy driver installation and configuration", false); + AppLogger.LogToGui("ERROR. vJoy initialization failed. Make sure that DS4Windows application can find vJoyInterface.dll library file", false); } } } From 27dbab06c551b9e3d1abba214f1f8182108e54e9 Mon Sep 17 00:00:00 2001 From: mika-n Date: Tue, 19 Feb 2019 11:10:16 +0200 Subject: [PATCH 08/14] Deadzone and anti-deadzone handling (the first rugged version, not perfect yet) and small performance improvements here and there. --- DS4Windows/DS4Control/Mapping.cs | 80 +++++++++++++++++++++++++---- DS4Windows/DS4Control/ScpUtil.cs | 4 +- DS4Windows/DS4Control/X360Device.cs | 24 +++++---- DS4Windows/DS4Forms/Options.cs | 4 +- DS4Windows/VJoyFeeder/vJoyFeeder.cs | 19 ++++--- 5 files changed, 102 insertions(+), 29 deletions(-) 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); + } } - } } From 4b0134faf9c2421487c91ab589ff26f968172e66 Mon Sep 17 00:00:00 2001 From: mika-n Date: Tue, 19 Feb 2019 15:19:10 +0200 Subject: [PATCH 09/14] merge --- DS4Windows/DS4Control/Mapping.cs | 444 +++++++++++++++++++++++++++++++ DS4Windows/DS4Windows.csproj | 1 + 2 files changed, 445 insertions(+) diff --git a/DS4Windows/DS4Control/Mapping.cs b/DS4Windows/DS4Control/Mapping.cs index 9a81c48..c9f5175 100644 --- a/DS4Windows/DS4Control/Mapping.cs +++ b/DS4Windows/DS4Control/Mapping.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using System.Diagnostics; using static DS4Windows.Global; +using System.Drawing; // Point struct namespace DS4Windows { @@ -1602,6 +1603,11 @@ namespace DS4Windows if (macroControl[24]) MappedState.RY = 0; } + if (GetSASteeringWheelEmulationAxis(device) != SASteeringWheelEmulationAxisType.None) + { + MappedState.SASteeringWheelEmulationUnit = Mapping.Scale360degreeGyroAxis(device, eState, ctrl); + } + calculateFinalMouseMovement(ref tempMouseDeltaX, ref tempMouseDeltaY, out mouseDeltaX, out mouseDeltaY); if (mouseDeltaX != 0 || mouseDeltaY != 0) @@ -1916,6 +1922,25 @@ namespace DS4Windows } actionDone[index].dev[device] = true; } + else if (action.typeID == SpecialAction.ActionTypeId.SASteeringWheelEmulationCalibrate) + { + actionFound = true; + + DS4Device d = ctrl.DS4Controllers[device]; + // If controller is not already in SASteeringWheelCalibration state then enable it now. If calibration is active then complete it (commit calibration values) + if (d.WheelRecalibrateActiveState == 0 && DateTime.UtcNow > (action.firstTap + TimeSpan.FromMilliseconds(3000))) + { + action.firstTap = DateTime.UtcNow; + d.WheelRecalibrateActiveState = 1; // Start calibration process + } + else if (d.WheelRecalibrateActiveState == 2 && DateTime.UtcNow > (action.firstTap + TimeSpan.FromMilliseconds(3000))) + { + action.firstTap = DateTime.UtcNow; + d.WheelRecalibrateActiveState = 3; // Complete calibration process + } + + actionDone[index].dev[device] = true; + } } else { @@ -3665,5 +3690,424 @@ namespace DS4Windows fieldMap.buttons[controlNum] = false; } } + + // BEGIN: SixAxis steering wheel emulation logic + + private const int C_WHEEL_ANGLE_PRECISION = 10; // 1=precision of one degree, 10=precision of 1/10 of degree. Bigger number means fine graned precision + private const int C_WHEEL_ANGLE_PRECISION_DECIMALS = (C_WHEEL_ANGLE_PRECISION == 1 ? 0 : C_WHEEL_ANGLE_PRECISION / 10); + + // "In-game" calibration process: + // - Place controller at "steering wheel center" position and press "Calibrate SA steering wheel" special action button to start the calibration + // - Hold the controller still for a while at center point and press "X" + // - Turn the controller at 90 degree left or right position and hold still for few seconds and press "X" + // - Turn the controller at 90 degree position on the opposite side and press "X" + // - Now you can check the calibratio by turning the wheel and see when the green lightbar starts to blink (it should blink at those three calibrated positions) + // - Press "Calibrate SA steering wheel" special action key to accept the calibration (result is saved to ControllerConfigs.xml xml file) + // + private static readonly DS4Color calibrationColor_0 = new DS4Color { red = 0xA0, green = 0x00, blue = 0x00 }; + private static readonly DS4Color calibrationColor_1 = new DS4Color { red = 0xFF, green = 0xFF, blue = 0x00 }; + private static readonly DS4Color calibrationColor_2 = new DS4Color { red = 0x00, green = 0x50, blue = 0x50 }; + private static readonly DS4Color calibrationColor_3 = new DS4Color { red = 0x00, green = 0xC0, blue = 0x00 }; + + private static DateTime latestDebugMsgTime; + private static string latestDebugData; + private static void LogToGuiSACalibrationDebugMsg(string data, bool forceOutput = false) + { + // Print debug calibration log messages only once per 2 secs to avoid flooding the log receiver + DateTime curTime = DateTime.Now; + if (forceOutput || ((TimeSpan)(curTime - latestDebugMsgTime)).TotalSeconds > 2) + { + latestDebugMsgTime = curTime; + if (data != latestDebugData) + { + AppLogger.LogToGui(data, false); + latestDebugData = data; + } + } + } + + // Return number of bits set in a value + protected static int CountNumOfSetBits(int bitValue) + { + int count = 0; + while (bitValue != 0) + { + count++; + bitValue &= (bitValue - 1); + } + return count; + } + + // Calculate and return the angle of the controller as -180...0...+180 value. + private static Int32 CalculateControllerAngle(int gyroAccelX, int gyroAccelZ, DS4Device controller) + { + Int32 result; + + if (gyroAccelX == controller.wheelCenterPoint.X && Math.Abs(gyroAccelZ - controller.wheelCenterPoint.Y) <= 1) + { + // When the current gyro position is "close enough" the wheel center point then no need to go through the hassle of calculating an angle + result = 0; + } + else + { + // Calculate two vectors based on "circle center" (ie. circle represents the 360 degree wheel turn and wheelCenterPoint and currentPosition vectors both start from circle center). + // To improve accuracy both left and right turns use a decicated calibration "circle" because DS4 gyro and DoItYourselfWheelRig may return slightly different SA sensor values depending on the tilt direction (well, only one or two degree difference so nothing major). + Point vectorAB; + Point vectorCD; + + if (gyroAccelX >= controller.wheelCenterPoint.X) + { + // "DS4 gyro wheel" tilted to right + vectorAB = new Point(controller.wheelCenterPoint.X - controller.wheelCircleCenterPointRight.X, controller.wheelCenterPoint.Y - controller.wheelCircleCenterPointRight.Y); + vectorCD = new Point(gyroAccelX - controller.wheelCircleCenterPointRight.X, gyroAccelZ - controller.wheelCircleCenterPointRight.Y); + } + else + { + // "DS4 gyro wheel" tilted to left + vectorAB = new Point(controller.wheelCenterPoint.X - controller.wheelCircleCenterPointLeft.X, controller.wheelCenterPoint.Y - controller.wheelCircleCenterPointLeft.Y); + vectorCD = new Point(gyroAccelX - controller.wheelCircleCenterPointLeft.X, gyroAccelZ - controller.wheelCircleCenterPointLeft.Y); + } + + // Calculate dot product and magnitude of vectors (center vector and the current tilt vector) + double dotProduct = vectorAB.X * vectorCD.X + vectorAB.Y * vectorCD.Y; + double magAB = Math.Sqrt(vectorAB.X * vectorAB.X + vectorAB.Y * vectorAB.Y); + double magCD = Math.Sqrt(vectorCD.X * vectorCD.X + vectorCD.Y * vectorCD.Y); + + // Calculate angle between vectors and convert radian to degrees + if (magAB == 0 || magCD == 0) + { + result = 0; + } + else + { + double angle = Math.Acos(dotProduct / (magAB * magCD)); + result = Convert.ToInt32(Global.Clamp( + -180.0 * C_WHEEL_ANGLE_PRECISION, + Math.Round((angle * (180.0 / Math.PI)), C_WHEEL_ANGLE_PRECISION_DECIMALS) * C_WHEEL_ANGLE_PRECISION, + 180.0 * C_WHEEL_ANGLE_PRECISION) + ); + } + + // Left turn is -180..0 and right turn 0..180 degrees + if (gyroAccelX < controller.wheelCenterPoint.X) result = -result; + } + + return result; + } + + // Calibrate sixaxis steering wheel emulation. Use DS4Windows configuration screen to start a calibration or press a special action key (if defined) + private static void SAWheelEmulationCalibration(int device, DS4StateExposed exposedState, ControlService ctrl, DS4State currentDeviceState, DS4Device controller) + { + int gyroAccelX, gyroAccelZ; + int result; + + 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) + { + AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} activated re-calibration of SA steering wheel emulation", false); + + controller.WheelRecalibrateActiveState = 2; + + controller.wheelPrevPhysicalAngle = 0; + controller.wheelPrevFullAngle = 0; + controller.wheelFullTurnCount = 0; + + // Clear existing calibration value and use current position as "center" point. + // This initial center value may be off-center because of shaking the controller while button was pressed. The value will be overriden with correct value once controller is stabilized and hold still few secs at the center point + controller.wheelCenterPoint.X = gyroAccelX; + controller.wheelCenterPoint.Y = gyroAccelZ; + controller.wheel90DegPointRight.X = gyroAccelX + 20; + controller.wheel90DegPointLeft.X = gyroAccelX - 20; + + // Clear bitmask for calibration points. All three calibration points need to be set before re-calibration process is valid + controller.wheelCalibratedAxisBitmask = DS4Device.WheelCalibrationPoint.None; + + controller.wheelPrevRecalibrateTime = new DateTime(2500, 1, 1); + } + else if (controller.WheelRecalibrateActiveState == 3) + { + AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} completed the calibration of SA steering wheel emulation. center=({controller.wheelCenterPoint.X}, {controller.wheelCenterPoint.Y}) 90L=({controller.wheel90DegPointLeft.X}, {controller.wheel90DegPointLeft.Y}) 90R=({controller.wheel90DegPointRight.X}, {controller.wheel90DegPointRight.Y})", false); + + // If any of the calibration points (center, left 90deg, right 90deg) are missing then reset back to default calibration values + if (((controller.wheelCalibratedAxisBitmask & DS4Device.WheelCalibrationPoint.All) == DS4Device.WheelCalibrationPoint.All)) + Global.SaveControllerConfigs(controller); + else + controller.wheelCenterPoint.X = controller.wheelCenterPoint.Y = 0; + + controller.WheelRecalibrateActiveState = 0; + controller.wheelPrevRecalibrateTime = DateTime.Now; + } + else if (controller.WheelRecalibrateActiveState == 4) + { + AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} cancelled the calibration of SA steering wheel emulation.", false); + + controller.WheelRecalibrateActiveState = 0; + controller.wheelPrevRecalibrateTime = DateTime.Now; + } + + if (controller.WheelRecalibrateActiveState > 0) + { + // Cross "X" key pressed. Set calibration point when the key is released and controller hold steady for a few seconds + if (currentDeviceState.Cross == true) controller.wheelPrevRecalibrateTime = DateTime.Now; + + // Make sure controller is hold steady (velocity of gyro axis) to avoid misaligments and set calibration few secs after the "X" key was released + if (Math.Abs(currentDeviceState.Motion.angVelPitch) < 0.5 && Math.Abs(currentDeviceState.Motion.angVelYaw) < 0.5 && Math.Abs(currentDeviceState.Motion.angVelRoll) < 0.5 + && ((TimeSpan)(DateTime.Now - controller.wheelPrevRecalibrateTime)).TotalSeconds > 1) + { + controller.wheelPrevRecalibrateTime = new DateTime(2500, 1, 1); + + if (controller.wheelCalibratedAxisBitmask == DS4Device.WheelCalibrationPoint.None) + { + controller.wheelCenterPoint.X = gyroAccelX; + controller.wheelCenterPoint.Y = gyroAccelZ; + + controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Center; + } + else if (controller.wheel90DegPointRight.X < gyroAccelX) + { + controller.wheel90DegPointRight.X = gyroAccelX; + controller.wheel90DegPointRight.Y = gyroAccelZ; + controller.wheelCircleCenterPointRight.X = controller.wheelCenterPoint.X; + controller.wheelCircleCenterPointRight.Y = controller.wheel90DegPointRight.Y; + + controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Right90; + } + else if (controller.wheel90DegPointLeft.X > gyroAccelX) + { + controller.wheel90DegPointLeft.X = gyroAccelX; + controller.wheel90DegPointLeft.Y = gyroAccelZ; + controller.wheelCircleCenterPointLeft.X = controller.wheelCenterPoint.X; + controller.wheelCircleCenterPointLeft.Y = controller.wheel90DegPointLeft.Y; + + controller.wheelCalibratedAxisBitmask |= DS4Device.WheelCalibrationPoint.Left90; + } + } + + // Show lightbar color feedback how the calibration process is proceeding. + // red / yellow / blue / green = No calibration anchors/one anchor/two anchors/all three anchors calibrated when color turns to green (center, 90DegLeft, 90DegRight). + int bitsSet = CountNumOfSetBits((int)controller.wheelCalibratedAxisBitmask); + if (bitsSet >= 3) DS4LightBar.forcedColor[device] = calibrationColor_3; + else if (bitsSet == 2) DS4LightBar.forcedColor[device] = calibrationColor_2; + else if (bitsSet == 1) DS4LightBar.forcedColor[device] = calibrationColor_1; + else DS4LightBar.forcedColor[device] = calibrationColor_0; + + result = CalculateControllerAngle(gyroAccelX, gyroAccelZ, controller); + + // Force lightbar flashing when controller is currently at calibration point (user can verify the calibration before accepting it by looking at flashing lightbar) + if (((controller.wheelCalibratedAxisBitmask & DS4Device.WheelCalibrationPoint.Center) != 0 && Math.Abs(result) <= 1 * C_WHEEL_ANGLE_PRECISION) + || ((controller.wheelCalibratedAxisBitmask & DS4Device.WheelCalibrationPoint.Left90) != 0 && result <= -89 * C_WHEEL_ANGLE_PRECISION && result >= -91 * C_WHEEL_ANGLE_PRECISION) + || ((controller.wheelCalibratedAxisBitmask & DS4Device.WheelCalibrationPoint.Right90) != 0 && result >= 89 * C_WHEEL_ANGLE_PRECISION && result <= 91 * C_WHEEL_ANGLE_PRECISION) + || ((controller.wheelCalibratedAxisBitmask & DS4Device.WheelCalibrationPoint.Left90) != 0 && Math.Abs(result) >= 179 * C_WHEEL_ANGLE_PRECISION)) + DS4LightBar.forcedFlash[device] = 2; + else + DS4LightBar.forcedFlash[device] = 0; + + DS4LightBar.forcelight[device] = true; + + LogToGuiSACalibrationDebugMsg($"Calibration values ({gyroAccelX}, {gyroAccelZ}) angle={result / (1.0 * C_WHEEL_ANGLE_PRECISION)}\n"); + } + else + { + // Re-calibration completed or cancelled. Set lightbar color back to normal color + DS4LightBar.forcedFlash[device] = 0; + DS4LightBar.forcedColor[device] = Global.getMainColor(device); + DS4LightBar.forcelight[device] = false; + DS4LightBar.updateLightBar(controller, device); + } + } + + protected static Int32 Scale360degreeGyroAxis(int device, DS4StateExposed exposedState, ControlService ctrl) + { + unchecked + { + DS4Device controller; + DS4State currentDeviceState; + + int gyroAccelX, gyroAccelZ; + int result; + + controller = ctrl.DS4Controllers[device]; + if (controller == null) return 0; + + currentDeviceState = controller.getCurrentStateRef(); + + // If calibration is active then do the calibration process instead of the normal "angle calculation" + if (controller.WheelRecalibrateActiveState > 0) + { + SAWheelEmulationCalibration(device, exposedState, ctrl, currentDeviceState, controller); + + // Return center wheel position while SA wheel emuation is being calibrated + return 0; + } + + 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) + { + if (!Global.LoadControllerConfigs(controller)) + { + AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} sixaxis steering wheel calibration data missing. It is recommended to run steering wheel calibration process by pressing SASteeringWheelEmulationCalibration special action key. Using estimated values until the controller is calibrated at least once.", false); + + // Use current controller position as "center point". Assume DS4Windows was started while controller was hold in center position (yes, dangerous assumption but can't do much until controller is calibrated) + controller.wheelCenterPoint.X = gyroAccelX; + controller.wheelCenterPoint.Y = gyroAccelZ; + + controller.wheel90DegPointRight.X = controller.wheelCenterPoint.X + 113; + controller.wheel90DegPointRight.Y = controller.wheelCenterPoint.Y + 110; + + controller.wheel90DegPointLeft.X = controller.wheelCenterPoint.X - 127; + controller.wheel90DegPointLeft.Y = controller.wheel90DegPointRight.Y; + } + + controller.wheelCircleCenterPointRight.X = controller.wheelCenterPoint.X; + controller.wheelCircleCenterPointRight.Y = controller.wheel90DegPointRight.Y; + controller.wheelCircleCenterPointLeft.X = controller.wheelCenterPoint.X; + controller.wheelCircleCenterPointLeft.Y = controller.wheel90DegPointLeft.Y; + + AppLogger.LogToGui($"Controller {ctrl.x360Bus.FirstController + device} steering wheel emulation calibration values. center=({controller.wheelCenterPoint.X}, {controller.wheelCenterPoint.Y}) 90L=({controller.wheel90DegPointLeft.X}, {controller.wheel90DegPointLeft.Y}) 90R=({controller.wheel90DegPointRight.X}, {controller.wheel90DegPointRight.Y})", false); + controller.wheelPrevRecalibrateTime = DateTime.Now; + } + + + 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) + { + if ((result - controller.wheelPrevPhysicalAngle) > 180 * C_WHEEL_ANGLE_PRECISION) + if (maxRangeRight > 360 * C_WHEEL_ANGLE_PRECISION) + wheelFullTurnCount--; + else + result = maxRangeLeft; + } + else if (controller.wheelPrevPhysicalAngle > 0 && result < 0) + { + if ((controller.wheelPrevPhysicalAngle - result) > 180 * C_WHEEL_ANGLE_PRECISION) + if (maxRangeRight > 360 * C_WHEEL_ANGLE_PRECISION) + wheelFullTurnCount++; + else + result = maxRangeRight; + } + controller.wheelPrevPhysicalAngle = result; + + if (wheelFullTurnCount != 0) + { + if (wheelFullTurnCount > 0) + result = (wheelFullTurnCount * 180 * C_WHEEL_ANGLE_PRECISION) + ((wheelFullTurnCount * 180 * C_WHEEL_ANGLE_PRECISION) + result); + else + result = (wheelFullTurnCount * 180 * C_WHEEL_ANGLE_PRECISION) - ((wheelFullTurnCount * -180 * C_WHEEL_ANGLE_PRECISION) - result); + } + + // If the new angle is more than 180 degrees further away then this is probably bogus value (controller shaking too much and gyro and velocity sensors went crazy). + // Accept the new angle only when the new angle is within a "stability threshold", otherwise use the previous full angle value. + if (Math.Abs(result - controller.wheelPrevFullAngle) <= 180 * C_WHEEL_ANGLE_PRECISION) + { + controller.wheelPrevFullAngle = result; + controller.wheelFullTurnCount = wheelFullTurnCount; + } + else + { + result = controller.wheelPrevFullAngle; + } + + result = Mapping.ClampInt(maxRangeLeft, result, maxRangeRight); + + // Debug log output of SA sensor values + //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); + + // 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); + 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; + + 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: + // Should never come here, but C# case statement syntax requires DEFAULT handler + return 0; + } + } + } + // END: SixAxis steering wheel emulation logic + } } diff --git a/DS4Windows/DS4Windows.csproj b/DS4Windows/DS4Windows.csproj index 46fc663..7a1ae54 100644 --- a/DS4Windows/DS4Windows.csproj +++ b/DS4Windows/DS4Windows.csproj @@ -298,6 +298,7 @@ DS4Form.cs + Designer DS4Form.cs From 48bd457d668863d794bf10011e660141231b0c2d Mon Sep 17 00:00:00 2001 From: mika-n Date: Tue, 19 Feb 2019 15:19:45 +0200 Subject: [PATCH 10/14] merge --- DS4Windows/DS4Control/Mapping.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/DS4Windows/DS4Control/Mapping.cs b/DS4Windows/DS4Control/Mapping.cs index c9f5175..9cae578 100644 --- a/DS4Windows/DS4Control/Mapping.cs +++ b/DS4Windows/DS4Control/Mapping.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using System.Diagnostics; using static DS4Windows.Global; - using System.Drawing; // Point struct namespace DS4Windows From e70aa452e34e9ea6660841f236c20a3a98784d00 Mon Sep 17 00:00:00 2001 From: mika-n Date: Tue, 19 Feb 2019 15:29:33 +0200 Subject: [PATCH 11/14] removed unnecessary using System.Draw reference. --- DS4Windows/DS4Control/X360Device.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/DS4Windows/DS4Control/X360Device.cs b/DS4Windows/DS4Control/X360Device.cs index e0873e2..f794d5e 100644 --- a/DS4Windows/DS4Control/X360Device.cs +++ b/DS4Windows/DS4Control/X360Device.cs @@ -4,8 +4,6 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; -using System.Drawing; // Point struct - namespace DS4Windows { public class X360Device : ScpDevice From a3c51c8688be204993486f48510ec1632eed2c77 Mon Sep 17 00:00:00 2001 From: mika-n Date: Wed, 20 Feb 2019 08:39:55 +0200 Subject: [PATCH 12/14] merge --- DS4Windows/DS4Forms/Options.Designer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/DS4Windows/DS4Forms/Options.Designer.cs b/DS4Windows/DS4Forms/Options.Designer.cs index 386cea7..f9cf0af 100644 --- a/DS4Windows/DS4Forms/Options.Designer.cs +++ b/DS4Windows/DS4Forms/Options.Designer.cs @@ -4294,7 +4294,6 @@ this.pnlLSTrack.ResumeLayout(false); this.pnlRSTrack.ResumeLayout(false); this.fLPTiltControls.ResumeLayout(false); - this.fLPTiltControls.PerformLayout(); this.tCControls.ResumeLayout(false); this.tPControls.ResumeLayout(false); this.pnlController.ResumeLayout(false); From abad91ca2a01eb570273ddb5baf632383def7a36 Mon Sep 17 00:00:00 2001 From: Travis Nickles Date: Fri, 22 Feb 2019 01:50:50 -0600 Subject: [PATCH 13/14] Changed class name to remove VS name violation warning --- DS4Windows/VJoyFeeder/vJoyFeeder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DS4Windows/VJoyFeeder/vJoyFeeder.cs b/DS4Windows/VJoyFeeder/vJoyFeeder.cs index fcee9ff..1e6a398 100644 --- a/DS4Windows/VJoyFeeder/vJoyFeeder.cs +++ b/DS4Windows/VJoyFeeder/vJoyFeeder.cs @@ -107,7 +107,7 @@ namespace DS4Windows.VJoyFeeder //namespace vJoyInterfaceWrap //{ [SuppressUnmanagedCodeSecurity] - public class vJoy + public class VJoy { /***************************************************/ @@ -634,7 +634,7 @@ namespace DS4Windows.VJoyFeeder static bool vJoyInitialized = false; static bool vJoyAvailable = false; - static vJoy vJoyObj = null; + static VJoy vJoyObj = null; vJoyFeeder() { @@ -657,7 +657,7 @@ namespace DS4Windows.VJoyFeeder try { - if (vJoyObj == null) vJoyObj = new vJoy(); + if (vJoyObj == null) vJoyObj = new VJoy(); if (vJoyObj.vJoyEnabled() && vJoyObj.GetVJDAxisExist(vJoyID, axis)) { From afea15f38f7a1627314c73cca33b8f960b31abca Mon Sep 17 00:00:00 2001 From: Travis Nickles Date: Fri, 22 Feb 2019 01:51:54 -0600 Subject: [PATCH 14/14] Output report len is constant No need to pull from property every poll --- DS4Windows/DS4Control/X360Device.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DS4Windows/DS4Control/X360Device.cs b/DS4Windows/DS4Control/X360Device.cs index f794d5e..256d817 100644 --- a/DS4Windows/DS4Control/X360Device.cs +++ b/DS4Windows/DS4Control/X360Device.cs @@ -100,7 +100,7 @@ namespace DS4Windows Output[4] = (Byte)(device + firstController); Output[9] = 0x14; - for (int i = 10, outLen = Output.Length; i < outLen; i++) + for (int i = 10; i < 28; i++) { Output[i] = 0; }