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
}