// "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;
}
}
}