// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System; using System.Collections.Generic; using UltimateXR.Core; using UltimateXR.Core.Math; using UltimateXR.Extensions.Unity.Math; using UltimateXR.Manipulation; using UnityEngine; namespace UltimateXR.Avatar.Rig { /// /// Stores information of an avatar rig's arm. /// [Serializable] public class UxrAvatarArmInfo { #region Inspector Properties/Serialized Fields [SerializeField] private UxrAvatar _avatar; [SerializeField] private UxrHandSide _side; [SerializeField] private float _upperArmLength; [SerializeField] private float _forearmLength; [SerializeField] private UxrUniversalLocalAxes _fingerUniversalLocalAxes; [SerializeField] private UxrUniversalLocalAxes _handUniversalLocalAxes; [SerializeField] private UxrUniversalLocalAxes _armUniversalLocalAxes; [SerializeField] private UxrUniversalLocalAxes _forearmUniversalLocalAxes; [SerializeField] private UxrUniversalLocalAxes _clavicleUniversalLocalAxes; [SerializeField] private UxrAvatarFingerInfo _thumbInfo; [SerializeField] private UxrAvatarFingerInfo _indexInfo; [SerializeField] private UxrAvatarFingerInfo _middleInfo; [SerializeField] private UxrAvatarFingerInfo _ringInfo; [SerializeField] private UxrAvatarFingerInfo _littleInfo; #endregion #region Public Types & Data /// /// Gets the thumb finger information. /// public UxrAvatarFingerInfo ThumbInfo => _thumbInfo; /// /// Gets the index finger information. /// public UxrAvatarFingerInfo IndexInfo => _indexInfo; /// /// Gets the middle finger information. /// public UxrAvatarFingerInfo MiddleInfo => _middleInfo; /// /// Gets the ring finger information. /// public UxrAvatarFingerInfo RingInfo => _ringInfo; /// /// Gets the little finger information. /// public UxrAvatarFingerInfo LittleInfo => _littleInfo; /// /// Enumerates all the finger information. /// public IEnumerable Fingers { get { yield return _thumbInfo; yield return _indexInfo; yield return _middleInfo; yield return _ringInfo; yield return _littleInfo; } } /// /// Gets the upper arm length. From shoulder to elbow. /// public float UpperArmLength { get => _upperArmLength; private set => _upperArmLength = value; } /// /// Gets the forearm length. From elbow to wrist. /// public float ForearmLength { get => _forearmLength; private set => _forearmLength = value; } /// /// Gets the universal coordinate system for the fingers: right = axis around which the finger curls, up = /// knuckles up, forward = finger direction. /// public UxrUniversalLocalAxes FingerUniversalLocalAxes { get => _fingerUniversalLocalAxes; private set => _fingerUniversalLocalAxes = value; } /// /// Gets the universal coordinate system for the hand: right = from wrist to right (thumb direction), up = -palm /// facing vector, forward = finger direction. /// public UxrUniversalLocalAxes HandUniversalLocalAxes { get => _handUniversalLocalAxes; private set => _handUniversalLocalAxes = value; } /// /// Gets the universal coordinate system for the arm: forward is arm->elbow, up is elbow rotation axis /// public UxrUniversalLocalAxes ArmUniversalLocalAxes { get => _armUniversalLocalAxes; private set => _armUniversalLocalAxes = value; } /// /// Gets the universal coordinate system for the forearm: forward is arm->hand, up is elbow rotation axis /// public UxrUniversalLocalAxes ForearmUniversalLocalAxes { get => _forearmUniversalLocalAxes; private set => _forearmUniversalLocalAxes = value; } /// /// Gets the universal coordinate system for the clavicle: forward is clavicle->arm, up is avatar up axis /// public UxrUniversalLocalAxes ClavicleUniversalLocalAxes { get => _clavicleUniversalLocalAxes; private set => _clavicleUniversalLocalAxes = value; } // Updated every frame /// /// Gets the wrist torsion info. /// public UxrWristTorsionInfo WristTorsionInfo { get; private set; } = new UxrWristTorsionInfo(); #endregion #region Internal Methods /// /// Computes and stores all the arm information of an avatar. /// /// Avatar whose arm to compute the information of /// Which side to compute internal void Compute(UxrAvatar avatar, UxrHandSide side) { _avatar = avatar; _side = side; SolveHandAndFingerAxes(avatar.GetHand(side), side); ComputeArmRigInfo(avatar, avatar.GetArm(side), side); } #endregion #region Private Methods /// /// Gets the outwards-pointing elbow rotation axis in world coordinates. /// /// The avatar the arm nodes belong to /// The arm's forearm transform /// The arm forward looking vector, the one pointing from shoulder to elbow /// The forearm forward looking vector, the one pointing from elbow to hand /// Elbow rotation axis private static Vector3 GetWorldElbowAxis(UxrAvatar avatar, Transform forearm, Vector3 armForward, Vector3 forearmForward) { bool isLeft = avatar.transform.InverseTransformPoint(forearm.position).x < 0.0f; float elbowAngle = Vector3.Angle(armForward, forearmForward); Vector3 elbowAxis = Vector3.Cross(forearmForward, isLeft ? -armForward : armForward).normalized; Transform leftHand = avatar.GetHandBone(UxrHandSide.Left); Transform rightHand = avatar.GetHandBone(UxrHandSide.Right); if (leftHand && rightHand) { Vector3 avatarRight = (rightHand.position - leftHand.position).normalized; Vector3 forward = Vector3.Cross(avatarRight, Vector3.up); elbowAxis = Vector3.Cross(isLeft ? forearmForward : -forearmForward, forward).normalized; } else if (elbowAngle < ElbowMinAngleThreshold) { // Assume T-pose if elbow angle is too small elbowAxis = Vector3.up; } return forearm.TransformDirection(forearm.InverseTransformDirection(elbowAxis).GetClosestAxis()); } /// /// Tries to find out which axes are pointing right/up/forward in the hand and finger nodes. These "universal" axes /// will be used to rotate the nodes, so that any coordinate system can be used no matter how the hand was authored. /// /// The hand to compute the axes for /// Whether it is a left hand or right hand /// Boolean telling whether the axes could be solved. If any necessary transform is missing it will fail private bool SolveHandAndFingerAxes(UxrAvatarHand hand, UxrHandSide side) { Transform indexProximal = hand.Index.Proximal; Transform indexDistal = hand.Index.Distal; Transform middleProximal = hand.Middle.Proximal; Transform ringProximal = hand.Ring.Proximal; if (!hand.Wrist || !indexProximal || !indexDistal || !middleProximal || !ringProximal) { return false; } float handCenter = 0.5f; // [0, 1] Vector3 handAxesRight = hand.Wrist.InverseTransformDirection(indexProximal.position - middleProximal.position).GetClosestAxis() * (side == UxrHandSide.Left ? 1.0f : -1.0f); Vector3 handAxesForward = hand.Wrist.InverseTransformDirection((Vector3.Lerp(ringProximal.position, middleProximal.position, handCenter) - hand.Wrist.position).normalized); Vector3 handAxesUp = Vector3.Cross(handAxesForward, handAxesRight).normalized; handAxesRight = Vector3.Cross(handAxesUp, handAxesForward).normalized; HandUniversalLocalAxes = UxrUniversalLocalAxes.FromAxes(hand.Wrist, handAxesRight, handAxesUp, handAxesForward); Vector3 fingerAxesRight = indexProximal.InverseTransformDirection(indexProximal.position - middleProximal.position).GetClosestAxis() * (side == UxrHandSide.Left ? 1.0f : -1.0f); Vector3 fingerAxesForward = indexProximal.InverseTransformDirection(indexDistal.position - indexProximal.position).GetClosestAxis(); Vector3 fingerAxesUp = Vector3.Cross(fingerAxesForward, fingerAxesRight); FingerUniversalLocalAxes = UxrUniversalLocalAxes.FromAxes(indexProximal, fingerAxesRight, fingerAxesUp, fingerAxesForward); return true; } /// /// Computes and stores information of the arm's rig. /// /// Avatar whose right arm to compute information of /// Arm to compute the information of /// Which side it is private void ComputeArmRigInfo(UxrAvatar avatar, UxrAvatarArm arm, UxrHandSide side) { if (arm.UpperArm != null && arm.Forearm != null && arm.Hand.Wrist != null) { Vector3 armForward = (arm.Forearm.position - arm.UpperArm.position).normalized; Vector3 forearmForward = (arm.Hand.Wrist.position - arm.Forearm.position).normalized; Vector3 elbowAxis = GetWorldElbowAxis(avatar, arm.Forearm, armForward, forearmForward); Vector3 armLocalForward = arm.UpperArm.InverseTransformDirection(armForward).GetClosestAxis(); Vector3 armLocalElbowAxis = arm.UpperArm.InverseTransformDirection(elbowAxis).GetClosestAxis(); Vector3 forearmLocalForward = arm.Forearm.InverseTransformDirection(forearmForward).GetClosestAxis(); Vector3 forearmLocalElbowAxis = arm.Forearm.InverseTransformDirection(elbowAxis).GetClosestAxis(); ArmUniversalLocalAxes = UxrUniversalLocalAxes.FromUpForward(arm.UpperArm, armLocalElbowAxis, armLocalForward); ForearmUniversalLocalAxes = UxrUniversalLocalAxes.FromUpForward(arm.Forearm, armLocalElbowAxis, forearmLocalForward); UpperArmLength = Vector3.Distance(arm.UpperArm.position, arm.Forearm.position); ForearmLength = Vector3.Distance(arm.Forearm.position, arm.Hand.Wrist.position); if (arm.Clavicle != null) { Vector3 clavicleForward = (arm.UpperArm.position - arm.Clavicle.position).normalized; Vector3 clavicleLocalForwardAxis = arm.Clavicle.InverseTransformDirection(clavicleForward).GetClosestAxis(); Vector3 clavicleLocalUpAxis = arm.Clavicle.InverseTransformDirection(avatar.transform.up).GetClosestAxis(); ClavicleUniversalLocalAxes = UxrUniversalLocalAxes.FromUpForward(arm.Clavicle, clavicleLocalUpAxis, clavicleLocalForwardAxis); } } UxrGrabber grabber = avatar.GetGrabber(side); if (grabber && grabber.HandRenderer && grabber.HandRenderer is SkinnedMeshRenderer handRenderer) { _thumbInfo = new UxrAvatarFingerInfo(); _indexInfo = new UxrAvatarFingerInfo(); _middleInfo = new UxrAvatarFingerInfo(); _ringInfo = new UxrAvatarFingerInfo(); _littleInfo = new UxrAvatarFingerInfo(); _thumbInfo.Compute(avatar, handRenderer, side, UxrFingerType.Thumb); _indexInfo.Compute(avatar, handRenderer, side, UxrFingerType.Index); _middleInfo.Compute(avatar, handRenderer, side, UxrFingerType.Middle); _ringInfo.Compute(avatar, handRenderer, side, UxrFingerType.Ring); _littleInfo.Compute(avatar, handRenderer, side, UxrFingerType.Little); } } #endregion #region Private Types & Data /// /// Minimum angle between arm and forearm to compute elbow axis using cross product. /// private const float ElbowMinAngleThreshold = 3.0f; #endregion } }