// "Wave SDK // © 2020 HTC Corporation. All Rights Reserved. // // Unless otherwise required by copyright law and practice, // upon the execution of HTC SDK license agreement, // HTC grants you access to and use of the Wave SDK(s). // You shall fully comply with all of HTC’s SDK license agreement terms and // conditions signed by you and all SDK and API requirements, // specifications, and documentation provided by HTC to You." using System; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.Events; #if UNITY_XR_HANDS using UnityEngine.XR.Hands; #endif namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction { #region Basic Hand Data /// /// This enum corresponds to HandManager.HandJoint and is used for categorizing joint types. /// public enum JointType : Int32 { Palm = 0, Wrist = 1, Thumb_Joint0 = 2, Thumb_Joint1 = 3, Thumb_Joint2 = 4, Thumb_Tip = 5, Index_Joint0 = 6, Index_Joint1 = 7, Index_Joint2 = 8, Index_Joint3 = 9, Index_Tip = 10, Middle_Joint0 = 11, Middle_Joint1 = 12, Middle_Joint2 = 13, Middle_Joint3 = 14, Middle_Tip = 15, Ring_Joint0 = 16, Ring_Joint1 = 17, Ring_Joint2 = 18, Ring_Joint3 = 19, Ring_Tip = 20, Pinky_Joint0 = 21, Pinky_Joint1 = 22, Pinky_Joint2 = 23, Pinky_Joint3 = 24, Pinky_Tip = 25, Count = Pinky_Tip + 1, } /// /// This class is designed to update hand tracking data. /// #if UNITY_XR_HANDS public static class DataWrapper { private static XRHandSubsystem handSubsystem = null; private static List s_XRHandSubsystems = new List(); /// /// Validate whether the hand tracking is active. /// /// True if the hand tracking is active; otherwise, false. public static bool Validate() { if (handSubsystem == null || !handSubsystem.running) { SubsystemManager.GetSubsystems(s_XRHandSubsystems); for (int i = 0; i < s_XRHandSubsystems.Count; i++) { if (handSubsystem != null) { handSubsystem = null; } handSubsystem = s_XRHandSubsystems[i]; } } return handSubsystem != null && handSubsystem.running; } /// /// Validate whether the hand tracking is successfully tracking. /// /// True if the hand is left; otherwise, false. /// True if the hand tracking is successfully tracking; otherwise, false. public static bool IsHandTracked(bool isLeft) { if (handSubsystem != null) { return isLeft ? handSubsystem.leftHand.isTracked : handSubsystem.rightHand.isTracked; } return false; } /// /// /// /// /// The reference to store the position of the joint. /// The reference to store the rotation of the joint. /// /// public static bool GetJointPose(JointType jointType, ref Vector3 position, ref Quaternion rotation, bool isLeft) { if (IsHandTracked(isLeft)) { XRHand hand = isLeft ? handSubsystem.leftHand : handSubsystem.rightHand; XRHandJoint xrHandJoint = hand.GetJoint(ConvertToXRHandJointID(jointType)); if (xrHandJoint.TryGetPose(out Pose pose)) { position = pose.position; rotation = pose.rotation; return true; } } return false; } private static XRHandJointID ConvertToXRHandJointID(JointType jointType) { int id = (int)jointType; switch (id) { case 0: return XRHandJointID.Palm; case 1: return XRHandJointID.Wrist; default: return (XRHandJointID)(id + 1); } } } #else public static class DataWrapper { public static bool Validate() { return false; } public static bool IsHandTracked(bool isLeft) { return false; } public static bool GetJointPose(JointType jointType, ref Vector3 position, ref Quaternion rotation, bool isLeft) { return false; } } #endif /// /// The enum is designed to define the IDs of joints. /// public enum JointId : Int32 { Invalid = -1, Joint0 = 0, Joint1 = 1, Joint2 = 2, Joint3 = 3, Tip = 4, Count = 5, } /// /// The struct is designed to record data for each joint. /// public struct JointData { public Vector3 position; public Quaternion rotation; public Vector3 velocity; public JointData(Vector3 in_pos, Quaternion in_rot, Vector3 in_vel) { position = in_pos; rotation = in_rot; velocity = in_vel; } public static JointData identity => new JointData(Vector3.zero, Quaternion.identity, Vector3.zero); public void Update(Vector3 in_pos, Quaternion in_rot, Vector3 in_vel) { position = in_pos; rotation = in_rot; velocity = in_vel; } public void Reset() { position = Vector3.zero; rotation = Quaternion.identity; velocity = Vector3.zero; } } /// /// The enum is designed to define the IDs of fingers. /// public enum FingerId : Int32 { Invalid = -1, Thumb = 0, Index = 1, Middle = 2, Ring = 3, Pinky = 4, Count = 5 } /// /// The enum is designed to define the flags of FingerId. /// [Flags] public enum FingerFlags { None = 0, Thumb = 1 << FingerId.Thumb, Index = 1 << FingerId.Index, Middle = 1 << FingerId.Middle, Ring = 1 << FingerId.Ring, Pinky = 1 << FingerId.Pinky, All = Thumb | Index | Middle | Ring | Pinky } /// /// The class is designed to record each joint data of finger. /// public class FingerData { public Vector3 direction = Vector3.zero; public JointData[] joints = null; public JointData joint0 { get { return joints[(Int32)JointId.Joint0]; } } public JointData joint1 { get { return joints[(Int32)JointId.Joint1]; } } public JointData joint2 { get { return joints[(Int32)JointId.Joint2]; } } public JointData joint3 { get { return joints[(Int32)JointId.Joint3]; } } public JointData tip { get { return joints[(Int32)JointId.Tip]; } } public FingerData() { joints = new JointData[(Int32)JointId.Count]; } } /// /// The enum is designed to define the IDs of hands. /// public enum HandId : Int32 { Right = 0, Left = 1, Both = 2 } /// /// This enum corresponds to HandId and is used for categorizing hand types. /// public enum Handedness { Right = HandId.Right, Left = HandId.Left, } /// /// The class is designed to record each finger data of hand and tracking state. /// public class HandData { // Local rotation of the fingers at different degrees of bending. private static readonly Dictionary> s_FingersBending = new Dictionary>() { { true, new Dictionary() { { FingerBendingLevel.Level0, new Quaternion[(int)JointType.Count] { Quaternion.identity, Quaternion.identity, new Quaternion(-0.11262f, 0.25416f, -0.69724f, 0.66074f), new Quaternion(-0.03846f, -0.00148f, 0.00000f, 0.99926f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(0.00743f, 0.00133f, 0.00060f, 0.99997f), new Quaternion(0.00000f, 0.04362f, 0.00000f, 0.99905f), new Quaternion(0.13198f, 0.09994f, -0.06426f, 0.98410f), new Quaternion(-0.10418f, -0.00017f, 0.00022f, 0.99456f), new Quaternion(0.00037f, -0.00007f, 0.00003f, 1.00000f), new Quaternion(-0.01159f, 0.00267f, -0.00007f, 0.99993f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(0.09351f, 0.06177f, -0.00584f, 0.99368f), new Quaternion(-0.10136f, 0.00105f, 0.00001f, 0.99485f), new Quaternion(0.00014f, 0.00000f, 0.00021f, 1.00000f), new Quaternion(-0.01364f, 0.00797f, 0.00001f, 0.99988f), new Quaternion(0.00000f, -0.08716f, 0.00000f, 0.99619f), new Quaternion(0.06461f, 0.07685f, 0.10882f, 0.98898f), new Quaternion(-0.10454f, -0.00005f, 0.00001f, 0.99452f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(-0.01527f, 0.00108f, 0.00021f, 0.99988f), new Quaternion(0.00000f, -0.13053f, 0.00000f, 0.99144f), new Quaternion(-0.00779f, -0.04296f, 0.17214f, 0.98410f), new Quaternion(0.00005f, 0.00004f, 0.00006f, 1.00000f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(-0.01093f, -0.00441f, -0.00067f, 0.99993f), } }, { FingerBendingLevel.Level1, new Quaternion[(int)JointType.Count] { Quaternion.identity, Quaternion.identity, new Quaternion(0.08584f, 0.41049f, -0.74290f, 0.52175f), new Quaternion(-0.01511f, 0.00187f, -0.00105f, 0.99988f), new Quaternion(0.00122f, 0.00035f, 0.00112f, 1.00000f), new Quaternion(0.00952f, 0.00195f, -0.00441f, 0.99994f), new Quaternion(0.00000f, 0.04362f, 0.00000f, 0.99905f), new Quaternion(-0.07049f, -0.04325f, -0.05602f, 0.99500f), new Quaternion(0.30987f, -0.00018f, 0.00052f, 0.95078f), new Quaternion(0.08450f, -0.00161f, 0.00108f, 0.99642f), new Quaternion(0.00826f, 0.00448f, -0.00361f, 0.99995f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(-0.08271f, -0.12642f, -0.01109f, 0.98846f), new Quaternion(0.35610f, -0.00018f, 0.00067f, 0.93445f), new Quaternion(0.06270f, -0.00060f, 0.00049f, 0.99803f), new Quaternion(-0.00183f, 0.00981f, -0.00822f, 0.99992f), new Quaternion(0.00000f, -0.08716f, 0.00000f, 0.99619f), new Quaternion(-0.06405f, -0.11035f, 0.09828f, 0.98695f), new Quaternion(0.23558f, 0.00047f, 0.00041f, 0.97186f), new Quaternion(0.26601f, 0.00557f, -0.00305f, 0.96395f), new Quaternion(-0.02288f, 0.00326f, -0.00459f, 0.99972f), new Quaternion(0.00000f, -0.13053f, 0.00000f, 0.99144f), new Quaternion(-0.00587f, -0.12947f, 0.15966f, 0.97863f), new Quaternion(0.21401f, -0.00210f, 0.00028f, 0.97683f), new Quaternion(0.19785f, -0.00033f, 0.00033f, 0.98023f), new Quaternion(0.00340f, 0.00377f, -0.00316f, 0.99998f), } }, { FingerBendingLevel.Level2, new Quaternion[(int)JointType.Count] { Quaternion.identity, Quaternion.identity, new Quaternion(0.13107f, 0.34075f, -0.75125f, 0.54985f), new Quaternion(0.02612f, 0.00033f, 0.00102f, 0.99966f), new Quaternion(0.13555f, 0.00348f, -0.00306f, 0.99076f), new Quaternion(-0.00298f, -0.00081f, 0.00466f, 0.99998f), new Quaternion(0.00000f, 0.04362f, 0.00000f, 0.99905f), new Quaternion(0.18517f, 0.06679f, -0.06457f, 0.97831f), new Quaternion(0.42630f, 0.00348f, -0.00361f, 0.90457f), new Quaternion(0.01349f, -0.00133f, 0.00466f, 0.99990f), new Quaternion(0.01817f, -0.00123f, 0.00384f, 0.99983f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(0.19922f, 0.06582f, -0.01357f, 0.97765f), new Quaternion(0.41682f, 0.00047f, -0.00043f, 0.90899f), new Quaternion(0.09295f, -0.00016f, 0.00089f, 0.99567f), new Quaternion(0.01949f, -0.00126f, 0.01275f, 0.99973f), new Quaternion(0.00000f, -0.08716f, 0.00000f, 0.99619f), new Quaternion(0.19167f, 0.06709f, 0.10157f, 0.97388f), new Quaternion(0.44857f, -0.00059f, 0.00108f, 0.89375f), new Quaternion(0.04810f, -0.00002f, 0.00029f, 0.99884f), new Quaternion(0.01730f, -0.00086f, 0.00529f, 0.99984f), new Quaternion(0.00000f, -0.13053f, 0.00000f, 0.99144f), new Quaternion(0.30474f, 0.01742f, 0.17554f, 0.93596f), new Quaternion(0.21203f, -0.00469f, 0.00512f, 0.97724f), new Quaternion(0.07055f, 0.00188f, -0.00330f, 0.99750f), new Quaternion(-0.01002f, -0.00507f, 0.00883f, 0.99990f), } }, { FingerBendingLevel.Level3, new Quaternion[(int)JointType.Count] { Quaternion.identity, Quaternion.identity, new Quaternion(0.19528f, 0.35744f, -0.76287f, 0.50212f), new Quaternion(-0.04099f, 0.00351f, 0.00061f, 0.99915f), new Quaternion(0.31070f, 0.00152f, -0.00356f, 0.95050f), new Quaternion(0.02296f, 0.00583f, -0.01160f, 0.99965f), new Quaternion(0.00000f, 0.04362f, 0.00000f, 0.99905f), new Quaternion(0.37185f, 0.08682f, -0.08873f, 0.91995f), new Quaternion(0.48089f, -0.00220f, 0.00500f, 0.87676f), new Quaternion(0.00267f, 0.00023f, -0.00010f, 1.00000f), new Quaternion(0.01532f, -0.00006f, -0.00193f, 0.99988f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(0.35511f, 0.03925f, -0.01449f, 0.93389f), new Quaternion(0.47741f, -0.00149f, 0.00245f, 0.87867f), new Quaternion(0.09010f, 0.00021f, 0.00067f, 0.99593f), new Quaternion(0.02853f, -0.00213f, -0.00656f, 0.99957f), new Quaternion(0.00000f, -0.08716f, 0.00000f, 0.99619f), new Quaternion(0.31547f, -0.03149f, 0.12452f, 0.94020f), new Quaternion(0.50233f, -0.00093f, 0.00163f, 0.86467f), new Quaternion(0.00506f, -0.00058f, 0.00043f, 0.99999f), new Quaternion(0.00671f, 0.00015f, -0.00312f, 0.99997f), new Quaternion(0.00000f, -0.13053f, 0.00000f, 0.99144f), new Quaternion(0.34884f, -0.07124f, 0.19724f, 0.91342f), new Quaternion(0.37271f, 0.00005f, 0.00052f, 0.92795f), new Quaternion(0.00246f, -0.00112f, 0.00072f, 1.00000f), new Quaternion(0.02218f, 0.00207f, -0.00514f, 0.99974f), } }, { FingerBendingLevel.Level4, new Quaternion[(int)JointType.Count] { Quaternion.identity, Quaternion.identity, new Quaternion(0.10211f, 0.44288f, -0.74597f, 0.48678f), new Quaternion(0.19065f, 0.00005f, 0.00039f, 0.98166f), new Quaternion(0.62796f, -0.00156f, -0.00539f, 0.77822f), new Quaternion(-0.01918f, 0.01012f, 0.00302f, 0.99976f), new Quaternion(0.00000f, 0.04362f, 0.00000f, 0.99905f), new Quaternion(0.52214f, -0.02285f, -0.04694f, 0.85126f), new Quaternion(0.67094f, 0.00003f, 0.00042f, 0.74152f), new Quaternion(0.29652f, 0.00077f, 0.00024f, 0.95503f), new Quaternion(0.00114f, -0.00613f, 0.00019f, 0.99998f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(0.51402f, -0.06984f, 0.04232f, 0.85388f), new Quaternion(0.67124f, -0.00060f, -0.00234f, 0.74124f), new Quaternion(0.40244f, 0.00146f, 0.00035f, 0.91544f), new Quaternion(-0.00075f, 0.00310f, -0.00062f, 0.99999f), new Quaternion(0.00000f, -0.08716f, 0.00000f, 0.99619f), new Quaternion(0.42444f, -0.01220f, 0.12585f, 0.89659f), new Quaternion(0.70429f, 0.00002f, 0.00069f, 0.70991f), new Quaternion(0.44565f, 0.00261f, 0.00074f, 0.89520f), new Quaternion(0.00448f, 0.00155f, -0.00006f, 0.99999f), new Quaternion(0.00000f, -0.13053f, 0.00000f, 0.99144f), new Quaternion(0.33697f, 0.02717f, 0.17449f, 0.92481f), new Quaternion(0.70669f, 0.00004f, -0.00011f, 0.70752f), new Quaternion(0.22958f, -0.00104f, -0.00111f, 0.97329f), new Quaternion(-0.02959f, 0.00321f, 0.00209f, 0.99955f), } }, { FingerBendingLevel.Level5, new Quaternion[(int)JointType.Count] { Quaternion.identity, Quaternion.identity, new Quaternion(0.06759f, 0.29327f, -0.73738f, 0.60473f), new Quaternion(-0.00881f, 0.00852f, -0.00361f, 0.99992f), new Quaternion(0.60423f, -0.00304f, 0.00496f, 0.79679f), new Quaternion(0.03855f, 0.00866f, 0.00562f, 0.99920f), new Quaternion(0.00000f, 0.04362f, 0.00000f, 0.99905f), new Quaternion(0.64690f, -0.02744f, -0.04492f, 0.76076f), new Quaternion(0.70685f, 0.00011f, 0.00020f, 0.70736f), new Quaternion(-0.34253f, 0.00102f, -0.00017f, -0.93950f), new Quaternion(-0.00715f, -0.00086f, 0.00053f, 0.99997f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(0.69063f, -0.05554f, 0.05341f, 0.71909f), new Quaternion(0.70712f, -0.00009f, -0.00005f, 0.70710f), new Quaternion(-0.41333f, 0.00119f, -0.00041f, -0.91058f), new Quaternion(-0.00444f, -0.00008f, 0.00022f, 0.99999f), new Quaternion(0.00000f, -0.08716f, 0.00000f, 0.99619f), new Quaternion(0.64441f, 0.03540f, 0.11369f, 0.75535f), new Quaternion(0.70712f, 0.00000f, -0.00001f, 0.70710f), new Quaternion(-0.44200f, 0.00351f, -0.00085f, -0.89701f), new Quaternion(0.00568f, -0.00249f, 0.00231f, 0.99998f), new Quaternion(0.00000f, -0.13053f, 0.00000f, 0.99144f), new Quaternion(0.57291f, 0.02400f, 0.18783f, 0.79744f), new Quaternion(0.70707f, -0.00001f, 0.00015f, 0.70714f), new Quaternion(0.44752f, -0.00011f, -0.00001f, 0.89427f), new Quaternion(0.01201f, -0.00357f, 0.00210f, 0.99992f), } }, } }, { false, new Dictionary() { { FingerBendingLevel.Level0, new Quaternion[(int)JointType.Count] { Quaternion.identity, Quaternion.identity, new Quaternion(0.11262f, 0.25416f, -0.69724f, -0.66074f), new Quaternion(-0.03846f, 0.00148f, 0.00000f, 0.99926f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(0.00743f, -0.00133f, -0.00060f, 0.99997f), new Quaternion(0.00000f, -0.04362f, 0.00000f, 0.99905f), new Quaternion(-0.13198f, 0.09994f, -0.06426f, -0.98410f), new Quaternion(-0.10418f, 0.00017f, -0.00022f, 0.99456f), new Quaternion(-0.00037f, -0.00007f, 0.00003f, -1.00000f), new Quaternion(-0.01159f, -0.00267f, 0.00007f, 0.99993f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(0.09351f, -0.06177f, 0.00584f, 0.99368f), new Quaternion(-0.10136f, -0.00105f, -0.00001f, 0.99485f), new Quaternion(0.00014f, 0.00000f, -0.00021f, 1.00000f), new Quaternion(-0.01364f, -0.00797f, -0.00001f, 0.99988f), new Quaternion(0.00000f, -0.08716f, 0.00000f, -0.99619f), new Quaternion(0.06461f, -0.07685f, -0.10882f, 0.98898f), new Quaternion(-0.10454f, 0.00005f, -0.00001f, 0.99452f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(0.01527f, 0.00108f, 0.00021f, -0.99988f), new Quaternion(0.00000f, -0.13053f, 0.00000f, -0.99144f), new Quaternion(-0.00779f, 0.04296f, -0.17214f, 0.98410f), new Quaternion(0.00005f, -0.00004f, -0.00006f, 1.00000f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(0.01093f, -0.00441f, -0.00067f, -0.99993f), } }, { FingerBendingLevel.Level1, new Quaternion[(int)JointType.Count] { Quaternion.identity, Quaternion.identity, new Quaternion(-0.08584f, 0.41049f, -0.74290f, -0.52175f), new Quaternion(-0.01511f, -0.00187f, 0.00105f, 0.99988f), new Quaternion(0.00122f, -0.00035f, -0.00112f, 1.00000f), new Quaternion(-0.00952f, 0.00195f, -0.00441f, -0.99994f), new Quaternion(0.00000f, -0.04362f, 0.00000f, 0.99905f), new Quaternion(0.07049f, -0.04325f, -0.05602f, -0.99500f), new Quaternion(0.30987f, 0.00018f, -0.00052f, 0.95078f), new Quaternion(-0.08450f, -0.00161f, 0.00108f, -0.99642f), new Quaternion(-0.00826f, 0.00448f, -0.00361f, -0.99995f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(0.08271f, -0.12642f, -0.01109f, -0.98846f), new Quaternion(0.35610f, 0.00018f, -0.00067f, 0.93445f), new Quaternion(-0.06270f, -0.00060f, 0.00049f, -0.99803f), new Quaternion(-0.00183f, -0.00981f, 0.00822f, 0.99992f), new Quaternion(0.00000f, -0.08716f, 0.00000f, -0.99619f), new Quaternion(-0.06405f, 0.11035f, -0.09828f, 0.98695f), new Quaternion(0.23558f, -0.00047f, -0.00041f, 0.97186f), new Quaternion(-0.26601f, 0.00557f, -0.00305f, -0.96395f), new Quaternion(-0.02288f, -0.00326f, 0.00459f, 0.99972f), new Quaternion(0.00000f, -0.13053f, 0.00000f, -0.99144f), new Quaternion(0.00587f, -0.12947f, 0.15966f, -0.97863f), new Quaternion(0.21401f, 0.00210f, -0.00028f, 0.97683f), new Quaternion(-0.19785f, -0.00033f, 0.00033f, -0.98023f), new Quaternion(-0.00340f, 0.00377f, -0.00316f, -0.99998f), } }, { FingerBendingLevel.Level2, new Quaternion[(int)JointType.Count] { Quaternion.identity, Quaternion.identity, new Quaternion(-0.13107f, 0.34075f, -0.75125f, -0.54985f), new Quaternion(0.02612f, -0.00033f, -0.00102f, 0.99966f), new Quaternion(-0.13555f, 0.00348f, -0.00306f, -0.99076f), new Quaternion(-0.00298f, 0.00081f, -0.00466f, 0.99998f), new Quaternion(0.00000f, -0.04362f, 0.00000f, 0.99905f), new Quaternion(-0.18517f, 0.06679f, -0.06457f, -0.97831f), new Quaternion(-0.42630f, 0.00348f, -0.00361f, -0.90457f), new Quaternion(-0.01349f, -0.00133f, 0.00466f, -0.99990f), new Quaternion(-0.01817f, -0.00123f, 0.00384f, -0.99983f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(-0.19922f, 0.06582f, -0.01357f, -0.97765f), new Quaternion(-0.41682f, 0.00047f, -0.00043f, -0.90899f), new Quaternion(-0.09295f, -0.00016f, 0.00089f, -0.99567f), new Quaternion(-0.01949f, -0.00126f, 0.01275f, -0.99973f), new Quaternion(0.00000f, -0.08716f, 0.00000f, -0.99619f), new Quaternion(0.19167f, -0.06709f, -0.10157f, 0.97388f), new Quaternion(-0.44857f, -0.00059f, 0.00108f, -0.89375f), new Quaternion(0.04810f, 0.00002f, -0.00029f, 0.99884f), new Quaternion(-0.01730f, -0.00086f, 0.00529f, -0.99984f), new Quaternion(0.00000f, -0.13053f, 0.00000f, -0.99144f), new Quaternion(0.30474f, -0.01742f, -0.17554f, 0.93596f), new Quaternion(-0.21203f, -0.00469f, 0.00512f, -0.97724f), new Quaternion(-0.07055f, 0.00188f, -0.00330f, -0.99750f), new Quaternion(-0.01002f, 0.00507f, -0.00883f, 0.99990f), } }, { FingerBendingLevel.Level3, new Quaternion[(int)JointType.Count] { Quaternion.identity, Quaternion.identity, new Quaternion(-0.19528f, 0.35744f, -0.76287f, -0.50212f), new Quaternion(0.04099f, 0.00351f, 0.00061f, -0.99915f), new Quaternion(-0.31070f, 0.00152f, -0.00356f, -0.95050f), new Quaternion(-0.02296f, 0.00583f, -0.01160f, -0.99965f), new Quaternion(0.00000f, -0.04362f, 0.00000f, 0.99905f), new Quaternion(-0.37185f, 0.08682f, -0.08873f, -0.91995f), new Quaternion(0.48089f, 0.00220f, -0.00500f, 0.87676f), new Quaternion(-0.00267f, 0.00023f, -0.00010f, -1.00000f), new Quaternion(0.01532f, 0.00006f, 0.00193f, 0.99988f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(0.35511f, -0.03925f, 0.01449f, 0.93389f), new Quaternion(-0.47741f, -0.00149f, 0.00245f, -0.87867f), new Quaternion(0.09010f, -0.00021f, -0.00067f, 0.99593f), new Quaternion(0.02853f, 0.00213f, 0.00656f, 0.99957f), new Quaternion(0.00000f, -0.08716f, 0.00000f, -0.99619f), new Quaternion(0.31547f, 0.03149f, -0.12452f, 0.94020f), new Quaternion(0.50233f, 0.00093f, -0.00163f, 0.86467f), new Quaternion(-0.00506f, -0.00058f, 0.00043f, -0.99999f), new Quaternion(-0.00671f, 0.00015f, -0.00312f, -0.99997f), new Quaternion(0.00000f, -0.13053f, 0.00000f, -0.99144f), new Quaternion(0.34884f, 0.07124f, -0.19724f, 0.91342f), new Quaternion(0.37271f, -0.00005f, -0.00052f, 0.92795f), new Quaternion(-0.00246f, -0.00112f, 0.00072f, -1.00000f), new Quaternion(-0.02218f, 0.00207f, -0.00514f, -0.99974f), } }, { FingerBendingLevel.Level4, new Quaternion[(int)JointType.Count] { Quaternion.identity, Quaternion.identity, new Quaternion(-0.10211f, 0.44288f, -0.74597f, -0.48678f), new Quaternion(0.19065f, -0.00005f, -0.00039f, 0.98166f), new Quaternion(0.62796f, 0.00156f, 0.00539f, 0.77822f), new Quaternion(0.01918f, 0.01012f, 0.00302f, -0.99976f), new Quaternion(0.00000f, -0.04362f, 0.00000f, 0.99905f), new Quaternion(0.52214f, 0.02285f, 0.04694f, 0.85126f), new Quaternion(0.67094f, -0.00003f, -0.00042f, 0.74152f), new Quaternion(0.29652f, -0.00077f, -0.00024f, 0.95503f), new Quaternion(-0.00114f, -0.00613f, 0.00019f, -0.99998f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(-0.51402f, -0.06984f, 0.04232f, -0.85388f), new Quaternion(0.67124f, 0.00060f, 0.00234f, 0.74124f), new Quaternion(0.40244f, -0.00146f, -0.00035f, 0.91544f), new Quaternion(-0.00075f, -0.00310f, 0.00062f, 0.99999f), new Quaternion(0.00000f, -0.08716f, 0.00000f, -0.99619f), new Quaternion(0.42444f, 0.01220f, -0.12585f, 0.89659f), new Quaternion(0.70429f, -0.00002f, -0.00069f, 0.70991f), new Quaternion(0.44565f, -0.00261f, -0.00074f, 0.89520f), new Quaternion(-0.00448f, 0.00155f, -0.00007f, -0.99999f), new Quaternion(0.00000f, -0.13053f, 0.00000f, -0.99144f), new Quaternion(0.33697f, -0.02717f, -0.17449f, 0.92481f), new Quaternion(0.70670f, -0.00004f, 0.00004f, 0.70751f), new Quaternion(0.22958f, 0.00104f, 0.00111f, 0.97329f), new Quaternion(0.02959f, 0.00321f, 0.00209f, -0.99955f), } }, { FingerBendingLevel.Level5, new Quaternion[(int)JointType.Count] { Quaternion.identity, Quaternion.identity, new Quaternion(-0.06759f, 0.29327f, -0.73738f, -0.60473f), new Quaternion(-0.00881f, -0.00852f, 0.00361f, 0.99992f), new Quaternion(0.60423f, 0.00304f, -0.00496f, 0.79679f), new Quaternion(0.03855f, -0.00866f, -0.00562f, 0.99920f), new Quaternion(0.00000f, -0.04362f, 0.00000f, 0.99905f), new Quaternion(0.64690f, 0.02744f, 0.04492f, 0.76076f), new Quaternion(0.70686f, -0.00011f, 0.00011f, 0.70735f), new Quaternion(0.34253f, 0.00102f, -0.00016f, 0.93950f), new Quaternion(-0.00715f, 0.00086f, -0.00053f, 0.99997f), new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f), new Quaternion(-0.69063f, -0.05554f, 0.05341f, -0.71909f), new Quaternion(-0.70711f, -0.00009f, 0.00009f, -0.70711f), new Quaternion(0.41333f, 0.00119f, -0.00041f, 0.91058f), new Quaternion(-0.00444f, 0.00008f, -0.00022f, 0.99999f), new Quaternion(0.00000f, -0.08716f, 0.00000f, -0.99619f), new Quaternion(0.64441f, -0.03540f, -0.11369f, 0.75535f), new Quaternion(0.70711f, 0.00000f, 0.00000f, 0.70711f), new Quaternion(0.44200f, 0.00351f, -0.00085f, 0.89701f), new Quaternion(-0.00568f, -0.00249f, 0.00231f, -0.99998f), new Quaternion(0.00000f, -0.13053f, 0.00000f, -0.99144f), new Quaternion(0.57291f, -0.02400f, -0.18783f, 0.79744f), new Quaternion(0.70711f, 0.00001f, -0.00001f, 0.70711f), new Quaternion(0.44752f, 0.00011f, 0.00001f, 0.89427f), new Quaternion(-0.01201f, -0.00357f, 0.00210f, -0.99992f), } }, } }, }; public bool valid = false; public bool isTracked = false; public Vector3 normal = Vector3.zero; public Vector3 direction = Vector3.zero; public JointData palm; public JointData wrist; public FingerData[] fingers = null; // size: FingerId.Count public FingerData thumb { get { return fingers[(Int32)FingerId.Thumb]; } } public FingerData index { get { return fingers[(Int32)FingerId.Index]; } } public FingerData middle { get { return fingers[(Int32)FingerId.Middle]; } } public FingerData ring { get { return fingers[(Int32)FingerId.Ring]; } } public FingerData pinky { get { return fingers[(Int32)FingerId.Pinky]; } } public HandData() { fingers = new FingerData[(Int32)FingerId.Count]; for (int i = 0; i < fingers.Length; i++) { fingers[i] = new FingerData(); } } /// /// Get the position of a specific joint. /// /// The type of joint to get. /// The reference to store the position of the joint. /// True if the joint position is successfully retrieved; otherwise, false. public bool GetJointPosition(JointType joint, ref Vector3 position) { GetJointIndex(joint, out int handId, out int jointId); switch (handId) { case 0: position = palm.position; break; case 1: position = wrist.position; break; case 2: position = thumb.joints[jointId].position; break; case 3: position = index.joints[jointId].position; break; case 4: position = middle.joints[jointId].position; break; case 5: position = ring.joints[jointId].position; break; case 6: position = pinky.joints[jointId].position; break; default: return false; } return true; } /// /// Get the rotation of a specific joint. /// /// The type of joint to get. /// The reference to store the rotation of the joint. /// True if the joint rotation is successfully retrieved; otherwise, false. public bool GetJointRotation(JointType joint, ref Quaternion rotation) { GetJointIndex(joint, out int handId, out int jointId); switch (handId) { case 0: rotation = palm.rotation; break; case 1: rotation = wrist.rotation; break; case 2: rotation = thumb.joints[jointId].rotation; break; case 3: rotation = index.joints[jointId].rotation; break; case 4: rotation = middle.joints[jointId].rotation; break; case 5: rotation = ring.joints[jointId].rotation; break; case 6: rotation = pinky.joints[jointId].rotation; break; default: return false; } return true; } /// /// Get the default joint rotation for a specific HandGrabGesture and joint type. /// /// True if the hand is left; otherwise, false. /// The HandGrabGesture is a gesture where a hand grabs. /// The type of joint. /// The reference to store the default joint rotation. /// True if the default joint rotation is successfully retrieved; otherwise, false. public static bool GetDefaultJointRotationInGesture(bool isLeft, HandGrabGesture handGrabGesture, JointType joint, ref Quaternion rotation) { FingerBendingLevel bendingLevel = FingerBendingLevel.Level0; GetJointIndex(joint, out int group, out int index); switch (group) { case 2: bendingLevel = handGrabGesture.thumbPose; break; case 3: bendingLevel = handGrabGesture.indexPose; break; case 4: bendingLevel = handGrabGesture.middlePose; break; case 5: bendingLevel = handGrabGesture.ringPose; break; case 6: bendingLevel = handGrabGesture.pinkyPose; break; default: return false; } rotation = s_FingersBending[isLeft][bendingLevel][(int)joint]; return true; } /// /// Determine the group and index of a given joint type. /// /// The type of joint. /// The group to which the joint belongs. /// The index of the joint within its group. public static void GetJointIndex(JointType joint, out int group, out int index) { int jointId = (int)joint + 1; group = 0; index = jointId; // palm, wrist, thumb, index, middle, ring, pinky int[] fingerGroup = { 1, 1, 4, 5, 5, 5, 5 }; while (index > fingerGroup[group]) { index -= fingerGroup[group]; group += 1; } index -= 1; } } /// /// The class is designed to temporarily store all hand data for this frame to avoid redundant updates. /// public static class CachedHand { public static readonly JointType[] s_ThumbJoints = new JointType[4] { JointType.Thumb_Joint0, JointType.Thumb_Joint1, JointType.Thumb_Joint2, JointType.Thumb_Tip }; public static readonly JointType[] s_IndexJoints = new JointType[5] { JointType.Index_Joint0, JointType.Index_Joint1, JointType.Index_Joint2, JointType.Index_Joint3, JointType.Index_Tip }; public static readonly JointType[] s_MiddleJoints = new JointType[5] { JointType.Middle_Joint0, JointType.Middle_Joint1, JointType.Middle_Joint2, JointType.Middle_Joint3, JointType.Middle_Tip }; public static readonly JointType[] s_RingJoints = new JointType[5] { JointType.Ring_Joint0, JointType.Ring_Joint1, JointType.Ring_Joint2, JointType.Ring_Joint3, JointType.Ring_Tip }; public static readonly JointType[] s_PinkyJoints = new JointType[5] { JointType.Pinky_Joint0, JointType.Pinky_Joint1, JointType.Pinky_Joint2, JointType.Pinky_Joint3, JointType.Pinky_Tip }; private static int updateFrameCountLeft = 0, updateFrameCountRight = 0, handCount = 2; private static HandData[] hands = new HandData[handCount]; /// /// Determine whether to allow updating hand data based on the handedness of the hand and frame count. /// /// True if the hand is left; otherwise, false. /// True if updating hand data is allowed; otherwise, false. private static bool AllowUpdateData(bool isLeft) { if (isLeft && (updateFrameCountLeft != Time.frameCount)) { updateFrameCountLeft = Time.frameCount; return true; } if (!isLeft && (updateFrameCountRight != Time.frameCount)) { updateFrameCountRight = Time.frameCount; return true; } return false; } /// /// Get data for a specific joint including position, rotation, and velocity. /// /// The type of joint. /// The reference to store the joint data. /// True if the hand is left; otherwise, false. private static void GetJointData(JointType id, ref JointData data, bool isLeft) { Vector3 lastPosition = data.position; DataWrapper.GetJointPose(id, ref data.position, ref data.rotation, isLeft); Vector3 poositionOffset = data.position - lastPosition; data.velocity = poositionOffset / Time.deltaTime; } /// /// Get data for a specific finger. /// /// The type of finger. /// The reference to store the finger data. /// True if the hand is left; otherwise, false. private static void GetFingerData(FingerId id, ref FingerData finger, bool isLeft) { JointType[] jointTypes = { }; switch (id) { case FingerId.Thumb: jointTypes = s_ThumbJoints; break; case FingerId.Index: jointTypes = s_IndexJoints; break; case FingerId.Middle: jointTypes = s_MiddleJoints; break; case FingerId.Ring: jointTypes = s_RingJoints; break; case FingerId.Pinky: jointTypes = s_PinkyJoints; break; default: return; } for (int i = 0; i < jointTypes.Length; i++) { Vector3 parentVel = i == 0 ? Vector3.zero : finger.joints[i - 1].velocity; JointData lastJoint = finger.joints[i]; GetJointData(jointTypes[i], ref finger.joints[i], isLeft); //As the velocity of child node should not be lower than the parent node. //Add the current parent node's velocity multiplied by time to the last position of child node, obtaining the new simulated position. if (parentVel.magnitude > finger.joints[i].velocity.magnitude) { lastJoint.position += parentVel * Time.deltaTime; finger.joints[i] = lastJoint; } } // Since the thumb does not have joint3, it is replaced by joint2. if (id == FingerId.Thumb) { finger.joints[(int)JointId.Tip] = finger.joint3; finger.direction = finger.tip.position - finger.joint2.position; } else { finger.direction = finger.tip.position - finger.joint3.position; } } /// /// Update the data for the left or right hand. /// /// True if the hand is left; otherwise, false. private static void UpdateData(bool isLeft) { if (!AllowUpdateData(isLeft)) { return; } HandData hand = hands[isLeft ? (Int32)HandId.Left : (Int32)HandId.Right]; if (hand == null) { hand = new HandData(); hands[isLeft ? (Int32)HandId.Left : (Int32)HandId.Right] = hand; } hand.valid = DataWrapper.Validate(); if (!hand.valid) { hand.isTracked = false; return; } hand.isTracked = DataWrapper.IsHandTracked(isLeft); // Palm GetJointData(JointType.Palm, ref hand.palm, isLeft); hand.direction = hand.palm.rotation * Vector3.up; hand.normal = hand.palm.rotation * Vector3.forward; // Wrist GetJointData(JointType.Wrist, ref hand.wrist, isLeft); // Thumb GetFingerData(FingerId.Thumb, ref hand.fingers[(Int32)FingerId.Thumb], isLeft); // Index GetFingerData(FingerId.Index, ref hand.fingers[(Int32)FingerId.Index], isLeft); // Middle GetFingerData(FingerId.Middle, ref hand.fingers[(Int32)FingerId.Middle], isLeft); // Ring GetFingerData(FingerId.Ring, ref hand.fingers[(Int32)FingerId.Ring], isLeft); // Pinky GetFingerData(FingerId.Pinky, ref hand.fingers[(Int32)FingerId.Pinky], isLeft); } /// /// Get the complete data for the left or right hand. /// /// True if the hand is left; otherwise, false. /// The hand data for the specified hand. public static HandData Get(bool isLeft) { UpdateData(isLeft); return hands[isLeft ? (Int32)HandId.Left : (Int32)HandId.Right]; } } #endregion /// /// The abstract class is designed to defines the necessary members or functions for grabbing. /// public abstract class GrabState { public abstract void UpdateState(); } /// /// The class is designed to record the grab state of a hand. /// public class HandGrabState : GrabState { /// /// The class is designed to record the pinch state of each finger. /// private class FingerPinchState { private struct FingerPinchThreshold { public float pinchOn; // pinch on when distance < threshold. public float pinchOff; // pinch off when distance > threshold. public float pinchOffset; // pinch off if distance > mini distance + offset threshold. public FingerPinchThreshold(float in_PinchOn, float in_PinchOff, float in_PinchOffset) { pinchOn = in_PinchOn; pinchOff = in_PinchOff; pinchOffset = in_PinchOffset; } public FingerPinchThreshold Identity => new FingerPinchThreshold(0f, 0f, 0f); public void Update(float in_PinchOn, float in_PinchOff, float in_PinchOffset) { pinchOn = in_PinchOn; pinchOff = in_PinchOff; pinchOffset = in_PinchOffset; } public void Reset() { this = Identity; } } const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandGrabState.FingerPinchState "; StringBuilder m_sb = null; StringBuilder sb { get { if (m_sb == null) { m_sb = new StringBuilder(); } return m_sb; } } void DEBUG(StringBuilder msg) { Debug.Log(msg); } private bool isLeft = false; private FingerData thumbData; private FingerData fingerData; #region Public States private FingerId m_finger = FingerId.Invalid; // should not be Thumb public FingerId finger { get { return m_finger; } } private bool m_isPinching = false; public bool isPinching { get { return m_isPinching; } } [Range(0, 1)] private float m_pinchStrength = 0; public float pinchStrength { get { return m_pinchStrength; } } private bool m_isChanged = false; public bool isChanged { get { return m_isChanged; } } #endregion private readonly Dictionary fingersPinchThreshold = new Dictionary() { { FingerId.Index, new FingerPinchThreshold(0.02f, 0.08f, 0.045f) }, { FingerId.Middle, new FingerPinchThreshold(0.02f, 0.10f, 0.055f) }, { FingerId.Ring, new FingerPinchThreshold(0.02f, 0.10f, 0.055f) }, { FingerId.Pinky, new FingerPinchThreshold(0.035f, 0.10f, 0.065f) }, }; private const float kPinchStrengthOnThreshold = 0.7f; private const float kPinchStrengthOffThreshold = 0.3f; private float minDistance = float.PositiveInfinity; public FingerPinchState(bool in_isLeft, FingerId in_finger) { isLeft = in_isLeft; m_finger = in_finger; } /// /// Checks if the finger is valid. /// /// True if the finger ID is valid. private bool Validate() { return (m_finger != FingerId.Invalid && m_finger != FingerId.Count && m_finger != FingerId.Thumb); } /// /// Calculates the shortest distance from the finger, including tip, joint3, and joint2, to the thumb, including tip, joint2, and joint1. /// /// The value for the shortest distance from the finger to the thumb. private float GetFingerToThumbDistance() { if (!Validate()) { return float.PositiveInfinity; } Vector3 thumbTip = thumbData.tip.position; Vector3 thumbJoint2 = thumbData.joint2.position; Vector3 thumbJoint1 = thumbData.joint1.position; Vector3[] fingerPos = { fingerData.tip.position, fingerData.joint3.position, fingerData.joint2.position}; float distance = float.PositiveInfinity; foreach (var fingerJointPos in fingerPos) { distance = Mathf.Min(distance, CalculateShortestDistance(fingerJointPos, thumbTip, thumbJoint2)); distance = Mathf.Min(distance, CalculateShortestDistance(fingerJointPos, thumbJoint2, thumbJoint1)); } return distance; } /// /// Calculates the shortest distance from a point to a line segment defined by two points. /// /// The point from which the distance is measured. /// The start point of the line segment. /// The end point of the line segment. /// The shortest distance from the point to the line segment. private float CalculateShortestDistance(Vector3 point, Vector3 start, Vector3 end) { Vector3 v1 = end - start; Vector3 v2 = point - start; float dot = Vector3.Dot(v1, v2); if (dot <= 0) { return Vector3.Distance(point, start); } float lengthSquared = v1.sqrMagnitude; if (dot >= lengthSquared) { return Vector3.Distance(point, end); } float distance = Vector3.Cross(v1, v2).magnitude / Mathf.Sqrt(lengthSquared); return distance; } /// /// Update the pinch states based on the calculated distance. /// /// The distance from the finger to the thumb. /// True if the states are updated; otherwise, false. private bool UpdateStates(float distance) { if (!Validate()) { return false; } // We use Clamp01 for // value > 1 if distance > kPinchOffThreshold // value < 0 if distance < kPinchOnThreshold FingerPinchThreshold threshold = fingersPinchThreshold[m_finger]; m_pinchStrength = 1f - Mathf.Clamp01((distance - threshold.pinchOn) / (threshold.pinchOff - threshold.pinchOn)); bool updated = false; if (!m_isPinching) { if (m_pinchStrength >= kPinchStrengthOnThreshold) { m_isPinching = true; minDistance = distance; sb.Clear().Append(LOG_TAG).Append(isLeft ? "Left " : "Right ").Append(m_finger.Name()) .Append(" UpdateState() pinch strength: ").Append(m_pinchStrength) .Append(", pinch on threshold: ").Append(kPinchStrengthOnThreshold) .Append(", is pinching: ").Append(m_isPinching) .Append(", pinch distance: ").Append(minDistance); DEBUG(sb); updated = true; } } else { minDistance = Mathf.Min(minDistance, distance); if (m_pinchStrength < kPinchStrengthOffThreshold || distance > minDistance + threshold.pinchOffset) { m_isPinching = false; sb.Clear().Append(LOG_TAG).Append(isLeft ? "Left " : "Right ").Append(m_finger.Name()) .Append(" UpdateState() pinch strength: ").Append(m_pinchStrength) .Append(", pinch off threshold: ").Append(kPinchStrengthOffThreshold) .Append(", is pinching: ").Append(m_isPinching) .Append(", pinch distance: ").Append(minDistance); DEBUG(sb); updated = true; } } return updated; } /// /// Update the FingerPinchState with the data of the thumb and finger. /// /// The FingerData of the thumb. /// The FingerData of the finger. public void Update(FingerData thumb, FingerData finger) { if (!Validate()) { return; } this.thumbData = thumb; this.fingerData = finger; float distance = GetFingerToThumbDistance(); m_isChanged = UpdateStates(distance); } /// /// Manually updates the FingerPinchState with specified pinch parameters. /// /// True if the finger is pinching; otherwise, false. /// The strength of the pinch. /// True if the state has changed; otherwise, false. public void ManualUpdate(bool isPinch, float pinchStrength, bool stateChange) { m_isPinching = isPinch; m_pinchStrength = pinchStrength; m_isChanged = stateChange; } public bool IsUsed(FingerFlags flags) { return ((Int32)flags & (1 << (Int32)m_finger)) != 0; } } private static readonly FingerId[] s_FingerIds = { FingerId.Thumb, FingerId.Index, FingerId.Middle, FingerId.Ring, FingerId.Pinky, }; private bool m_IsLeft = false; public bool isLeft { get { return m_IsLeft; } } private HandData hand { get { return CachedHand.Get(m_IsLeft); } } private FingerPinchState[] s_PinchStates = null; private FingerFlags pinchFingers = FingerFlags.None; private int updateFrame = -1; public HandGrabState(bool isLeft) { m_IsLeft = isLeft; s_PinchStates = new FingerPinchState[] { new FingerPinchState(m_IsLeft, FingerId.Thumb), new FingerPinchState(m_IsLeft, FingerId.Index), new FingerPinchState(m_IsLeft, FingerId.Middle), new FingerPinchState(m_IsLeft, FingerId.Ring), new FingerPinchState(m_IsLeft, FingerId.Pinky), }; } /// /// Update all FingerPinchStates of a hand. /// public override void UpdateState() { pinchFingers = FingerFlags.None; if (updateFrame == Time.frameCount || s_PinchStates == null || !hand.isTracked) { return; } updateFrame = Time.frameCount; float bestPinchStrength = float.MinValue; int bestFingerId = -1; for (int i = s_PinchStates.Length - 1; i >= 0; i--) { if (s_PinchStates[i].finger != FingerId.Thumb) { s_PinchStates[i].Update(hand.fingers[0], hand.fingers[i]); if (s_PinchStates[i].pinchStrength > bestPinchStrength) { bestPinchStrength = s_PinchStates[i].pinchStrength; bestFingerId = i; } } else if (bestFingerId != -1) { bool lastPinching = s_PinchStates[i].isPinching; s_PinchStates[i].ManualUpdate(s_PinchStates[bestFingerId].isPinching, s_PinchStates[bestFingerId].pinchStrength, lastPinching != s_PinchStates[bestFingerId].isPinching); } if (s_PinchStates[i].isPinching) pinchFingers |= s_PinchStates[i].finger.Flag(); } } /// /// Checks if the state of the FingerPinchState for a specific finger has changed as expected. /// /// The FingerId of the finger. /// The expected state of the finger pinch. /// True if the state has changed as expected; otherwise, false. private bool FingerPinchStateIsChanged(FingerId finger, bool expectState) { if (s_PinchStates == null) { return false; } for (int i = 0; i < s_PinchStates.Length; i++) { if (s_PinchStates[i].finger == finger && s_PinchStates[i].isPinching == expectState && s_PinchStates[i].isChanged) { return true; } } return false; } /// /// Checks if the state of the FingerPinchState for each finger has changed as expected. /// /// The finger requirements for grabbing. /// The expected state of the hand grab. /// True if the requirement is met as expected; otherwise, false. public bool HandGrabStateIsChanged(FingerRequirement fingerRequirement, bool expectState) { Int32 pinchedValue = (Int32)pinchFingers; Int32 requiredValue = (Int32)fingerRequirement.Required(); Int32 optionalValue = (Int32)fingerRequirement.Optional(); // All required fingers have the expected state. // Checks the state change of each required finger. for (int i = 0; i < s_FingerIds.Length; i++) { if (fingerRequirement[s_FingerIds[i]] == GrabRequirement.Required && FingerPinchStateIsChanged(s_FingerIds[i], expectState)) { return true; } } // All required fingers have the expected state but no required finger state is changed. // Checks the state change of each optional finger if no required finger. if (requiredValue == 0) { for (int i = 0; i < s_FingerIds.Length; i++) { if (fingerRequirement[s_FingerIds[i]] == GrabRequirement.Optional && FingerPinchStateIsChanged(s_FingerIds[i], expectState)) { if (expectState && (pinchedValue & optionalValue) != 0) { // State changed to grabbed if any optional finger is pinching. return true; } if (!expectState && (pinchedValue & optionalValue) == 0) { // State changed to released if all optional fingers are unpinched. return true; } } } } return false; } /// /// Determine whether the hand is keeping grabbing based on finger requirements. /// /// The finger requirements for grabbing. /// True if the hand is keeping grabbing; otherwise, false. public bool HandKeepGrabbing(FingerRequirement fingerRequirement) { Int32 pinchedValue = (Int32)pinchFingers; Int32 requiredValue = (Int32)fingerRequirement.Required(); Int32 optionalValue = (Int32)fingerRequirement.Optional(); // Keep grabbing if all required fingers are pinching. if (requiredValue != 0 && (pinchedValue & requiredValue) >= requiredValue) { return true; } // No required fingers and at least one optional finger is pinching. if (requiredValue == 0 && (pinchedValue & optionalValue) != 0) { return true; } return false; } /// /// Calculates the score of the current hand grab state based on FingerRequirement. /// /// The finger requirements for grabbing. /// The score of the current hand grab state based on FingerRequirement. public float HandGrabNearStrength(FingerRequirement fingerRequirement) { /// Since the hand is not grabbed, the grab state depends on "all" required fingers. /// The hand will become grabbed from the frame of the "last" required finger become pinched. /// E.g. Index, Middle and Ring are required. /// If Index and Middle are already pinched, the hand will become grabbed when Ring become pinched. /// So the requiredStrength depends on the "last" required finger which has the minimum pinch strength. float requiredStrength = float.MaxValue; /// Since the hand is not grabbed, the grab state depends on "one of" the optional fingers. /// The hand will become grabbed from the frame of the "first" optional finger become pinched. /// So the optionalStrength depends on the "first" optional finger which has the maximum pinch strength. float optionalStrength = float.MinValue; for (int i = 0; i < s_FingerIds.Length; i++) { if (fingerRequirement[s_FingerIds[i]] == GrabRequirement.Required) { requiredStrength = Mathf.Min(requiredStrength, s_PinchStates[i].pinchStrength); } if (fingerRequirement[s_FingerIds[i]] == GrabRequirement.Optional) { optionalStrength = Mathf.Max(optionalStrength, s_PinchStates[i].pinchStrength); } } requiredStrength = requiredStrength == float.MaxValue ? 0 : requiredStrength; optionalStrength = optionalStrength == float.MinValue ? 0 : optionalStrength; /// Uses optionalStrength if no required finger. return (fingerRequirement.Required() == FingerFlags.None ? optionalStrength : requiredStrength); } /// /// Get the pose of the specified joint. /// /// The type of joint. /// The pose of the specified joint. public Pose GetJointPose(JointType jointType) { return GetJointPose((int)jointType); } /// /// Get the pose of the specified joint. /// /// The index of joint. /// The pose of the specified joint. public Pose GetJointPose(int jointId) { if (jointId >= (int)JointType.Count) { return Pose.identity; } Vector3 pos = Vector3.zero; Quaternion rot = Quaternion.identity; hand.GetJointPosition((JointType)jointId, ref pos); hand.GetJointRotation((JointType)jointId, ref rot); return new Pose(pos, rot); } } #region Grab Framework /// /// The enum is designed to define the requirement level for grabbing. /// public enum GrabRequirement : Int32 { Ignored = 0, Required = 1, Optional = 2, } /// /// The struct is designed to represent the requirement level for each finger during grabbing. /// [Serializable] public struct FingerRequirement { [SerializeField] public GrabRequirement thumb; [SerializeField] public GrabRequirement index; [SerializeField] public GrabRequirement middle; [SerializeField] public GrabRequirement ring; [SerializeField] public GrabRequirement pinky; public FingerRequirement(GrabRequirement in_Thumb, GrabRequirement in_Index, GrabRequirement in_Middle, GrabRequirement in_Ring, GrabRequirement in_Pinky) { thumb = in_Thumb; index = in_Index; middle = in_Middle; ring = in_Ring; pinky = in_Pinky; } public FingerRequirement Identity => new FingerRequirement(GrabRequirement.Ignored, GrabRequirement.Ignored, GrabRequirement.Ignored, GrabRequirement.Ignored, GrabRequirement.Ignored); public void Update(GrabRequirement in_Thumb, GrabRequirement in_Index, GrabRequirement in_Middle, GrabRequirement in_Ring, GrabRequirement in_Pinky) { thumb = in_Thumb; index = in_Index; middle = in_Middle; ring = in_Ring; pinky = in_Pinky; } public void Update(FingerId fingerID, GrabRequirement grabRequirement) { switch (fingerID) { case FingerId.Thumb: thumb = grabRequirement; break; case FingerId.Index: index = grabRequirement; break; case FingerId.Middle: middle = grabRequirement; break; case FingerId.Ring: ring = grabRequirement; break; case FingerId.Pinky: pinky = grabRequirement; break; } } public void Reset() { this = Identity; } /// /// Gets or sets the grab requirement for a specific finger. /// /// The ID of the finger. /// The grab requirement for the specified finger. public GrabRequirement this[FingerId fingerID] { get { switch (fingerID) { case FingerId.Thumb: return thumb; case FingerId.Index: return index; case FingerId.Middle: return middle; case FingerId.Ring: return ring; case FingerId.Pinky: return pinky; } return GrabRequirement.Ignored; } set { switch (fingerID) { case FingerId.Thumb: thumb = value; break; case FingerId.Index: index = value; break; case FingerId.Middle: middle = value; break; case FingerId.Ring: ring = value; break; case FingerId.Pinky: pinky = value; break; } } } public FingerFlags Required() { FingerFlags flag = FingerFlags.None; if (thumb == GrabRequirement.Required) { flag |= FingerFlags.Thumb; } if (index == GrabRequirement.Required) { flag |= FingerFlags.Index; } if (middle == GrabRequirement.Required) { flag |= FingerFlags.Middle; } if (ring == GrabRequirement.Required) { flag |= FingerFlags.Ring; } if (pinky == GrabRequirement.Required) { flag |= FingerFlags.Pinky; } return flag; } public FingerFlags Optional() { FingerFlags flag = FingerFlags.None; if (thumb == GrabRequirement.Optional) { flag |= FingerFlags.Thumb; } if (index == GrabRequirement.Optional) { flag |= FingerFlags.Index; } if (middle == GrabRequirement.Optional) { flag |= FingerFlags.Middle; } if (ring == GrabRequirement.Optional) { flag |= FingerFlags.Ring; } if (pinky == GrabRequirement.Optional) { flag |= FingerFlags.Pinky; } return flag; } } /// /// The struct is designed to represent the bending level of a finger. /// public enum FingerBendingLevel : UInt32 { Level0 = 0, Level1 = 1, Level2 = 2, Level3 = 3, Level4 = 4, Level5 = 5, } /// /// The struct is designed to represent the bending level of each finger in a hand grab gesture. /// [Serializable] public struct HandGrabGesture { [SerializeField] public FingerBendingLevel thumbPose; [SerializeField] public FingerBendingLevel indexPose; [SerializeField] public FingerBendingLevel middlePose; [SerializeField] public FingerBendingLevel ringPose; [SerializeField] public FingerBendingLevel pinkyPose; public HandGrabGesture(FingerBendingLevel in_Thumb, FingerBendingLevel in_Index, FingerBendingLevel in_Middle, FingerBendingLevel in_Ring, FingerBendingLevel in_Pinky) { thumbPose = in_Thumb; indexPose = in_Index; middlePose = in_Middle; ringPose = in_Ring; pinkyPose = in_Pinky; } public static HandGrabGesture Identity => new HandGrabGesture(FingerBendingLevel.Level0, FingerBendingLevel.Level0, FingerBendingLevel.Level0, FingerBendingLevel.Level0, FingerBendingLevel.Level0); public void Update(FingerBendingLevel in_Thumb, FingerBendingLevel in_Index, FingerBendingLevel in_Middle, FingerBendingLevel in_Ring, FingerBendingLevel in_Pinky) { thumbPose = in_Thumb; indexPose = in_Index; middlePose = in_Middle; ringPose = in_Ring; pinkyPose = in_Pinky; } public void Reset() { this = Identity; } public override bool Equals(object obj) { return obj is HandGrabGesture pose && thumbPose == pose.thumbPose && indexPose == pose.indexPose && middlePose == pose.middlePose && ringPose == pose.ringPose && pinkyPose == pose.pinkyPose; } public override int GetHashCode() { return thumbPose.GetHashCode() ^ indexPose.GetHashCode() ^ middlePose.GetHashCode() ^ ringPose.GetHashCode() ^ pinkyPose.GetHashCode(); } public static bool operator ==(HandGrabGesture source, HandGrabGesture target) => source.Equals(target); public static bool operator !=(HandGrabGesture source, HandGrabGesture target) => !(source == (target)); } /// /// The struct is designed to record the position and rotation offsets between the grabber and grabbable objects. /// [Serializable] public struct GrabOffset { [SerializeField] public Vector3 sourcePosition; [SerializeField] public Quaternion sourceRotation; [SerializeField] public Vector3 targetPosition; [SerializeField] public Quaternion targetRotation; public Vector3 posOffset => targetPosition - sourcePosition; public Quaternion rotOffset => Quaternion.Inverse(sourceRotation) * targetRotation; public GrabOffset(Vector3 in_SourcePosition, Quaternion in_SourceRotation, Vector3 in_TargetPosition, Quaternion in_targetRotation) { sourcePosition = in_SourcePosition; sourceRotation = in_SourceRotation; targetPosition = in_TargetPosition; targetRotation = in_targetRotation; } public static GrabOffset Identity => new GrabOffset(Vector3.zero, Quaternion.identity, Vector3.zero, Quaternion.identity); public void UpdateSource(Vector3 pos, Quaternion rot) { sourcePosition = pos; sourceRotation = rot; } public void UpdateTarget(Vector3 pos, Quaternion rot) { targetPosition = pos; targetRotation = rot; } public void Update(Vector3 in_SourcePosition, Quaternion in_SourceRotation, Vector3 in_TargetPosition, Quaternion in_targetRotation) { sourcePosition = in_SourcePosition; sourceRotation = in_SourceRotation; targetPosition = in_TargetPosition; targetRotation = in_targetRotation; } public void Reset() { this = Identity; } public override bool Equals(object obj) { return obj is GrabOffset grabOffset && sourcePosition == grabOffset.sourcePosition && sourceRotation == grabOffset.sourceRotation && targetPosition == grabOffset.targetPosition && targetRotation == grabOffset.targetRotation; } public override int GetHashCode() { return sourcePosition.GetHashCode() ^ sourceRotation.GetHashCode() ^ targetPosition.GetHashCode() ^ targetRotation.GetHashCode(); } public static bool operator ==(GrabOffset source, GrabOffset target) => source.Equals(target); public static bool operator !=(GrabOffset source, GrabOffset target) => !(source == target); } /// /// The struct is designed to represent an indicator for grabbing. /// [Serializable] public struct Indicator { [SerializeField] public bool enableIndicator; [SerializeField] public bool autoIndicator; [SerializeField] public GameObject target; [SerializeField] public GrabOffset grabOffset; public Indicator(bool in_EnableIndicator, bool in_AutoIndicator, GameObject in_Target) { enableIndicator = in_EnableIndicator; autoIndicator = in_AutoIndicator; target = in_Target; grabOffset = GrabOffset.Identity; } public static Indicator Identity => new Indicator(true, true, null); public void Update(bool enableIndicator, bool autoIndicator, GameObject target) { this.enableIndicator = enableIndicator; this.autoIndicator = autoIndicator; this.target = target; } public void Reset() { this = Identity; } /// /// Enable or disable the indicator if the target of indicator is not null. /// /// True to enable the indicator, false to deactivate it. public void SetActive(bool enable) { if (target != null) { target.SetActive(enable); } } /// /// Check if there is a need to generate a new indicator. /// /// True if there is a need to generate a new indicator; otherwise, false. public bool NeedGenerateIndicator() { return autoIndicator || target == null; } /// /// Calculates the grab offset based on the reference transform. /// /// The reference transform used for calculation. public void CalculateGrabOffset(Vector3 grabbablePos, Quaternion grabbableRot) { if (target != null) { grabOffset.Update(grabbablePos, grabbableRot, target.transform.position, target.transform.rotation); } } /// /// Update the position and rotation of the target GameObject based on the reference transform. /// /// The reference transform used for updating position and rotation. public void UpdatePositionAndRotation(Vector3 grabbablePos, Quaternion grabbableRot) { if (target != null) { Quaternion handRotDiff = grabbableRot * Quaternion.Inverse(grabOffset.sourceRotation); target.transform.position = grabbablePos + handRotDiff * grabOffset.posOffset; target.transform.rotation = grabbableRot * grabOffset.rotOffset; } } public override bool Equals(object obj) { return obj is Indicator indicator && enableIndicator == indicator.enableIndicator && autoIndicator == indicator.autoIndicator && target == indicator.target && grabOffset == indicator.grabOffset; } public override int GetHashCode() { return enableIndicator.GetHashCode() ^ autoIndicator.GetHashCode() ^ target.GetHashCode() ^ grabOffset.GetHashCode(); } public static bool operator ==(Indicator source, Indicator target) => source.Equals(target); public static bool operator !=(Indicator source, Indicator target) => !(source == target); } /// /// The struct is designed to define the information about hand gestures when grabbing /// [Serializable] public struct GrabPose { [SerializeField] public string grabPoseName; [SerializeField] public HandGrabGesture handGrabGesture; [SerializeField] public Quaternion[] recordedGrabRotations; [SerializeField] public bool isLeft; [SerializeField] public Indicator indicator; [SerializeField] public GrabOffset grabOffset; public GrabPose(string in_GrabPoseName, HandGrabGesture in_GrabGesture, bool in_IsLeft) { grabPoseName = in_GrabPoseName; handGrabGesture = in_GrabGesture; recordedGrabRotations = Array.Empty(); isLeft = in_IsLeft; indicator = Indicator.Identity; grabOffset = GrabOffset.Identity; } public GrabPose(string in_GrabPoseName, Quaternion[] in_RecordedGrabRotations, bool in_IsLeft) { grabPoseName = in_GrabPoseName; handGrabGesture = HandGrabGesture.Identity; recordedGrabRotations = in_RecordedGrabRotations; isLeft = in_IsLeft; indicator = Indicator.Identity; grabOffset = GrabOffset.Identity; } public static GrabPose Identity => new GrabPose(string.Empty, HandGrabGesture.Identity, true); public void Update(string grabPoseName, HandGrabGesture grabGesture, bool isLeft) { this.grabPoseName = grabPoseName; this.handGrabGesture = grabGesture; this.isLeft = isLeft; } public void Update(string grabPoseName, Quaternion[] recordedGrabRotations, bool isLeft) { this.grabPoseName = grabPoseName; this.recordedGrabRotations = recordedGrabRotations; this.isLeft = isLeft; } public void Reset() { this = Identity; } public override bool Equals(object obj) { return obj is GrabPose grabPose && grabPoseName == grabPose.grabPoseName && handGrabGesture == grabPose.handGrabGesture && recordedGrabRotations == grabPose.recordedGrabRotations && isLeft == grabPose.isLeft && indicator == grabPose.indicator && grabOffset == grabPose.grabOffset; } public override int GetHashCode() { return grabPoseName.GetHashCode() ^ handGrabGesture.GetHashCode() ^ recordedGrabRotations.GetHashCode() ^ isLeft.GetHashCode() ^ indicator.GetHashCode() ^ grabOffset.GetHashCode(); } public static bool operator ==(GrabPose source, GrabPose target) => source.Equals(target); public static bool operator !=(GrabPose source, GrabPose target) => !(source == target); } [Serializable] public class HandGrabberEvent : UnityEvent { }; [Serializable] public class HandGrabbableEvent : UnityEvent { }; [Obsolete("Please use HandGrabberEvent instead.")] public delegate void OnBeginGrab(IGrabber grabber); [Obsolete("Please use HandGrabberEvent instead.")] public delegate void OnEndGrab(IGrabber grabber); [Obsolete("Please use HandGrabbableEvent instead.")] public delegate void OnBeginGrabbed(IGrabbable grabbable); [Obsolete("Please use HandGrabbableEvent instead.")] public delegate void OnEndGrabbed(IGrabbable grabbable); /// /// Interface for objects capable of grabbing. /// public interface IGrabber { IGrabbable grabbable { get; } bool isGrabbing { get; } HandGrabberEvent onBeginGrab { get; } HandGrabberEvent onEndGrab { get; } [Obsolete("Please use onBeginGrab instead.")] void AddBeginGrabListener(OnBeginGrab handler); [Obsolete("Please use onBeginGrab instead.")] void RemoveBeginGrabListener(OnBeginGrab handler); [Obsolete("Please use onEndGrab instead.")] void AddEndGrabListener(OnEndGrab handler); [Obsolete("Please use onEndGrab instead.")] void RemoveEndGrabListener(OnEndGrab handler); } /// /// Interface for hands capable of grabbing. /// public interface IHandGrabber : IGrabber { Handedness handedness { get; } HandGrabState handGrabState { get; } } /// /// Interface for objects capable of being grabbed. /// public interface IGrabbable { IGrabber grabber { get; } bool isGrabbed { get; } bool isGrabbable { get; } HandGrabbableEvent onBeginGrabbed { get; } HandGrabbableEvent onEndGrabbed { get; } void SetGrabber(IGrabber grabber); [Obsolete("Please use onBeginGrabbed instead.")] void AddBeginGrabbedListener(OnBeginGrabbed handler); [Obsolete("Please use onBeginGrabbed instead.")] void RemoveBeginGrabbedListener(OnBeginGrabbed handler); [Obsolete("Please use onEndGrabbed instead.")] void AddEndGrabbedListener(OnEndGrabbed handler); [Obsolete("Please use onEndGrabbed instead.")] void RemoveEndGrabbedListener(OnEndGrabbed handler); } /// /// Interface for objects capable of being grabbed by hands. /// public interface IHandGrabbable : IGrabbable { FingerRequirement fingerRequirement { get; } } [Serializable] public struct ConstraintInfo { [SerializeField] public bool enableConstraint; [SerializeField] public float value; public ConstraintInfo(bool in_EnableConstraint, float in_Value) { enableConstraint = in_EnableConstraint; value = in_Value; } public static ConstraintInfo Identity => new ConstraintInfo(false, 0.0f); } public abstract class IMovement : MonoBehaviour { public abstract void Initialize(IGrabbable grabbable); public abstract void OnBeginGrabbed(IGrabbable grabbable); public abstract void UpdatePose(Pose updatedPose); public abstract void OnEndGrabbed(IGrabbable grabbable); } public abstract class IOneHandContraintMovement : IMovement { } /// /// The class is designed to serve as the Hand Grab API. /// public static class Grab { /// /// Checks if the hand grabber is beginning to grab grabbable object. /// /// The hand grabber capable of grabbing. /// The object being grabbed. /// True if the hand is beginning to grab; otherwise, false. public static bool HandBeginGrab(IHandGrabber grabber, IHandGrabbable grabbable) { if (grabbable == null || grabber == null) { return false; } return grabber.handGrabState.HandGrabStateIsChanged(grabbable.fingerRequirement, true); } /// /// Check if the hand grabber is grabbing the grabbable object. /// /// The hand grabber capable of grabbing. /// The object being grabbed. /// True if the hand is grabbing the grabbable object; otherwise, false. public static bool HandIsGrabbing(IHandGrabber grabber, IHandGrabbable grabbable) { if (grabbable == null || grabber == null) { return false; } return grabber.handGrabState.HandKeepGrabbing(grabbable.fingerRequirement); } /// /// Check if the hand grabber has just finished grabbing the grabbable. /// /// The hand grabber capable of grabbing. /// The object being grabbed. /// True if the hand has finished grabbing the grabbable object; otherwise, false. public static bool HandDoneGrab(IHandGrabber grabber, IHandGrabbable grabbable) { if (grabbable == null || grabber == null) { return true; } return grabber.handGrabState.HandGrabStateIsChanged(grabbable.fingerRequirement, false); } /// /// Calculate the grab score between the grabber and the grabbable object. /// /// The hand grabber capable of grabbing. /// The object being grabbed. /// The value representing the grab score between the grabber and the grabbable object. public static float CalculateHandGrabScore(IHandGrabber grabber, IHandGrabbable grabbable) { if (grabbable == null || grabber == null) { return -1; } return grabber.handGrabState.HandGrabNearStrength(grabbable.fingerRequirement); } } #endregion public static class HandInteractionHelper { public static string Name(this FingerId id) { if (id == FingerId.Thumb) { return "Thumb"; } if (id == FingerId.Index) { return "Index"; } if (id == FingerId.Middle) { return "Middle"; } if (id == FingerId.Ring) { return "Ring"; } if (id == FingerId.Pinky) { return "Pinky"; } return ""; } public static FingerFlags Flag(this FingerId id) { if (id == FingerId.Thumb) { return FingerFlags.Thumb; } if (id == FingerId.Index) { return FingerFlags.Index; } if (id == FingerId.Middle) { return FingerFlags.Middle; } if (id == FingerId.Ring) { return FingerFlags.Ring; } if (id == FingerId.Pinky) { return FingerFlags.Pinky; } return FingerFlags.None; } } }