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) {