using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.XR.Hands; #if UNITY_EDITOR using UnityEditor; #endif namespace XRMultiplayer { /// /// This class controls the curl of the fingers based on hand tracking or controller tracking. /// public class JointBasedHand : MonoBehaviour { /// /// Controls how the fingers are updated. /// Curl allows for a much more lightweight approximation of finger movements. /// public bool useCurl { get => m_UseCurl; set => m_UseCurl = value; } bool m_UseCurl; /// /// Controls the level of fidelity for fingers. /// /// Set from . public int fidelityLevel { get => m_FidelityLevel; set => m_FidelityLevel = value; } int m_FidelityLevel; [Header("Setup Settings")] /// /// Specify where the root of the hand is. /// [SerializeField, Tooltip("Specify where the root of the hand is.")] protected Transform m_HandRoot; /// /// Specify the names of the fingers. /// [SerializeField, Tooltip("Specify the names of the fingers.")] protected string[] m_FingerNames = { "Thumb", "Index", "Middle", "Ring", "Little" }; /// /// Specify the start index of the finger joints. /// [SerializeField, Tooltip("Specify the start index of the finger joints.")] protected XRHandJointID[] m_FingerStartJointIds = { XRHandJointID.ThumbMetacarpal, XRHandJointID.IndexMetacarpal, XRHandJointID.MiddleMetacarpal, XRHandJointID.RingMetacarpal, XRHandJointID.LittleMetacarpal }; [Header("Hand Fidelity Options")] /// /// Groups of . /// [Tooltip("Groups of Joint To Transform References. Use Context Menu for auto generation.")] public HandFidelityOption[] handFidelityOptions; /// /// Sets the Min/Max euler rotation of the fingers. /// [SerializeField, Tooltip("Sets the Min/Max euler rotation of the fingers.")] protected Vector2 m_MinMaxEulerX = new Vector2(0, 100); //3, 4, 5 -- Thumb //7, 8, 9, 10 -- Index //12, 13, 14, 15 -- Middle //17, 18, 19, 20 -- Ring //22, 23, 24, 25 -- Little /// private void Update() { if (!m_UseCurl) return; m_FidelityLevel = Mathf.Clamp(m_FidelityLevel, 0, handFidelityOptions.Length); foreach (var joint in handFidelityOptions[m_FidelityLevel].fingerJoints) { foreach (var finger in joint.jointTransformReferences) { Vector3 rot = Vector3.zero; rot.x = Mathf.Lerp(m_MinMaxEulerX.x, m_MinMaxEulerX.y, joint.curlAmount); finger.jointTransform.localRotation = Quaternion.Euler(rot); } } } /// /// Controls the Curl level of fingers. /// /// Called from . /// ID of the specific finger. /// Amount to curl the finger. public void SetCurl(int fingerID, float curlAmount) { handFidelityOptions[m_FidelityLevel].fingerJoints[fingerID].curlAmount = curlAmount; } #if UNITY_EDITOR /// /// Clears the hand references. /// public void ClearHandReferences() { handFidelityOptions = new HandFidelityOption[3]; } /// /// Attempts to find and automatically assign the hand references. /// [ContextMenu("Setup Hand References")] public void SetupHandReferences() { try { handFidelityOptions = new HandFidelityOption[3]; for (int i = 0; i < handFidelityOptions.Length; i++) { handFidelityOptions[i].fingerJoints = new FingerJoints[i <= 1 ? 5 : 3]; for (int j = 0; j < handFidelityOptions[i].fingerJoints.Length; j++) { handFidelityOptions[i].fingerJoints[j].fingerName = m_FingerNames[j]; handFidelityOptions[i].fingerJoints[j].jointTransformReferences = new List(); int jointDepth = i == 0 ? 4 : 3; if(j == 0) jointDepth -= 1; // Thumb has 1 less joint than the other fingers int startDepth = i == 0 ? 0 : 1; handFidelityOptions[i].fingerJoints[j].jointTransformReferences = GetFingerJoints(m_FingerNames[j], startDepth, jointDepth, m_FingerStartJointIds[j]); } //Get extra fingers as mittens if(i == 2) { handFidelityOptions[i].fingerJoints[2].jointTransformReferences.AddRange(GetFingerJoints(m_FingerNames[3], 1, 3, m_FingerStartJointIds[3])); handFidelityOptions[i].fingerJoints[2].jointTransformReferences.AddRange(GetFingerJoints(m_FingerNames[4], 1, 3, m_FingerStartJointIds[4])); } } } catch (Exception e) { Utils.LogError($"Error in FindNetworkHandReferences: {e}"); } } List GetFingerJoints(string fingerName, int startDepth, int jointDepth, XRHandJointID fingerStartJointId) { try { List fingerJoints = new(); JointToTransformReference currentJoint = new(); foreach (Transform child in m_HandRoot) { if (child.name.Contains(fingerName)) { Transform currentChild = child; //Navigate to the starting joint based on the startDepth for(int i = 0; i < startDepth; i++) { currentChild = currentChild.GetChild(0); } // Get all joints in the finger and add them to the list based on the jointDepth for (int i = 0; i < jointDepth; i++) { currentJoint.jointTransform = currentChild; int currentHandJointId = (int)fingerStartJointId + i + startDepth; currentJoint.xrHandJointID = (XRHandJointID)currentHandJointId; fingerJoints.Add(currentJoint); currentChild = currentChild.GetChild(0); } } } return fingerJoints; } catch (Exception e) { Utils.LogError($"Error in GetFingerJoints: {e}"); return null; } } #endif } [Serializable] public struct HandFidelityOption { public FingerJoints[] fingerJoints; } [Serializable] public struct FingerJoints { /// /// Finger Name. /// public string fingerName; /// /// The current curl amount of the finger. /// [Range(0.0f, 1.0f)] public float curlAmount; /// /// List. /// public List jointTransformReferences; } #if UNITY_EDITOR [CustomEditor(typeof(JointBasedHand))] public class HandCurlerEditor : Editor { public override void OnInspectorGUI() { DrawDefaultInspector(); JointBasedHand myScript = (JointBasedHand)target; if (GUILayout.Button("Setup References")) { myScript.SetupHandReferences(); } if (GUILayout.Button("Clear Hand References")) { myScript.ClearHandReferences(); } } } #endif }