diff --git a/Race Element.Data/Common/SimulatorData/LocalCar/LocalCarData.cs b/Race Element.Data/Common/SimulatorData/LocalCar/LocalCarData.cs
index a00defdba..1255e0307 100644
--- a/Race Element.Data/Common/SimulatorData/LocalCar/LocalCarData.cs
+++ b/Race Element.Data/Common/SimulatorData/LocalCar/LocalCarData.cs
@@ -143,6 +143,24 @@ public sealed record PhysicsData
/// The speed of the car in kilometers per hour.
///
public float Velocity { get; internal set; }
+
+ /// Wheel suspension travel.
+ public float[] SuspensionTravel { get; internal set; } = new float[4];
+
+ /// Damage to each side.
+ public float[] Damage { get; internal set; } = new float[5];
+
+ /// Vibrations generated from driving on curbs and off track.
+ public float KerbVibration;
+
+ /// Vibrations generated from tire slip.
+ public float SlipVibrations;
+
+ /// Vibrations generated from g-forces.
+ public float Gvibrations;
+
+ /// Vibrations generated from brake ABS.
+ public float AbsVibrations;
}
public sealed record InputsData
{
diff --git a/Race Element.Data/Games/AssettoCorsaEvo/AssettoCorsaEvoDataProvider.cs b/Race Element.Data/Games/AssettoCorsaEvo/AssettoCorsaEvoDataProvider.cs
index dd4d271b4..ff6907fa0 100644
--- a/Race Element.Data/Games/AssettoCorsaEvo/AssettoCorsaEvoDataProvider.cs
+++ b/Race Element.Data/Games/AssettoCorsaEvo/AssettoCorsaEvoDataProvider.cs
@@ -23,6 +23,7 @@ internal sealed class AssettoCorsaEvoDataProvider : AbstractSimDataProvider
public sealed override void Update(ref LocalCarData localCar, ref SessionData sessionData, ref GameData gameData)
{
var physicsPage = AcEvoSharedMemory.Instance.ReadPhysicsPageFile();
+ // no need to remap the physics page if packet is the same
if (lastPhysicsPacketId == physicsPage.PacketId) // no need to remap the physics page if packet is the same
{
lastPhysicsPacketId = physicsPage.PacketId;
@@ -34,7 +35,7 @@ public sealed override void Update(ref LocalCarData localCar, ref SessionData se
lastPhysicsPacketId = physicsPage.PacketId;
SimDataProvider.GameData.IsGamePaused = false;
}
-
+
LocalCarMapper.AddPhysics(ref physicsPage, ref localCar, ref sessionData);
gameData.Name = GameName;
@@ -49,6 +50,7 @@ public sealed override void Update(ref LocalCarData localCar, ref SessionData se
//SessionData.Instance.PlayerCarIndex = graphicsPage.PlayerCarID;
//SimDataProvider.LocalCar.CarModel.CarClass = dummyCarClass;
+ lastPhysicsPacketId = physicsPage.PacketId;
}
//private LogFileJob _logFileJob;
diff --git a/Race Element.Data/Games/AssettoCorsaEvo/DataMapper/LocalCarMapper.cs b/Race Element.Data/Games/AssettoCorsaEvo/DataMapper/LocalCarMapper.cs
index 8491e726f..38d08e438 100644
--- a/Race Element.Data/Games/AssettoCorsaEvo/DataMapper/LocalCarMapper.cs
+++ b/Race Element.Data/Games/AssettoCorsaEvo/DataMapper/LocalCarMapper.cs
@@ -11,11 +11,17 @@ internal static partial class LocalCarMapper
{
internal static void AddPhysics(ref SPageFilePhysics pagePhysics, ref LocalCarData commonData, ref SessionData sessionData)
{
+ commonData.Physics.SuspensionTravel = pagePhysics.SuspensionTravel;
+ commonData.Physics.KerbVibration = pagePhysics.KerbVibration;
+ commonData.Physics.SlipVibrations = pagePhysics.SlipVibrations;
+ commonData.Physics.Gvibrations = pagePhysics.Gvibrations;
+ commonData.Physics.AbsVibrations = pagePhysics.AbsVibrations;
commonData.Physics.Acceleration = new(pagePhysics.AccG[0], pagePhysics.AccG[1], pagePhysics.AccG[2]);
+ commonData.Physics.Damage = pagePhysics.CarDamage;
+
commonData.Engine.IsPitLimiterOn = pagePhysics.PitLimiterOn;
commonData.Engine.MaxRpm = pagePhysics.CurrentMaxRpm;
commonData.Engine.Rpm = pagePhysics.Rpms;
-
commonData.Engine.IsRunning = commonData.Engine.Rpm > 0;
commonData.Inputs.Steering = pagePhysics.SteerAngle;
diff --git a/Race Element.Data/Games/AssettoCorsaEvo/SharedMemory/AcEvoSharedMemory.cs b/Race Element.Data/Games/AssettoCorsaEvo/SharedMemory/AcEvoSharedMemory.cs
index cc9859065..8aaa0f7a2 100644
--- a/Race Element.Data/Games/AssettoCorsaEvo/SharedMemory/AcEvoSharedMemory.cs
+++ b/Race Element.Data/Games/AssettoCorsaEvo/SharedMemory/AcEvoSharedMemory.cs
@@ -691,16 +691,16 @@ public sealed class SPageFilePhysics
/// Engine running?
[MarshalAs(UnmanagedType.Bool)] public bool IsEngineRunning;
- /// Vibrations sent to the FFB, could be used for motion rigs
+ /// Vibrations generated from driving on curbs and off track.
public float KerbVibration;
- /// Vibrations sent to the FFB, could be used for motion rigs
+ /// Vibrations generated from tire slip.
public float SlipVibrations;
- /// Vibrations sent to the FFB, could be used for motion rigs
+ /// Vibrations generated from g-forces.
public float Gvibrations;
- /// Vibrations sent to the FFB, could be used for motion rigs
+ /// Vibrations generated from brake ABS.
public float AbsVibrations;
public static readonly int Size = Marshal.SizeOf(typeof(SPageFilePhysics));
diff --git a/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseConfiguration.cs b/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseConfiguration.cs
new file mode 100644
index 000000000..ab03098c6
--- /dev/null
+++ b/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseConfiguration.cs
@@ -0,0 +1,151 @@
+using RaceElement.HUD.Overlay.Configuration;
+using RaceElement.Data.Games;
+
+namespace RaceElement.HUD.Common.Overlays.Driving.DualSense;
+
+internal sealed class DualSenseConfiguration : OverlayConfiguration
+{
+ public DualSenseConfiguration()
+ {
+ GenericConfiguration.AlwaysOnTop = false;
+ GenericConfiguration.Window = false;
+ GenericConfiguration.Opacity = 1.0f;
+ GenericConfiguration.AllowRescale = false;
+ }
+
+ [HideForGame(Game.RaceRoom | Game.AssettoCorsa1)]
+ [ConfigGrouping("Rumble", "Adjust the rumble effects.")]
+ public RumbleParams Rumble { get; init; } = new();
+ public sealed class RumbleParams
+ {
+ ///
+ /// Kerb rumble coef
+ ///
+ [ToolTip("Kerb rumble coef.")]
+ [FloatRange(0.0f, 2.0f, 0.01f, 2)]
+ public float KerbCoef { get; init; } = 1.2f;
+
+ ///
+ /// ABS rumble coef
+ ///
+ [ToolTip("ABS rumble coef.")]
+ [FloatRange(0.0f, 2.0f, 0.01f, 2)]
+ public float ABSCoef { get; init; } = 1f;
+
+ ///
+ /// Damage rumble coef
+ ///
+ [ToolTip("Damage rumble coef.")]
+ [FloatRange(0.0f, 100.0f, 0.01f, 2)]
+ public float DamageCoef { get; init; } = 60f;
+
+ ///
+ /// Gear shift rumble coef
+ ///
+ [ToolTip("Gear shift rumble coef.")]
+ [FloatRange(0.0f, 100.0f, 0.01f, 2)]
+ public float GearCoef { get; init; } = 32f;
+
+ ///
+ /// How slowly rumble accum decays (low = fast)
+ ///
+ [ToolTip("How fast rumble accum decays. (low = fast)")]
+ [FloatRange(0.5f, 0.9f, 0.01f, 2)]
+ public float AccumDecay { get; init; } = 0.7f;
+
+ ///
+ /// RPM % at which rumble will start
+ ///
+ [ToolTip("RPM % at which right rumble will start.")]
+ [FloatRange(0.0f, 1.0f, 0.01f, 2)]
+ public float RPMStart { get; init; } = 0.9f;
+
+ ///
+ /// RPM right rumble coef
+ ///
+ [ToolTip("RPM right rumble coef.")]
+ [FloatRange(0.0f, 1.0f, 0.01f, 2)]
+ public float RPMCoef { get; init; } = 0.5f;
+ }
+
+ [ConfigGrouping("Brake Slip", "Adjust the slip effect whilst applying the brakes.")]
+ public BrakeSlipHaptics BrakeSlip { get; init; } = new();
+ public sealed class BrakeSlipHaptics
+ {
+ ///
+ /// The brake in percentage (divide by 100f if you want 0-1 value)
+ ///
+ [ToolTip("The minimum brake percentage before any effects are applied. See this like a deadzone.")]
+ [FloatRange(0.1f, 99f, 0.1f, 1)]
+ public float BrakeThreshold { get; init; } = 3f;
+
+ [ToolTip("Front/back slip ratio balance to feed into effect. Front[0..1]Back")]
+ [FloatRange(0.0f, 1.0f, 0.002f, 3)]
+ public float SlipRatioBias { get; init; } = 0.5f;
+
+ [ToolTip("Minimum slip ratio at which the effect will start.")]
+ [FloatRange(0.0f, 1.0f, 0.002f, 3)]
+ public float MinSlipRatio { get; init; } = 0.240f;
+
+ [ToolTip("Maximum slip ratio after which frequency of effect will be clamped.")]
+ [FloatRange(1.0f, 10.0f, 0.002f, 3)]
+ public float MaxSlipRatio { get; init; } = 2.0f;
+
+ [ToolTip("Sets the frequency of the trigger vibration effect when slip ratio is <= MinSlipRatio.")]
+ [FloatRange(10.0f, 255.0f, 0.5f, 2)]
+ public float MinSlipFrequency { get; init; } = 150.0f;
+
+ [ToolTip("Sets the frequency of the trigger vibration effect when slip ratio is >= MaxSlipRatio.")]
+ [FloatRange(10.0f, 255.0f, 0.5f, 2)]
+ public float MaxSlipFrequency { get; init; } = 20.0f;
+
+ [ToolTip("Gain for the effect amplitude as SlipRatio increases.")]
+ [FloatRange(1.0f, 10.0f, 0.1f, 1)]
+ public float AmpGain { get; init; } = 2.8f;
+
+ [ToolTip("Maximum effect amplitude.")]
+ [IntRange(1, 8, 1)]
+ public int MaxAmp { get; init; } = 3;
+ }
+
+ [ConfigGrouping("Throttle Slip", "Adjust the slip effect whilst applying the throttle.\nModify the threshold to increase or decrease sensitivity in different situations.")]
+ public ThrottleSlipHaptics ThrottleSlip { get; init; } = new();
+ public sealed class ThrottleSlipHaptics
+ {
+ ///
+ /// The throttle in percentage (divide by 100f if you want 0-1 value)
+ ///
+ [ToolTip("The minimum throttle percentage before any effects are applied. See this like a deadzone.")]
+ [FloatRange(0.1f, 99f, 0.1f, 1)]
+ public float ThrottleThreshold { get; init; } = 3f;
+
+ [ToolTip("Front/back slip ratio balance to feed into effect. Front[0..1]Back")]
+ [FloatRange(0.0f, 1.0f, 0.002f, 3)]
+ public float SlipRatioBias { get; init; } = 0.5f;
+
+ [ToolTip("Minimum slip ratio at which the effect will start.")]
+ [FloatRange(0.0f, 1.0f, 0.002f, 3)]
+ public float MinSlipRatio { get; init; } = 0.5f;
+
+ [ToolTip("Maximum slip ratio after which frequency of effect will be clamped.")]
+ [FloatRange(1.0f, 10.0f, 0.002f, 3)]
+ public float MaxSlipRatio { get; init; } = 2.5f;
+
+ [ToolTip("Sets the frequency of the trigger vibration effect when slip ratio is <= MinSlip.")]
+ [FloatRange(10.0f, 255.0f, 0.5f, 2)]
+ public float MinSlipFrequency { get; init; } = 165.0f;
+
+ [ToolTip("Sets the frequency of the trigger vibration effect when slip ratio is >= MaxSlip.")]
+ [FloatRange(10.0f, 255.0f, 0.5f, 2)]
+ public float MaxSlipFrequency { get; init; } = 20.0f;
+
+ [ToolTip("Gain for the effect amplitude as SlipRatio increases.")]
+ [FloatRange(1.0f, 10.0f, 0.1f, 1)]
+ public float AmpGain { get; init; } = 2.5f;
+
+ [ToolTip("Maximum effect amplitude.")]
+ [IntRange(1, 8, 1)]
+ public int MaxAmp { get; init; } = 3;
+ }
+
+}
diff --git a/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseJob.cs b/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseJob.cs
new file mode 100644
index 000000000..0a129a52a
--- /dev/null
+++ b/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseJob.cs
@@ -0,0 +1,28 @@
+using RaceElement.Core.Jobs.Loop;
+using RaceElement.Data.Common;
+using RaceElement.Data.Games;
+
+using static RaceElement.HUD.Common.Overlays.Driving.DualSense.DS5W;
+using static RaceElement.HUD.Common.Overlays.Driving.DualSense.Util;
+
+namespace RaceElement.HUD.Common.Overlays.Driving.DualSense;
+
+internal sealed class DualSenseJob(DualSenseOverlay overlay) : AbstractLoopJob
+{
+ private TriggerHaptics _triggerHaptics = new();
+ public sealed override void RunAction()
+ {
+ //if (!overlay.ShouldRender())
+ // return;
+ ds5w_batch_begin();
+ _triggerHaptics.HandleAcceleration(overlay._config.ThrottleSlip);
+ _triggerHaptics.HandleBraking(overlay._config.BrakeSlip);
+ if (!GameManager.CurrentGame.HasFlag(Game.RaceRoom) &&
+ !GameManager.CurrentGame.HasFlag(Game.AssettoCorsa1))
+ _triggerHaptics.HandleRumble(overlay._config.Rumble);
+ ds5w_batch_end();
+ }
+ public override void AfterCancel()
+ {
+ }
+}
diff --git a/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseOverlay.cs b/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseOverlay.cs
new file mode 100644
index 000000000..89e935688
--- /dev/null
+++ b/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseOverlay.cs
@@ -0,0 +1,77 @@
+using RaceElement.Data.Games;
+using RaceElement.HUD.Overlay.Internal;
+using System.Diagnostics;
+using System.Drawing;
+using System.Reflection;
+using System.Text;
+
+using static RaceElement.HUD.Common.Overlays.Driving.DualSense.DS5W;
+
+namespace RaceElement.HUD.Common.Overlays.Driving.DualSense;
+
+[Overlay(Name = "DualSense",
+ Description = "Adds trigger effects to the DualSense Controller.\n See Guide in the Discord of Race Element for instructions.",
+ OverlayCategory = OverlayCategory.Inputs,
+ OverlayType = OverlayType.Drive,
+ Game = Game.RaceRoom | Game.AssettoCorsa1 | Game.AssettoCorsaEvo,
+ Authors = ["Reinier Klarenberg", "Guillaume Stordeur"]
+)]
+internal sealed class DualSenseOverlay : CommonAbstractOverlay
+{
+ internal readonly DualSenseConfiguration _config = new();
+ private DualSenseJob _DualSenseJob;
+
+ public DualSenseOverlay(Rectangle rectangle) : base(rectangle, "DualSense")
+ {
+ Width = 1; Height = 1;
+ RefreshRateHz = 1;
+ AllowReposition = false;
+ }
+
+ public sealed override void BeforeStart()
+ {
+ if (IsPreviewing) return;
+
+ ExtractDs5ApiDll();
+
+ ds5w_init();
+ _DualSenseJob = new DualSenseJob(this) { IntervalMillis = 1000 / 200 };
+ _DualSenseJob.Run();
+ }
+ public sealed override void BeforeStop()
+ {
+ if (IsPreviewing) return;
+
+ _DualSenseJob?.CancelJoin();
+ ds5w_shutdown();
+ }
+
+ ///
+ /// Extracts the embbed dll in this namespace folder, to the executing assembly folder.
+ /// only extracts if it does not exists. Does not check for version!!!
+ ///
+ private static void ExtractDs5ApiDll()
+ {
+ string dllPath = Path.Combine(AppContext.BaseDirectory, Path.GetFileName("ds5w_x64.dll"));
+ FileInfo dllFile = new(dllPath);
+ if (dllFile.Exists) return;
+
+ string resourceName = "RaceElement.HUD.Common.Overlays.Driving.DualSense.ds5w_x64.dll";
+ var assembly = Assembly.GetExecutingAssembly();
+ using (var stream = assembly.GetManifestResourceStream(resourceName))
+ {
+ if (stream == null)
+ throw new FileNotFoundException($"Could not find resource: {resourceName}");
+
+ using var fileStream = dllFile.Open(FileMode.Create, FileAccess.Write);
+ stream.CopyTo(fileStream);
+ stream.Close();
+ fileStream.Close();
+ }
+ }
+
+ public sealed override bool ShouldRender() => DefaultShouldRender() && !IsPreviewing;
+
+ public sealed override void Render(Graphics g) { }
+
+}
diff --git a/Race Element.HUD.Common/Overlays/Driving/DualSense/Resources.cs b/Race Element.HUD.Common/Overlays/Driving/DualSense/Resources.cs
new file mode 100644
index 000000000..b7bc387d0
--- /dev/null
+++ b/Race Element.HUD.Common/Overlays/Driving/DualSense/Resources.cs
@@ -0,0 +1,44 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace RaceElement.HUD.Common.Overlays.Driving.DualSense;
+
+internal class Util
+{
+ public static void DebugOut(string msg)
+ {
+ StackTrace st = new StackTrace(false);
+ string caller = st.GetFrame(1).GetMethod().Name;
+ Debug.WriteLine(caller + ": " + msg);
+ }
+}
+
+internal static partial class DS5W
+{
+ [LibraryImport("ds5w_x64.dll")]
+ public static partial int ds5w_init();
+
+ [LibraryImport("ds5w_x64.dll")]
+ public static partial void ds5w_shutdown();
+
+ [LibraryImport("ds5w_x64.dll")]
+ public static partial void ds5w_batch_begin();
+
+ [LibraryImport("ds5w_x64.dll")]
+ public static partial int ds5w_batch_end();
+
+ [LibraryImport("ds5w_x64.dll")]
+ public static partial int ds5w_set_rumble(int left, int strength);
+
+ [LibraryImport("ds5w_x64.dll")]
+ public static partial int ds5w_set_trigger_effect_off(int left);
+
+ [LibraryImport("ds5w_x64.dll")]
+ public static partial int ds5w_set_trigger_effect_vibration(int left, int pos, int amp, int freq);
+
+ [LibraryImport("ds5w_x64.dll")]
+ public static partial int ds5w_set_trigger_effect_feedback(int left, int pos, int strength);
+
+ [LibraryImport("ds5w_x64.dll")]
+ public static partial int ds5w_set_trigger_effect_weapon(int left, int start, int end, int strength);
+}
diff --git a/Race Element.HUD.Common/Overlays/Driving/DualSense/TriggerHaptics.cs b/Race Element.HUD.Common/Overlays/Driving/DualSense/TriggerHaptics.cs
new file mode 100644
index 000000000..f5643faa1
--- /dev/null
+++ b/Race Element.HUD.Common/Overlays/Driving/DualSense/TriggerHaptics.cs
@@ -0,0 +1,109 @@
+using RaceElement.Data.Common;
+using RaceElement.Util.SystemExtensions;
+using static RaceElement.Util.SystemExtensions.FloatExtensions;
+using static RaceElement.HUD.Common.Overlays.Driving.DualSense.DS5W;
+using static RaceElement.HUD.Common.Overlays.Driving.DualSense.Util;
+using static RaceElement.HUD.Common.Overlays.Driving.DualSense.DualSenseConfiguration;
+
+namespace RaceElement.HUD.Common.Overlays.Driving.DualSense;
+
+internal class TriggerHaptics
+{
+ private float mLeftAccum;
+ private int mGear;
+ private float[] mDamage;
+
+ public TriggerHaptics()
+ {
+ mGear = 0;
+ mLeftAccum = 0.0f;
+ mDamage = new float[5];
+ }
+ private float UpdateDamage(float[] damage)
+ {
+ float newDamage = 0.0f;
+ for (int i = 0; i < 5; ++i)
+ {
+ if (damage[i] > mDamage[i])
+ {
+ newDamage += damage[i] - mDamage[i];
+ }
+ mDamage[i] = damage[i];
+ }
+ return newDamage;
+ }
+
+ public void HandleRumble(RumbleParams config)
+ {
+ // Combine values for left (strong) motor rumble
+ float damage = UpdateDamage(SimDataProvider.LocalCar.Physics.Damage) * config.DamageCoef;
+ float gear = (SimDataProvider.LocalCar.Inputs.Gear != mGear) ? config.GearCoef : 0.0f;
+ mGear = SimDataProvider.LocalCar.Inputs.Gear;
+ mLeftAccum = (mLeftAccum * config.AccumDecay) + damage + gear;
+ float kerb = Math.Max(0.0f, SimDataProvider.LocalCar.Physics.KerbVibration) * config.KerbCoef * 255.0f;
+ float abs = Math.Abs(SimDataProvider.LocalCar.Physics.AbsVibrations) * config.ABSCoef * 255.0f;
+ int lRumble = Math.Clamp((int)Math.Round(kerb + abs + mLeftAccum), 0, 255);
+ // Combine values for right (weak) motor rumble
+ float rpm = (float)SimDataProvider.LocalCar.Engine.Rpm / (float)SimDataProvider.LocalCar.Engine.MaxRpm;
+ int rRumble = rpm > config.RPMStart ? (int)Math.Round(config.RPMCoef * 255.0f): 0;
+ DebugOut("lRumble: " + lRumble + ", rRumble: " + rRumble);
+ ds5w_set_rumble(1, lRumble);
+ ds5w_set_rumble(0, rRumble);
+ }
+ public void HandleAcceleration(ThrottleSlipHaptics config)
+ {
+ float[] slipRatios = SimDataProvider.LocalCar.Tyres.SlipRatio;
+ if (SimDataProvider.LocalCar.Inputs.Throttle <= config.ThrottleThreshold / 100f ||
+ slipRatios.Length != 4)
+ {
+ ds5w_set_trigger_effect_off(0);
+ return;
+ }
+ //DebugOut("Slip Ratios: " + slipRatios[0] + ", " + slipRatios[1] + ", " + slipRatios[2] + ", " + slipRatios[3]);
+ float slipRatioFront = 0.5f * (slipRatios[0] + slipRatios[1]);
+ float slipRatioBack = 0.5f * (slipRatios[2] + slipRatios[3]);
+ float slipRatio = Lerp(config.SlipRatioBias, slipRatioFront, slipRatioBack);
+ if (slipRatio < config.MinSlipRatio)
+ {
+ ds5w_set_trigger_effect_off(0);
+ return;
+ }
+ // Calculate frequency from slipRatio
+ float srWeight = SmoothStep(slipRatio, config.MinSlipRatio, config.MaxSlipRatio);
+ float freqf = Lerp(srWeight, config.MinSlipFrequency, config.MaxSlipFrequency);
+ int freq = Math.Clamp((int)Math.Round(freqf), 0, 255);
+ // Calculate amplitude from slipRatio
+ int amp = 1 + (int)Math.Round(Math.Sqrt(slipRatio - config.MinSlipRatio) * config.AmpGain);
+ amp.ClipMax(config.MaxAmp);
+ //DebugOut("slipRatio: " + slipRatio + ", freq: " + freq + ", amp: " + amp);
+ ds5w_set_trigger_effect_vibration(0, 0, amp, freq);
+ }
+ public void HandleBraking(BrakeSlipHaptics config)
+ {
+ float[] slipRatios = SimDataProvider.LocalCar.Tyres.SlipRatio;
+ if (SimDataProvider.LocalCar.Inputs.Brake <= config.BrakeThreshold / 100f ||
+ slipRatios.Length != 4)
+ {
+ ds5w_set_trigger_effect_off(1);
+ return;
+ }
+ //DebugOut("Slip Ratios: " + slipRatios[0] + ", " + slipRatios[1] + ", " + slipRatios[2] + ", " + slipRatios[3]);
+ float slipRatioFront = 0.5f * (slipRatios[0] + slipRatios[1]);
+ float slipRatioBack = 0.5f * (slipRatios[2] + slipRatios[3]);
+ float slipRatio = Lerp(config.SlipRatioBias, slipRatioFront, slipRatioBack);
+ if (slipRatio < config.MinSlipRatio)
+ {
+ ds5w_set_trigger_effect_off(1);
+ return;
+ }
+ // Calculate frequency from slipRatio
+ float srWeight = SmoothStep(slipRatio, config.MinSlipRatio, config.MaxSlipRatio);
+ float freqf = Lerp(srWeight, config.MinSlipFrequency, config.MaxSlipFrequency);
+ int freq = Math.Clamp((int)Math.Round(freqf), 0, 255);
+ // Calculate amplitude from slip
+ int amp = 1 + (int)Math.Round(Math.Sqrt(slipRatio - config.MinSlipRatio) * config.AmpGain);
+ amp.ClipMax(config.MaxAmp);
+ //DebugOut("slipRatio: " + slipRatio + ", freq: " + freq + ", amp: " + amp);
+ ds5w_set_trigger_effect_vibration(1, 0, amp, freq);
+ }
+}
diff --git a/Race Element.HUD.Common/Overlays/Driving/DualSense/ds5w_x64.dll b/Race Element.HUD.Common/Overlays/Driving/DualSense/ds5w_x64.dll
new file mode 100644
index 000000000..fb6435a87
Binary files /dev/null and b/Race Element.HUD.Common/Overlays/Driving/DualSense/ds5w_x64.dll differ
diff --git a/Race Element.HUD.Common/Race Element.HUD.Common.csproj b/Race Element.HUD.Common/Race Element.HUD.Common.csproj
index fdeee2483..06724a230 100644
--- a/Race Element.HUD.Common/Race Element.HUD.Common.csproj
+++ b/Race Element.HUD.Common/Race Element.HUD.Common.csproj
@@ -24,6 +24,12 @@
True
True
+
+
+
+
+
+
@@ -38,7 +44,6 @@
-
+
-
diff --git a/Race_Element.Util/SystemExtensions/FloatExtensions.cs b/Race_Element.Util/SystemExtensions/FloatExtensions.cs
index 80a62ce4d..301bb6053 100644
--- a/Race_Element.Util/SystemExtensions/FloatExtensions.cs
+++ b/Race_Element.Util/SystemExtensions/FloatExtensions.cs
@@ -45,7 +45,22 @@ public static float ClipMin(ref this float value, float min)
return value;
}
-
+ public static float Cubic(float x)
+ {
+ return x * x * (3.0f - 2.0f * x);
+ }
+ public static float SmoothStep(float x, float edge0, float edge1)
+ {
+ // Scale, bias and clamp x to 0..1 range
+ x = (x - edge0) / (edge1 - edge0);
+ x = Math.Min(1.0f, Math.Max(0.0f, x));
+ // Evaluate polynomial
+ return Cubic(x);
+ }
+ public static float Lerp(float weight, float a, float b)
+ {
+ return a * (1.0f - weight) + b * weight;
+ }
public static string ToString(this float[] values, int decimals)
{