diff --git a/GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs b/GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs deleted file mode 100644 index 0807a2a..0000000 --- a/GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs +++ /dev/null @@ -1,203 +0,0 @@ -using ABI_RC.Core.InteractionSystem; -using ABI_RC.Core.InteractionSystem.Base; -using UnityEngine; - -// TODO: -// Fix multi-grab (limitation of Pickupable) -// Think this can be fixed by forcing ungrab & monitoring ourselves for release -// Add configurable override for steering range -// Fix steering wheel resetting immediatly on release -// Fix input patch not being multiplicative (so joysticks can still work) -// Prevent pickup in Desktop - -public class SteeringWheelPickup : Pickupable -{ - private RCC_CarControllerV3 _carController; - - private static readonly Dictionary ActiveWheels = new(); - - public static float GetSteerInput(RCC_CarControllerV3 carController) - { - if (ActiveWheels.TryGetValue(carController, out SteeringWheelPickup wheel) && wheel.IsPickedUp) - return wheel.GetNormalizedValue(); - return 0f; - } - - public void SetupSteeringWheel(RCC_CarControllerV3 carController) - { - _carController = carController; - if (!ActiveWheels.ContainsKey(carController)) - { - ActiveWheels[carController] = this; - carController.useCounterSteering = false; - carController.useSteeringSmoother = false; - } - } - - private void OnDestroy() - { - if (_carController != null && ActiveWheels.ContainsKey(_carController)) - ActiveWheels.Remove(_carController); - } - - #region Configuration Properties Override - - public override bool DisallowTheft => true; - public override float MaxGrabDistance => 0.8f; - public override float MaxPushDistance => 0f; - public override bool IsAutoHold => false; - public override bool IsObjectRotationAllowed => false; - public override bool IsObjectPushPullAllowed => false; - public override bool IsObjectUseAllowed => false; - - public override bool CanPickup => IsPickupable && _carController?.SteeringWheel != null; - - #endregion Configuration Properties Override - - #region RCC Stuff - - private float GetMaxSteeringRange() - => _carController.steerAngle * Mathf.Abs(_carController.steeringWheelAngleMultiplier); - - private float GetSteeringWheelSign() - => Mathf.Sign(_carController.steeringWheelAngleMultiplier) * -1f; // Idk - - private Vector3 GetSteeringWheelLocalAxis() - { - return _carController.steeringWheelRotateAround switch - { - RCC_CarControllerV3.SteeringWheelRotateAround.XAxis => Vector3.right, - RCC_CarControllerV3.SteeringWheelRotateAround.YAxis => Vector3.up, - RCC_CarControllerV3.SteeringWheelRotateAround.ZAxis => Vector3.forward, - _ => Vector3.forward - }; - } - - #endregion RCC Stuff - - #region Rotation Tracking - - private readonly List _trackedTransforms = new(); - private readonly List _lastPositions = new(); - private readonly List _totalAngles = new(); - private bool _isTracking; - private float _averageAngle; - - private void StartTrackingTransform(Transform trans) - { - if (trans == null) return; - - _trackedTransforms.Add(trans); - _lastPositions.Add(GetLocalPositionWithoutRotation(transform.position)); - _totalAngles.Add(0f); - _isTracking = true; - } - - private void StopTrackingTransform(Transform trans) - { - int index = _trackedTransforms.IndexOf(trans); - if (index != -1) - { - _trackedTransforms.RemoveAt(index); - _lastPositions.RemoveAt(index); - _totalAngles.RemoveAt(index); - } - - _isTracking = _trackedTransforms.Count > 0; - } - - private void UpdateRotationTracking() - { - if (!_isTracking || _trackedTransforms.Count == 0) return; - - Vector3 trackingAxis = GetSteeringWheelLocalAxis(); - - for (int i = 0; i < _trackedTransforms.Count; i++) - { - if (_trackedTransforms[i] == null) continue; - - Vector3 currentPosition = GetLocalPositionWithoutRotation(_trackedTransforms[i].position); - if (currentPosition == _lastPositions[i]) continue; - - Vector3 previousVector = _lastPositions[i]; - Vector3 currentVector = currentPosition; - - previousVector = Vector3.ProjectOnPlane(previousVector, trackingAxis).normalized; - currentVector = Vector3.ProjectOnPlane(currentVector, trackingAxis).normalized; - - if (previousVector.sqrMagnitude > 0.001f && currentVector.sqrMagnitude > 0.001f) - { - float deltaAngle = Vector3.SignedAngle(previousVector, currentVector, trackingAxis); - if (Mathf.Abs(deltaAngle) < 90f) _totalAngles[i] += deltaAngle; // Prevent big tracking jumps - } - - _lastPositions[i] = currentPosition; - } - - // Calculate average every frame using only valid transforms - float sumAngles = 0f; - int validTransforms = 0; - - for (int i = 0; i < _trackedTransforms.Count; i++) - { - if (_trackedTransforms[i] == null) continue; - sumAngles += _totalAngles[i]; - validTransforms++; - } - - if (validTransforms > 0) - _averageAngle = sumAngles / validTransforms; - } - - private float GetNormalizedValue() - { - float maxRange = GetMaxSteeringRange(); - // return Mathf.Clamp(_averageAngle / (maxRange * 0.5f), -1f, 1f); - return Mathf.Clamp(_averageAngle / (maxRange), -1f, 1f) * GetSteeringWheelSign(); - } - - private Vector3 GetLocalPositionWithoutRotation(Vector3 worldPosition) - { - Transform steeringTransform = _carController.SteeringWheel; - - Quaternion localRotation = steeringTransform.localRotation; - steeringTransform.localRotation = _carController.orgSteeringWheelRot; - - Vector3 localPosition = steeringTransform.InverseTransformPoint(worldPosition); - steeringTransform.localRotation = localRotation; - - return localPosition; - } - - #endregion Rotation Tracking - - public override void OnGrab(InteractionContext context, Vector3 grabPoint) - { - if (ControllerRay?.pivotPoint == null) - return; - - StartTrackingTransform(ControllerRay.transform); - } - - public override void OnDrop(InteractionContext context) - { - if (ControllerRay?.transform != null) - StopTrackingTransform(ControllerRay.transform); - } - - private void Update() - { - if (!IsPickedUp || ControllerRay?.pivotPoint == null || _carController == null) - return; - - UpdateRotationTracking(); - } - - #region Unused Abstract Method Implementations - - public override void OnUseDown(InteractionContext context) { } - public override void OnUseUp(InteractionContext context) { } - public override void OnFlingTowardsTarget(Vector3 target) { } - - #endregion Unused Abstract Method Implementations -} \ No newline at end of file diff --git a/GrabbableSteeringWheel/Main.cs b/GrabbableSteeringWheel/Main.cs deleted file mode 100644 index 1ced7e0..0000000 --- a/GrabbableSteeringWheel/Main.cs +++ /dev/null @@ -1,61 +0,0 @@ -using MelonLoader; -using NAK.GrabbableSteeringWheel.Patches; - -namespace NAK.GrabbableSteeringWheel; - -public class GrabbableSteeringWheelMod : MelonMod -{ - internal static MelonLogger.Instance Logger; - - #region Melon Preferences - - // private static readonly MelonPreferences_Category Category = - // MelonPreferences.CreateCategory(nameof(GrabbableSteeringWheelMod)); - // - // private static readonly MelonPreferences_Entry EntryEnabled = - // Category.CreateEntry( - // "use_legacy_mitigation", - // true, - // "Enabled", - // description: "Enable legacy content camera hack when in Legacy worlds."); - - #endregion Melon Preferences - - #region Melon Events - - public override void OnInitializeMelon() - { - Logger = LoggerInstance; - - ApplyPatches(typeof(RCCCarControllerV3_Patches)); - ApplyPatches(typeof(CVRInputManager_Patches)); - } - - #endregion Melon Events - - #region Melon Mod Utilities - - private static void InitializeIntegration(string modName, Action integrationAction) - { - if (RegisteredMelons.All(it => it.Info.Name != modName)) - return; - - Logger.Msg($"Initializing {modName} integration."); - integrationAction.Invoke(); - } - - private void ApplyPatches(Type type) - { - try - { - HarmonyInstance.PatchAll(type); - } - catch (Exception e) - { - LoggerInstance.Msg($"Failed while patching {type.Name}!"); - LoggerInstance.Error(e); - } - } - - #endregion Melon Mod Utilities -} \ No newline at end of file diff --git a/GrabbableSteeringWheel/ModSettings.cs b/GrabbableSteeringWheel/ModSettings.cs deleted file mode 100644 index 26a56be..0000000 --- a/GrabbableSteeringWheel/ModSettings.cs +++ /dev/null @@ -1,24 +0,0 @@ -using MelonLoader; - -namespace NAK.GrabbableSteeringWheel; - -internal static class ModSettings -{ - #region Constants - - internal const string ModName = nameof(GrabbableSteeringWheel); - // internal const string LCM_SettingsCategory = "Legacy Content Mitigation"; - - #endregion Constants - - #region Melon Preferences - - private static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(ModName); - - // internal static readonly MelonPreferences_Entry EntryAutoForLegacyWorlds = - // Category.CreateEntry("auto_for_legacy_worlds", true, - // "Auto For Legacy Worlds", description: "Should Legacy View be auto enabled for detected Legacy worlds?"); - // - #endregion Melon Preferences -} \ No newline at end of file diff --git a/RCCVirtualSteeringWheel/Main.cs b/RCCVirtualSteeringWheel/Main.cs new file mode 100644 index 0000000..79fae31 --- /dev/null +++ b/RCCVirtualSteeringWheel/Main.cs @@ -0,0 +1,40 @@ +using MelonLoader; +using NAK.RCCVirtualSteeringWheel.Patches; + +namespace NAK.RCCVirtualSteeringWheel; + +public class RCCVirtualSteeringWheelMod : MelonMod +{ + private static MelonLogger.Instance Logger; + + #region Melon Events + + public override void OnInitializeMelon() + { + Logger = LoggerInstance; + + ApplyPatches(typeof(RCCCarControllerV3_Patches)); + ApplyPatches(typeof(CVRInputManager_Patches)); + + Logger.Msg(ModSettings.EntryCustomSteeringRange); + } + + #endregion Melon Events + + #region Melon Mod Utilities + + private void ApplyPatches(Type type) + { + try + { + HarmonyInstance.PatchAll(type); + } + catch (Exception e) + { + LoggerInstance.Msg($"Failed while patching {type.Name}!"); + LoggerInstance.Error(e); + } + } + + #endregion Melon Mod Utilities +} \ No newline at end of file diff --git a/RCCVirtualSteeringWheel/ModSettings.cs b/RCCVirtualSteeringWheel/ModSettings.cs new file mode 100644 index 0000000..f07436c --- /dev/null +++ b/RCCVirtualSteeringWheel/ModSettings.cs @@ -0,0 +1,31 @@ +using MelonLoader; + +namespace NAK.RCCVirtualSteeringWheel; + +internal static class ModSettings +{ + #region Constants + + private const string ModName = nameof(RCCVirtualSteeringWheel); + + #endregion Constants + + #region Melon Preferences + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(ModName); + + internal static readonly MelonPreferences_Entry EntryOverrideSteeringRange = + Category.CreateEntry("override_steering_range", false, + "Override Steering Range", description: "Should the steering wheel use a custom steering range instead of the vehicle's default?"); + + internal static readonly MelonPreferences_Entry EntryCustomSteeringRange = + Category.CreateEntry("custom_steering_range", 60f, + "Custom Steering Range", description: "The custom steering range in degrees when override is enabled (default: 60)"); + + internal static readonly MelonPreferences_Entry EntryInvertSteering = + Category.CreateEntry("invert_steering", false, + "Invert Steering", description: "Inverts the steering direction"); + + #endregion Melon Preferences +} \ No newline at end of file diff --git a/GrabbableSteeringWheel/Patches.cs b/RCCVirtualSteeringWheel/Patches.cs similarity index 62% rename from GrabbableSteeringWheel/Patches.cs rename to RCCVirtualSteeringWheel/Patches.cs index c6a828c..c4824ce 100644 --- a/GrabbableSteeringWheel/Patches.cs +++ b/RCCVirtualSteeringWheel/Patches.cs @@ -1,21 +1,21 @@ using ABI_RC.Systems.InputManagement; using ABI_RC.Systems.Movement; using HarmonyLib; +using NAK.RCCVirtualSteeringWheel.Util; using UnityEngine; -namespace NAK.GrabbableSteeringWheel.Patches; +namespace NAK.RCCVirtualSteeringWheel.Patches; internal static class RCCCarControllerV3_Patches { [HarmonyPostfix] [HarmonyPatch(typeof(RCC_CarControllerV3), nameof(RCC_CarControllerV3.Awake))] - private static void Postfix_RCC_CarControllerV3_Awake(ref RCC_CarControllerV3 __instance) + private static void Postfix_RCC_CarControllerV3_Awake(RCC_CarControllerV3 __instance) { Transform steeringWheelTransform = __instance.SteeringWheel; if (steeringWheelTransform == null) return; - RCC_CarControllerV3 v3 = __instance; BoneVertexBoundsUtility.CalculateBoneWeightedBounds( steeringWheelTransform, 0.8f, @@ -25,12 +25,7 @@ internal static class RCCCarControllerV3_Patches if (!result.IsValid) return; - BoxCollider boxCollider = steeringWheelTransform.gameObject.AddComponent(); - boxCollider.center = result.LocalBounds.center; - boxCollider.size = result.LocalBounds.size; - - SteeringWheelPickup steeringWheel = steeringWheelTransform.gameObject.AddComponent(); - steeringWheel.SetupSteeringWheel(v3); + SteeringWheelRoot.SetupSteeringWheel(__instance, result.LocalBounds); }); } } @@ -42,8 +37,11 @@ internal static class CVRInputManager_Patches private static void Postfix_CVRInputManager_UpdateInput(ref CVRInputManager __instance) { // Steering input is clamped in RCC component - if (BetterBetterCharacterController.Instance.IsSittingOnControlSeat()) - __instance.steering += SteeringWheelPickup.GetSteerInput( - BetterBetterCharacterController.Instance._lastCvrSeat._carController); + if (BetterBetterCharacterController.Instance.IsSittingOnControlSeat() + && SteeringWheelRoot.TryGetWheelInput( + BetterBetterCharacterController.Instance._lastCvrSeat._carController, out float steeringValue)) + { + __instance.steering = steeringValue; + } } } \ No newline at end of file diff --git a/GrabbableSteeringWheel/Properties/AssemblyInfo.cs b/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs similarity index 71% rename from GrabbableSteeringWheel/Properties/AssemblyInfo.cs rename to RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs index 6441f79..c82b5de 100644 --- a/GrabbableSteeringWheel/Properties/AssemblyInfo.cs +++ b/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs @@ -1,21 +1,21 @@ using System.Reflection; using MelonLoader; -using NAK.GrabbableSteeringWheel; -using NAK.GrabbableSteeringWheel.Properties; +using NAK.RCCVirtualSteeringWheel; +using NAK.RCCVirtualSteeringWheel.Properties; [assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.GrabbableSteeringWheel))] +[assembly: AssemblyTitle(nameof(NAK.RCCVirtualSteeringWheel))] [assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.GrabbableSteeringWheel))] +[assembly: AssemblyProduct(nameof(NAK.RCCVirtualSteeringWheel))] [assembly: MelonInfo( - typeof(GrabbableSteeringWheelMod), - nameof(NAK.GrabbableSteeringWheel), + typeof(RCCVirtualSteeringWheelMod), + nameof(NAK.RCCVirtualSteeringWheel), AssemblyInfoParams.Version, AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/GrabbableSteeringWheel" + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel" )] [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] @@ -25,7 +25,7 @@ using NAK.GrabbableSteeringWheel.Properties; [assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] -namespace NAK.GrabbableSteeringWheel.Properties; +namespace NAK.RCCVirtualSteeringWheel.Properties; internal static class AssemblyInfoParams { public const string Version = "1.0.0"; diff --git a/GrabbableSteeringWheel/GrabbableSteeringWheel.csproj b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.csproj similarity index 86% rename from GrabbableSteeringWheel/GrabbableSteeringWheel.csproj rename to RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.csproj index 9855f81..8920c39 100644 --- a/GrabbableSteeringWheel/GrabbableSteeringWheel.csproj +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.csproj @@ -2,6 +2,7 @@ net48 + GrabbableSteeringWheel @@ -11,7 +12,4 @@ ..\.ManagedLibs\TheClapper.dll - - - diff --git a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs new file mode 100644 index 0000000..d809150 --- /dev/null +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs @@ -0,0 +1,36 @@ +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.InteractionSystem.Base; +using UnityEngine; + +namespace NAK.RCCVirtualSteeringWheel; + +public class SteeringWheelPickup : Pickupable +{ + #region Public Properties + public override bool DisallowTheft => true; + public override float MaxGrabDistance => 0.8f; + public override float MaxPushDistance => 0f; + public override bool IsAutoHold => false; + public override bool IsObjectRotationAllowed => false; + public override bool IsObjectPushPullAllowed => false; + public override bool IsObjectUseAllowed => false; + public override bool CanPickup => IsPickupable && !IsPickedUp; + internal SteeringWheelRoot root; + #endregion + + #region Public Methods + public override void OnUseDown(InteractionContext context) { } + public override void OnUseUp(InteractionContext context) { } + public override void OnFlingTowardsTarget(Vector3 target) { } + + public override void OnGrab(InteractionContext context, Vector3 grabPoint) { + if (ControllerRay?.pivotPoint != null) + root.StartTrackingTransform(ControllerRay.transform); + } + + public override void OnDrop(InteractionContext context) { + if (ControllerRay?.transform != null) + root.StopTrackingTransform(ControllerRay.transform); + } + #endregion +} \ No newline at end of file diff --git a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs new file mode 100644 index 0000000..cc293aa --- /dev/null +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs @@ -0,0 +1,313 @@ +using UnityEngine; + +namespace NAK.RCCVirtualSteeringWheel; + +public class SteeringWheelRoot : MonoBehaviour +{ + #region Static Variables + + private static readonly Dictionary ActiveWheels = new(); + + #endregion Static Variables + + #region Static Methods + + public static bool TryGetWheelInput(RCC_CarControllerV3 carController, out float steeringInput) + { + if (ActiveWheels.TryGetValue(carController, out SteeringWheelRoot wheel) && wheel._averageAngle != 0f) + { + steeringInput = wheel.GetNormalizedValue(); + return true; + } + + steeringInput = 0f; + return false; + } + + public static void SetupSteeringWheel(RCC_CarControllerV3 carController, Bounds steeringWheelBounds) + { + Transform steeringWheel = carController.SteeringWheel; + if (carController == null) return; + + SteeringWheelRoot wheel = steeringWheel.gameObject.AddComponent(); + wheel._carController = carController; + + Array.Resize(ref wheel._pickups, 2); + CreatePickup(out wheel._pickups[0]); + CreatePickup(out wheel._pickups[1]); + + return; + + void CreatePickup(out SteeringWheelPickup wheelPickup) + { + GameObject pickup = new() + { + transform = + { + parent = steeringWheel.transform, + localPosition = Vector3.zero, + localRotation = Quaternion.identity, + localScale = Vector3.one + } + }; + + BoxCollider collider = pickup.AddComponent(); + collider.size = steeringWheelBounds.size; + collider.center = steeringWheelBounds.center; + + wheelPickup = pickup.AddComponent(); + wheelPickup.root = wheel; + } + } + + #endregion Static Methods + + #region Public Properties + + private bool IsWheelBeingHeld => _pickups[0].IsPickedUp || _pickups[1].IsPickedUp; + + #endregion Public Properties + + #region Private Variables + + private RCC_CarControllerV3 _carController; + private SteeringWheelPickup[] _pickups; + + private float _originalSteeringWheelAngleMultiplier; + private float _originalSteeringWheelSign; + + private readonly List _trackedTransforms = new(); + private readonly List _lastPositions = new(); + private readonly List _totalAngles = new(); + + private bool _isTracking; + private float _averageAngle; + private float _timeWheelReleased = -1f; + private const float RETURN_TO_CENTER_DURATION = 2f; + + #endregion Private Variables + + #region Unity Events + + private void Start() + { + ActiveWheels.TryAdd(_carController, this); + InitializeWheel(); + } + + private void Update() + { + if (_carController == null) return; + UpdateWheelState(); + UpdateSteeringBehavior(); + } + + private void OnDestroy() + { + ActiveWheels.Remove(_carController); + } + + #endregion Unity Events + + #region Public Methods + + internal void StartTrackingTransform(Transform trans) + { + if (trans == null) return; + + var currentAngle = 0f; + if (_isTracking) + { + var sum = 0f; + var validTransforms = 0; + for (var i = 0; i < _trackedTransforms.Count; i++) + if (_trackedTransforms[i] != null) + { + sum += _totalAngles[i]; + validTransforms++; + } + + if (validTransforms > 0) + currentAngle = sum / validTransforms; + } + + _trackedTransforms.Add(trans); + _lastPositions.Add(GetLocalPositionWithoutRotation(transform.position)); + _totalAngles.Add(currentAngle); + _isTracking = true; + } + + internal void StopTrackingTransform(Transform trans) + { + var index = _trackedTransforms.IndexOf(trans); + if (index == -1) return; + + var currentAverage = CalculateCurrentAverage(); + _trackedTransforms.RemoveAt(index); + _lastPositions.RemoveAt(index); + _totalAngles.RemoveAt(index); + + if (_trackedTransforms.Count <= 0) + return; + + for (var i = 0; i < _totalAngles.Count; i++) + _totalAngles[i] = currentAverage; + } + + private float CalculateCurrentAverage() + { + var sum = 0f; + var validTransforms = 0; + for (var i = 0; i < _trackedTransforms.Count; i++) + if (_trackedTransforms[i] != null) + { + sum += _totalAngles[i]; + validTransforms++; + } + + return validTransforms > 0 ? sum / validTransforms : 0f; + } + + #endregion Public Methods + + #region Private Methods + + private void InitializeWheel() + { + _originalSteeringWheelAngleMultiplier = _carController.steeringWheelAngleMultiplier; + _originalSteeringWheelSign = Mathf.Sign(_originalSteeringWheelAngleMultiplier); + _carController.useCounterSteering = false; + _carController.useSteeringSmoother = false; + } + + private void UpdateWheelState() + { + var isHeld = IsWheelBeingHeld; + if (!isHeld && _timeWheelReleased < 0f) + _timeWheelReleased = Time.time; + else if (isHeld) + _timeWheelReleased = -1f; + } + + private void UpdateSteeringBehavior() + { + UpdateSteeringMultiplier(); + + if (IsWheelBeingHeld) + UpdateRotationTracking(); + else if (_timeWheelReleased >= 0f) + HandleWheelReturn(); + } + + private void UpdateSteeringMultiplier() + { + _carController.steeringWheelAngleMultiplier = ModSettings.EntryOverrideSteeringRange.Value + ? ModSettings.EntryCustomSteeringRange.Value * _originalSteeringWheelSign / _carController.steerAngle + : _originalSteeringWheelAngleMultiplier; + } + + private void HandleWheelReturn() + { + var timeSinceRelease = Time.time - _timeWheelReleased; + if (timeSinceRelease < RETURN_TO_CENTER_DURATION) + { + var t = timeSinceRelease / RETURN_TO_CENTER_DURATION; + _averageAngle = Mathf.Lerp(_averageAngle, 0f, t); + + for (var i = 0; i < _totalAngles.Count; i++) + _totalAngles[i] = _averageAngle; + } + else + { + _averageAngle = 0f; + for (var i = 0; i < _totalAngles.Count; i++) + _totalAngles[i] = 0f; + } + } + + private float GetMaxSteeringRange() + { + return _carController.steerAngle * Mathf.Abs(_carController.steeringWheelAngleMultiplier); + } + + private float GetSteeringWheelSign() + { + return _originalSteeringWheelSign * (ModSettings.EntryInvertSteering.Value ? 1f : -1f); + } + + private Vector3 GetSteeringWheelLocalAxis() + { + return _carController.steeringWheelRotateAround switch + { + RCC_CarControllerV3.SteeringWheelRotateAround.XAxis => Vector3.right, + RCC_CarControllerV3.SteeringWheelRotateAround.YAxis => Vector3.up, + RCC_CarControllerV3.SteeringWheelRotateAround.ZAxis => Vector3.forward, + _ => Vector3.forward + }; + } + + private Vector3 GetLocalPositionWithoutRotation(Vector3 worldPosition) + { + Transform steeringTransform = _carController.SteeringWheel; + Quaternion localRotation = steeringTransform.localRotation; + steeringTransform.localRotation = _carController.orgSteeringWheelRot; + Vector3 localPosition = steeringTransform.InverseTransformPoint(worldPosition); + steeringTransform.localRotation = localRotation; + return localPosition; + } + + private float GetNormalizedValue() + { + return Mathf.Clamp(_averageAngle / GetMaxSteeringRange(), -1f, 1f) * GetSteeringWheelSign(); + } + + private void UpdateRotationTracking() + { + if (!_isTracking || _trackedTransforms.Count == 0) return; + + Vector3 trackingAxis = GetSteeringWheelLocalAxis(); + UpdateTransformAngles(trackingAxis); + UpdateAverageAngle(); + } + + private void UpdateTransformAngles(Vector3 trackingAxis) + { + for (var i = 0; i < _trackedTransforms.Count; i++) + { + if (_trackedTransforms[i] == null) continue; + + Vector3 currentPosition = GetLocalPositionWithoutRotation(_trackedTransforms[i].position); + if (currentPosition == _lastPositions[i]) continue; + + Vector3 previousVector = Vector3.ProjectOnPlane(_lastPositions[i], trackingAxis).normalized; + Vector3 currentVector = Vector3.ProjectOnPlane(currentPosition, trackingAxis).normalized; + + if (previousVector.sqrMagnitude > 0.001f && currentVector.sqrMagnitude > 0.001f) + { + var deltaAngle = Vector3.SignedAngle(previousVector, currentVector, trackingAxis); + if (Mathf.Abs(deltaAngle) < 90f) + _totalAngles[i] += deltaAngle; + } + + _lastPositions[i] = currentPosition; + } + } + + private void UpdateAverageAngle() + { + var sumAngles = 0f; + var validTransforms = 0; + + for (var i = 0; i < _trackedTransforms.Count; i++) + { + if (_trackedTransforms[i] == null) continue; + sumAngles += _totalAngles[i]; + validTransforms++; + } + + if (validTransforms > 0) + _averageAngle = sumAngles / validTransforms; + } + + #endregion Private Methods +} \ No newline at end of file diff --git a/GrabbableSteeringWheel/GrabbableSteeringWheel/BoneVertexBoundsUtility.cs b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util/BoneVertexBoundsUtility.cs similarity index 99% rename from GrabbableSteeringWheel/GrabbableSteeringWheel/BoneVertexBoundsUtility.cs rename to RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util/BoneVertexBoundsUtility.cs index 2864f49..093620c 100644 --- a/GrabbableSteeringWheel/GrabbableSteeringWheel/BoneVertexBoundsUtility.cs +++ b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util/BoneVertexBoundsUtility.cs @@ -6,6 +6,8 @@ using Unity.Profiling; using UnityEngine; using UnityEngine.Animations; +namespace NAK.RCCVirtualSteeringWheel.Util; + public class BoneVertexBoundsUtility : MonoBehaviour { private static readonly ProfilerMarker s_calculateBoundsMarker = new("BoneVertexBounds.Calculate"); @@ -201,7 +203,7 @@ public class BoneVertexBoundsUtility : MonoBehaviour { yield return StartCoroutine(ProcessSkinnedMeshRenderersLocal(bone, weightThreshold, skinnedPoints => { - if (skinnedPoints == null || skinnedPoints.Count <= 0) return; + if (skinnedPoints is not { Count: > 0 }) return; allWeightedPoints.AddRange(skinnedPoints); hasValidPoints = true; })); diff --git a/GrabbableSteeringWheel/README.md b/RCCVirtualSteeringWheel/README.md similarity index 95% rename from GrabbableSteeringWheel/README.md rename to RCCVirtualSteeringWheel/README.md index 08942df..b396266 100644 --- a/GrabbableSteeringWheel/README.md +++ b/RCCVirtualSteeringWheel/README.md @@ -1,7 +1,7 @@ -# GrabbableSteeringWheel +# RCCVirtualSteeringWheel Experiment with making rigged RCC vehicle steering wheels work for VR steering input. -~~~~ + --- Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI. @@ -12,3 +12,4 @@ https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games > Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use. > To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive. +~~~~ \ No newline at end of file diff --git a/GrabbableSteeringWheel/format.json b/RCCVirtualSteeringWheel/format.json similarity index 100% rename from GrabbableSteeringWheel/format.json rename to RCCVirtualSteeringWheel/format.json