387 lines
12 KiB
C#
387 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using VIVE.OpenXR.Toolkits.Common;
|
|
|
|
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
|
{
|
|
/// <summary>
|
|
/// This class is designed to manage the positions of various joint nodes in the hand model.
|
|
/// </summary>
|
|
public class HandMeshManager : MonoBehaviour
|
|
{
|
|
#region Log
|
|
|
|
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}"); }
|
|
int logFrame = 0;
|
|
bool printIntervalLog => logFrame == 0;
|
|
|
|
#endregion
|
|
|
|
[SerializeField]
|
|
private Handedness m_Handedness;
|
|
public bool isLeft { get { return m_Handedness == Handedness.Left; } }
|
|
|
|
[SerializeField]
|
|
private bool m_EnableCollider = false;
|
|
public bool enableCollider
|
|
{
|
|
get { return m_EnableCollider; }
|
|
set
|
|
{
|
|
m_EnableCollider = value;
|
|
if (m_EnableCollider && m_HandCollider == null)
|
|
{
|
|
InitHandCollider();
|
|
}
|
|
else if (m_HandCollider != null)
|
|
{
|
|
m_HandCollider.enableCollider = m_EnableCollider;
|
|
}
|
|
}
|
|
}
|
|
|
|
private HandColliderController m_HandCollider = null;
|
|
public HandColliderController handCollider => m_HandCollider;
|
|
|
|
[SerializeField]
|
|
private Transform[] m_HandJoints = new Transform[k_JointCount];
|
|
|
|
private SkinnedMeshRenderer skinnedMeshRenderer = null;
|
|
private const int k_JointCount = (int)JointType.Count;
|
|
private const int k_RootId = (int)JointType.Wrist;
|
|
private bool updateRoot = false;
|
|
private int updatedFrameCount = 0;
|
|
private bool isGrabbing = false;
|
|
private bool isConstraint = false;
|
|
private HandGrabInteractor handGrabber;
|
|
private Quaternion[] grabJointsRotation = new Quaternion[k_JointCount];
|
|
|
|
#region MonoBehaviours
|
|
|
|
private void OnEnable()
|
|
{
|
|
bool empty = m_HandJoints.Any(x => x == null);
|
|
if (empty)
|
|
{
|
|
ClearJoints();
|
|
FindJoints();
|
|
}
|
|
|
|
if (m_EnableCollider && m_HandCollider == null)
|
|
{
|
|
InitHandCollider();
|
|
}
|
|
|
|
MeshHandPose meshHandPose = transform.gameObject.AddComponent<MeshHandPose>();
|
|
meshHandPose.SetHandMeshRenderer(this);
|
|
|
|
skinnedMeshRenderer = transform.GetComponentInChildren<SkinnedMeshRenderer>();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (m_HandCollider != null)
|
|
{
|
|
Destroy(m_HandCollider);
|
|
}
|
|
|
|
MeshHandPose meshHandPose = transform.GetComponent<MeshHandPose>();
|
|
if (meshHandPose != null)
|
|
{
|
|
Destroy(meshHandPose);
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
bool isTracked = VIVEInput.IsHandTracked(isLeft ? Common.Handedness.Left : Common.Handedness.Right);
|
|
EnableHandModel(isTracked);
|
|
if (!isTracked) { return; }
|
|
|
|
//if (m_UseRuntimeModel || (!m_UseRuntimeModel && m_UseScale))
|
|
//{
|
|
// Vector3 scale = Vector3.one;
|
|
// if (GetHandScale(ref scale, isLeft))
|
|
// {
|
|
// m_HandJoints[rootId].localScale = scale;
|
|
// }
|
|
// else
|
|
// {
|
|
// m_HandJoints[rootId].localScale = Vector3.one;
|
|
// }
|
|
//}
|
|
|
|
if (Time.frameCount - updatedFrameCount > 5)
|
|
{
|
|
updateRoot = false;
|
|
}
|
|
if (!updateRoot)
|
|
{
|
|
VIVEInput.GetJointPose(isLeft ? Common.Handedness.Left : Common.Handedness.Right, HandJointType.Wrist, out Pose jointPose);
|
|
m_HandJoints[k_RootId].localPosition = jointPose.position;
|
|
m_HandJoints[k_RootId].localRotation = jointPose.rotation;
|
|
}
|
|
|
|
for (int i = 0; i < m_HandJoints.Length; i++)
|
|
{
|
|
if (m_HandJoints[i] == null || i == k_RootId) { continue; }
|
|
|
|
VIVEInput.GetJointPose(isLeft ? Common.Handedness.Left : Common.Handedness.Right, (HandJointType)i, out Pose jointPose);
|
|
m_HandJoints[i].rotation = m_HandJoints[k_RootId].parent.rotation * jointPose.rotation;
|
|
}
|
|
|
|
if (VIVERig.Instance)
|
|
{
|
|
m_HandJoints[k_RootId].rotation = VIVERig.Instance.transform.rotation * m_HandJoints[k_RootId].localRotation;
|
|
m_HandJoints[k_RootId].position = VIVERig.Instance.transform.position + VIVERig.Instance.transform.rotation * m_HandJoints[k_RootId].localPosition;
|
|
}
|
|
|
|
if (isGrabbing)
|
|
{
|
|
for (int i = 0; i < m_HandJoints.Length; i++)
|
|
{
|
|
if (i == k_RootId) { continue; }
|
|
|
|
Quaternion currentRotation = m_HandJoints[i].rotation;
|
|
Quaternion maxRotation = m_HandJoints[i].parent.rotation * grabJointsRotation[i];
|
|
if (isConstraint ||
|
|
handGrabber.IsRequiredJoint((JointType)i) ||
|
|
OverFlex(currentRotation, maxRotation) >= 0 ||
|
|
FlexAngle(currentRotation, maxRotation) >= 110)
|
|
{
|
|
m_HandJoints[i].rotation = maxRotation;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Interface
|
|
|
|
public void OnHandBeginGrab(IGrabber grabber)
|
|
{
|
|
if (grabber is HandGrabInteractor handGrabber)
|
|
{
|
|
this.handGrabber = handGrabber;
|
|
|
|
if (grabber.grabbable is HandGrabInteractable handGrabbable)
|
|
{
|
|
if (handGrabbable.bestGrabPose != GrabPose.Identity)
|
|
{
|
|
if (handGrabbable.bestGrabPose.recordedGrabRotations.Length == (int)JointType.Count)
|
|
{
|
|
grabJointsRotation = handGrabbable.bestGrabPose.recordedGrabRotations;
|
|
}
|
|
else if (handGrabbable.bestGrabPose.handGrabGesture != HandGrabGesture.Identity)
|
|
{
|
|
for (int i = 0; i < grabJointsRotation.Length; i++)
|
|
{
|
|
HandData.GetDefaultJointRotationInGesture(isLeft, handGrabbable.bestGrabPose.handGrabGesture, (JointType)i, ref grabJointsRotation[i]);
|
|
}
|
|
}
|
|
isGrabbing = true;
|
|
isConstraint = handGrabbable.isContraint;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_EnableCollider && m_HandCollider != null)
|
|
{
|
|
m_HandCollider.OnHandBeginGrab(grabber);
|
|
}
|
|
}
|
|
|
|
public void OnHandEndGrab(IGrabber grabber)
|
|
{
|
|
isGrabbing = false;
|
|
this.handGrabber = null;
|
|
|
|
if (m_EnableCollider && handCollider != null)
|
|
{
|
|
handCollider.OnHandEndGrab(grabber);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the position and rotation of the specified joint.
|
|
/// </summary>
|
|
/// <param name="joint">The joint type to get position and rotation from.</param>
|
|
/// <param name="position">The position of the joint.</param>
|
|
/// <param name="rotation">The rotation of the joint.</param>
|
|
/// <param name="local">Whether to get the local position and rotation.</param>
|
|
/// <returns>True if the joint position and rotation are successfully obtained; otherwise, false.</returns>
|
|
public bool GetJointPositionAndRotation(JointType joint, out Vector3 position, out Quaternion rotation, bool local = false)
|
|
{
|
|
position = Vector3.zero;
|
|
rotation = Quaternion.identity;
|
|
int jointId = (int)joint;
|
|
if (jointId >= 0 && jointId < k_JointCount && m_HandJoints[jointId] != null)
|
|
{
|
|
if (!local)
|
|
{
|
|
position = m_HandJoints[jointId].position;
|
|
rotation = m_HandJoints[jointId].rotation;
|
|
}
|
|
else
|
|
{
|
|
position = m_HandJoints[jointId].localPosition;
|
|
rotation = m_HandJoints[jointId].localRotation;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the position and rotation of the specified joint.
|
|
/// </summary>
|
|
/// <param name="joint">The joint type to set position and rotation for.</param>
|
|
/// <param name="position">The new position of the joint.</param>
|
|
/// <param name="rotation">The new rotation of the joint.</param>
|
|
/// <param name="local">Whether to set the local position and rotation.</param>
|
|
/// <returns>True if the joint position and rotation are successfully set; otherwise, false.</returns>
|
|
public bool SetJointPositionAndRotation(JointType joint, Vector3 position, Quaternion rotation, bool local = false)
|
|
{
|
|
int jointId = (int)joint;
|
|
if (jointId >= 0 && jointId < k_JointCount && m_HandJoints[jointId] != null)
|
|
{
|
|
if (!local)
|
|
{
|
|
m_HandJoints[jointId].position = position;
|
|
m_HandJoints[jointId].rotation = rotation;
|
|
}
|
|
else
|
|
{
|
|
m_HandJoints[jointId].localPosition = position;
|
|
m_HandJoints[jointId].localRotation = rotation;
|
|
}
|
|
|
|
if (joint == JointType.Wrist)
|
|
{
|
|
updatedFrameCount = Time.frameCount;
|
|
updateRoot = true;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Name Definition
|
|
// The order of joint name MUST align with runtime's definition
|
|
private readonly string[] JointsName = new string[]
|
|
{
|
|
"WaveBone_0", // WVR_HandJoint_Palm = 0
|
|
"WaveBone_1", // WVR_HandJoint_Wrist = 1
|
|
"WaveBone_2", // WVR_HandJoint_Thumb_Joint0 = 2
|
|
"WaveBone_3", // WVR_HandJoint_Thumb_Joint1 = 3
|
|
"WaveBone_4", // WVR_HandJoint_Thumb_Joint2 = 4
|
|
"WaveBone_5", // WVR_HandJoint_Thumb_Tip = 5
|
|
"WaveBone_6", // WVR_HandJoint_Index_Joint0 = 6
|
|
"WaveBone_7", // WVR_HandJoint_Index_Joint1 = 7
|
|
"WaveBone_8", // WVR_HandJoint_Index_Joint2 = 8
|
|
"WaveBone_9", // WVR_HandJoint_Index_Joint3 = 9
|
|
"WaveBone_10", // WVR_HandJoint_Index_Tip = 10
|
|
"WaveBone_11", // WVR_HandJoint_Middle_Joint0 = 11
|
|
"WaveBone_12", // WVR_HandJoint_Middle_Joint1 = 12
|
|
"WaveBone_13", // WVR_HandJoint_Middle_Joint2 = 13
|
|
"WaveBone_14", // WVR_HandJoint_Middle_Joint3 = 14
|
|
"WaveBone_15", // WVR_HandJoint_Middle_Tip = 15
|
|
"WaveBone_16", // WVR_HandJoint_Ring_Joint0 = 16
|
|
"WaveBone_17", // WVR_HandJoint_Ring_Joint1 = 17
|
|
"WaveBone_18", // WVR_HandJoint_Ring_Joint2 = 18
|
|
"WaveBone_19", // WVR_HandJoint_Ring_Joint3 = 19
|
|
"WaveBone_20", // WVR_HandJoint_Ring_Tip = 20
|
|
"WaveBone_21", // WVR_HandJoint_Pinky_Joint0 = 21
|
|
"WaveBone_22", // WVR_HandJoint_Pinky_Joint0 = 22
|
|
"WaveBone_23", // WVR_HandJoint_Pinky_Joint0 = 23
|
|
"WaveBone_24", // WVR_HandJoint_Pinky_Joint0 = 24
|
|
"WaveBone_25" // WVR_HandJoint_Pinky_Tip = 25
|
|
};
|
|
#endregion
|
|
|
|
private void GetAllChildrenTransforms(Transform parent, ref List<Transform> childrenTransforms)
|
|
{
|
|
foreach (Transform child in parent)
|
|
{
|
|
childrenTransforms.Add(child);
|
|
GetAllChildrenTransforms(child, ref childrenTransforms);
|
|
}
|
|
}
|
|
|
|
public void FindJoints()
|
|
{
|
|
List<Transform> totalTransforms = new List<Transform>() { transform };
|
|
GetAllChildrenTransforms(transform, ref totalTransforms);
|
|
|
|
for (int i = 0; i < m_HandJoints.Length; i++)
|
|
{
|
|
Transform jointTransform = totalTransforms.FirstOrDefault(x => x.name == JointsName[i]);
|
|
if (jointTransform != null)
|
|
{
|
|
m_HandJoints[i] = jointTransform;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ClearJoints()
|
|
{
|
|
Array.Clear(m_HandJoints, 0, m_HandJoints.Length);
|
|
}
|
|
|
|
private void InitHandCollider()
|
|
{
|
|
m_HandCollider = gameObject.AddComponent<HandColliderController>();
|
|
m_HandCollider.InitJointColliders(m_HandJoints[k_RootId]);
|
|
m_HandCollider.handMesh = this;
|
|
}
|
|
|
|
private void EnableHandModel(bool enable)
|
|
{
|
|
if (m_HandJoints[k_RootId].gameObject.activeSelf != enable)
|
|
{
|
|
m_HandJoints[k_RootId].gameObject.SetActive(enable);
|
|
|
|
if (skinnedMeshRenderer)
|
|
{
|
|
skinnedMeshRenderer.enabled = enable;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate whether the current rotation exceeds the maximum rotation.
|
|
/// If the product is greater than 0, it exceeds.
|
|
/// </summary>
|
|
/// <param name="currentRot">Current rotation</param>
|
|
/// <param name="maxRot">Maximum rotation</param>
|
|
/// <returns>The return value represents the dot product between the cross product of two rotations and the -x axis direction of the current rotation.</returns>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the angle between the y-axis directions of two rotations.
|
|
/// </summary>
|
|
/// <param name="currentRot">Current rotation</param>
|
|
/// <param name="maxRot">Maximum rotation</param>
|
|
/// <returns>The return value represents the angle between the up directions of the two rotation</returns>
|
|
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;
|
|
}
|
|
}
|
|
}
|