using System;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
///
/// This class is designed to manage the positions of various joint nodes in the hand model.
///
public class HandMeshManager : MonoBehaviour
{
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandMeshManager";
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
private enum RootType
{
Palm = JointType.Palm,
Wrist = JointType.Wrist,
}
[SerializeField]
private Handedness m_Handedness;
public bool IsLeft { get { return m_Handedness == Handedness.Left; } }
[SerializeField]
private HandGrabInteractor m_HandGrabber;
public HandGrabInteractor HandGrabber { get { return m_HandGrabber; } }
[SerializeField]
private RootType m_RootJointType;
public JointType RootJointType { get { return (JointType)m_RootJointType; } }
[SerializeField]
private Transform m_HandRootJoint;
public Transform HandRootJoint { get { return m_HandRootJoint; } }
[SerializeField]
private Transform[] m_HandJoints = new Transform[(int)JointType.Count];
public Transform[] HandJoints { get { return m_HandJoints; } }
private const int k_JointCount = 26;
private const int k_JointChildCount = 6;
private void OnEnable()
{
if (m_HandGrabber == null)
{
WARNING("Not to set HandGrabInteractor so it won't stop when colliding with Immovable objects.");
}
}
private void Update()
{
HandData hand = CachedHand.Get(IsLeft);
if (!hand.isTracked)
{
return;
}
var parentTransform = m_HandRootJoint.parent;
int rootId = m_RootJointType == RootType.Palm ? 0 : 1;
Pose jointPose = GetJointPose(hand, rootId);
m_HandJoints[rootId].rotation = parentTransform.rotation * jointPose.rotation;
m_HandJoints[rootId].localPosition = jointPose.position;
m_HandJoints[rootId].localRotation = jointPose.rotation;
for (int i = 0; i < m_HandJoints.Length; i++)
{
if (m_HandJoints[i] == null || i == rootId) { continue; }
jointPose = GetJointPose(hand, i);
m_HandJoints[i].rotation = parentTransform.rotation * jointPose.rotation;
if (m_HandGrabber != null && m_HandGrabber.isGrabbing &&
m_HandGrabber.GetGrabPoseJointRotation(i, out Quaternion localStaticRot))
{
Quaternion currentRotation = m_HandJoints[i].rotation;
Quaternion maxRotation = m_HandJoints[i].parent.rotation * localStaticRot;
if (m_HandGrabber.IsRequiredJoint((JointType)i) ||
OverFlex(currentRotation, maxRotation) >= 0 ||
FlexAngle(currentRotation, maxRotation) >= 100)
{
m_HandJoints[i].rotation = maxRotation;
}
}
}
}
///
/// Calculate whether the current rotation exceeds the maximum rotation.
/// If the product is greater than 0, it exceeds.
///
/// Current rotation
/// Maximum rotation
/// The return value represents the dot product between the cross product of two rotations and the -x axis direction of the current rotation.
private float OverFlex(Quaternion currentRot, Quaternion maxRot)
{
Vector3 currFwd = currentRot * Vector3.forward;
Vector3 maxFwd = maxRot * Vector3.forward;
return Vector3.Dot(currentRot * Vector3.left, Vector3.Cross(currFwd, maxFwd));
}
///
/// Calculate the angle between the y-axis directions of two rotations.
///
/// Current rotation
/// Maximum rotation
/// The return value represents the angle between the up directions of the two rotation
private float FlexAngle(Quaternion currentRot, Quaternion maxRot)
{
Vector3 currFwd = currentRot * Vector3.up;
Vector3 maxFwd = maxRot * Vector3.up;
return Mathf.Acos(Vector3.Dot(currFwd, maxFwd) / (currFwd.magnitude * maxFwd.magnitude)) * Mathf.Rad2Deg;
}
///
/// Get the pose of the joint based on the joint ID.
///
/// The current result of hand tracking.
/// ID of the specified joint.
/// Return the pose of the specified joint.
private Pose GetJointPose(HandData hand, int jointId)
{
if (m_HandGrabber != null)
{
return m_HandGrabber.GetCurrentJointPose(jointId);
}
Vector3 jointPosition = Vector3.zero;
Quaternion jointRotation = Quaternion.identity;
hand.GetJointPosition((JointType)jointId, ref jointPosition);
hand.GetJointRotation((JointType)jointId, ref jointRotation);
return new Pose(jointPosition, jointRotation);
}
///
/// Get the transform of the joint.
///
/// JointType of the specified joint.
/// Output the transform of the joint.
/// Return true if successfully get the transform, otherwise false.
public bool GetJointTransform(JointType joint, out Transform jointTransform)
{
jointTransform = null;
int id = (int)joint;
if (id >= 0 && id < m_HandJoints.Length)
{
if (m_HandJoints[id] != null)
{
jointTransform = m_HandJoints[id];
return true;
}
}
return false;
}
///
/// Set all joints through gesture.
///
/// The gesture of grabbing.
/// Return true if successfully set the gesture, otherwise false.
public bool SetJointsFromGrabGesture(HandGrabGesture handGrabGesture)
{
if (m_HandGrabber == null) { return false; }
for (int i = 0; i < m_HandJoints.Length; i++)
{
m_HandJoints[i].localRotation = m_HandGrabber.handGrabState.GetDefaultJointRotationInGesture(handGrabGesture, i);
}
return true;
}
#if UNITY_EDITOR
public void FindJoints()
{
if (m_HandRootJoint != null)
{
int fingerJoint = (int)JointType.Thumb_Joint0;
if (m_HandRootJoint.childCount == k_JointChildCount)
{
for (int i = 0; i < m_HandRootJoint.childCount; i++)
{
Transform child = m_HandRootJoint.GetChild(i);
switch (child.childCount)
{
case 0:
if (m_RootJointType == RootType.Palm)
{
m_HandJoints[(int)JointType.Palm] = m_HandRootJoint;
m_HandJoints[(int)JointType.Wrist] = child;
}
else
{
m_HandJoints[(int)JointType.Palm] = child;
m_HandJoints[(int)JointType.Wrist] = m_HandRootJoint;
}
break;
case 4:
case 5:
for (int j = 0; j < child.childCount; j++)
{
m_HandJoints[fingerJoint] = child.GetChild(j);
fingerJoint++;
}
break;
default:
fingerJoint = RecursiveFind(fingerJoint, child);
break;
}
}
}
else if (m_HandRootJoint.childCount == k_JointCount - 1)
{
Transform child = m_HandRootJoint.GetChild(0);
if (m_RootJointType == RootType.Palm)
{
m_HandJoints[(int)JointType.Palm] = m_HandRootJoint;
m_HandJoints[(int)JointType.Wrist] = child;
}
else
{
m_HandJoints[(int)JointType.Palm] = child;
m_HandJoints[(int)JointType.Wrist] = m_HandRootJoint;
}
for (int i = 1; i < m_HandRootJoint.childCount; i++)
{
m_HandJoints[i + 1] = m_HandRootJoint.GetChild(i);
}
}
}
}
private int RecursiveFind(int jointId, Transform transform)
{
m_HandJoints[jointId] = transform;
jointId++;
if (transform.childCount > 0)
{
for (int i = 0; i < transform.childCount; i++)
{
jointId = RecursiveFind(jointId, transform.GetChild(i));
}
}
return jointId;
}
public void ClearJoints()
{
Array.Clear(m_HandJoints, 0, m_HandJoints.Length);
}
#endif
}
}