mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-06 00:09:22 +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
|
@ -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
|
||||
}
|
|
@ -0,0 +1,403 @@
|
|||
using System.Collections;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
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");
|
||||
private static readonly ProfilerMarker s_processChildRenderersMarker = new("BoneVertexBounds.ProcessChildRenderers");
|
||||
private static readonly ProfilerMarker s_processSkinnedMeshesMarker = new("BoneVertexBounds.ProcessSkinnedMeshes");
|
||||
private static readonly ProfilerMarker s_processVerticesMarker = new("BoneVertexBounds.ProcessVertices");
|
||||
private static readonly ProfilerMarker s_processConstraintsMarker = new("BoneVertexBounds.ProcessConstraints");
|
||||
private static readonly ProfilerMarker s_jobExecutionMarker = new("BoneVertexBounds.JobExecution");
|
||||
private static readonly ProfilerMarker s_meshCopyMarker = new("BoneVertexBounds.MeshCopy");
|
||||
|
||||
private static BoneVertexBoundsUtility instance;
|
||||
|
||||
private static BoneVertexBoundsUtility Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance != null) return instance;
|
||||
GameObject go = new("BoneVertexBoundsUtility");
|
||||
instance = go.AddComponent<BoneVertexBoundsUtility>();
|
||||
DontDestroyOnLoad(go);
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum BoundsCalculationFlags
|
||||
{
|
||||
None = 0,
|
||||
IncludeChildren = 1 << 0,
|
||||
IncludeSkinnedMesh = 1 << 1,
|
||||
IncludeConstraints = 1 << 2,
|
||||
All = IncludeChildren | IncludeSkinnedMesh | IncludeConstraints
|
||||
}
|
||||
|
||||
public struct BoundsResult
|
||||
{
|
||||
public bool IsValid;
|
||||
public Bounds LocalBounds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the bounds of a transform based on:
|
||||
/// - Children Renderers
|
||||
/// - Skinned Mesh Weights
|
||||
/// - Constrained Child Renderers & Skinned Mesh Weights
|
||||
/// </summary>
|
||||
public static void CalculateBoneWeightedBounds(Transform bone, float weightThreshold, BoundsCalculationFlags flags, Action<BoundsResult> onComplete)
|
||||
=> Instance.StartCoroutine(Instance.CalculateBoundsCoroutine(bone, weightThreshold, flags, onComplete));
|
||||
|
||||
private IEnumerator CalculateBoundsCoroutine(Transform bone, float weightThreshold, BoundsCalculationFlags flags, Action<BoundsResult> onComplete)
|
||||
{
|
||||
using (s_calculateBoundsMarker.Auto())
|
||||
{
|
||||
BoundsResult result = new();
|
||||
var allWeightedPoints = new List<Vector3>();
|
||||
bool hasValidPoints = false;
|
||||
|
||||
// Child renderers
|
||||
IEnumerator ProcessChildRenderersLocal(Transform targetBone, Action<List<Vector3>> onChildPoints)
|
||||
{
|
||||
using (s_processChildRenderersMarker.Auto())
|
||||
{
|
||||
var points = new List<Vector3>();
|
||||
var childRenderers = targetBone.GetComponentsInChildren<Renderer>()
|
||||
.Where(r => r is not SkinnedMeshRenderer)
|
||||
.ToArray();
|
||||
|
||||
foreach (Renderer childRend in childRenderers)
|
||||
{
|
||||
Bounds bounds = childRend.localBounds;
|
||||
var corners = new Vector3[8];
|
||||
Vector3 ext = bounds.extents;
|
||||
Vector3 center = bounds.center;
|
||||
|
||||
corners[0] = new Vector3(center.x - ext.x, center.y - ext.y, center.z - ext.z);
|
||||
corners[1] = new Vector3(center.x + ext.x, center.y - ext.y, center.z - ext.z);
|
||||
corners[2] = new Vector3(center.x - ext.x, center.y + ext.y, center.z - ext.z);
|
||||
corners[3] = new Vector3(center.x + ext.x, center.y + ext.y, center.z - ext.z);
|
||||
corners[4] = new Vector3(center.x - ext.x, center.y - ext.y, center.z + ext.z);
|
||||
corners[5] = new Vector3(center.x + ext.x, center.y - ext.y, center.z + ext.z);
|
||||
corners[6] = new Vector3(center.x - ext.x, center.y + ext.y, center.z + ext.z);
|
||||
corners[7] = new Vector3(center.x + ext.x, center.y + ext.y, center.z + ext.z);
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
points.Add(targetBone.InverseTransformPoint(childRend.transform.TransformPoint(corners[i])));
|
||||
}
|
||||
|
||||
onChildPoints?.Invoke(points);
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Skinned mesh renderers
|
||||
IEnumerator ProcessSkinnedMeshRenderersLocal(Transform targetBone, float threshold, Action<List<Vector3>> onSkinnedPoints)
|
||||
{
|
||||
using (s_processSkinnedMeshesMarker.Auto())
|
||||
{
|
||||
var points = new List<Vector3>();
|
||||
var siblingAndParentSkinnedMesh = targetBone.root.GetComponentsInChildren<SkinnedMeshRenderer>();
|
||||
var relevantMeshes = siblingAndParentSkinnedMesh.Where(smr => DoesMeshUseBone(smr, targetBone)).ToArray();
|
||||
|
||||
foreach (SkinnedMeshRenderer smr in relevantMeshes)
|
||||
{
|
||||
yield return StartCoroutine(ProcessSkinnedMesh(smr, targetBone, threshold, meshPoints =>
|
||||
{
|
||||
if (meshPoints is { Length: > 0 })
|
||||
points.AddRange(meshPoints);
|
||||
}));
|
||||
}
|
||||
|
||||
onSkinnedPoints?.Invoke(points);
|
||||
}
|
||||
}
|
||||
|
||||
// Constraints
|
||||
IEnumerator ProcessConstraintsLocal(Transform targetBone, float threshold, BoundsCalculationFlags constraintFlags, Action<List<Vector3>> onConstraintPoints)
|
||||
{
|
||||
using (s_processConstraintsMarker.Auto())
|
||||
{
|
||||
var points = new List<Vector3>();
|
||||
var processedTransforms = new HashSet<Transform>();
|
||||
var constrainedTransforms = new List<Transform>();
|
||||
|
||||
// Find all constrained objects that reference our bone
|
||||
var constraints = targetBone.root.GetComponentsInChildren<IConstraint>();
|
||||
|
||||
foreach (IConstraint constraint in constraints)
|
||||
{
|
||||
for (int i = 0; i < constraint.sourceCount; i++)
|
||||
{
|
||||
if (constraint.GetSource(i).sourceTransform != targetBone) continue;
|
||||
constrainedTransforms.Add(((Behaviour)constraint).transform);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Process each constrained transform
|
||||
foreach (Transform constrainedTransform in constrainedTransforms)
|
||||
{
|
||||
if (!processedTransforms.Add(constrainedTransform))
|
||||
continue;
|
||||
|
||||
var localPoints = new List<Vector3>();
|
||||
bool hasLocalPoints = false;
|
||||
|
||||
// Process child renderers if enabled
|
||||
if ((constraintFlags & BoundsCalculationFlags.IncludeChildren) != 0)
|
||||
{
|
||||
yield return StartCoroutine(ProcessChildRenderersLocal(constrainedTransform, childPoints =>
|
||||
{
|
||||
if (childPoints is not { Count: > 0 }) return;
|
||||
localPoints.AddRange(childPoints);
|
||||
hasLocalPoints = true;
|
||||
}));
|
||||
}
|
||||
|
||||
// Process skinned mesh if enabled
|
||||
if ((constraintFlags & BoundsCalculationFlags.IncludeSkinnedMesh) != 0)
|
||||
{
|
||||
yield return StartCoroutine(ProcessSkinnedMeshRenderersLocal(constrainedTransform, threshold, skinnedPoints =>
|
||||
{
|
||||
if (skinnedPoints is not { Count: > 0 }) return;
|
||||
localPoints.AddRange(skinnedPoints);
|
||||
hasLocalPoints = true;
|
||||
}));
|
||||
}
|
||||
|
||||
if (!hasLocalPoints)
|
||||
continue;
|
||||
|
||||
// Convert all points to bone space
|
||||
foreach (Vector3 point in localPoints)
|
||||
points.Add(targetBone.InverseTransformPoint(constrainedTransform.TransformPoint(point)));
|
||||
}
|
||||
|
||||
onConstraintPoints?.Invoke(points);
|
||||
}
|
||||
}
|
||||
|
||||
// Process child renderers
|
||||
if ((flags & BoundsCalculationFlags.IncludeChildren) != 0)
|
||||
{
|
||||
yield return StartCoroutine(ProcessChildRenderersLocal(bone, childPoints =>
|
||||
{
|
||||
if (childPoints is not { Count: > 0 }) return;
|
||||
allWeightedPoints.AddRange(childPoints);
|
||||
hasValidPoints = true;
|
||||
}));
|
||||
}
|
||||
|
||||
// Process skinned mesh renderers
|
||||
if ((flags & BoundsCalculationFlags.IncludeSkinnedMesh) != 0)
|
||||
{
|
||||
yield return StartCoroutine(ProcessSkinnedMeshRenderersLocal(bone, weightThreshold, skinnedPoints =>
|
||||
{
|
||||
if (skinnedPoints is not { Count: > 0 }) return;
|
||||
allWeightedPoints.AddRange(skinnedPoints);
|
||||
hasValidPoints = true;
|
||||
}));
|
||||
}
|
||||
|
||||
// Process constraints
|
||||
if ((flags & BoundsCalculationFlags.IncludeConstraints) != 0)
|
||||
{
|
||||
// Use only Children and SkinnedMesh flags for constraint processing to prevent recursion (maybe make optional)?
|
||||
BoundsCalculationFlags constraintFlags = flags & ~BoundsCalculationFlags.IncludeConstraints;
|
||||
yield return StartCoroutine(ProcessConstraintsLocal(bone, weightThreshold, constraintFlags, constraintPoints =>
|
||||
{
|
||||
if (constraintPoints is not { Count: > 0 }) return;
|
||||
allWeightedPoints.AddRange(constraintPoints);
|
||||
hasValidPoints = true;
|
||||
}));
|
||||
}
|
||||
|
||||
if (!hasValidPoints)
|
||||
{
|
||||
result.IsValid = false;
|
||||
onComplete?.Invoke(result);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Calculate final bounds in bone space
|
||||
Bounds bounds = new(allWeightedPoints[0], Vector3.zero);
|
||||
foreach (Vector3 point in allWeightedPoints)
|
||||
bounds.Encapsulate(point);
|
||||
|
||||
// Ensure minimum size
|
||||
Vector3 size = bounds.size;
|
||||
size = Vector3.Max(size, Vector3.one * 0.01f);
|
||||
bounds.size = size;
|
||||
|
||||
result.IsValid = true;
|
||||
result.LocalBounds = bounds;
|
||||
|
||||
onComplete?.Invoke(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool DoesMeshUseBone(SkinnedMeshRenderer smr, Transform bone)
|
||||
=> smr.bones != null && smr.bones.Contains(bone);
|
||||
|
||||
private IEnumerator ProcessSkinnedMesh(SkinnedMeshRenderer smr, Transform bone, float weightThreshold,
|
||||
Action<Vector3[]> onComplete)
|
||||
{
|
||||
Mesh mesh = smr.sharedMesh;
|
||||
if (mesh == null)
|
||||
{
|
||||
onComplete?.Invoke(null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Find bone index
|
||||
int boneIndex = Array.IndexOf(smr.bones, bone);
|
||||
if (boneIndex == -1)
|
||||
{
|
||||
onComplete?.Invoke(null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
Mesh meshToUse = mesh;
|
||||
GameObject tempGO = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Handle non-readable meshes (ReadItAnyway lmao)
|
||||
if (!mesh.isReadable)
|
||||
{
|
||||
using (s_meshCopyMarker.Auto())
|
||||
{
|
||||
tempGO = new GameObject("TempMeshReader");
|
||||
SkinnedMeshRenderer tempSMR = tempGO.AddComponent<SkinnedMeshRenderer>();
|
||||
tempSMR.sharedMesh = mesh;
|
||||
meshToUse = new Mesh();
|
||||
tempSMR.BakeMesh(meshToUse);
|
||||
}
|
||||
}
|
||||
|
||||
Mesh.MeshDataArray meshDataArray = Mesh.AcquireReadOnlyMeshData(meshToUse);
|
||||
Mesh.MeshData meshData = meshDataArray[0];
|
||||
|
||||
var vertexCount = meshData.vertexCount;
|
||||
var vertices = new NativeArray<Vector3>(vertexCount, Allocator.TempJob);
|
||||
var weights = new NativeArray<BoneWeight>(vertexCount, Allocator.TempJob);
|
||||
var results = new NativeArray<VertexResult>(vertexCount, Allocator.TempJob);
|
||||
|
||||
meshData.GetVertices(vertices);
|
||||
weights.CopyFrom(mesh.boneWeights);
|
||||
|
||||
// Debug.Log(vertices.Length);
|
||||
// Debug.Log(weights.Length);
|
||||
|
||||
using (s_processVerticesMarker.Auto())
|
||||
{
|
||||
try
|
||||
{
|
||||
Transform rootBone = smr.rootBone ? smr.rootBone.transform : smr.transform;
|
||||
Matrix4x4 meshToWorld = Matrix4x4.TRS(smr.transform.position, smr.transform.rotation, rootBone.lossyScale);
|
||||
|
||||
// Fixes setup where mesh was in diff hierarchy & 0.001 scale, bone & root bone outside & above
|
||||
meshToWorld *= Matrix4x4.TRS(Vector3.zero, Quaternion.identity, smr.transform.localScale);
|
||||
|
||||
ProcessVerticesJob processJob = new()
|
||||
{
|
||||
Vertices = vertices,
|
||||
BoneWeights = weights,
|
||||
Results = results,
|
||||
BoneIndex = boneIndex,
|
||||
WeightThreshold = weightThreshold,
|
||||
MeshToWorld = meshToWorld,
|
||||
WorldToBone = bone.worldToLocalMatrix
|
||||
};
|
||||
|
||||
using (s_jobExecutionMarker.Auto())
|
||||
{
|
||||
int batchCount = Mathf.Max(1, vertexCount / 64);
|
||||
JobHandle jobHandle = processJob.Schedule(vertexCount, batchCount);
|
||||
while (!jobHandle.IsCompleted)
|
||||
yield return null;
|
||||
|
||||
jobHandle.Complete();
|
||||
}
|
||||
|
||||
// Collect valid points
|
||||
var validPoints = new List<Vector3>();
|
||||
for (int i = 0; i < results.Length; i++)
|
||||
if (results[i].IsValid) validPoints.Add(results[i].Position);
|
||||
|
||||
onComplete?.Invoke(validPoints.ToArray());
|
||||
}
|
||||
finally
|
||||
{
|
||||
vertices.Dispose();
|
||||
weights.Dispose();
|
||||
results.Dispose();
|
||||
meshDataArray.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Destroy duplicated baked mesh if we created one to read mesh data
|
||||
if (!mesh.isReadable && meshToUse != mesh) Destroy(meshToUse);
|
||||
if (tempGO != null) Destroy(tempGO);
|
||||
}
|
||||
}
|
||||
|
||||
private struct VertexResult
|
||||
{
|
||||
public Vector3 Position;
|
||||
public bool IsValid;
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private struct ProcessVerticesJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly] public NativeArray<Vector3> Vertices;
|
||||
[ReadOnly] public NativeArray<BoneWeight> BoneWeights;
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeArray<VertexResult> Results;
|
||||
[ReadOnly] public int BoneIndex;
|
||||
[ReadOnly] public float WeightThreshold;
|
||||
[ReadOnly] public Matrix4x4 MeshToWorld;
|
||||
[ReadOnly] public Matrix4x4 WorldToBone;
|
||||
|
||||
public void Execute(int i)
|
||||
{
|
||||
BoneWeight weight = BoneWeights[i];
|
||||
float totalWeight = 0f;
|
||||
|
||||
if (weight.boneIndex0 == BoneIndex) totalWeight += weight.weight0;
|
||||
if (weight.boneIndex1 == BoneIndex) totalWeight += weight.weight1;
|
||||
if (weight.boneIndex2 == BoneIndex) totalWeight += weight.weight2;
|
||||
if (weight.boneIndex3 == BoneIndex) totalWeight += weight.weight3;
|
||||
|
||||
if (totalWeight >= WeightThreshold)
|
||||
{
|
||||
// Transform vertex to bone space
|
||||
Vector3 worldPos = MeshToWorld.MultiplyPoint3x4(Vertices[i]);
|
||||
Vector3 boneLocalPos = WorldToBone.MultiplyPoint3x4(worldPos);
|
||||
|
||||
Results[i] = new VertexResult
|
||||
{
|
||||
Position = boneLocalPos,
|
||||
IsValid = true
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
Results[i] = new VertexResult { IsValid = false };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue