RCCVirtualSteeringWheel: renamed mod, fixed things

This commit is contained in:
NotAKidoS 2025-01-07 02:36:44 -06:00
parent 3a00ff104a
commit 5c8c724b58
13 changed files with 445 additions and 314 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View 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
}

View 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
}

View file

@ -1,21 +1,21 @@
using ABI_RC.Systems.InputManagement; using ABI_RC.Systems.InputManagement;
using ABI_RC.Systems.Movement; using ABI_RC.Systems.Movement;
using HarmonyLib; using HarmonyLib;
using NAK.RCCVirtualSteeringWheel.Util;
using UnityEngine; using UnityEngine;
namespace NAK.GrabbableSteeringWheel.Patches; namespace NAK.RCCVirtualSteeringWheel.Patches;
internal static class RCCCarControllerV3_Patches internal static class RCCCarControllerV3_Patches
{ {
[HarmonyPostfix] [HarmonyPostfix]
[HarmonyPatch(typeof(RCC_CarControllerV3), nameof(RCC_CarControllerV3.Awake))] [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; Transform steeringWheelTransform = __instance.SteeringWheel;
if (steeringWheelTransform == null) if (steeringWheelTransform == null)
return; return;
RCC_CarControllerV3 v3 = __instance;
BoneVertexBoundsUtility.CalculateBoneWeightedBounds( BoneVertexBoundsUtility.CalculateBoneWeightedBounds(
steeringWheelTransform, steeringWheelTransform,
0.8f, 0.8f,
@ -25,12 +25,7 @@ internal static class RCCCarControllerV3_Patches
if (!result.IsValid) if (!result.IsValid)
return; return;
BoxCollider boxCollider = steeringWheelTransform.gameObject.AddComponent<BoxCollider>(); SteeringWheelRoot.SetupSteeringWheel(__instance, result.LocalBounds);
boxCollider.center = result.LocalBounds.center;
boxCollider.size = result.LocalBounds.size;
SteeringWheelPickup steeringWheel = steeringWheelTransform.gameObject.AddComponent<SteeringWheelPickup>();
steeringWheel.SetupSteeringWheel(v3);
}); });
} }
} }
@ -42,8 +37,11 @@ internal static class CVRInputManager_Patches
private static void Postfix_CVRInputManager_UpdateInput(ref CVRInputManager __instance) private static void Postfix_CVRInputManager_UpdateInput(ref CVRInputManager __instance)
{ {
// Steering input is clamped in RCC component // Steering input is clamped in RCC component
if (BetterBetterCharacterController.Instance.IsSittingOnControlSeat()) if (BetterBetterCharacterController.Instance.IsSittingOnControlSeat()
__instance.steering += SteeringWheelPickup.GetSteerInput( && SteeringWheelRoot.TryGetWheelInput(
BetterBetterCharacterController.Instance._lastCvrSeat._carController); BetterBetterCharacterController.Instance._lastCvrSeat._carController, out float steeringValue))
{
__instance.steering = steeringValue;
}
} }
} }

View file

@ -1,21 +1,21 @@
using System.Reflection; using System.Reflection;
using MelonLoader; using MelonLoader;
using NAK.GrabbableSteeringWheel; using NAK.RCCVirtualSteeringWheel;
using NAK.GrabbableSteeringWheel.Properties; using NAK.RCCVirtualSteeringWheel.Properties;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] [assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.GrabbableSteeringWheel))] [assembly: AssemblyTitle(nameof(NAK.RCCVirtualSteeringWheel))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)] [assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.GrabbableSteeringWheel))] [assembly: AssemblyProduct(nameof(NAK.RCCVirtualSteeringWheel))]
[assembly: MelonInfo( [assembly: MelonInfo(
typeof(GrabbableSteeringWheelMod), typeof(RCCVirtualSteeringWheelMod),
nameof(NAK.GrabbableSteeringWheel), nameof(NAK.RCCVirtualSteeringWheel),
AssemblyInfoParams.Version, AssemblyInfoParams.Version,
AssemblyInfoParams.Author, 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")] [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
@ -25,7 +25,7 @@ using NAK.GrabbableSteeringWheel.Properties;
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red [assembly: MelonAuthorColor(255, 158, 21, 32)] // red
[assembly: HarmonyDontPatchAll] [assembly: HarmonyDontPatchAll]
namespace NAK.GrabbableSteeringWheel.Properties; namespace NAK.RCCVirtualSteeringWheel.Properties;
internal static class AssemblyInfoParams internal static class AssemblyInfoParams
{ {
public const string Version = "1.0.0"; public const string Version = "1.0.0";

View file

@ -2,6 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net48</TargetFramework> <TargetFramework>net48</TargetFramework>
<RootNamespace>GrabbableSteeringWheel</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="BTKUILib"> <Reference Include="BTKUILib">
@ -11,7 +12,4 @@
<HintPath>..\.ManagedLibs\TheClapper.dll</HintPath> <HintPath>..\.ManagedLibs\TheClapper.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Utility\" />
</ItemGroup>
</Project> </Project>

View file

@ -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
}

View file

@ -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
}

View file

@ -6,6 +6,8 @@ using Unity.Profiling;
using UnityEngine; using UnityEngine;
using UnityEngine.Animations; using UnityEngine.Animations;
namespace NAK.RCCVirtualSteeringWheel.Util;
public class BoneVertexBoundsUtility : MonoBehaviour public class BoneVertexBoundsUtility : MonoBehaviour
{ {
private static readonly ProfilerMarker s_calculateBoundsMarker = new("BoneVertexBounds.Calculate"); private static readonly ProfilerMarker s_calculateBoundsMarker = new("BoneVertexBounds.Calculate");
@ -201,7 +203,7 @@ public class BoneVertexBoundsUtility : MonoBehaviour
{ {
yield return StartCoroutine(ProcessSkinnedMeshRenderersLocal(bone, weightThreshold, skinnedPoints => yield return StartCoroutine(ProcessSkinnedMeshRenderersLocal(bone, weightThreshold, skinnedPoints =>
{ {
if (skinnedPoints == null || skinnedPoints.Count <= 0) return; if (skinnedPoints is not { Count: > 0 }) return;
allWeightedPoints.AddRange(skinnedPoints); allWeightedPoints.AddRange(skinnedPoints);
hasValidPoints = true; hasValidPoints = true;
})); }));

View file

@ -1,7 +1,7 @@
# GrabbableSteeringWheel # RCCVirtualSteeringWheel
Experiment with making rigged RCC vehicle steering wheels work for VR steering input. 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. 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. > 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. > To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.
~~~~