diff --git a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.csproj b/GrabbableSteeringWheel/GrabbableSteeringWheel.csproj similarity index 86% rename from RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.csproj rename to GrabbableSteeringWheel/GrabbableSteeringWheel.csproj index 8920c39..9855f81 100644 --- a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel.csproj +++ b/GrabbableSteeringWheel/GrabbableSteeringWheel.csproj @@ -2,7 +2,6 @@ net48 - GrabbableSteeringWheel @@ -12,4 +11,7 @@ ..\.ManagedLibs\TheClapper.dll + + + diff --git a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util/BoneVertexBoundsUtility.cs b/GrabbableSteeringWheel/GrabbableSteeringWheel/BoneVertexBoundsUtility.cs similarity index 99% rename from RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util/BoneVertexBoundsUtility.cs rename to GrabbableSteeringWheel/GrabbableSteeringWheel/BoneVertexBoundsUtility.cs index 093620c..2864f49 100644 --- a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Util/BoneVertexBoundsUtility.cs +++ b/GrabbableSteeringWheel/GrabbableSteeringWheel/BoneVertexBoundsUtility.cs @@ -6,8 +6,6 @@ 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"); @@ -203,7 +201,7 @@ public class BoneVertexBoundsUtility : MonoBehaviour { yield return StartCoroutine(ProcessSkinnedMeshRenderersLocal(bone, weightThreshold, skinnedPoints => { - if (skinnedPoints is not { Count: > 0 }) return; + if (skinnedPoints == null || skinnedPoints.Count <= 0) return; allWeightedPoints.AddRange(skinnedPoints); hasValidPoints = true; })); diff --git a/GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs b/GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs new file mode 100644 index 0000000..0807a2a --- /dev/null +++ b/GrabbableSteeringWheel/GrabbableSteeringWheel/SteeringWheelPickup.cs @@ -0,0 +1,203 @@ +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 new file mode 100644 index 0000000..1ced7e0 --- /dev/null +++ b/GrabbableSteeringWheel/Main.cs @@ -0,0 +1,61 @@ +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 new file mode 100644 index 0000000..26a56be --- /dev/null +++ b/GrabbableSteeringWheel/ModSettings.cs @@ -0,0 +1,24 @@ +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/Patches.cs b/GrabbableSteeringWheel/Patches.cs similarity index 62% rename from RCCVirtualSteeringWheel/Patches.cs rename to GrabbableSteeringWheel/Patches.cs index c4824ce..c6a828c 100644 --- a/RCCVirtualSteeringWheel/Patches.cs +++ b/GrabbableSteeringWheel/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.RCCVirtualSteeringWheel.Patches; +namespace NAK.GrabbableSteeringWheel.Patches; internal static class RCCCarControllerV3_Patches { [HarmonyPostfix] [HarmonyPatch(typeof(RCC_CarControllerV3), nameof(RCC_CarControllerV3.Awake))] - private static void Postfix_RCC_CarControllerV3_Awake(RCC_CarControllerV3 __instance) + private static void Postfix_RCC_CarControllerV3_Awake(ref RCC_CarControllerV3 __instance) { Transform steeringWheelTransform = __instance.SteeringWheel; if (steeringWheelTransform == null) return; + RCC_CarControllerV3 v3 = __instance; BoneVertexBoundsUtility.CalculateBoneWeightedBounds( steeringWheelTransform, 0.8f, @@ -25,7 +25,12 @@ internal static class RCCCarControllerV3_Patches if (!result.IsValid) return; - SteeringWheelRoot.SetupSteeringWheel(__instance, result.LocalBounds); + BoxCollider boxCollider = steeringWheelTransform.gameObject.AddComponent(); + boxCollider.center = result.LocalBounds.center; + boxCollider.size = result.LocalBounds.size; + + SteeringWheelPickup steeringWheel = steeringWheelTransform.gameObject.AddComponent(); + steeringWheel.SetupSteeringWheel(v3); }); } } @@ -37,11 +42,8 @@ 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() - && SteeringWheelRoot.TryGetWheelInput( - BetterBetterCharacterController.Instance._lastCvrSeat._carController, out float steeringValue)) - { - __instance.steering = steeringValue; - } + if (BetterBetterCharacterController.Instance.IsSittingOnControlSeat()) + __instance.steering += SteeringWheelPickup.GetSteerInput( + BetterBetterCharacterController.Instance._lastCvrSeat._carController); } } \ No newline at end of file diff --git a/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs b/GrabbableSteeringWheel/Properties/AssemblyInfo.cs similarity index 71% rename from RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs rename to GrabbableSteeringWheel/Properties/AssemblyInfo.cs index c82b5de..6441f79 100644 --- a/RCCVirtualSteeringWheel/Properties/AssemblyInfo.cs +++ b/GrabbableSteeringWheel/Properties/AssemblyInfo.cs @@ -1,21 +1,21 @@ using System.Reflection; using MelonLoader; -using NAK.RCCVirtualSteeringWheel; -using NAK.RCCVirtualSteeringWheel.Properties; +using NAK.GrabbableSteeringWheel; +using NAK.GrabbableSteeringWheel.Properties; [assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.RCCVirtualSteeringWheel))] +[assembly: AssemblyTitle(nameof(NAK.GrabbableSteeringWheel))] [assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.RCCVirtualSteeringWheel))] +[assembly: AssemblyProduct(nameof(NAK.GrabbableSteeringWheel))] [assembly: MelonInfo( - typeof(RCCVirtualSteeringWheelMod), - nameof(NAK.RCCVirtualSteeringWheel), + typeof(GrabbableSteeringWheelMod), + nameof(NAK.GrabbableSteeringWheel), AssemblyInfoParams.Version, AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel" + downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/GrabbableSteeringWheel" )] [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] @@ -25,7 +25,7 @@ using NAK.RCCVirtualSteeringWheel.Properties; [assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: HarmonyDontPatchAll] -namespace NAK.RCCVirtualSteeringWheel.Properties; +namespace NAK.GrabbableSteeringWheel.Properties; internal static class AssemblyInfoParams { public const string Version = "1.0.0"; diff --git a/RCCVirtualSteeringWheel/README.md b/GrabbableSteeringWheel/README.md similarity index 95% rename from RCCVirtualSteeringWheel/README.md rename to GrabbableSteeringWheel/README.md index b396266..08942df 100644 --- a/RCCVirtualSteeringWheel/README.md +++ b/GrabbableSteeringWheel/README.md @@ -1,7 +1,7 @@ -# RCCVirtualSteeringWheel +# GrabbableSteeringWheel 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,4 +12,3 @@ 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/RCCVirtualSteeringWheel/format.json b/GrabbableSteeringWheel/format.json similarity index 100% rename from RCCVirtualSteeringWheel/format.json rename to GrabbableSteeringWheel/format.json diff --git a/LegacyContentMitigation/Components/FakeMultiPassHack.cs b/LegacyContentMitigation/Components/FakeMultiPassHack.cs index fcc7169..31924db 100644 --- a/LegacyContentMitigation/Components/FakeMultiPassHack.cs +++ b/LegacyContentMitigation/Components/FakeMultiPassHack.cs @@ -207,11 +207,9 @@ public class FakeMultiPassHack : MonoBehaviour eyeCamera.targetTexture = targetTexture; eyeCamera.cullingMask = CachedCullingMask; eyeCamera.stereoTargetEye = StereoTargetEyeMask.None; - eyeCamera.cullingMatrix = _mainCamera.cullingMatrix; eyeCamera.projectionMatrix = _mainCamera.GetStereoProjectionMatrix(eye); eyeCamera.worldToCameraMatrix = _mainCamera.GetStereoViewMatrix(eye); eyeCamera.Render(); - eyeCamera.ResetCullingMatrix(); } } diff --git a/LegacyContentMitigation/Main.cs b/LegacyContentMitigation/Main.cs index 5d1c1b9..8db0910 100644 --- a/LegacyContentMitigation/Main.cs +++ b/LegacyContentMitigation/Main.cs @@ -33,7 +33,6 @@ public class LegacyContentMitigationMod : MelonMod ApplyPatches(typeof(Patches.CVRWorld_Patches)); // post processing shit ApplyPatches(typeof(Patches.CVRTools_Patches)); // prevent shader replacement when fix is active ApplyPatches(typeof(Patches.HeadHiderManager_Patches)); // prevent main cam triggering early head hide - ApplyPatches(typeof(Patches.CVRMirror_Patches)); InitializeIntegration("BTKUILib", Integrations.BtkUiAddon.Initialize); // quick menu options } diff --git a/LegacyContentMitigation/Patches.cs b/LegacyContentMitigation/Patches.cs index 2b151e8..e2dabaf 100644 --- a/LegacyContentMitigation/Patches.cs +++ b/LegacyContentMitigation/Patches.cs @@ -6,8 +6,6 @@ using ABI_RC.Core.Player; using ABI_RC.Core.Player.LocalClone; using ABI_RC.Core.Player.TransformHider; using ABI.CCK.Components; -using cohtml; -using cohtml.Net; using HarmonyLib; using UnityEngine; using UnityEngine.Rendering.PostProcessing; @@ -127,15 +125,4 @@ internal static class HeadHiderManager_Patches transformHiderManager._resetAfterThisRender = flag; } } -} - -internal static class CVRMirror_Patches -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(CVRMirror), nameof(CVRMirror.CopyCameraProperties))] - private static void Postfix_CVRMirror_CopyCameraProperties(ref CVRMirror __instance) - { - __instance.m_ReflectionCamera.ResetCullingMatrix(); - } - } \ No newline at end of file diff --git a/LegacyContentMitigation/Properties/AssemblyInfo.cs b/LegacyContentMitigation/Properties/AssemblyInfo.cs index 5969d71..e460687 100644 --- a/LegacyContentMitigation/Properties/AssemblyInfo.cs +++ b/LegacyContentMitigation/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.LegacyContentMitigation.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.2"; + public const string Version = "1.0.1"; public const string Author = "Exterrata & NotAKidoS"; } \ No newline at end of file diff --git a/RCCVirtualSteeringWheel/Main.cs b/RCCVirtualSteeringWheel/Main.cs deleted file mode 100644 index 79fae31..0000000 --- a/RCCVirtualSteeringWheel/Main.cs +++ /dev/null @@ -1,40 +0,0 @@ -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 deleted file mode 100644 index f07436c..0000000 --- a/RCCVirtualSteeringWheel/ModSettings.cs +++ /dev/null @@ -1,31 +0,0 @@ -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/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs b/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs deleted file mode 100644 index d809150..0000000 --- a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelPickup.cs +++ /dev/null @@ -1,36 +0,0 @@ -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 deleted file mode 100644 index cc293aa..0000000 --- a/RCCVirtualSteeringWheel/RCCVirtualSteeringWheel/Components/SteeringWheelRoot.cs +++ /dev/null @@ -1,313 +0,0 @@ -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