Files
VIVE-OpenXR-Unity/com.htc.upm.vive.openxr/Runtime/Toolkits/RealisticHandInteraction(experimental)/Scripts/HandInteractionUtils.cs
2025-01-10 17:31:06 +08:00

2049 lines
71 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// "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 HTCs 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;
using VIVE.OpenXR.Toolkits.Common;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
#region Basic Hand Data
/// <summary>
/// This enum corresponds to HandManager.HandJoint and is used for categorizing joint types.
/// </summary>
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,
}
/// <summary>
/// This class is designed to update hand tracking data.
/// </summary>
public static class DataWrapper
{
/// <summary>
/// Validate whether the hand tracking is active.
/// </summary>
/// <returns>True if the hand tracking is active; otherwise, false.</returns>
public static bool Validate()
{
return VIVEInput.IsHandValidate();
}
/// <summary>
/// Validate whether the hand tracking is successfully tracking.
/// </summary>
/// <param name="isLeft">True if the hand is left; otherwise, false.</param>
/// <returns>True if the hand tracking is successfully tracking; otherwise, false.</returns>
public static bool IsHandTracked(bool isLeft)
{
Common.Handedness handedness = isLeft ? Common.Handedness.Left : Common.Handedness.Right;
return VIVEInput.IsHandTracked(handedness);
}
/// <summary>
///
/// </summary>
/// <param name="jointType"></param>
/// <param name="rotation">The reference to store the position of the joint.</param>
/// <param name="rotation">The reference to store the rotation of the joint.</param>
/// <param name="isLeft"></param>
/// <returns></returns>
public static bool GetJointPose(JointType jointType, ref Vector3 position, ref Quaternion rotation, bool isLeft)
{
Common.Handedness handedness = isLeft ? Common.Handedness.Left : Common.Handedness.Right;
if (IsHandTracked(isLeft) &&
VIVEInput.GetJointPose(handedness, (HandJointType)jointType, out Pose jointPose))
{
position = jointPose.position;
rotation = jointPose.rotation;
return true;
}
return false;
}
}
/// <summary>
/// The enum is designed to define the IDs of joints.
/// </summary>
public enum JointId : Int32
{
Invalid = -1,
Joint0 = 0,
Joint1 = 1,
Joint2 = 2,
Joint3 = 3,
Tip = 4,
Count = 5,
}
/// <summary>
/// The struct is designed to record data for each joint.
/// </summary>
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;
}
}
/// <summary>
/// The enum is designed to define the IDs of fingers.
/// </summary>
public enum FingerId : Int32
{
Invalid = -1,
Thumb = 0,
Index = 1,
Middle = 2,
Ring = 3,
Pinky = 4,
Count = 5
}
/// <summary>
/// The enum is designed to define the flags of FingerId.
/// </summary>
[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
}
/// <summary>
/// The class is designed to record each joint data of finger.
/// </summary>
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];
}
}
/// <summary>
/// The enum is designed to define the IDs of hands.
/// </summary>
public enum HandId : Int32
{
Right = 0,
Left = 1,
Both = 2
}
/// <summary>
/// This enum corresponds to HandId and is used for categorizing hand types.
/// </summary>
public enum Handedness
{
Right = HandId.Right,
Left = HandId.Left,
}
/// <summary>
/// The class is designed to record each finger data of hand and tracking state.
/// </summary>
public class HandData
{
// Local rotation of the fingers at different degrees of bending.
private static readonly Dictionary<bool, Dictionary<FingerBendingLevel, Quaternion[]>> s_FingersBending = new Dictionary<bool, Dictionary<FingerBendingLevel, Quaternion[]>>()
{
{
true, new Dictionary<FingerBendingLevel, Quaternion[]>()
{
{
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, Quaternion[]>()
{
{
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),
}
},
}
},
};
// palm, wrist, thumb, index, middle, ring, pinky
private static readonly int[] fingerGroup = { 1, 1, 4, 5, 5, 5, 5 };
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();
}
}
/// <summary>
/// Get the position of a specific joint.
/// </summary>
/// <param name="joint">The type of joint to get.</param>
/// <param name="position">The reference to store the position of the joint.</param>
/// <returns>True if the joint position is successfully retrieved; otherwise, false.</returns>
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;
}
/// <summary>
/// Get the rotation of a specific joint.
/// </summary>
/// <param name="joint">The type of joint to get.</param>
/// <param name="rotation">The reference to store the rotation of the joint.</param>
/// <returns>True if the joint rotation is successfully retrieved; otherwise, false.</returns>
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;
}
/// <summary>
/// Get the default joint rotation for a specific HandGrabGesture and joint type.
/// </summary>
/// <param name="isLeft">True if the hand is left; otherwise, false.</param>
/// <param name="handGrabGesture">The HandGrabGesture is a gesture where a hand grabs.</param>
/// <param name="joint">The type of joint.</param>
/// <param name="rotation">The reference to store the default joint rotation.</param>
/// <returns>True if the default joint rotation is successfully retrieved; otherwise, false.</returns>
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;
}
/// <summary>
/// Determine the group and index of a given joint type.
/// </summary>
/// <param name="joint">The type of joint.</param>
/// <param name="group">The group to which the joint belongs.</param>
/// <param name="index">The index of the joint within its group.</param>
public static void GetJointIndex(JointType joint, out int group, out int index)
{
int jointId = (int)joint + 1;
group = 0;
index = jointId;
for (int i = 0; i < fingerGroup.Length; i++)
{
if (index <= fingerGroup[i])
{
group = i;
index -= 1; // Adjust to 0-based index
return;
}
index -= fingerGroup[i];
}
}
}
/// <summary>
/// The class is designed to temporarily store all hand data for this frame to avoid redundant updates.
/// </summary>
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];
/// <summary>
/// Determine whether to allow updating hand data based on the handedness of the hand and frame count.
/// </summary>
/// <param name="isLeft">True if the hand is left; otherwise, false.</param>
/// <returns>True if updating hand data is allowed; otherwise, false.</returns>
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;
}
/// <summary>
/// Get data for a specific joint including position, rotation, and velocity.
/// </summary>
/// <param name="id">The type of joint.</param>
/// <param name="data">The reference to store the joint data.</param>
/// <param name="isLeft">True if the hand is left; otherwise, false.</param>
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;
}
/// <summary>
/// Get data for a specific finger.
/// </summary>
/// <param name="id">The type of finger.</param>
/// <param name="finger">The reference to store the finger data.</param>
/// <param name="isLeft">True if the hand is left; otherwise, false.</param>
private static void GetFingerData(FingerId id, ref FingerData finger, bool isLeft)
{
JointType[] jointTypes = GetJointTypes(id);
if (jointTypes == null) return;
float deltaTime = Time.deltaTime;
Vector3 parentVel = Vector3.zero;
for (int i = 0; i < jointTypes.Length; i++)
{
ref JointData joint = ref finger.joints[i];
Vector3 lastPosition = joint.position;
DataWrapper.GetJointPose(jointTypes[i], ref joint.position, ref joint.rotation, isLeft);
joint.velocity = (joint.position - lastPosition) / deltaTime;
//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 > joint.velocity.magnitude)
{
joint.position += parentVel * deltaTime;
}
parentVel = joint.velocity;
}
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;
}
}
private static JointType[] GetJointTypes(FingerId id)
{
return id switch
{
FingerId.Thumb => s_ThumbJoints,
FingerId.Index => s_IndexJoints,
FingerId.Middle => s_MiddleJoints,
FingerId.Ring => s_RingJoints,
FingerId.Pinky => s_PinkyJoints,
_ => null
};
}
/// <summary>
/// Update the data for the left or right hand.
/// </summary>
/// <param name="isLeft">True if the hand is left; otherwise, false.</param>
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);
}
/// <summary>
/// Get the complete data for the left or right hand.
/// </summary>
/// <param name="isLeft">True if the hand is left; otherwise, false.</param>
/// <returns>The hand data for the specified hand.</returns>
public static HandData Get(bool isLeft)
{
UpdateData(isLeft);
return hands[isLeft ? (Int32)HandId.Left : (Int32)HandId.Right];
}
}
#endregion
/// <summary>
/// The abstract class is designed to defines the necessary members or functions for grabbing.
/// </summary>
public abstract class GrabState
{
public abstract void UpdateState();
}
/// <summary>
/// The class is designed to record the grab state of a hand.
/// </summary>
public class HandGrabState : GrabState
{
/// <summary>
/// The class is designed to record the pinch state of each finger.
/// </summary>
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(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
bool printIntervalLog = false;
int logFrame = 0;
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<FingerId, FingerPinchThreshold> fingersPinchThreshold = new Dictionary<FingerId, FingerPinchThreshold>()
{
{
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;
}
/// <summary>
/// Checks if the finger is valid.
/// </summary>
/// <returns>True if the finger ID is valid.</returns>
private bool Validate()
{
return (m_finger != FingerId.Invalid && m_finger != FingerId.Count && m_finger != FingerId.Thumb);
}
/// <summary>
/// Calculates the shortest distance from the finger, including tip, joint3, and joint2, to the thumb, including tip, joint2, and joint1.
/// </summary>
/// <returns>The value for the shortest distance from the finger to the thumb.</returns>
private float GetFingerToThumbDistance()
{
if (!Validate()) { return float.PositiveInfinity; }
Vector3 thumbTip = thumbData.tip.position;
Vector3 thumbJoint2 = thumbData.joint2.position;
Vector3 thumbJoint1 = thumbData.joint1.position;
float distance = float.PositiveInfinity;
distance = Mathf.Min(distance, CalculateShortestDistance(fingerData.tip.position, thumbTip, thumbJoint2));
distance = Mathf.Min(distance, CalculateShortestDistance(fingerData.tip.position, thumbJoint2, thumbJoint1));
distance = Mathf.Min(distance, CalculateShortestDistance(fingerData.joint3.position, thumbTip, thumbJoint2));
distance = Mathf.Min(distance, CalculateShortestDistance(fingerData.joint3.position, thumbJoint2, thumbJoint1));
distance = Mathf.Min(distance, CalculateShortestDistance(fingerData.joint2.position, thumbTip, thumbJoint2));
distance = Mathf.Min(distance, CalculateShortestDistance(fingerData.joint2.position, thumbJoint2, thumbJoint1));
return distance;
}
/// <summary>
/// Calculates the shortest distance from a point to a line segment defined by two points.
/// </summary>
/// <param name="point">The point from which the distance is measured.</param>
/// <param name="start">The start point of the line segment.</param>
/// <param name="end">The end point of the line segment.</param>
/// <returns>The shortest distance from the point to the line segment.</returns>
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;
}
/// <summary>
/// Update the pinch states based on the calculated distance.
/// </summary>
/// <param name="distance">The distance from the finger to the thumb.</param>
/// <returns>True if the states are updated; otherwise, false.</returns>
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;
if (printIntervalLog)
{
DEBUG($"{(isLeft ? "Left " : "Right ")} {finger.Name()}" +
$" UpdateState() pinch strength: {m_pinchStrength}" +
$", pinch on threshold: {kPinchStrengthOnThreshold}" +
$", is pinching: {m_isPinching}" +
$", pinch distance: {minDistance}");
}
updated = true;
}
}
else
{
minDistance = Mathf.Min(minDistance, distance);
if (m_pinchStrength < kPinchStrengthOffThreshold ||
distance > minDistance + threshold.pinchOffset)
{
m_isPinching = false;
if (printIntervalLog)
{
DEBUG($"{(isLeft ? "Left " : "Right ")} {finger.Name()}" +
$" UpdateState() pinch strength: {m_pinchStrength}" +
$", pinch off threshold: {kPinchStrengthOffThreshold}" +
$", is pinching: {m_isPinching}" +
$", pinch distance: {minDistance}");
}
updated = true;
}
}
return updated;
}
/// <summary>
/// Update the FingerPinchState with the data of the thumb and finger.
/// </summary>
/// <param name="thumb">The FingerData of the thumb.</param>
/// <param name="finger">The FingerData of the finger.</param>
public void Update(FingerData thumb, FingerData finger)
{
logFrame++;
logFrame %= 300;
printIntervalLog = logFrame == 0;
if (!Validate()) { return; }
this.thumbData = thumb;
this.fingerData = finger;
float distance = GetFingerToThumbDistance();
m_isChanged = UpdateStates(distance);
}
/// <summary>
/// Manually updates the FingerPinchState with specified pinch parameters.
/// </summary>
/// <param name="isPinch">True if the finger is pinching; otherwise, false.</param>
/// <param name="pinchStrength">The strength of the pinch.</param>
/// <param name="stateChange">True if the state has changed; otherwise, false.</param>
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),
};
}
/// <summary>
/// Update all FingerPinchStates of a hand.
/// </summary>
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();
}
}
/// <summary>
/// Checks if the state of the FingerPinchState for a specific finger has changed as expected.
/// </summary>
/// <param name="finger">The FingerId of the finger.</param>
/// <param name="expectState">The expected state of the finger pinch.</param>
/// <returns>True if the state has changed as expected; otherwise, false.</returns>
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;
}
/// <summary>
/// Checks if the state of the FingerPinchState for each finger has changed as expected.
/// </summary>
/// <param name="fingerRequirement">The finger requirements for grabbing.</param>
/// <param name="expectState">The expected state of the hand grab.</param>
/// <returns>True if the requirement is met as expected; otherwise, false.</returns>
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;
}
/// <summary>
/// Determine whether the hand is keeping grabbing based on finger requirements.
/// </summary>
/// <param name="fingerRequirement">The finger requirements for grabbing.</param>
/// <returns>True if the hand is keeping grabbing; otherwise, false.</returns>
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;
}
/// <summary>
/// Calculates the score of the current hand grab state based on FingerRequirement.
/// </summary>
/// <param name="requirements">The finger requirements for grabbing.</param>
/// <returns>The score of the current hand grab state based on FingerRequirement.</returns>
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);
}
/// <summary>
/// Get the pose of the specified joint.
/// </summary>
/// <param name="jointType">The type of joint.</param>
/// <returns>The pose of the specified joint. </returns>
public Pose GetJointPose(JointType jointType)
{
return GetJointPose((int)jointType);
}
/// <summary>
/// Get the pose of the specified joint.
/// </summary>
/// <param name="jointId">The index of joint.</param>
/// <returns>The pose of the specified joint.</returns>
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
/// <summary>
/// The enum is designed to define the requirement level for grabbing.
/// </summary>
public enum GrabRequirement : Int32
{
Ignored = 0,
Required = 1,
Optional = 2,
}
/// <summary>
/// The struct is designed to represent the requirement level for each finger during grabbing.
/// </summary>
[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;
}
/// <summary>
/// Gets or sets the grab requirement for a specific finger.
/// </summary>
/// <param name="fingerID">The ID of the finger.</param>
/// <returns>The grab requirement for the specified finger.</returns>
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;
}
}
/// <summary>
/// The struct is designed to represent the bending level of a finger.
/// </summary>
public enum FingerBendingLevel : UInt32
{
Level0 = 0,
Level1 = 1,
Level2 = 2,
Level3 = 3,
Level4 = 4,
Level5 = 5,
}
/// <summary>
/// The struct is designed to represent the bending level of each finger in a hand grab gesture.
/// </summary>
[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));
}
/// <summary>
/// The struct is designed to record the position and rotation offsets between the grabber and grabbable objects.
/// </summary>
[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);
}
/// <summary>
/// The struct is designed to represent an indicator for grabbing.
/// </summary>
[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;
}
/// <summary>
/// Enable or disable the indicator if the target of indicator is not null.
/// </summary>
/// <param name="enable">True to enable the indicator, false to deactivate it.</param>
public void SetActive(bool enable)
{
if (target)
{
if (enable)
{
target.transform.localScale = Vector3.one;
}
else
{
target.transform.localScale = Vector3.zero;
}
}
}
/// <summary>
/// Check if there is a need to generate a new indicator.
/// </summary>
/// <returns>True if there is a need to generate a new indicator; otherwise, false. </returns>
public bool NeedGenerateIndicator()
{
return autoIndicator || target == null;
}
/// <summary>
/// Calculates the grab offset based on the reference transform.
/// </summary>
/// <param name="refTransform">The reference transform used for calculation.</param>
public void CalculateGrabOffset(Vector3 grabbablePos, Quaternion grabbableRot)
{
if (target != null)
{
grabOffset.Update(grabbablePos, grabbableRot, target.transform.position, target.transform.rotation);
}
}
/// <summary>
/// Update the position and rotation of the target GameObject based on the reference transform.
/// </summary>
/// <param name="refTransform">The reference transform used for updating position and rotation.</param>
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);
}
/// <summary>
/// The struct is designed to define the information about hand gestures when grabbing
/// </summary>
[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<Quaternion>();
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<IGrabber> { };
[Serializable]
public class HandGrabbableEvent : UnityEvent<IGrabbable> { };
/// <summary>
/// Interface for objects capable of grabbing.
/// </summary>
public interface IGrabber
{
IGrabbable grabbable { get; }
bool isGrabbing { get; }
HandGrabberEvent onBeginGrab { get; }
HandGrabberEvent onEndGrab { get; }
}
/// <summary>
/// Interface for hands capable of grabbing.
/// </summary>
public interface IHandGrabber : IGrabber
{
Handedness handedness { get; }
HandGrabState handGrabState { get; }
}
/// <summary>
/// Interface for objects capable of being grabbed.
/// </summary>
public interface IGrabbable
{
IGrabber grabber { get; }
bool isGrabbed { get; }
bool isGrabbable { get; }
HandGrabbableEvent onBeginGrabbed { get; }
HandGrabbableEvent onEndGrabbed { get; }
void SetGrabber(IGrabber grabber);
}
/// <summary>
/// Interface for objects capable of being grabbed by hands.
/// </summary>
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 { }
/// <summary>
/// The class is designed to serve as the Hand Grab API.
/// </summary>
public static class Grab
{
/// <summary>
/// Checks if the hand grabber is beginning to grab grabbable object.
/// </summary>
/// <param name="grabber">The hand grabber capable of grabbing.</param>
/// <param name="grabbable">The object being grabbed.</param>
/// <returns>True if the hand is beginning to grab; otherwise, false.</returns>
public static bool HandBeginGrab(IHandGrabber grabber, IHandGrabbable grabbable)
{
if (grabbable == null || grabber == null) { return false; }
return grabber.handGrabState.HandGrabStateIsChanged(grabbable.fingerRequirement, true);
}
/// <summary>
/// Check if the hand grabber is grabbing the grabbable object.
/// </summary>
/// <param name="grabber">The hand grabber capable of grabbing.</param>
/// <param name="grabbable">The object being grabbed.</param>
/// <returns>True if the hand is grabbing the grabbable object; otherwise, false.</returns>
public static bool HandIsGrabbing(IHandGrabber grabber, IHandGrabbable grabbable)
{
if (grabbable == null || grabber == null) { return false; }
return grabber.handGrabState.HandKeepGrabbing(grabbable.fingerRequirement);
}
/// <summary>
/// Check if the hand grabber has just finished grabbing the grabbable.
/// </summary>
/// <param name="grabber">The hand grabber capable of grabbing.</param>
/// <param name="grabbable">The object being grabbed.</param>
/// <returns>True if the hand has finished grabbing the grabbable object; otherwise, false.</returns>
public static bool HandDoneGrab(IHandGrabber grabber, IHandGrabbable grabbable)
{
if (grabbable == null || grabber == null) { return true; }
return grabber.handGrabState.HandGrabStateIsChanged(grabbable.fingerRequirement, false);
}
/// <summary>
/// Calculate the grab score between the grabber and the grabbable object.
/// </summary>
/// <param name="grabber">The hand grabber capable of grabbing.</param>
/// <param name="grabbable">The object being grabbed.</param>
/// <returns>The value representing the grab score between the grabber and the grabbable object.</returns>
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;
}
}
}