// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using UnityEngine; namespace UltimateXR.Avatar.Rig { /// /// Stores information about wrist torsion. Updated by . /// public class UxrWristTorsionInfo { #region Public Types & Data /// /// The wrist rotation along the elbow->hand axis. /// public float WristTorsionAngle { get; private set; } #endregion #region Constructors & Finalizer /// /// Constructor. /// public UxrWristTorsionInfo() { _wristTorsionMinAngle = WristTorsionLimitSideLong; _wristTorsionMaxAngle = -WristTorsionLimitSideShort; } #endregion #region Public Methods /// /// Updates the wrist torsion information to the current frame. /// /// Forearm bone /// Hand bone /// Arm information public void UpdateInfo(Transform forearm, Transform hand, UxrAvatarArmInfo armInfo) { if (hand && forearm && armInfo.ForearmUniversalLocalAxes != null && armInfo.HandUniversalLocalAxes != null) { Vector3 currentHandForwardInForearm = forearm.InverseTransformDirection(armInfo.HandUniversalLocalAxes.WorldForward); Vector3 currentHandUpInForearm = forearm.InverseTransformDirection(armInfo.HandUniversalLocalAxes.WorldUp); Vector3 currentHandRightInForearm = forearm.InverseTransformDirection(armInfo.HandUniversalLocalAxes.WorldRight); currentHandUpInForearm = Vector3.ProjectOnPlane(currentHandUpInForearm, armInfo.ForearmUniversalLocalAxes.LocalForward); currentHandRightInForearm = Vector3.ProjectOnPlane(currentHandRightInForearm, armInfo.ForearmUniversalLocalAxes.LocalForward); float angleRight = Vector3.SignedAngle(armInfo.ForearmUniversalLocalAxes.LocalRight, currentHandRightInForearm, armInfo.ForearmUniversalLocalAxes.LocalForward); float angle = angleRight; // Works better than angleUp because of hand movement constraints // Check for twists greater than 180 degrees, to know which way to turn to if (WristTorsionAngle > WristTorsionOvershootAngleThreshold && angle < -WristTorsionOvershootAngleThreshold) { _wristTorsionOvershoot++; } else if (WristTorsionAngle < -WristTorsionOvershootAngleThreshold && angle > WristTorsionOvershootAngleThreshold) { _wristTorsionOvershoot--; } // Compute the overshoot if necessary float finalAngle = angle; if (_wristTorsionOvershoot > 0) { finalAngle = 180.0f + 360.0f * (_wristTorsionOvershoot - 1) + (angle + 180.0f); } else if (_wristTorsionOvershoot < 0) { finalAngle = -180.0f - -360.0f * (_wristTorsionOvershoot + 1) - (180.0f - angle); } if (finalAngle > _wristTorsionMinAngle && _wristTorsionOvershoot != 0) { _wristTorsionOvershoot = 0; finalAngle = finalAngle + 360.0f; } else if (finalAngle < _wristTorsionMaxAngle && _wristTorsionOvershoot != 0) { _wristTorsionOvershoot = 0; finalAngle = finalAngle - 360.0f; } // Rotation WristTorsionAngle = finalAngle; } } #endregion #region Private Types & Data // Constants private const float WristTorsionOvershootAngleThreshold = 150.0f; private const float WristTorsionLimitSideShort = 200.0f; private const float WristTorsionLimitSideLong = 300.0f; // Internal vars private readonly float _wristTorsionMinAngle; private readonly float _wristTorsionMaxAngle; private int _wristTorsionOvershoot; #endregion } }