mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-01 22:09:23 +00:00
RCCVirtualSteeringWheel: renamed mod, fixed things
This commit is contained in:
parent
3a00ff104a
commit
5c8c724b58
13 changed files with 445 additions and 314 deletions
|
@ -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<RCC_CarControllerV3, SteeringWheelPickup> 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<Transform> _trackedTransforms = new();
|
||||
private readonly List<Vector3> _lastPositions = new();
|
||||
private readonly List<float> _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
|
||||
}
|
|
@ -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<bool> 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
|
||||
}
|
|
@ -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<bool> 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
|
||||
}
|
40
RCCVirtualSteeringWheel/Main.cs
Normal file
40
RCCVirtualSteeringWheel/Main.cs
Normal file
|
@ -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
|
||||
}
|
31
RCCVirtualSteeringWheel/ModSettings.cs
Normal file
31
RCCVirtualSteeringWheel/ModSettings.cs
Normal file
|
@ -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<bool> 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<float> 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<bool> EntryInvertSteering =
|
||||
Category.CreateEntry("invert_steering", false,
|
||||
"Invert Steering", description: "Inverts the steering direction");
|
||||
|
||||
#endregion Melon Preferences
|
||||
}
|
|
@ -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>();
|
||||
boxCollider.center = result.LocalBounds.center;
|
||||
boxCollider.size = result.LocalBounds.size;
|
||||
|
||||
SteeringWheelPickup steeringWheel = steeringWheelTransform.gameObject.AddComponent<SteeringWheelPickup>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
|
@ -2,6 +2,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<RootNamespace>GrabbableSteeringWheel</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BTKUILib">
|
||||
|
@ -11,7 +12,4 @@
|
|||
<HintPath>..\.ManagedLibs\TheClapper.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Utility\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,313 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace NAK.RCCVirtualSteeringWheel;
|
||||
|
||||
public class SteeringWheelRoot : MonoBehaviour
|
||||
{
|
||||
#region Static Variables
|
||||
|
||||
private static readonly Dictionary<RCC_CarControllerV3, SteeringWheelRoot> 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<SteeringWheelRoot>();
|
||||
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<BoxCollider>();
|
||||
collider.size = steeringWheelBounds.size;
|
||||
collider.center = steeringWheelBounds.center;
|
||||
|
||||
wheelPickup = pickup.AddComponent<SteeringWheelPickup>();
|
||||
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<Transform> _trackedTransforms = new();
|
||||
private readonly List<Vector3> _lastPositions = new();
|
||||
private readonly List<float> _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
|
||||
}
|
|
@ -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;
|
||||
}));
|
|
@ -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.
|
||||
~~~~
|
Loading…
Add table
Add a link
Reference in a new issue