version 2.4.0

This commit is contained in:
Sean Lu(呂祥榮)
2024-07-03 14:58:53 +08:00
parent 7f2a459592
commit 3dd72f5f56
162 changed files with 23632 additions and 27154 deletions

View File

@@ -31,7 +31,7 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
public class Plane
{
public Vector3 scale; // Only width(X) and height(Y) are valid, Z is always 1.
public Vector3 center; // Should always be Vector3.Zero.
public Vector3 center; // Should always be Vector3.Zero.
public Vector3[] verticesRaw; // The original vertices from <see cref="XrPlaneDetectorPolygonBufferEXT"/>
public Vector3[] verticesGenerated; // generated vertices for creating Mesh.
public Vector2[] uvsGenerated;
@@ -94,7 +94,12 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
IntPtr planeDetector = IntPtr.Zero;
VivePlaneDetection feature = null;
public PlaneDetector(IntPtr pd, VivePlaneDetection f)
/// <summary>
/// Should not create PlaneDetector directly. Use <see cref="PlaneDetectionManager.CreatePlaneDetector" /> instead.
/// </summary>
/// <param name="pd">The native handle of plane detector</param>
/// <param name="f">the feature</param>
internal PlaneDetector(IntPtr pd, VivePlaneDetection f)
{
feature = f;
planeDetector = pd;
@@ -184,10 +189,10 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
locations = new List<PlaneDetectorLocation>();
// The plane's neutral pose is horizontal, and not like the plane pose in unity which is vertical.
// Therefore, we wil perform a rotation to convert from the OpenXR's forward to unity's forward.
// In Unity, the rotation applied order is in ZXY order.
Quaternion forward = Quaternion.Euler(-90, 180, 0);
// The plane's neutral pose is horizontal, and not like the plane pose in unity which is vertical.
// Therefore, we wil perform a rotation to convert from the OpenXR's forward to unity's forward.
// In Unity, the rotation applied order is in ZXY order.
Quaternion forward = Quaternion.Euler(-90, 180, 0);
for (int i = 0; i < locationsRaw.planeLocationCountOutput; i++)
{
XrPlaneDetectorLocationEXT location = locationsArray[i];
@@ -273,9 +278,9 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
public static class PlaneDetectionManager
{
static VivePlaneDetection feature = null;
static bool isSupported = false;
static bool isSupported = false;
static void CheckFeature()
static void CheckFeature()
{
if (feature != null) return;
feature = OpenXRSettings.Instance.GetFeature<VivePlaneDetection>();
@@ -289,16 +294,16 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
/// <returns></returns>
public static VivePlaneDetection GetFeature()
{
try
{
CheckFeature();
}
catch (NotSupportedException)
{
Debug.LogWarning("PlaneDetection feature is not enabled");
return null;
}
return feature;
try
{
CheckFeature();
}
catch (NotSupportedException)
{
Debug.LogWarning("PlaneDetection feature is not enabled");
return null;
}
return feature;
}
/// <summary>
@@ -308,7 +313,7 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
public static bool IsSupported()
{
if (GetFeature() == null) return false;
if (isSupported) return true;
if (isSupported) return true;
if (feature == null) return false;
bool ret = false;
@@ -317,9 +322,9 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
{
Debug.Log("PlaneDetection: IsSupported() properties.supportedFeatures: " + properties.supportedFeatures);
ret = (properties.supportedFeatures & CAPABILITY_PLANE_DETECTION_BIT_EXT) > 0;
isSupported = ret;
}
else
isSupported = ret;
}
else
{
Debug.Log("PlaneDetection: IsSupported() GetSystemProperties failed.");
}
@@ -342,6 +347,7 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
/// <summary>
/// Plane detector is a session of detect plane. You don't need to create multiple plane detector in VIVE's implemention. You need destroy it.
/// Should call <see cref="IsSupported"/> first to check if the feature is supported.
/// </summary>
/// <returns>PlaneDetector's handle</returns>
public static PlaneDetector CreatePlaneDetector()
@@ -349,6 +355,8 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
CheckFeature();
if (feature == null)
return null;
if (IsSupported() == false)
return null;
var createInfo = MakeXrPlaneDetectorCreateInfoEXT();
var ret = feature.CreatePlaneDetector(createInfo, out var planeDetector);
@@ -370,4 +378,4 @@ namespace VIVE.OpenXR.Toolkits.PlaneDetection
feature.DestroyPlaneDetector(pd.GetDetectorRaw());
}
}
}
}

View File

@@ -0,0 +1,382 @@
// "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 UnityEngine;
using System;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditorInternal;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
[CustomEditor(typeof(HandGrabInteractable))]
public class HandGrabInteractableEditor : UnityEditor.Editor
{
private static HandGrabInteractable activeGrabbable = null;
private HandGrabInteractable handGrabbable = null;
private SerializedProperty m_IsGrabbable, m_FingerRequirement, m_Rigidbody, m_GrabPoses, m_ShowAllIndicator, m_OnBeginGrabbed, m_OnEndGrabbed,
m_OneHandContraintMovement, m_PreviewIndex, grabPoseName, gestureThumbPose, gestureIndexPose, gestureMiddlePose, gestureRingPose, gesturePinkyPose,
recordedGrabRotations, isLeft, enableIndicator, autoIndicator, indicatorObject, grabOffset;
private ReorderableList grabPoseList;
private bool showGrabPoses = false;
private bool showConstraint = false;
private bool showEvent = false;
private void OnEnable()
{
handGrabbable = target as HandGrabInteractable;
m_IsGrabbable = serializedObject.FindProperty("m_IsGrabbable");
m_FingerRequirement = serializedObject.FindProperty("m_FingerRequirement");
m_Rigidbody = serializedObject.FindProperty("m_Rigidbody");
m_GrabPoses = serializedObject.FindProperty("m_GrabPoses");
m_ShowAllIndicator = serializedObject.FindProperty("m_ShowAllIndicator");
m_OnBeginGrabbed = serializedObject.FindProperty("m_OnBeginGrabbed");
m_OnEndGrabbed = serializedObject.FindProperty("m_OnEndGrabbed");
m_OneHandContraintMovement = serializedObject.FindProperty("m_OneHandContraintMovement");
m_PreviewIndex = serializedObject.FindProperty("m_PreviewIndex");
#region ReorderableList
grabPoseList = new ReorderableList(serializedObject, m_GrabPoses, true, true, true, true);
grabPoseList.drawHeaderCallback = (Rect rect) =>
{
EditorGUI.LabelField(rect, "Grab Pose List");
};
grabPoseList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
if (!UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(index))) { return; }
if (string.IsNullOrEmpty(grabPoseName.stringValue))
{
grabPoseName.stringValue = $"Grab Pose {index + 1}";
}
Rect elementRect = new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight);
grabPoseName.stringValue = EditorGUI.TextField(elementRect, grabPoseName.stringValue);
DrawGrabGesture(ref elementRect);
DrawHandedness(ref elementRect);
DrawIndicator(ref elementRect);
DrawMirrorButton(ref elementRect);
DrawPoseOffset(ref elementRect);
DrawFineTune(ref elementRect, index);
};
grabPoseList.elementHeightCallback = (int index) =>
{
if (!UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(index))) { return EditorGUIUtility.singleLineHeight; }
// Including Title, Handness, Show Indicator, Mirror Pose, Position, Rotation, Fine Tune
int minHeight = 7;
// To Show GrabGesture
if (recordedGrabRotations.arraySize == 0)
{
minHeight += 5;
}
if (enableIndicator.boolValue)
{
// To Show Auto Indicator
minHeight += 1;
// To Show Indicator Gameobject
if (!autoIndicator.boolValue)
{
minHeight += 1;
}
}
return EditorGUIUtility.singleLineHeight * minHeight + EditorGUIUtility.standardVerticalSpacing * minHeight;
};
grabPoseList.onAddCallback = (ReorderableList list) =>
{
m_GrabPoses.arraySize++;
if (UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(list.count - 1)))
{
grabPoseName.stringValue = $"Grab Pose {list.count}";
}
};
#endregion
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_Rigidbody);
EditorGUILayout.PropertyField(m_IsGrabbable);
EditorGUILayout.PropertyField(m_FingerRequirement);
showGrabPoses = EditorGUILayout.Foldout(showGrabPoses, "Grab Pose Settings");
if (showGrabPoses)
{
if (m_GrabPoses.arraySize == 0)
{
grabPoseList.elementHeight = EditorGUIUtility.singleLineHeight;
}
grabPoseList.DoLayoutList();
bool isToggle = EditorGUILayout.Toggle("Show All Indicator", m_ShowAllIndicator.boolValue);
if (isToggle != m_ShowAllIndicator.boolValue)
{
m_ShowAllIndicator.boolValue = isToggle;
for (int i = 0; i < m_GrabPoses.arraySize; i++)
{
if (UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(i)))
{
enableIndicator.boolValue = m_ShowAllIndicator.boolValue;
}
}
}
}
showEvent = EditorGUILayout.Foldout(showEvent, "Grabbed Event");
if (showEvent)
{
EditorGUILayout.PropertyField(m_OnBeginGrabbed);
EditorGUILayout.PropertyField(m_OnEndGrabbed);
}
showConstraint = EditorGUILayout.Foldout(showConstraint, "Constraint Movement (Optional)");
if (showConstraint)
{
EditorGUILayout.PropertyField(m_OneHandContraintMovement);
}
serializedObject.ApplyModifiedProperties();
}
private bool UpdateGrabPose(SerializedProperty grabPose)
{
SerializedProperty indicator = grabPose.FindPropertyRelative("indicator");
if (grabPose == null || indicator == null) { return false; }
grabPoseName = grabPose.FindPropertyRelative("grabPoseName");
gestureThumbPose = grabPose.FindPropertyRelative("handGrabGesture.thumbPose");
gestureIndexPose = grabPose.FindPropertyRelative("handGrabGesture.indexPose");
gestureMiddlePose = grabPose.FindPropertyRelative("handGrabGesture.middlePose");
gestureRingPose = grabPose.FindPropertyRelative("handGrabGesture.ringPose");
gesturePinkyPose = grabPose.FindPropertyRelative("handGrabGesture.pinkyPose");
recordedGrabRotations = grabPose.FindPropertyRelative("recordedGrabRotations");
isLeft = grabPose.FindPropertyRelative("isLeft");
enableIndicator = indicator.FindPropertyRelative("enableIndicator");
autoIndicator = indicator.FindPropertyRelative("autoIndicator");
indicatorObject = indicator.FindPropertyRelative("target");
grabOffset = grabPose.FindPropertyRelative("grabOffset");
return true;
}
private void AddElementHeight(ref Rect rect)
{
rect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
}
private void DrawGrabGesture(ref Rect rect)
{
if (recordedGrabRotations.arraySize == 0)
{
AddElementHeight(ref rect);
EditorGUI.PropertyField(rect, gestureThumbPose);
AddElementHeight(ref rect);
EditorGUI.PropertyField(rect, gestureIndexPose);
AddElementHeight(ref rect);
EditorGUI.PropertyField(rect, gestureMiddlePose);
AddElementHeight(ref rect);
EditorGUI.PropertyField(rect, gestureRingPose);
AddElementHeight(ref rect);
EditorGUI.PropertyField(rect, gesturePinkyPose);
}
}
private void DrawHandedness(ref Rect rect)
{
AddElementHeight(ref rect);
bool isToggle = EditorGUI.Toggle(rect, "Is Left", isLeft.boolValue);
if (isToggle != isLeft.boolValue)
{
isLeft.boolValue = isToggle;
SwitchRotations(ref recordedGrabRotations);
}
}
private void DrawIndicator(ref Rect rect)
{
AddElementHeight(ref rect);
enableIndicator.boolValue = EditorGUI.Toggle(rect, "Show Indicator", enableIndicator.boolValue);
if (enableIndicator.boolValue)
{
AddElementHeight(ref rect);
autoIndicator.boolValue = EditorGUI.Toggle(rect, "Auto Generator Indicator", autoIndicator.boolValue);
if (!autoIndicator.boolValue)
{
AddElementHeight(ref rect);
indicatorObject.objectReferenceValue = (GameObject)EditorGUI.ObjectField(rect, "Indicator", (GameObject)indicatorObject.objectReferenceValue, typeof(GameObject), true);
}
}
else
{
m_ShowAllIndicator.boolValue = false;
}
}
private void DrawMirrorButton(ref Rect rect)
{
AddElementHeight(ref rect);
Rect labelRect = new Rect(rect.x, rect.y, EditorGUIUtility.labelWidth, rect.height);
EditorGUI.PrefixLabel(labelRect, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Mirror Pose"));
Rect mirrorXRect = new Rect(rect.x + EditorGUIUtility.labelWidth + EditorGUIUtility.standardVerticalSpacing, rect.y,
(rect.width - EditorGUIUtility.labelWidth - EditorGUIUtility.standardVerticalSpacing * 4) / 3, rect.height);
Rect mirrorYRect = new Rect(mirrorXRect.x + mirrorXRect.width + EditorGUIUtility.standardVerticalSpacing, rect.y, mirrorXRect.width, rect.height);
Rect mirrorZRect = new Rect(mirrorYRect.x + mirrorYRect.width + EditorGUIUtility.standardVerticalSpacing, rect.y, mirrorYRect.width, rect.height);
if (GUI.Button(mirrorXRect, "Align X axis"))
{
MirrorPose(ref grabOffset, Vector3.right);
}
if (GUI.Button(mirrorYRect, "Align Y axis"))
{
MirrorPose(ref grabOffset, Vector3.up);
}
if (GUI.Button(mirrorZRect, "Align Z axis"))
{
MirrorPose(ref grabOffset, Vector3.forward);
}
}
private void DrawPoseOffset(ref Rect rect)
{
SerializedProperty srcPos = grabOffset.FindPropertyRelative("sourcePosition");
SerializedProperty srcRot = grabOffset.FindPropertyRelative("sourceRotation");
SerializedProperty tgtPos = grabOffset.FindPropertyRelative("targetPosition");
SerializedProperty tgtRot = grabOffset.FindPropertyRelative("targetRotation");
AddElementHeight(ref rect);
EditorGUI.Vector3Field(rect, "Position Offset", tgtPos.vector3Value - srcPos.vector3Value);
AddElementHeight(ref rect);
Vector3 rotEulerAngles = (Quaternion.Inverse(srcRot.quaternionValue) * tgtRot.quaternionValue).eulerAngles;
for (int i = 0; i < 3; i++)
{
if (rotEulerAngles[i] > 180)
{
rotEulerAngles[i] = 360.0f - rotEulerAngles[i];
}
}
EditorGUI.Vector3Field(rect, "Rotation Offset", rotEulerAngles);
}
private void DrawFineTune(ref Rect rect, int index)
{
AddElementHeight(ref rect);
Rect labelRect = new Rect(rect.x, rect.y, EditorGUIUtility.labelWidth, rect.height);
EditorGUI.PrefixLabel(labelRect, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Fine Tune"));
Rect previewRect = new Rect(rect.x + EditorGUIUtility.labelWidth + EditorGUIUtility.standardVerticalSpacing, rect.y,
(rect.width - EditorGUIUtility.labelWidth - EditorGUIUtility.standardVerticalSpacing * 4) / 2, rect.height);
Rect updateRect = new Rect(previewRect.x + previewRect.width + EditorGUIUtility.standardVerticalSpacing, rect.y, previewRect.width, rect.height);
if (GUI.Button(previewRect, "Preview Grab Pose") && Application.isPlaying)
{
activeGrabbable = handGrabbable;
m_PreviewIndex.intValue = index;
ShowMeshHandPose();
}
GUI.enabled = activeGrabbable == handGrabbable && m_PreviewIndex.intValue == index;
if (GUI.Button(updateRect, "Update Grab Pose"))
{
UpdateGrabPose();
}
GUI.enabled = true;
}
/// <summary>
/// Convert the rotation of joints of the current hand into those of another hand.
/// </summary>
/// <param name="rotations">Rotation of joints of the current hand.</param>
private void SwitchRotations(ref SerializedProperty rotations)
{
for (int i = 0; i < rotations.arraySize; i++)
{
Quaternion rotation = rotations.GetArrayElementAtIndex(i).quaternionValue;
Quaternion newRotation = Quaternion.Euler(rotation.eulerAngles.x, -rotation.eulerAngles.y, -rotation.eulerAngles.z);
rotations.GetArrayElementAtIndex(i).quaternionValue = newRotation;
}
}
/// <summary>
/// Mirrors the pose properties (position and rotation) of a serialized object along a specified mirror axis.
/// </summary>
/// <param name="pose">The serialized property representing the pose to be mirrored.</param>
/// <param name="mirrorAxis">The axis along which the mirroring should occur.</param>
private void MirrorPose(ref SerializedProperty pose, Vector3 mirrorAxis)
{
Vector3 sourcePosition = grabOffset.FindPropertyRelative("sourcePosition").vector3Value;
Quaternion sourceRotation = grabOffset.FindPropertyRelative("sourceRotation").quaternionValue;
Vector3 targetPosition = grabOffset.FindPropertyRelative("targetPosition").vector3Value;
Quaternion targetRotation = grabOffset.FindPropertyRelative("targetRotation").quaternionValue;
Vector3 reflectNormal = targetRotation * mirrorAxis;
Vector3 diffPos = sourcePosition - targetPosition;
Vector3 mirrorPosition = targetPosition + Vector3.Reflect(diffPos, reflectNormal);
pose.FindPropertyRelative("sourcePosition").vector3Value = mirrorPosition;
Vector3 sourceForward = sourceRotation * Vector3.forward;
Vector3 sourceUp = sourceRotation * Vector3.up;
Quaternion mirroredRotation = Quaternion.LookRotation(Vector3.Reflect(sourceForward, reflectNormal), Vector3.Reflect(sourceUp, reflectNormal));
pose.FindPropertyRelative("sourceRotation").quaternionValue = mirroredRotation;
}
/// <summary>
/// Obtain the MeshHand and set its position and rotation based on the grabOffset of grabpose.
/// </summary>
private void ShowMeshHandPose()
{
HandPose handPose = HandPoseProvider.GetHandPose(isLeft.boolValue ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
if (handPose != null && handPose is MeshHandPose meshHandPose)
{
GrabOffset grabOffsetObj = handGrabbable.grabPoses[m_PreviewIndex.intValue].grabOffset;
Quaternion handRot = handGrabbable.transform.rotation * Quaternion.Inverse(grabOffsetObj.rotOffset);
Quaternion handRotDiff = handRot * Quaternion.Inverse(grabOffsetObj.sourceRotation);
Vector3 handPos = handGrabbable.transform.position - handRotDiff * grabOffsetObj.posOffset;
meshHandPose.SetJointPose(JointType.Wrist, new Pose(handPos, handRot));
foreach (JointType joint in Enum.GetValues(typeof(JointType)))
{
if (joint == JointType.Wrist || joint == JointType.Count) { continue; }
meshHandPose.GetPosition(joint, out Vector3 pos, local: true);
Quaternion rot = recordedGrabRotations.GetArrayElementAtIndex((int)joint).quaternionValue;
meshHandPose.SetJointPose(joint, new Pose(pos, rot), local: true);
}
}
}
/// <summary>
/// Update the grabpose based on position and rotation of the MeshHand and Object.
/// </summary>
private void UpdateGrabPose()
{
HandPose handPose = HandPoseProvider.GetHandPose(isLeft.boolValue ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
if (handPose != null && handPose is MeshHandPose meshHandPose)
{
meshHandPose.GetPosition(JointType.Wrist, out Vector3 wristPosition);
meshHandPose.GetRotation(JointType.Wrist, out Quaternion wristRotation);
Quaternion[] fingerJointRotation = new Quaternion[(int)JointType.Count];
for (int i = 0; i < fingerJointRotation.Length; i++)
{
meshHandPose.GetRotation((JointType)i, out Quaternion jointRotation, local: true);
fingerJointRotation[i] = jointRotation;
}
GrabPose grabPose = handGrabbable.grabPoses[m_PreviewIndex.intValue];
grabPose.Update(grabPoseName.stringValue, fingerJointRotation, isLeft.boolValue);
grabPose.grabOffset.Update(wristPosition, wristRotation, handGrabbable.transform.position, handGrabbable.transform.rotation);
handGrabbable.grabPoses[m_PreviewIndex.intValue] = grabPose;
GrabbablePoseRecorder.SaveChanges();
}
}
}
}
#endif

View File

@@ -0,0 +1,37 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
[CustomEditor(typeof(HandGrabInteractor))]
public class HandGrabInteractorEditor : UnityEditor.Editor
{
private SerializedProperty m_Handedness, m_GrabDistance, m_OnBeginGrab, m_OnEndGrab;
private bool showEvent = false;
private void OnEnable()
{
m_Handedness = serializedObject.FindProperty("m_Handedness");
m_GrabDistance = serializedObject.FindProperty("m_GrabDistance");
m_OnBeginGrab = serializedObject.FindProperty("m_OnBeginGrab");
m_OnEndGrab = serializedObject.FindProperty("m_OnEndGrab");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_Handedness);
EditorGUILayout.PropertyField(m_GrabDistance);
showEvent = EditorGUILayout.Foldout(showEvent, "Grab Event");
if (showEvent)
{
EditorGUILayout.PropertyField(m_OnBeginGrab);
EditorGUILayout.PropertyField(m_OnEndGrab);
}
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ba0bbb9482b5a5d479cf11f2253cdc3e
guid: a8e7c213fed939e4b859638cde97a31c
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,85 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
[CustomEditor(typeof(HandMeshManager))]
public class HandMeshManagerEditor : Editor
{
private HandMeshManager m_HandMesh;
private SerializedProperty m_Handedness, m_EnableCollider, m_HandJoints;
private bool showJoints = false;
public static readonly GUIContent findJoints = EditorGUIUtility.TrTextContent("Find Joints");
public static readonly GUIContent clearJoints = EditorGUIUtility.TrTextContent("All Clear");
private void OnEnable()
{
m_HandMesh = target as HandMeshManager;
m_Handedness = serializedObject.FindProperty("m_Handedness");
m_EnableCollider = serializedObject.FindProperty("m_EnableCollider");
m_HandJoints = serializedObject.FindProperty("m_HandJoints");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("Please check if your model is used to bind left hand poses", MessageType.None);
EditorGUILayout.PropertyField(m_Handedness, new GUIContent("Handedness"));
EditorGUILayout.HelpBox("Please check if you want the hand model with collision enabled.", MessageType.None);
EditorGUILayout.PropertyField(m_EnableCollider, new GUIContent("Enable Collider"));
showJoints = EditorGUILayout.Foldout(showJoints, "Hand Bones Reference");
if (showJoints)
{
EditorGUILayout.HelpBox("Please change rotation to make sure your model should palm faces forward and fingers points up in global axis.", MessageType.Info);
using (new EditorGUILayout.HorizontalScope())
{
using (new EditorGUI.DisabledScope())
{
if (GUILayout.Button(findJoints))
{
m_HandMesh.FindJoints();
}
}
using (new EditorGUI.DisabledScope())
{
if (GUILayout.Button(clearJoints))
{
m_HandMesh.ClearJoints();
}
}
}
bool isDetected = false;
for (int i = 0; i < m_HandJoints.arraySize; i++)
{
SerializedProperty bone = m_HandJoints.GetArrayElementAtIndex(i);
Transform boneTransform = (Transform)bone.objectReferenceValue;
if (boneTransform != null)
{
isDetected = true;
break;
}
}
if (isDetected)
{
for (int i = 0; i < m_HandJoints.arraySize; i++)
{
SerializedProperty bone = m_HandJoints.GetArrayElementAtIndex(i);
EditorGUILayout.PropertyField(bone, new GUIContent(((JointType)i).ToString()));
}
}
}
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3744e08fd384e154c82d53686f0c82a6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 44fadaef2720de846af62a7bbb2ec370
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,72 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!91 &9100000
AnimatorController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: LeftHandGrab
serializedVersion: 5
m_AnimatorParameters: []
m_AnimatorLayers:
- serializedVersion: 5
m_Name: Base Layer
m_StateMachine: {fileID: 9113736214857964471}
m_Mask: {fileID: 0}
m_Motions: []
m_Behaviours: []
m_BlendingMode: 0
m_SyncedLayerIndex: -1
m_DefaultWeight: 0
m_IKPass: 0
m_SyncedLayerAffectsTiming: 0
m_Controller: {fileID: 9100000}
--- !u!1102 &83218441301297147
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: LeftHandGrab
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: 44fadaef2720de846af62a7bbb2ec370, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1107 &9113736214857964471
AnimatorStateMachine:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Base Layer
m_ChildStates:
- serializedVersion: 1
m_State: {fileID: 83218441301297147}
m_Position: {x: 200, y: 0, z: 0}
m_ChildStateMachines: []
m_AnyStateTransitions: []
m_EntryTransitions: []
m_StateMachineTransitions: {}
m_StateMachineBehaviours: []
m_AnyStatePosition: {x: 50, y: 20, z: 0}
m_EntryPosition: {x: 50, y: 120, z: 0}
m_ExitPosition: {x: 800, y: 120, z: 0}
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
m_DefaultState: {fileID: 83218441301297147}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1504c5149524a8a44a3854dfd4f94d1e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 9100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a10ddbfcbce64f84787e026f4fc003a4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,72 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1107 &-3394168293143139300
AnimatorStateMachine:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Base Layer
m_ChildStates:
- serializedVersion: 1
m_State: {fileID: 3187830036533893518}
m_Position: {x: 200, y: 0, z: 0}
m_ChildStateMachines: []
m_AnyStateTransitions: []
m_EntryTransitions: []
m_StateMachineTransitions: {}
m_StateMachineBehaviours: []
m_AnyStatePosition: {x: 50, y: 20, z: 0}
m_EntryPosition: {x: 50, y: 120, z: 0}
m_ExitPosition: {x: 800, y: 120, z: 0}
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
m_DefaultState: {fileID: 3187830036533893518}
--- !u!91 &9100000
AnimatorController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: RightHandGrab
serializedVersion: 5
m_AnimatorParameters: []
m_AnimatorLayers:
- serializedVersion: 5
m_Name: Base Layer
m_StateMachine: {fileID: -3394168293143139300}
m_Mask: {fileID: 0}
m_Motions: []
m_Behaviours: []
m_BlendingMode: 0
m_SyncedLayerIndex: -1
m_DefaultWeight: 0
m_IKPass: 0
m_SyncedLayerAffectsTiming: 0
m_Controller: {fileID: 9100000}
--- !u!1102 &3187830036533893518
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: RightHandGrab
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: a10ddbfcbce64f84787e026f4fc003a4, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 950573398b7eb5149bea536bfa6107ca
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 9100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -699,9 +699,8 @@ GameObject:
serializedVersion: 6
m_Component:
- component: {fileID: 4806409459047702211}
- component: {fileID: 1039589470112983840}
- component: {fileID: 1039589470112983849}
- component: {fileID: 1039589470112983855}
- component: {fileID: 1039589470112983840}
- component: {fileID: 1039589470112983852}
m_Layer: 0
m_Name: VIVEXRHandGrabberLeft
@@ -726,22 +725,6 @@ Transform:
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1039589470112983840
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4806409459047702212}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a3155365f073fdb45ba7a61887f8cf06, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Handedness: 1
m_GrabDistance: 0.03
m_EnableCollider: 1
colliderManager: {fileID: 1039589470112983855}
--- !u!114 &1039589470112983849
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -755,9 +738,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Handedness: 1
m_HandGrabber: {fileID: 1039589470112983840}
m_RootJointType: 1
m_HandRootJoint: {fileID: 5303072143640256792}
m_EnableCollider: 1
m_HandJoints:
- {fileID: 5188698556818484879}
- {fileID: 5303072143640256792}
@@ -785,7 +766,7 @@ MonoBehaviour:
- {fileID: 8396010436565412298}
- {fileID: 235556365879058843}
- {fileID: 950065069935064886}
--- !u!114 &1039589470112983855
--- !u!114 &1039589470112983840
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -794,10 +775,43 @@ MonoBehaviour:
m_GameObject: {fileID: 4806409459047702212}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b9b8d210c92da6a49ac85755f7b15cbb, type: 3}
m_Script: {fileID: 11500000, guid: a3155365f073fdb45ba7a61887f8cf06, type: 3}
m_Name:
m_EditorClassIdentifier:
jointManager: {fileID: 1039589470112983849}
m_Handedness: 1
m_GrabDistance: 0.03
m_OnBeginGrab:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 1039589470112983849}
m_TargetAssemblyTypeName: VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandMeshManager,
VIVE.OpenXR
m_MethodName: OnHandBeginGrab
m_Mode: 0
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
m_OnEndGrab:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 1039589470112983849}
m_TargetAssemblyTypeName: VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandMeshManager,
VIVE.OpenXR
m_MethodName: OnHandEndGrab
m_Mode: 0
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!114 &1039589470112983852
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -810,12 +824,12 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: fd5957dc7b39bd249885b5bb53749b7a, type: 3}
m_Name:
m_EditorClassIdentifier:
m_GrabGesture:
thumbPose: 0
indexPose: 0
middlePose: 0
ringPose: 0
pinkyPose: 0
animationClip: {fileID: 7400000, guid: 44fadaef2720de846af62a7bbb2ec370, type: 2}
thumbBending: 1
indexBending: 1
middleBending: 1
ringBending: 1
pinkyBending: 1
--- !u!1 &5494989479935978129
GameObject:
m_ObjectHideFlags: 0

View File

@@ -142,7 +142,7 @@ Transform:
- {fileID: 480636851454228699}
m_Father: {fileID: 9030882590547856196}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 60, y: 0, z: 0}
--- !u!1 &371782450477550846
GameObject:
m_ObjectHideFlags: 0
@@ -211,7 +211,7 @@ Transform:
- {fileID: 1087333469102448396}
m_Father: {fileID: 2530992191103151071}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 105, y: 0, z: 0}
--- !u!1 &867837920217133379
GameObject:
m_ObjectHideFlags: 0
@@ -274,7 +274,7 @@ Transform:
- {fileID: 6884329196261524271}
m_Father: {fileID: 5474254061592058453}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 82.94443, y: 0, z: 0}
--- !u!1 &1093720478279676966
GameObject:
m_ObjectHideFlags: 0
@@ -306,7 +306,7 @@ Transform:
- {fileID: 3049281197821631965}
m_Father: {fileID: 1486325851925083500}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 105, y: 0, z: 0}
--- !u!1 &1096893291684292564
GameObject:
m_ObjectHideFlags: 0
@@ -338,7 +338,7 @@ Transform:
- {fileID: 1486325851925083500}
m_Father: {fileID: 7523515941797842363}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 0, y: -5, z: 0}
--- !u!1 &1525779470575164424
GameObject:
m_ObjectHideFlags: 0
@@ -370,7 +370,7 @@ Transform:
- {fileID: 2500751938805992760}
m_Father: {fileID: 7301969967702472780}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 105, y: 0, z: 0}
--- !u!1 &1707378898674977318
GameObject:
m_ObjectHideFlags: 0
@@ -433,7 +433,7 @@ Transform:
- {fileID: 4902734759167959098}
m_Father: {fileID: 6519278044083438900}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 80, y: -1, z: 0}
--- !u!1 &2548084297442148389
GameObject:
m_ObjectHideFlags: 0
@@ -498,7 +498,7 @@ Transform:
- {fileID: 2530992191103151071}
m_Father: {fileID: 7523515941797842363}
m_RootOrder: 4
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 0, y: 10, z: 0}
--- !u!1 &2826201843829277550
GameObject:
m_ObjectHideFlags: 0
@@ -530,7 +530,7 @@ Transform:
- {fileID: 6157606951436879209}
m_Father: {fileID: 3722484432303427184}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 80, y: 3, z: 0}
--- !u!1 &3500162954800955667
GameObject:
m_ObjectHideFlags: 0
@@ -593,7 +593,7 @@ Transform:
- {fileID: 5479178590596712992}
m_Father: {fileID: 1749534150074822731}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 60, y: 0, z: 0}
--- !u!1 &3763232688899205820
GameObject:
m_ObjectHideFlags: 0
@@ -625,7 +625,7 @@ Transform:
- {fileID: 1749534150074822731}
m_Father: {fileID: 6712021276771197211}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 80, y: 3, z: 0}
--- !u!1 &5145593096174785248
GameObject:
m_ObjectHideFlags: 0
@@ -657,7 +657,7 @@ Transform:
- {fileID: 5474254061592058453}
m_Father: {fileID: 7523515941797842363}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 41.8069, y: -11.3199, z: 103.867}
--- !u!1 &5587360065563961240
GameObject:
m_ObjectHideFlags: 0
@@ -721,7 +721,7 @@ Transform:
- {fileID: 2069893865991323087}
m_Father: {fileID: 8818665000486502236}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 105, y: 0, z: 0}
--- !u!1 &6561246079134113306
GameObject:
m_ObjectHideFlags: 0
@@ -784,7 +784,7 @@ Transform:
- {fileID: 9030882590547856196}
m_Father: {fileID: 565333190440153845}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 80, y: 0, z: 0}
--- !u!1 &6983350266902418966
GameObject:
m_ObjectHideFlags: 0
@@ -816,7 +816,7 @@ Transform:
- {fileID: 3233556780283288469}
m_Father: {fileID: 6157606951436879209}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 60, y: 0, z: 0}
--- !u!1 &7056793026468759881
GameObject:
m_ObjectHideFlags: 0
@@ -848,7 +848,7 @@ Transform:
- {fileID: 484802358358613714}
m_Father: {fileID: 4902734759167959098}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 60, y: 0, z: 0}
--- !u!1 &7152668487518777765
GameObject:
m_ObjectHideFlags: 0
@@ -858,9 +858,8 @@ GameObject:
serializedVersion: 6
m_Component:
- component: {fileID: 7152668487518777764}
- component: {fileID: 3431167848623168881}
- component: {fileID: 3431167848623168894}
- component: {fileID: 3431167848623168893}
- component: {fileID: 3431167848623168881}
- component: {fileID: 3431167848623168882}
m_Layer: 0
m_Name: VIVEXRHandGrabberRight
@@ -885,22 +884,6 @@ Transform:
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &3431167848623168881
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7152668487518777765}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a3155365f073fdb45ba7a61887f8cf06, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Handedness: 0
m_GrabDistance: 0.03
m_EnableCollider: 1
colliderManager: {fileID: 3431167848623168893}
--- !u!114 &3431167848623168894
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -914,9 +897,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Handedness: 0
m_HandGrabber: {fileID: 3431167848623168881}
m_RootJointType: 1
m_HandRootJoint: {fileID: 7523515941797842363}
m_EnableCollider: 1
m_HandJoints:
- {fileID: 7633984271078933036}
- {fileID: 7523515941797842363}
@@ -944,7 +925,7 @@ MonoBehaviour:
- {fileID: 6157606951436879209}
- {fileID: 2500751938805992760}
- {fileID: 3233556780283288469}
--- !u!114 &3431167848623168893
--- !u!114 &3431167848623168881
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -953,10 +934,43 @@ MonoBehaviour:
m_GameObject: {fileID: 7152668487518777765}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b9b8d210c92da6a49ac85755f7b15cbb, type: 3}
m_Script: {fileID: 11500000, guid: a3155365f073fdb45ba7a61887f8cf06, type: 3}
m_Name:
m_EditorClassIdentifier:
jointManager: {fileID: 3431167848623168894}
m_Handedness: 0
m_GrabDistance: 0.03
m_OnBeginGrab:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 3431167848623168894}
m_TargetAssemblyTypeName: VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandMeshManager,
VIVE.OpenXR
m_MethodName: OnHandBeginGrab
m_Mode: 0
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
m_OnEndGrab:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 3431167848623168894}
m_TargetAssemblyTypeName: VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandMeshManager,
VIVE.OpenXR
m_MethodName: OnHandEndGrab
m_Mode: 0
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!114 &3431167848623168882
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -969,12 +983,12 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: fd5957dc7b39bd249885b5bb53749b7a, type: 3}
m_Name:
m_EditorClassIdentifier:
m_GrabGesture:
thumbPose: 0
indexPose: 0
middlePose: 0
ringPose: 0
pinkyPose: 0
animationClip: {fileID: 7400000, guid: a10ddbfcbce64f84787e026f4fc003a4, type: 2}
thumbBending: 1
indexBending: 1
middleBending: 1
ringBending: 1
pinkyBending: 1
--- !u!1 &7904580642040219186
GameObject:
m_ObjectHideFlags: 0
@@ -1006,7 +1020,7 @@ Transform:
- {fileID: 7301969967702472780}
m_Father: {fileID: 7523515941797842363}
m_RootOrder: 5
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 0, y: 15, z: 0}
--- !u!1 &8086845646762513573
GameObject:
m_ObjectHideFlags: 0
@@ -1038,7 +1052,7 @@ Transform:
- {fileID: 1021821948623702842}
m_Father: {fileID: 2947514034224435963}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_LocalEulerAnglesHint: {x: 11.60709, y: 0, z: 0}
--- !u!1 &8136935140357987531
GameObject:
m_ObjectHideFlags: 0

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ea1b2b1a3faba024aa3df5391529aec0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,378 @@
// "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.Collections.Generic;
using System.Text;
using System;
using UnityEditor;
using UnityEngine;
using System.Linq;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
/// <summary>
/// This class is designed to edit grab gestures.
/// </summary>
[RequireComponent(typeof(HandMeshManager))]
public class CustomGrabPose : MonoBehaviour
{
#if UNITY_EDITOR
#region Log
const string LOG_TAG = "Wave.Essence.Hand.Interaction.CustomGrabPose";
private StringBuilder m_sb = null;
internal StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
private void DEBUG(StringBuilder msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(StringBuilder msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(StringBuilder msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
int logFrame = 0;
bool printIntervalLog => logFrame == 0;
#endregion
/// <summary>
/// This structure is designed to record the rotation values of each joint at different bend angles.
/// </summary>
private struct JointBendingRotation
{
public FingerId fingerId;
public string jointPath;
public int bending;
public Quaternion rotation;
public JointBendingRotation(FingerId in_FingerId, string in_JointPath, int in_Bending, Quaternion in_Rotation)
{
fingerId = in_FingerId;
jointPath = in_JointPath;
bending = in_Bending;
rotation = in_Rotation;
}
public JointBendingRotation Identity => new JointBendingRotation(FingerId.Invalid, "", -1, Quaternion.identity);
}
[SerializeField]
private AnimationClip animationClip;
[SerializeField]
[Range(1, 30)]
private int thumbBending = 1;
[SerializeField]
[Range(1, 30)]
private int indexBending = 1;
[SerializeField]
[Range(1, 30)]
private int middleBending = 1;
[SerializeField]
[Range(1, 30)]
private int ringBending = 1;
[SerializeField]
[Range(1, 30)]
private int pinkyBending = 1;
private HandMeshManager m_HandMesh;
private Dictionary<FingerId, int> fingersBendingMapping = new Dictionary<FingerId, int>()
{
{FingerId.Thumb, 1 },
{FingerId.Index, 1 },
{FingerId.Middle, 1 },
{FingerId.Ring, 1 },
{FingerId.Pinky, 1 },
};
private static readonly Dictionary<string, FingerId> jointsPathMapping = new Dictionary<string, FingerId>()
{
{"WaveBone_0", FingerId.Invalid },
{"WaveBone_1", FingerId.Invalid },
{"WaveBone_2", FingerId.Thumb },
{"WaveBone_2/WaveBone_3", FingerId.Thumb },
{"WaveBone_2/WaveBone_3/WaveBone_4", FingerId.Thumb },
{"WaveBone_2/WaveBone_3/WaveBone_4/WaveBone_5", FingerId.Thumb },
{"WaveBone_6", FingerId.Index },
{"WaveBone_6/WaveBone_7", FingerId.Index },
{"WaveBone_6/WaveBone_7/WaveBone_8", FingerId.Index },
{"WaveBone_6/WaveBone_7/WaveBone_8/WaveBone_9", FingerId.Index },
{"WaveBone_6/WaveBone_7/WaveBone_8/WaveBone_9/WaveBone_10", FingerId.Index },
{"WaveBone_11", FingerId.Middle },
{"WaveBone_11/WaveBone_12", FingerId.Middle },
{"WaveBone_11/WaveBone_12/WaveBone_13", FingerId.Middle },
{"WaveBone_11/WaveBone_12/WaveBone_13/WaveBone_14", FingerId.Middle },
{"WaveBone_11/WaveBone_12/WaveBone_13/WaveBone_14/WaveBone_15", FingerId.Middle },
{"WaveBone_16", FingerId.Ring },
{"WaveBone_16/WaveBone_17", FingerId.Ring },
{"WaveBone_16/WaveBone_17/WaveBone_18", FingerId.Ring },
{"WaveBone_16/WaveBone_17/WaveBone_18/WaveBone_19", FingerId.Ring },
{"WaveBone_16/WaveBone_17/WaveBone_18/WaveBone_19/WaveBone_20", FingerId.Ring },
{"WaveBone_21", FingerId.Pinky },
{"WaveBone_21/WaveBone_22", FingerId.Pinky },
{"WaveBone_21/WaveBone_22/WaveBone_23", FingerId.Pinky },
{"WaveBone_21/WaveBone_22/WaveBone_23/WaveBone_24", FingerId.Pinky },
{"WaveBone_21/WaveBone_22/WaveBone_23/WaveBone_24/WaveBone_25", FingerId.Pinky },
};
private List<JointBendingRotation> jointsBending = new List<JointBendingRotation>();
private readonly float k_GrabDistance = 0.1f;
private HandGrabInteractable candidate = null;
private Pose wristPose = Pose.identity;
private Quaternion[] fingerJointRotation = new Quaternion[jointsPathMapping.Count];
#region MonoBehaviours
private void OnEnable()
{
m_HandMesh = transform.GetComponent<HandMeshManager>();
if (m_HandMesh == null)
{
sb.Clear().Append("Failed to find HandMeshRenderer.");
ERROR(sb);
}
if (animationClip != null)
{
EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(animationClip);
foreach (string propertyName in jointsPathMapping.Keys)
{
SetEachFrameRotation(curveBindings, propertyName);
}
}
else
{
sb.Clear().Append("Failed to find hand grab animation. The hand model will not change when you change bend angles of finger.");
sb.Append("However, you still can record grab pose if you use direct preview mode.");
WARNING(sb);
}
}
private void OnDisable()
{
jointsBending.Clear();
}
private void Update()
{
if (IsFingerBendingUpdated())
{
for (int i = 0; i < fingerJointRotation.Length; i++)
{
var jointInfo = jointsPathMapping.ElementAt(i);
string jointPath = jointInfo.Key;
FingerId fingerId = jointInfo.Value;
int bending = -1;
if (fingersBendingMapping.ContainsKey(fingerId))
{
bending = fingersBendingMapping[fingerId] - 1;
}
if (jointsBending.Count(x => x.fingerId == fingerId && x.jointPath == jointPath && x.bending == bending) > 0)
{
JointBendingRotation jointRotation = jointsBending.FirstOrDefault(x => x.fingerId == fingerId && x.jointPath == jointPath && x.bending == bending);
fingerJointRotation[i] = jointRotation.rotation;
}
else
{
fingerJointRotation[i] = Quaternion.identity;
}
}
if (m_HandMesh != null)
{
for (int i = 0; i < fingerJointRotation.Length; i++)
{
JointType joint = (JointType)i;
m_HandMesh.GetJointPositionAndRotation(joint, out Vector3 jointPosition, out _, local: true);
m_HandMesh.SetJointPositionAndRotation(joint, jointPosition, fingerJointRotation[i], local: true);
}
}
}
if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter))
{
FindNearInteractable();
SavePoseWithCandidate();
}
}
#endregion
/// <summary>
/// Reads the rotation of each joint frame by frame and records it.
/// </summary>
/// <param name="curveBindings">All the float curve bindings currently stored in the clip.</param>
/// <param name="jointPath">The path of the joint.</param>
private void SetEachFrameRotation(EditorCurveBinding[] curveBindings, string jointPath)
{
const int propertyCount = 4;
const int animeCount = 30;
const float animeFPS = 60.0f;
List<EditorCurveBinding> matchCurve = new List<EditorCurveBinding>();
foreach (EditorCurveBinding binding in curveBindings)
{
if (binding.path.Equals(jointPath))
{
matchCurve.Add(binding);
}
if (matchCurve.Count == propertyCount)
{
break;
}
}
if (matchCurve.Count == propertyCount)
{
for (int i = 0; i < animeCount; i++)
{
Quaternion rotation = Quaternion.identity;
foreach (var curveBinding in matchCurve)
{
AnimationCurve curve = AnimationUtility.GetEditorCurve(animationClip, curveBinding);
switch (curveBinding.propertyName)
{
case "m_LocalRotation.x":
rotation.x = curve.Evaluate(i / animeFPS);
break;
case "m_LocalRotation.y":
rotation.y = curve.Evaluate(i / animeFPS);
break;
case "m_LocalRotation.z":
rotation.z = curve.Evaluate(i / animeFPS);
break;
case "m_LocalRotation.w":
rotation.w = curve.Evaluate(i / animeFPS);
break;
}
}
jointsBending.Add(new JointBendingRotation(jointsPathMapping[jointPath], jointPath, i, rotation));
}
}
}
/// <summary>
/// Checks if the current finger bend angle has changed.
/// </summary>
/// <returns>True if the bend angle has changed; otherwise, false.</returns>
private bool IsFingerBendingUpdated()
{
bool updated = false;
if (fingersBendingMapping[FingerId.Thumb] != thumbBending)
{
fingersBendingMapping[FingerId.Thumb] = thumbBending;
updated = true;
}
if (fingersBendingMapping[FingerId.Index] != indexBending)
{
fingersBendingMapping[FingerId.Index] = indexBending;
updated = true;
}
if (fingersBendingMapping[FingerId.Middle] != middleBending)
{
fingersBendingMapping[FingerId.Middle] = middleBending;
updated = true;
}
if (fingersBendingMapping[FingerId.Ring] != ringBending)
{
fingersBendingMapping[FingerId.Ring] = ringBending;
updated = true;
}
if (fingersBendingMapping[FingerId.Pinky] != pinkyBending)
{
fingersBendingMapping[FingerId.Pinky] = pinkyBending;
updated = true;
}
return updated;
}
/// <summary>
/// Update hand pose from HandMeshRenderer.
/// </summary>
/// <returns>Return true if updating hand pose from HandMeshRenderer; otherwise.</returns>
private bool UpdateHandPose()
{
bool updated = false;
if (m_HandMesh != null)
{
for (int i = 0; i < fingerJointRotation.Length; i++)
{
if (i == (int)JointType.Wrist)
{
m_HandMesh.GetJointPositionAndRotation(JointType.Wrist, out wristPose.position, out wristPose.rotation);
}
m_HandMesh.GetJointPositionAndRotation((JointType)i, out _, out fingerJointRotation[i], local: true);
}
updated = true;
}
if (!updated)
{
sb.Clear().Append("Failed to update hand pose.");
DEBUG(sb);
}
return updated;
}
/// <summary>
/// Finds the nearest interactable object to the hand.
/// </summary>
public void FindNearInteractable()
{
if (!UpdateHandPose()) { return; }
candidate = null;
float maxScore = 0;
foreach (HandGrabInteractable interactable in GrabManager.handGrabbables)
{
float distanceScore = interactable.CalculateDistanceScore(wristPose.position, k_GrabDistance);
if (distanceScore > maxScore)
{
maxScore = distanceScore;
candidate = interactable;
}
}
if (candidate == null)
{
sb.Clear().Append("Unable to find a suitable candidate.");
WARNING(sb);
}
}
/// <summary>
/// Save the position and rotation offset with the candidate.
/// </summary>
public void SavePoseWithCandidate()
{
if (!UpdateHandPose() || candidate == null) { return; }
Quaternion[] clone = new Quaternion[fingerJointRotation.Length];
Array.Copy(fingerJointRotation, clone, fingerJointRotation.Length);
GrabPose grabPose = GrabPose.Identity;
grabPose.Update($"Grab Pose {candidate.grabPoses.Count + 1}", clone, m_HandMesh.isLeft);
grabPose.grabOffset = new GrabOffset(wristPose.position, wristPose.rotation, candidate.transform.position, candidate.transform.rotation);
if (!candidate.grabPoses.Contains(grabPose))
{
candidate.grabPoses.Add(grabPose);
}
GrabbablePoseRecorder.SaveChanges();
sb.Clear().Append("Save grab pose successfully.");
DEBUG(sb);
}
#endif
}
}

View File

@@ -12,6 +12,7 @@ using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
@@ -61,6 +62,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
grabbable.grabPoses.AddRange(updatedGrabPose);
}
}
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
}
File.Delete(filepath);
File.Delete(metaFilepath);

View File

@@ -8,7 +8,9 @@
// 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;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
@@ -18,6 +20,26 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// </summary>
public class HandGrabInteractable : MonoBehaviour, IHandGrabbable
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandGrabInteractable";
private StringBuilder m_sb = null;
internal StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
int logFrame = 0;
bool printIntervalLog => logFrame == 0;
#endregion
#region Interface Implement
private HandGrabInteractor m_Grabber = null;
public IGrabber grabber => m_Grabber;
@@ -28,30 +50,47 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
private bool m_IsGrabbable = true;
public bool isGrabbable { get { return m_IsGrabbable; } set { m_IsGrabbable = value; } }
[SerializeField]
private bool m_ForceMovable = true;
public bool forceMovable { get { return m_ForceMovable; } set { m_ForceMovable = value; } }
[SerializeField]
private FingerRequirement m_FingerRequirement;
public FingerRequirement fingerRequirement => m_FingerRequirement;
[SerializeField]
HandGrabbableEvent m_OnBeginGrabbed = new HandGrabbableEvent();
public HandGrabbableEvent onBeginGrabbed => m_OnBeginGrabbed;
[SerializeField]
HandGrabbableEvent m_OnEndGrabbed = new HandGrabbableEvent();
public HandGrabbableEvent onEndGrabbed => m_OnEndGrabbed;
#endregion
#region Public State
[SerializeField]
private Rigidbody m_Rigidbody = null;
public new Rigidbody rigidbody => m_Rigidbody;
[SerializeField]
private List<GrabPose> m_GrabPoses = new List<GrabPose>();
public List<GrabPose> grabPoses => m_GrabPoses;
public GrabPose bestGrabPose => bestGrabPoseId != -1 ? m_GrabPoses[bestGrabPoseId] : GrabPose.Identity;
private GrabPose m_BestGrabPose = GrabPose.Identity;
public GrabPose bestGrabPose => m_BestGrabPose;
#endregion
[SerializeField]
private bool m_ShowAllIndicator = false;
private List<Collider> allColliders = new List<Collider>();
private HandGrabInteractor closestGrabber = null;
private int bestGrabPoseId = -1;
private OnBeginGrabbed onBeginGrabbed;
private OnEndGrabbed onEndGrabbed;
private OnBeginGrabbed beginGrabbed;
private OnEndGrabbed endGrabbed;
[SerializeField]
private IOneHandContraintMovement m_OneHandContraintMovement;
public bool isContraint => m_OneHandContraintMovement != null;
[SerializeField]
private int m_PreviewIndex = -1;
#region MonoBehaviour
private void Awake()
@@ -78,18 +117,24 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// <param name="grabber">The grabber to set.</param>
public void SetGrabber(IGrabber grabber)
{
if (grabber is HandGrabInteractor)
if (grabber is HandGrabInteractor handGrabber)
{
HandGrabInteractor handGrabber = grabber as HandGrabInteractor;
m_Grabber = handGrabber;
UpdateBestGrabPose(handGrabber.isLeft, handGrabber.handGrabState.GetJointPose(JointType.Palm));
onBeginGrabbed?.Invoke(this);
HandPose handPose = HandPoseProvider.GetHandPose(handGrabber.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
UpdateBestGrabPose(handGrabber.isLeft, new Pose(wristPos, wristRot));
beginGrabbed?.Invoke(this);
m_OnBeginGrabbed?.Invoke(this);
DEBUG($"{transform.name} is grabbed by {handGrabber.name}");
}
else
{
m_Grabber = null;
bestGrabPoseId = -1;
onEndGrabbed?.Invoke(this);
m_BestGrabPose = GrabPose.Identity;
endGrabbed?.Invoke(this);
m_OnEndGrabbed?.Invoke(this);
DEBUG($"{transform.name} is released.");
}
}
@@ -109,7 +154,10 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
}
else
{
int index = FindBestGrabPose(grabber.isLeft, grabber.handGrabState.GetJointPose((int)JointType.Palm));
HandPose handPose = HandPoseProvider.GetHandPose(grabber.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
int index = FindBestGrabPose(grabber.isLeft, new Pose(wristPos, wristRot));
ShowIndicatorByIndex(index);
}
}
@@ -141,36 +189,60 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// Add a listener for the event triggered when the grabbable object is grabbed.
/// </summary>
/// <param name="handler">The method to be called when the grabbable object is grabbed.</param>
[Obsolete("Please use onBeginGrabbed instead.")]
public void AddBeginGrabbedListener(OnBeginGrabbed handler)
{
onBeginGrabbed += handler;
beginGrabbed += handler;
}
/// <summary>
/// Remove a listener for the event triggered when the grabbable object is grabbed.
/// </summary>
/// <param name="handler">The method to be removed from the event listeners.</param>
[Obsolete("Please use onBeginGrabbed instead.")]
public void RemoveBeginGrabbedListener(OnBeginGrabbed handler)
{
onBeginGrabbed -= handler;
beginGrabbed -= handler;
}
/// <summary>
/// Add a listener for the event triggered when the grabbable object is released.
/// </summary>
/// <param name="handler">The method to be called when the grabbable object is released.</param>
[Obsolete("Please use onEndGrabbed instead.")]
public void AddEndGrabbedListener(OnEndGrabbed handler)
{
onEndGrabbed += handler;
endGrabbed += handler;
}
/// <summary>
/// Remove a listener for the event triggered when the grabbable object is released.
/// </summary>
/// <param name="handler">The method to be removed from the event listeners.</param>
[Obsolete("Please use onEndGrabbed instead.")]
public void RemoveEndGrabbedListener(OnEndGrabbed handler)
{
onEndGrabbed -= handler;
endGrabbed -= handler;
}
/// <summary>
/// Update the position and rotation of the self with the pose of the hand that is grabbing it.
/// </summary>
/// <param name="grabberPose">The pose of the hand.</param>
public void UpdatePositionAndRotation(Pose grabberPose)
{
if (m_OneHandContraintMovement == null)
{
Quaternion handRotDiff = grabberPose.rotation * Quaternion.Inverse(m_BestGrabPose.grabOffset.sourceRotation);
transform.position = grabberPose.position + handRotDiff * m_BestGrabPose.grabOffset.posOffset;
transform.rotation = grabberPose.rotation * m_BestGrabPose.grabOffset.rotOffset;
}
if (m_OneHandContraintMovement != null)
{
m_OneHandContraintMovement.UpdatePose(grabberPose);
UpdateMeshPoseByGrabbable();
}
}
#endregion
@@ -179,6 +251,13 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// </summary>
private void Initialize()
{
if (m_OneHandContraintMovement != null)
{
m_OneHandContraintMovement.Initialize(this);
onBeginGrabbed.AddListener(m_OneHandContraintMovement.OnBeginGrabbed);
onEndGrabbed.AddListener(m_OneHandContraintMovement.OnEndGrabbed);
}
for (int i = 0; i < m_GrabPoses.Count; i++)
{
if (m_GrabPoses[i].indicator.enableIndicator || m_ShowAllIndicator)
@@ -190,7 +269,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
else
{
GrabPose grabPose = m_GrabPoses[i];
grabPose.indicator.CalculateGrabOffset(transform);
grabPose.indicator.CalculateGrabOffset(transform.position, transform.rotation);
m_GrabPoses[i] = grabPose;
}
}
@@ -205,16 +284,12 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
private void AutoGenerateIndicator(int index)
{
AutoGenIndicator autoGenIndicator = new GameObject($"Indicator {index}", typeof(AutoGenIndicator)).GetComponent<AutoGenIndicator>();
GrabPose grabPose = m_GrabPoses[index];
// The grabPose.grabOffset was calculated as the position of the object minus the position of the hand,
// so inverse calculation is needed here to infer the position of the hand.
Vector3 offset = transform.rotation * Quaternion.Inverse(grabPose.grabOffset.targetRotation) * -grabPose.grabOffset.position;
Vector3 defaultPosition = transform.position + offset;
Vector3 closestPoint = GetClosestPoint(defaultPosition);
autoGenIndicator.SetPose(closestPoint, offset.normalized);
Pose handPose = CalculateActualHandPose(grabPose.grabOffset);
Vector3 closestPoint = GetClosestPoint(handPose.position);
autoGenIndicator.SetPose(closestPoint, closestPoint - transform.position);
grabPose.indicator.Update(true, true, autoGenIndicator.gameObject);
grabPose.indicator.CalculateGrabOffset(transform);
grabPose.indicator.CalculateGrabOffset(transform.position, transform.rotation);
m_GrabPoses[index] = grabPose;
}
@@ -239,10 +314,10 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
if (hit.collider == collider)
{
float hitDistnace = Vector3.Distance(sourcePos, hit.point);
if (distance > hitDistnace)
float hitDistance = Vector3.Distance(sourcePos, hit.point);
if (distance > hitDistance)
{
distance = hitDistnace;
distance = hitDistance;
closePoint = hit.point;
}
}
@@ -264,16 +339,27 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// <param name="isLeft">Whether the grabber is the left hand.</param>
/// <param name="grabberPose">The pose of the grabber.</param>
/// <returns>True if a best grab pose is found; otherwise, false.</returns>
private bool UpdateBestGrabPose(bool isLeft, Pose grabberPose)
private void UpdateBestGrabPose(bool isLeft, Pose grabberPose)
{
int index = FindBestGrabPose(isLeft, grabberPose);
if (index != -1 && index < m_GrabPoses.Count)
{
bestGrabPoseId = index;
return true;
m_BestGrabPose = m_GrabPoses[index];
}
else
{
m_BestGrabPose.grabOffset = new GrabOffset(grabberPose.position, grabberPose.rotation, transform.position, transform.rotation);
HandPose handPose = HandPoseProvider.GetHandPose(isLeft ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
if (handPose is MeshHandPose meshHandPose)
{
Quaternion[] grabRotations = new Quaternion[(int)JointType.Count];
for (int i = 0; i < (int)JointType.Count; i++)
{
meshHandPose.GetRotation((JointType)i, out grabRotations[i], local: true);
}
m_BestGrabPose.recordedGrabRotations = grabRotations;
}
}
index = -1;
return false;
}
/// <summary>
@@ -291,7 +377,8 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
if (m_GrabPoses[i].isLeft == isLeft)
{
Vector3 grabDirection = transform.rotation * Quaternion.Inverse(m_GrabPoses[i].grabOffset.targetRotation) * -m_GrabPoses[i].grabOffset.position;
Pose handPose = CalculateActualHandPose(m_GrabPoses[i].grabOffset);
Vector3 grabDirection = handPose.position - transform.position;
float dot = Vector3.Dot(currentDirection.normalized, grabDirection.normalized);
if (dot > maxDot)
{
@@ -316,7 +403,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
if (index >= 0 && index < m_GrabPoses.Count &&
m_GrabPoses[index].indicator.enableIndicator)
{
m_GrabPoses[index].indicator.UpdatePositionAndRotation(transform);
m_GrabPoses[index].indicator.UpdatePositionAndRotation(transform.position, transform.rotation);
m_GrabPoses[index].indicator.SetActive(true);
}
}
@@ -335,10 +422,31 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
if (grabPose.isLeft == isLeft)
{
grabPose.indicator.UpdatePositionAndRotation(transform);
grabPose.indicator.UpdatePositionAndRotation(transform.position, transform.rotation);
grabPose.indicator.SetActive(true);
}
}
}
private void UpdateMeshPoseByGrabbable()
{
if (grabber is HandGrabInteractor handGrabber)
{
HandPose handPose = HandPoseProvider.GetHandPose(handGrabber.isLeft ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
if (handPose != null && handPose is MeshHandPose meshHandPose)
{
Pose realHandPose = CalculateActualHandPose(m_BestGrabPose.grabOffset);
meshHandPose.SetJointPose(JointType.Wrist, realHandPose);
}
}
}
private Pose CalculateActualHandPose(GrabOffset grabOffset)
{
Quaternion handRot = transform.rotation * Quaternion.Inverse(grabOffset.rotOffset);
Quaternion handRotDiff = handRot * Quaternion.Inverse(grabOffset.sourceRotation);
Vector3 handPos = transform.position - handRotDiff * grabOffset.posOffset;
return new Pose(handPos, handRot);
}
}
}

View File

@@ -8,6 +8,8 @@
// conditions signed by you and all SDK and API requirements,
// specifications, and documentation provided by HTC to You."
using System;
using System.Text;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
@@ -17,6 +19,26 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// </summary>
public class HandGrabInteractor : MonoBehaviour, IHandGrabber
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandGrabInteractor";
private StringBuilder m_sb = null;
internal StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
int logFrame = 0;
bool printIntervalLog => logFrame == 0;
#endregion
private enum GrabState
{
None,
@@ -40,43 +62,23 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
private float m_GrabDistance = 0.03f;
public float grabDistance { get { return m_GrabDistance; } set { m_GrabDistance = value; } }
[SerializeField]
private bool m_EnableCollider = true;
public bool enableCollider
{
get { return m_EnableCollider; }
set
{
m_EnableCollider = value;
if (colliderManager != null)
{
colliderManager.EnableCollider(m_EnableCollider);
}
}
}
public bool isLeft => handedness == Handedness.Left;
#endregion
[SerializeField]
private GrabColliderManager colliderManager;
private HandGrabberEvent m_OnBeginGrab = new HandGrabberEvent();
public HandGrabberEvent onBeginGrab => m_OnBeginGrab;
[SerializeField]
private HandGrabberEvent m_OnEndGrab = new HandGrabberEvent();
public HandGrabberEvent onEndGrab => m_OnEndGrab;
#endregion
private readonly float MinGrabScore = 0.25f;
private readonly float MinDistanceScore = 0.25f;
private HandGrabInteractable currentCaidate = null;
private GrabPose grabPose = GrabPose.Identity;
private HandGrabInteractable currentCandidate = null;
private GrabState m_State = GrabState.None;
private Pose[] fingerTipPoses => new Pose[]
{
m_HandGrabState.GetJointPose(JointType.Thumb_Tip),
m_HandGrabState.GetJointPose(JointType.Index_Tip),
m_HandGrabState.GetJointPose(JointType.Middle_Tip),
m_HandGrabState.GetJointPose(JointType.Ring_Tip),
m_HandGrabState.GetJointPose(JointType.Pinky_Tip),
};
private Pose palmPose => m_HandGrabState.GetJointPose(JointType.Palm);
private Pose[] frozenPoses = new Pose[(int)JointType.Count];
private bool isFrozen = false;
private Pose wristPose = Pose.identity;
private Vector3[] fingerTipPosition = new Vector3[(int)FingerId.Count];
private OnBeginGrab beginGrabHandler;
private OnEndGrab endGrabHandler;
@@ -86,45 +88,36 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
m_HandGrabState = new HandGrabState(isLeft);
}
private void Start()
{
if (colliderManager != null)
{
colliderManager.EnableCollider(m_EnableCollider);
}
}
private void OnEnable()
{
GrabManager.RegisterGrabber(this);
if (colliderManager != null)
{
colliderManager.AddImmovableCollisionListener(FreezeHandPose);
}
}
private void OnDisable()
{
GrabManager.UnregisterGrabber(this);
if (colliderManager != null)
{
colliderManager.RemoveImmovableCollisionListener(FreezeHandPose);
}
}
private void Update()
{
m_HandGrabState.UpdateState();
if (isFrozen)
HandPose handPose = HandPoseProvider.GetHandPose(isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
if (handPose != null)
{
return;
handPose.GetPosition(JointType.Wrist, out wristPose.position);
handPose.GetRotation(JointType.Wrist, out wristPose.rotation);
handPose.GetPosition(JointType.Thumb_Tip, out fingerTipPosition[(int)FingerId.Thumb]);
handPose.GetPosition(JointType.Index_Tip, out fingerTipPosition[(int)FingerId.Index]);
handPose.GetPosition(JointType.Middle_Tip, out fingerTipPosition[(int)FingerId.Middle]);
handPose.GetPosition(JointType.Ring_Tip, out fingerTipPosition[(int)FingerId.Ring]);
handPose.GetPosition(JointType.Pinky_Tip, out fingerTipPosition[(int)FingerId.Pinky]);
}
if (m_State != GrabState.Grabbing)
{
FindCandidate();
}
switch (m_State)
{
case GrabState.None:
@@ -142,51 +135,15 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
#region Public Interface
/// <summary>
/// Get the current joint pose of the grabber.
/// Checks if the specified joint type is required.
/// </summary>
/// <param name="jointId">The id of the joint for which to get the pose.</param>
/// <returns>The current pose of the specified joint.</returns>
public Pose GetCurrentJointPose(int jointId)
{
if (isFrozen)
{
return frozenPoses[jointId];
}
return m_HandGrabState.GetJointPose(jointId);
}
/// <summary>
/// Get the rotation of the joint in the grab pose.
/// </summary>
/// <param name="jointId">The id of the joint for which to get the rotation.</param>
/// <returns>The rotation of the joint in the grab pose.</returns>
public bool GetGrabPoseJointRotation(int jointId, out Quaternion rotation)
{
rotation = Quaternion.identity;
if (m_Grabbable == null) { return false; }
if (jointId >= 0 && grabPose.recordedGrabRotations.Length > jointId)
{
rotation = grabPose.recordedGrabRotations[jointId];
return true;
}
else if (grabPose.handGrabGesture != HandGrabGesture.Identity)
{
rotation = m_HandGrabState.GetDefaultJointRotationInGesture(grabPose.handGrabGesture, jointId);
return true;
}
return false;
}
/// <summary>
/// Check if the specific joint is necessary for grabbing.
/// </summary>
/// <param name="joint">JointType of the specified joint.</param>
/// <returns>Return true if this joint is needed for grabbing, otherwise false.</returns>
/// <param name="joint">The joint that need to check.</param>
/// <returns>True if the joint is required; otherwise, false.</returns>
public bool IsRequiredJoint(JointType joint)
{
if (m_Grabbable != null)
{
GetJointIndex(joint, out int group, out _);
HandData.GetJointIndex(joint, out int group, out _);
switch (group)
{
case 2: return m_Grabbable.fingerRequirement.thumb == GrabRequirement.Required;
@@ -203,6 +160,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// Add a listener for the event triggered when the grabber begins grabbing.
/// </summary>
/// <param name="handler">The method to be called when the grabber begins grabbing.</param>
[Obsolete("Please use onBeginGrab instead.")]
public void AddBeginGrabListener(OnBeginGrab handler)
{
beginGrabHandler += handler;
@@ -212,6 +170,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// Remove a listener for the event triggered when the grabber begins grabbing.
/// </summary>
/// <param name="handler">The method to be removed from the event listeners.</param>
[Obsolete("Please use onBeginGrab instead.")]
public void RemoveBeginGrabListener(OnBeginGrab handler)
{
beginGrabHandler -= handler;
@@ -221,6 +180,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// Add a listener for the event triggered when the grabber ends grabbing.
/// </summary>
/// <param name="handler">The method to be called when the grabber ends grabbing.</param>
[Obsolete("Please use onEndGrab instead.")]
public void AddEndGrabListener(OnEndGrab handler)
{
endGrabHandler += handler;
@@ -230,6 +190,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// Remove a listener for the event triggered when the grabber ends grabbing.
/// </summary>
/// <param name="handler">The method to be removed from the event listeners.</param>
[Obsolete("Please use onEndGrab instead.")]
public void RemoveEndGrabListener(OnEndGrab handler)
{
endGrabHandler -= handler;
@@ -241,19 +202,20 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// </summary>
private void FindCandidate()
{
currentCandidate = null;
float distanceScore = float.MinValue;
if (GetClosestGrabbable(m_GrabDistance, out HandGrabInteractable grabbable, out float score) && score > distanceScore)
{
distanceScore = score;
currentCaidate = grabbable;
currentCandidate = grabbable;
}
if (currentCaidate != null)
if (currentCandidate != null)
{
float grabScore = Grab.CalculateHandGrabScore(this, currentCaidate);
float grabScore = Grab.CalculateHandGrabScore(this, currentCandidate);
if (distanceScore < MinDistanceScore || grabScore < MinGrabScore)
{
currentCaidate = null;
currentCandidate = null;
}
}
}
@@ -272,9 +234,10 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
foreach (HandGrabInteractable interactable in GrabManager.handGrabbables)
{
interactable.ShowIndicator(false, this);
foreach (Pose fingerTipPose in fingerTipPoses)
foreach (Vector3 tipPos in fingerTipPosition)
{
float distanceScore = interactable.CalculateDistanceScore(fingerTipPose.position, grabDistance);
float distanceScore = interactable.CalculateDistanceScore(tipPos, grabDistance);
if (distanceScore > maxScore)
{
maxScore = distanceScore;
@@ -294,7 +257,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// </summary>
private void NoneUpdate()
{
if (currentCaidate != null)
if (currentCandidate != null)
{
m_State = GrabState.Hover;
}
@@ -305,27 +268,22 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// </summary>
private void HoverUpdate()
{
if (currentCaidate == null)
if (currentCandidate == null)
{
m_State = GrabState.None;
return;
}
if (Grab.HandBeginGrab(this, currentCaidate))
if (Grab.HandBeginGrab(this, currentCandidate))
{
m_State = GrabState.Grabbing;
m_Grabbable = currentCaidate;
m_Grabbable = currentCandidate;
m_Grabbable.SetGrabber(this);
m_Grabbable.ShowIndicator(false, this);
grabPose = m_Grabbable.bestGrabPose;
if (grabPose == GrabPose.Identity)
{
Vector3 posOffset = m_Grabbable.transform.position - palmPose.position;
Quaternion rotOffset = palmPose.rotation;
grabPose.grabOffset = new GrabOffset(m_Grabbable.transform.position, m_Grabbable.transform.rotation, posOffset, rotOffset);
}
beginGrabHandler?.Invoke(this);
onBeginGrab?.Invoke(this);
DEBUG($"The {(m_Handedness == Handedness.Left ? "left" : "right")} hand begins to grab the {m_Grabbable.name}");
}
}
@@ -336,55 +294,16 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
if (Grab.HandDoneGrab(this, m_Grabbable) || !Grab.HandIsGrabbing(this, m_Grabbable))
{
DEBUG($"The {(m_Handedness == Handedness.Left ? "left" : "right")} hand ends to grab the {m_Grabbable.name}");
endGrabHandler?.Invoke(this);
onEndGrab?.Invoke(this);
m_Grabbable.SetGrabber(null);
m_Grabbable = null;
m_State = GrabState.Hover;
endGrabHandler?.Invoke(this);
return;
}
Quaternion handRotOffset = palmPose.rotation * Quaternion.Inverse(grabPose.grabOffset.rotation);
Vector3 currentPos = palmPose.position + handRotOffset * grabPose.grabOffset.position;
Quaternion currentRot = handRotOffset * grabPose.grabOffset.targetRotation;
m_Grabbable.transform.SetPositionAndRotation(currentPos, currentRot);
}
/// <summary>
/// Freezes or unfreezes the hand pose.
/// </summary>
/// <param name="enable">True to freeze the hand pose; False to unfreeze.</param>
private void FreezeHandPose(bool enable)
{
isFrozen = enable;
if (isFrozen)
{
for (int i = 0; i < frozenPoses.Length; i++)
{
frozenPoses[i] = m_HandGrabState.GetJointPose(i);
}
}
}
/// <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>
private 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;
m_Grabbable.UpdatePositionAndRotation(wristPose);
}
}
}

View File

@@ -0,0 +1,148 @@
using System.Text;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
public class OneGrabMoveConstraint : IOneHandContraintMovement
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.OneGrabMoveConstraint";
private StringBuilder m_sb = null;
internal StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
int logFrame = 0;
bool printIntervalLog => logFrame == 0;
#endregion
[SerializeField]
private Transform m_Constraint;
[SerializeField]
private ConstraintInfo m_NegativeXMove = ConstraintInfo.Identity;
private float defaultNegativeXPos = 0.0f;
[SerializeField]
private ConstraintInfo m_PositiveXMove = ConstraintInfo.Identity;
private float defaultPositiveXPos = 0.0f;
[SerializeField]
private ConstraintInfo m_NegativeYMove = ConstraintInfo.Identity;
private float defaultNegativeYPos = 0.0f;
[SerializeField]
private ConstraintInfo m_PositiveYMove = ConstraintInfo.Identity;
private float defaultPositiveYPos = 0.0f;
[SerializeField]
private ConstraintInfo m_NegativeZMove = ConstraintInfo.Identity;
private float defaultNegativeZPos = 0.0f;
[SerializeField]
private ConstraintInfo m_PositiveZMove = ConstraintInfo.Identity;
private float defaultPositiveZPos = 0.0f;
private Pose previousHandPose = Pose.identity;
private GrabPose currentGrabPose = GrabPose.Identity;
public override void Initialize(IGrabbable grabbable)
{
if (grabbable is HandGrabInteractable handGrabbable)
{
if (m_Constraint == null)
{
m_Constraint = handGrabbable.transform;
WARNING("Since no constraint object is set, self will be used as the constraint object.");
}
}
if (m_NegativeXMove.enableConstraint) { defaultNegativeXPos = m_Constraint.position.x - m_NegativeXMove.value; }
if (m_PositiveXMove.enableConstraint) { defaultPositiveXPos = m_Constraint.position.x + m_PositiveXMove.value; }
if (m_NegativeYMove.enableConstraint) { defaultNegativeYPos = m_Constraint.position.y - m_NegativeYMove.value; }
if (m_PositiveYMove.enableConstraint) { defaultPositiveYPos = m_Constraint.position.y + m_PositiveYMove.value; }
if (m_NegativeZMove.enableConstraint) { defaultNegativeZPos = m_Constraint.position.z - m_NegativeZMove.value; }
if (m_PositiveZMove.enableConstraint) { defaultPositiveZPos = m_Constraint.position.z + m_PositiveZMove.value; }
}
public override void OnBeginGrabbed(IGrabbable grabbable)
{
if (grabbable is HandGrabInteractable handGrabbable)
{
currentGrabPose = handGrabbable.bestGrabPose;
}
if (grabbable.grabber is HandGrabInteractor handGrabber)
{
HandPose handPose = HandPoseProvider.GetHandPose(handGrabber.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
previousHandPose = new Pose(wristPos, wristRot);
}
}
public override void UpdatePose(Pose handPose)
{
if (previousHandPose == Pose.identity)
{
previousHandPose = handPose;
return;
}
Quaternion previousRotOffset = previousHandPose.rotation * Quaternion.Inverse(currentGrabPose.grabOffset.sourceRotation);
Vector3 previousPos = previousHandPose.position + previousRotOffset * currentGrabPose.grabOffset.posOffset;
Quaternion currentRotOffset = handPose.rotation * Quaternion.Inverse(currentGrabPose.grabOffset.sourceRotation);
Vector3 currentPos = handPose.position + currentRotOffset * currentGrabPose.grabOffset.posOffset;
Vector3 handOffset = currentPos - previousPos;
if (m_NegativeXMove.enableConstraint)
{
float x = (m_Constraint.position + handOffset).x;
x = Mathf.Max(defaultNegativeXPos, x);
m_Constraint.position = new Vector3(x, m_Constraint.position.y, m_Constraint.position.z);
}
if (m_PositiveXMove.enableConstraint)
{
float x = (m_Constraint.position + handOffset).x;
x = Mathf.Min(defaultPositiveXPos, x);
m_Constraint.position = new Vector3(x, m_Constraint.position.y, m_Constraint.position.z);
}
if (m_NegativeYMove.enableConstraint)
{
float y = (m_Constraint.position + handOffset).y;
y = Mathf.Max(defaultNegativeYPos, y);
m_Constraint.position = new Vector3(m_Constraint.position.x, y, m_Constraint.position.z);
}
if (m_PositiveYMove.enableConstraint)
{
float y = (m_Constraint.position + handOffset).y;
y = Mathf.Min(defaultPositiveYPos, y);
m_Constraint.position = new Vector3(m_Constraint.position.x, y, m_Constraint.position.z);
}
if (m_NegativeZMove.enableConstraint)
{
float z = (m_Constraint.position + handOffset).z;
z = Mathf.Max(defaultNegativeZPos, z);
m_Constraint.position = new Vector3(m_Constraint.position.x, m_Constraint.position.y, z);
}
if (m_PositiveZMove.enableConstraint)
{
float z = (m_Constraint.position + handOffset).z;
z = Mathf.Min(defaultPositiveZPos, z);
m_Constraint.position = new Vector3(m_Constraint.position.x, m_Constraint.position.y, z);
}
previousHandPose = handPose;
}
public override void OnEndGrabbed(IGrabbable grabbable)
{
currentGrabPose = GrabPose.Identity;
previousHandPose = Pose.identity;
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b9b8d210c92da6a49ac85755f7b15cbb
guid: 6d521c71dbb8287409daf6ef81005d79
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,123 @@
using System.Text;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
public class OneGrabRotateConstraint : IOneHandContraintMovement
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.OneGrabRotateConstraint";
private StringBuilder m_sb = null;
internal StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
int logFrame = 0;
bool printIntervalLog => logFrame == 0;
#endregion
private enum RotationAxis
{
XAxis = 0,
YAxis = 1,
ZAxis = 2,
}
[SerializeField]
private Transform m_Constraint;
[SerializeField]
private Transform m_Pivot;
[SerializeField]
private RotationAxis m_RotationAxis = RotationAxis.XAxis;
[SerializeField]
private ConstraintInfo m_ClockwiseAngle = ConstraintInfo.Identity;
[SerializeField]
private ConstraintInfo m_CounterclockwiseAngle = ConstraintInfo.Identity;
private float totalRotationAngle = 0.0f;
private Pose previousHandPose = Pose.identity;
public override void Initialize(IGrabbable grabbable)
{
if (grabbable is HandGrabInteractable handGrabbable)
{
if (m_Constraint == null)
{
m_Constraint = handGrabbable.transform;
WARNING("Since no constraint object is set, self will be used as the constraint object.");
}
if (m_Pivot == null)
{
m_Pivot = handGrabbable.transform;
WARNING("Since no pivot is set, self will be used as the pivot.");
}
}
}
public override void OnBeginGrabbed(IGrabbable grabbable)
{
if (grabbable.grabber is HandGrabInteractor handGrabber)
{
HandPose handPose = HandPoseProvider.GetHandPose(handGrabber.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
handPose.GetPosition(JointType.Wrist, out Vector3 wristPos);
handPose.GetRotation(JointType.Wrist, out Quaternion wristRot);
previousHandPose = new Pose(wristPos, wristRot);
}
}
public override void UpdatePose(Pose handPose)
{
if (previousHandPose == Pose.identity)
{
previousHandPose = handPose;
return;
}
Vector3 axis = Vector3.zero;
switch (m_RotationAxis)
{
case RotationAxis.XAxis: axis = Vector3.right; break;
case RotationAxis.YAxis: axis = Vector3.up; break;
case RotationAxis.ZAxis: axis = Vector3.forward; break;
}
Vector3 worldAxis = m_Pivot.TransformDirection(axis);
Vector3 previousOffset = previousHandPose.position - m_Pivot.position;
Vector3 previousVector = Vector3.ProjectOnPlane(previousOffset, worldAxis);
Vector3 targetOffset = handPose.position - m_Pivot.position;
Vector3 targetVector = Vector3.ProjectOnPlane(targetOffset, worldAxis);
float angleDelta = Vector3.Angle(previousVector, targetVector);
angleDelta *= Vector3.Dot(Vector3.Cross(previousVector, targetVector), worldAxis) > 0.0f ? 1.0f : -1.0f;
float previousAngle = totalRotationAngle;
totalRotationAngle += angleDelta;
if (m_CounterclockwiseAngle.enableConstraint)
{
totalRotationAngle = Mathf.Max(totalRotationAngle, -m_CounterclockwiseAngle.value);
}
if (m_ClockwiseAngle.enableConstraint)
{
totalRotationAngle = Mathf.Min(totalRotationAngle, m_ClockwiseAngle.value);
}
angleDelta = totalRotationAngle - previousAngle;
m_Constraint.RotateAround(m_Pivot.position, worldAxis, angleDelta);
previousHandPose = handPose;
}
public override void OnEndGrabbed(IGrabbable grabbable)
{
previousHandPose = Pose.identity;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f25051021522804d90dc5448b7657a2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,371 @@
// "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.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
public class HandColliderController : MonoBehaviour
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.HandColliderController";
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
int logFrame = 0;
bool printIntervalLog => logFrame == 0;
#endregion
private HandMeshManager m_HandMesh;
public HandMeshManager handMesh { get { return m_HandMesh; } set { m_HandMesh = value; } }
private bool m_EnableCollider = true;
public bool enableCollider
{
get { return m_EnableCollider; }
set
{
m_EnableCollider = value;
rootJoint.gameObject.SetActive(m_EnableCollider);
}
}
#region Name Definition
// The order of joint name MUST align with runtime's definition
private readonly string[] JointsName = new string[]
{
"WaveBone_0", // WVR_HandJoint_Palm = 0
"WaveBone_1", // WVR_HandJoint_Wrist = 1
"WaveBone_2", // WVR_HandJoint_Thumb_Joint0 = 2
"WaveBone_3", // WVR_HandJoint_Thumb_Joint1 = 3
"WaveBone_4", // WVR_HandJoint_Thumb_Joint2 = 4
"WaveBone_5", // WVR_HandJoint_Thumb_Tip = 5
"WaveBone_6", // WVR_HandJoint_Index_Joint0 = 6
"WaveBone_7", // WVR_HandJoint_Index_Joint1 = 7
"WaveBone_8", // WVR_HandJoint_Index_Joint2 = 8
"WaveBone_9", // WVR_HandJoint_Index_Joint3 = 9
"WaveBone_10", // WVR_HandJoint_Index_Tip = 10
"WaveBone_11", // WVR_HandJoint_Middle_Joint0 = 11
"WaveBone_12", // WVR_HandJoint_Middle_Joint1 = 12
"WaveBone_13", // WVR_HandJoint_Middle_Joint2 = 13
"WaveBone_14", // WVR_HandJoint_Middle_Joint3 = 14
"WaveBone_15", // WVR_HandJoint_Middle_Tip = 15
"WaveBone_16", // WVR_HandJoint_Ring_Joint0 = 16
"WaveBone_17", // WVR_HandJoint_Ring_Joint1 = 17
"WaveBone_18", // WVR_HandJoint_Ring_Joint2 = 18
"WaveBone_19", // WVR_HandJoint_Ring_Joint3 = 19
"WaveBone_20", // WVR_HandJoint_Ring_Tip = 20
"WaveBone_21", // WVR_HandJoint_Pinky_Joint0 = 21
"WaveBone_22", // WVR_HandJoint_Pinky_Joint0 = 22
"WaveBone_23", // WVR_HandJoint_Pinky_Joint0 = 23
"WaveBone_24", // WVR_HandJoint_Pinky_Joint0 = 24
"WaveBone_25" // WVR_HandJoint_Pinky_Tip = 25
};
#endregion
private float handMass = 1.0f;
private JointCollider[] jointsCollider = new JointCollider[(int)JointType.Count];
private Transform rootJoint = null;
private Transform rootJointParent = null;
private Rigidbody rootJointRigidbody = null;
private Vector3 lastRootPos;
private Quaternion lastRotation;
private bool isInit = false;
private bool isTracked = true;
private List<Vector3> collisionDirections = new List<Vector3>();
private bool isGrabbing = false;
#region MonoBehaviour
private void OnEnable()
{
StartCoroutine(WaitForInit());
}
private void OnDisable()
{
if (rootJoint != null)
{
JointCollider jointCollider = rootJoint.GetComponent<JointCollider>();
if (jointCollider != null)
{
jointCollider.RemoveJointCollisionListener(OnJointCollision);
}
Destroy(rootJoint.gameObject);
}
isInit = false;
}
private void Start()
{
JointCollider[] fullJointsCollider = FindObjectsOfType<JointCollider>(true);
for (int i = 0; i < fullJointsCollider.Length - 1; i++)
{
for (int j = i + 1; j < fullJointsCollider.Length; j++)
{
Physics.IgnoreCollision(fullJointsCollider[i].Collider, fullJointsCollider[j].Collider);
}
}
}
private void Update()
{
if (!isInit) { return; }
HandPose handPose = HandPoseProvider.GetHandPose(m_HandMesh.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
if (isTracked != handPose.IsTracked())
{
isTracked = handPose.IsTracked();
ChangeTrackingState(isTracked);
}
if (!isTracked) { return; }
for (int i = 0; i < jointsCollider.Length; i++)
{
if (jointsCollider[i] == null) { continue; }
handPose.GetPosition((JointType)i, out Vector3 jointPosition);
handPose.GetRotation((JointType)i, out Quaternion jointRotation);
if (i == (int)JointType.Wrist)
{
if (lastRootPos == Vector3.zero || lastRotation == Quaternion.identity)
{
rootJoint.localPosition = jointPosition;
rootJoint.localRotation = jointRotation;
}
lastRootPos = jointPosition;
lastRotation = jointRotation;
}
jointsCollider[i].transform.rotation = rootJointParent.rotation * jointRotation;
}
if (!isGrabbing)
{
m_HandMesh.SetJointPositionAndRotation(JointType.Wrist, rootJoint.position, rootJoint.rotation);
}
}
private void FixedUpdate()
{
if (lastRootPos == Vector3.zero || lastRotation == Quaternion.identity) { return; }
if (isGrabbing)
{
rootJointRigidbody.velocity = Vector3.zero;
rootJointRigidbody.angularVelocity = Vector3.zero;
rootJoint.localPosition = lastRootPos;
rootJoint.localRotation = lastRotation;
}
else
{
UpdateVelocity();
UpdateAngularVelocity();
}
}
#endregion
private IEnumerator WaitForInit()
{
yield return new WaitUntil(() => m_HandMesh != null);
if (rootJoint != null)
{
JointCollider jointCollider = rootJoint.GetComponent<JointCollider>();
jointCollider.AddJointCollisionListener(OnJointCollision);
rootJointRigidbody = jointCollider.InitRigidbody(handMass);
isInit = true;
DEBUG("Hand Collider init successfully.");
}
}
/// <summary>
/// Initializes by copying the structure of the hand mesh and sequentially adding joint collider.
/// </summary>
public void InitJointColliders(Transform handRootJoint)
{
rootJointParent = handRootJoint.parent;
rootJoint = Instantiate(handRootJoint, rootJointParent);
rootJoint.name = handRootJoint.name;
List<Transform> totalTransforms = new List<Transform>() { rootJoint };
GetAllChildrenTransforms(rootJoint, ref totalTransforms);
for (int i = 0; i < (int)JointType.Count; i++)
{
Transform target = totalTransforms.FirstOrDefault(x => x.name == JointsName[i]);
if (target != null)
{
JointCollider jointCollider = target.gameObject.AddComponent<JointCollider>();
jointCollider.SetJointId(i);
jointsCollider[i] = jointCollider;
}
}
}
private void GetAllChildrenTransforms(Transform parent, ref List<Transform> childrenTransforms)
{
foreach (Transform child in parent)
{
childrenTransforms.Add(child);
GetAllChildrenTransforms(child, ref childrenTransforms);
}
}
/// <summary>
/// Updates the velocity of a Rigidbody.
/// </summary>
private void UpdateVelocity()
{
Vector3 vel = (lastRootPos - rootJoint.position) / Time.deltaTime;
if (IsValidVelocity(vel))
{
if (collisionDirections.Count > 0)
{
float minAngle = float.MaxValue;
Vector3 closestDirection = Vector3.zero;
foreach (Vector3 direction in collisionDirections.ToList())
{
float angle = Mathf.Abs(Vector3.Angle(direction, vel));
if (angle < minAngle)
{
minAngle = angle;
closestDirection = direction;
}
}
collisionDirections.Clear();
Vector3 adjustedDirection = closestDirection;
if (Vector3.Dot(vel, closestDirection) > 0)
{
adjustedDirection *= -1f;
}
vel = Vector3.ProjectOnPlane(vel, adjustedDirection);
if (vel.magnitude > 1)
{
vel.Normalize();
}
}
rootJointRigidbody.velocity = vel;
}
}
/// <summary>
/// Updates the angularVelocity of a Rigidbody.
/// </summary>
private void UpdateAngularVelocity()
{
Quaternion diffRotation = Quaternion.Inverse(rootJoint.rotation) * lastRotation;
diffRotation.ToAngleAxis(out float angleInDegree, out Vector3 rotationAxis);
Vector3 angVel = (angleInDegree * rotationAxis * Mathf.Deg2Rad) / Time.deltaTime;
if (IsValidVelocity(angVel))
{
rootJointRigidbody.angularVelocity = angVel;
}
}
/// <summary>
/// Checks if the vector is valid.
/// </summary>
/// <param name="vector">The vector to be checked.</param>
/// <returns>rue if the vector is valid; otherwise, false.</returns>
private bool IsValidVelocity(Vector3 vector)
{
return !float.IsNaN(vector.x) && !float.IsNaN(vector.y) && !float.IsNaN(vector.z)
&& !float.IsInfinity(vector.x) && !float.IsInfinity(vector.y) && !float.IsInfinity(vector.z);
}
#region Event CallBack
/// <summary>
/// When tracking state changing, reset the pose and enable/disable collider.
/// </summary>
/// <param name="isTracked">The bool that tracking state.</param>
private void ChangeTrackingState(bool isTracked)
{
if (!isTracked)
{
lastRootPos = Vector3.zero;
lastRotation = Quaternion.identity;
rootJointRigidbody.velocity = Vector3.zero;
rootJointRigidbody.angularVelocity = Vector3.zero;
}
foreach (JointCollider jointCollider in jointsCollider)
{
jointCollider.Collider.enabled = m_EnableCollider && isTracked;
}
}
/// <summary>
/// Called when the hand begins grabbing an object.
/// </summary>
/// <param name="grabber">The grabber that grabbing something.</param>
public void OnHandBeginGrab(IGrabber grabber)
{
EnableKinematic(true);
isGrabbing = true;
}
/// <summary>
/// Called when the hand ends grabbing an object.
/// </summary>
/// <param name="grabber">The grabber that releasing something.</param>
public void OnHandEndGrab(IGrabber grabber)
{
EnableKinematic(false);
isGrabbing = false;
}
/// <summary>
/// Enable/Disable the rigidbody of hand collider.
/// </summary>
/// <param name="enable">The bool that enable or disable the rigidbody.</param>
private void EnableKinematic(bool enable)
{
if (rootJointRigidbody != null)
{
rootJointRigidbody.isKinematic = enable;
}
}
/// <summary>
/// Called when a joint collider collides with another object.
/// </summary>
/// <param name="collision">The collision data.</param>
/// <param name="state">The state of the collision.</param>
private void OnJointCollision(Collision collision, JointCollider.CollisionState state)
{
if (isGrabbing) { return; }
switch (state)
{
case JointCollider.CollisionState.Enter:
case JointCollider.CollisionState.Stay:
if (collision.contactCount > 0 && (collision.rigidbody == null || collision.rigidbody.isKinematic))
{
ContactPoint[] contactPoints = new ContactPoint[collision.contactCount];
collision.GetContacts(contactPoints);
foreach (ContactPoint contactPoint in contactPoints)
{
collisionDirections.Add(contactPoint.normal * -1f);
}
}
break;
}
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 199fd8ec06e6e7e49a3fab9a9b7e7988
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -12,6 +12,8 @@ using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
#if UNITY_XR_HANDS
using UnityEngine.XR.Hands;
@@ -779,7 +781,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// <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 bool GetDefaultJointRotationInGesture(bool isLeft, HandGrabGesture handGrabGesture, JointType joint, ref Quaternion rotation)
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);
@@ -802,7 +804,7 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// <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>
private static void GetJointIndex(JointType joint, out int group, out int index)
public static void GetJointIndex(JointType joint, out int group, out int index)
{
int jointId = (int)joint + 1;
group = 0;
@@ -1448,19 +1450,6 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
hand.GetJointRotation((JointType)jointId, ref rot);
return new Pose(pos, rot);
}
/// <summary>
/// Get the default joint rotation for a specific HandGrabGesture and joint type.
/// </summary>
/// <param name="handGrabGesture">The HandGrabGesture is a gesture where a hand grabs.</param>
/// <param name="jointId">The index of joint.</param>
/// <returns>The default rotation of the specified joint in local coordinates.</returns>
public Quaternion GetDefaultJointRotationInGesture(HandGrabGesture handGrabGesture, int jointId)
{
Quaternion rotation = Quaternion.identity;
hand.GetDefaultJointRotationInGesture(m_IsLeft, handGrabGesture, (JointType)jointId, ref rotation);
return rotation;
}
}
#region Grab Framework
@@ -1665,35 +1654,46 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
[Serializable]
public struct GrabOffset
{
[SerializeField]
public Vector3 sourcePosition;
[SerializeField]
public Quaternion sourceRotation;
[SerializeField]
public Vector3 targetPosition;
[SerializeField]
public Quaternion targetRotation;
[SerializeField]
public Vector3 position;
[SerializeField]
public Quaternion rotation;
public GrabOffset(Vector3 in_TargetPosition, Quaternion in_targetRotation, Vector3 in_Position, Quaternion in_Rotation)
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;
position = in_Position;
rotation = in_Rotation;
}
public static GrabOffset Identity => new GrabOffset(Vector3.zero, Quaternion.identity, Vector3.zero, Quaternion.identity);
public void Update(Pose OffsetPose)
public void UpdateSource(Vector3 pos, Quaternion rot)
{
position = OffsetPose.position;
rotation = OffsetPose.rotation;
sourcePosition = pos;
sourceRotation = rot;
}
public void Update(Vector3 pos, Quaternion rot)
public void UpdateTarget(Vector3 pos, Quaternion rot)
{
position = pos;
rotation = 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()
@@ -1704,16 +1704,17 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
public override bool Equals(object obj)
{
return obj is GrabOffset grabOffset &&
position == grabOffset.position &&
rotation == grabOffset.rotation;
sourcePosition == grabOffset.sourcePosition &&
sourceRotation == grabOffset.sourceRotation &&
targetPosition == grabOffset.targetPosition &&
targetRotation == grabOffset.targetRotation;
}
public override int GetHashCode()
{
return position.GetHashCode() ^ rotation.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>
@@ -1729,14 +1730,14 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
[SerializeField]
public GameObject target;
[SerializeField]
public GrabOffset grabOffSet;
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;
grabOffset = GrabOffset.Identity;
}
public static Indicator Identity => new Indicator(true, true, null);
@@ -1778,13 +1779,11 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// Calculates the grab offset based on the reference transform.
/// </summary>
/// <param name="refTransform">The reference transform used for calculation.</param>
public void CalculateGrabOffset(Transform refTransform)
public void CalculateGrabOffset(Vector3 grabbablePos, Quaternion grabbableRot)
{
if (target != null)
{
Vector3 pos = target.transform.position - refTransform.position;
Quaternion rot = Quaternion.Inverse(refTransform.rotation) * target.transform.rotation;
grabOffSet.Update(pos, rot);
grabOffset.Update(grabbablePos, grabbableRot, target.transform.position, target.transform.rotation);
}
}
@@ -1792,12 +1791,13 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
/// 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(Transform refTransform)
public void UpdatePositionAndRotation(Vector3 grabbablePos, Quaternion grabbableRot)
{
if (target != null)
{
target.transform.position = refTransform.position + refTransform.rotation * grabOffSet.position;
target.transform.rotation = refTransform.rotation * grabOffSet.rotation;
Quaternion handRotDiff = grabbableRot * Quaternion.Inverse(grabOffset.sourceRotation);
target.transform.position = grabbablePos + handRotDiff * grabOffset.posOffset;
target.transform.rotation = grabbableRot * grabOffset.rotOffset;
}
}
@@ -1807,11 +1807,11 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
enableIndicator == indicator.enableIndicator &&
autoIndicator == indicator.autoIndicator &&
target == indicator.target &&
grabOffSet == indicator.grabOffSet;
grabOffset == indicator.grabOffset;
}
public override int GetHashCode()
{
return enableIndicator.GetHashCode() ^ autoIndicator.GetHashCode() ^ target.GetHashCode() ^ grabOffSet.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);
@@ -1896,9 +1896,18 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
public static bool operator !=(GrabPose source, GrabPose target) => !(source == target);
}
[Serializable]
public class HandGrabberEvent : UnityEvent<IGrabber> { };
[Serializable]
public class HandGrabbableEvent : UnityEvent<IGrabbable> { };
[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);
/// <summary>
@@ -1908,9 +1917,16 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
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);
}
@@ -1931,11 +1947,17 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
IGrabber grabber { get; }
bool isGrabbed { get; }
bool isGrabbable { get; }
bool forceMovable { 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);
}
@@ -1947,6 +1969,33 @@ namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
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>

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e199f8a96e0fdf7498bbb0bf45e5b11a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,76 @@
using System.Linq;
using System.Text;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
public abstract class HandPose : MonoBehaviour
{
#region Log
private const string LOG_TAG = "Wave.Essence.Hand.Interaction.HandPose";
private static StringBuilder m_sb = null;
protected static StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
protected void DEBUG(string msg) { Debug.Log($"{LOG_TAG}.{m_PoseType}, {msg}"); }
protected void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}.{m_PoseType}, {msg}"); }
protected void ERROR(string msg) { Debug.LogError($"{LOG_TAG}.{m_PoseType}, {msg}"); }
#endregion
protected HandPoseType m_PoseType = HandPoseType.UNKNOWN;
protected bool m_Initialized = false;
protected bool m_IsTracked = false;
protected const int poseCount = (int)JointType.Count;
protected Vector3[] m_Position = Enumerable.Repeat(Vector3.zero, poseCount).ToArray();
protected Vector3[] m_LocalPosition = Enumerable.Repeat(Vector3.zero, poseCount).ToArray();
protected Quaternion[] m_Rotation = Enumerable.Repeat(Quaternion.identity, poseCount).ToArray();
protected Quaternion[] m_LocalRotation = Enumerable.Repeat(Quaternion.identity, poseCount).ToArray();
protected virtual void OnEnable()
{
HandPoseProvider.RegisterHandPose(m_PoseType, this);
}
protected virtual void OnDisable()
{
HandPoseProvider.UnregisterHandPose(m_PoseType);
}
public virtual void SetType(HandPoseType poseType)
{
m_PoseType = poseType;
m_Initialized = true;
}
public virtual bool IsTracked()
{
return m_IsTracked;
}
public virtual bool GetRotation(JointType joint, out Quaternion value, bool local = false)
{
value = Quaternion.identity;
if (joint != JointType.Count)
{
value = local ? m_LocalRotation[(int)joint] : m_Rotation[(int)joint];
return true;
}
return false;
}
public virtual bool GetPosition(JointType joint, out Vector3 value, bool local = false)
{
value = Vector3.zero;
if (joint != JointType.Count)
{
value = local ? m_LocalPosition[(int)joint] : m_Position[(int)joint];
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ced5448f0990b040b82475106e1b1d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
public enum HandPoseType : UInt32
{
UNKNOWN = 0x7FFFFFFF,
HAND_LEFT = 100,
HAND_RIGHT = 101,
MESH_LEFT = 200,
MESH_RIGHT = 201,
}
public static class HandPoseProvider
{
private static Dictionary<HandPoseType, HandPose> m_HandPoseMap = new Dictionary<HandPoseType, HandPose>();
public static Dictionary<HandPoseType, HandPose> HandPoseMap
{
get
{
if (m_HandPoseMap == null) { m_HandPoseMap = new Dictionary<HandPoseType, HandPose>(); }
return m_HandPoseMap;
}
private set { m_HandPoseMap = value; }
}
public static bool RegisterHandPose(in HandPoseType poseType, in HandPose handPose)
{
if (!HandPoseMap.ContainsKey(poseType))
{
HandPoseMap.Add(poseType, handPose);
return true;
}
return false;
}
public static bool UnregisterHandPose(in HandPoseType poseType)
{
if (HandPoseMap.ContainsKey(poseType))
{
HandPoseMap.Remove(poseType);
return true;
}
return false;
}
public static HandPose GetHandPose(in HandPoseType poseType)
{
if (HandPoseMap.ContainsKey(poseType))
{
return HandPoseMap[poseType];
}
if (poseType == HandPoseType.HAND_LEFT || poseType == HandPoseType.MESH_LEFT)
{
return GetDefaultHandPose("LeftHandPose", HandPoseType.HAND_LEFT);
}
else if (poseType == HandPoseType.HAND_RIGHT || poseType == HandPoseType.MESH_RIGHT)
{
return GetDefaultHandPose("RightHandPose", HandPoseType.HAND_RIGHT);
}
return null;
}
public static string Name(this HandPoseType poseType)
{
string name = "";
switch (poseType)
{
case HandPoseType.HAND_LEFT: name = "HAND_LEFT"; break;
case HandPoseType.HAND_RIGHT: name = "HAND_LEFT"; break;
case HandPoseType.MESH_LEFT: name = "MESH_LEFT"; break;
case HandPoseType.MESH_RIGHT: name = "MESH_RIGHT"; break;
}
return name;
}
private static HandPose GetDefaultHandPose(string poseName, HandPoseType poseType)
{
if (!HandPoseMap.ContainsKey(poseType))
{
GameObject handPoseObject = new GameObject(poseName);
RealHandPose realHandPose = handPoseObject.AddComponent<RealHandPose>();
realHandPose.SetType(poseType);
RegisterHandPose(poseType, realHandPose);
return realHandPose;
}
return HandPoseMap[poseType];
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 059d07a57fa09e543bf2f2b6d5788bb7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,84 @@
using System.Collections;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
public class MeshHandPose : HandPose
{
[SerializeField]
private HandMeshManager m_HandMesh;
private bool keepUpdate = false;
protected override void OnEnable()
{
StartCoroutine(WaitInit());
}
protected override void OnDisable()
{
base.OnDisable();
if (keepUpdate)
{
keepUpdate = false;
StopCoroutine(UpdatePose());
}
}
public void SetHandMeshRenderer(HandMeshManager handMeshRenderer)
{
m_HandMesh = handMeshRenderer;
SetType(handMeshRenderer.isLeft ? HandPoseType.MESH_LEFT : HandPoseType.MESH_RIGHT);
}
public bool SetJointPose(JointType joint, Pose jointPose, bool local = false)
{
if (m_HandMesh != null)
{
return m_HandMesh.SetJointPositionAndRotation(joint, jointPose.position, jointPose.rotation, local);
}
return false;
}
private IEnumerator WaitInit()
{
yield return new WaitUntil(() => m_Initialized);
base.OnEnable();
if (!keepUpdate)
{
keepUpdate = true;
StartCoroutine(UpdatePose());
}
}
private IEnumerator UpdatePose()
{
while (keepUpdate)
{
yield return new WaitForFixedUpdate();
HandPose handPose = HandPoseProvider.GetHandPose(m_HandMesh.isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
m_IsTracked = handPose.IsTracked();
for (int i = 0; i < poseCount; i++)
{
if (m_HandMesh.GetJointPositionAndRotation((JointType)i, out Vector3 position, out Quaternion rotation) &&
m_HandMesh.GetJointPositionAndRotation((JointType)i, out Vector3 localPosition, out Quaternion localRotation, local: true))
{
m_Position[i] = position;
m_Rotation[i] = rotation;
m_LocalPosition[i] = localPosition;
m_LocalRotation[i] = localRotation;
}
else
{
m_Position[i] = Vector3.zero;
m_Rotation[i] = Quaternion.identity;
m_LocalPosition[i] = Vector3.zero;
m_LocalRotation[i] = Quaternion.identity;
}
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5e78e2d8c82d5584faaf4b66d40d1057
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,85 @@
using System.Collections;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
public class RealHandPose : HandPose
{
[SerializeField]
private Handedness m_Handedness;
private bool isLeft => m_Handedness == Handedness.Left;
private bool keepUpdate = false;
protected override void OnEnable()
{
StartCoroutine(WaitInit());
}
protected override void OnDisable()
{
base.OnDisable();
if (keepUpdate)
{
keepUpdate = false;
StopCoroutine(UpdatePose());
}
}
public override void SetType(HandPoseType poseType)
{
if (poseType == HandPoseType.HAND_LEFT)
{
m_Handedness = Handedness.Left;
}
else if (poseType == HandPoseType.HAND_RIGHT)
{
m_Handedness = Handedness.Right;
}
base.SetType(poseType);
}
private IEnumerator WaitInit()
{
yield return new WaitUntil(() => m_Initialized);
base.OnEnable();
if (!keepUpdate)
{
keepUpdate = true;
StartCoroutine(UpdatePose());
}
}
private IEnumerator UpdatePose()
{
Vector3 position = Vector3.zero;
Quaternion rotation = Quaternion.identity;
while (keepUpdate)
{
yield return new WaitForEndOfFrame();
HandData handData = CachedHand.Get(isLeft);
m_IsTracked = handData.isTracked;
if (!m_IsTracked) { continue; }
for (int i = 0; i < poseCount; i++)
{
if (handData.GetJointPosition((JointType)i, ref position) && handData.GetJointRotation((JointType)i, ref rotation))
{
m_Position[i] = position;
m_Rotation[i] = rotation;
m_LocalPosition[i] = position;
m_LocalRotation[i] = rotation;
}
else
{
m_Position[i] = Vector3.zero;
m_Rotation[i] = Quaternion.identity;
m_LocalPosition[i] = Vector3.zero;
m_LocalRotation[i] = Quaternion.identity;
}
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 52790aba0e3d55f4fb27aded6c698d8b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,251 @@
// "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 UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
/// <summary>
/// This class is designed to generate appropriately sized colliders for each joint.
/// </summary>
public class JointCollider : MonoBehaviour
{
public enum CollisionState
{
Enter = 0,
Stay = 1,
Exit = 2,
}
private CapsuleCollider m_Collider = null;
public Collider Collider => m_Collider;
public delegate void OnJointCollision(Collision collision, CollisionState state);
private OnJointCollision onJointCollision;
private const float k_ColliderRadius = 1E-6f;
private const float k_ColliderHeight = 1E-6f;
private JointType jointType = JointType.Count;
private void OnEnable()
{
InitCollider();
}
/// <summary>
/// Set the joint id and adjust collider size..
/// </summary>
/// <param name="id">JointType of joint.</param>
public void SetJointId(int id)
{
InitCollider();
jointType = (JointType)id;
switch (jointType)
{
case JointType.Palm:
m_Collider.center = new Vector3(0f, -0.006f, -0.0025f);
m_Collider.radius = 0.016f;
m_Collider.height = 0.075f;
m_Collider.direction = 0;
break;
case JointType.Wrist:
m_Collider.center = new Vector3(0f, -0.003f, 0f);
m_Collider.radius = 0.02f;
m_Collider.height = 0.055f;
m_Collider.direction = 0;
break;
case JointType.Thumb_Joint0:
m_Collider.center = new Vector3(0f, -0.005f, 0f);
m_Collider.radius = 0.02f;
m_Collider.height = 0.05f;
break;
case JointType.Thumb_Joint1:
m_Collider.center = Vector3.zero;
m_Collider.radius = 0.012f;
m_Collider.height = 0.040f;
break;
case JointType.Thumb_Joint2:
m_Collider.center = new Vector3(0f, -0.003f, 0f);
m_Collider.radius = 0.01f;
m_Collider.height = 0.01f;
break;
case JointType.Thumb_Tip:
m_Collider.center = new Vector3(0f, 0f, -0.005f);
m_Collider.radius = 0.01f;
m_Collider.height = 0.025f;
break;
case JointType.Index_Joint0:
m_Collider.center = new Vector3(0f, 0f, 0.02f);
m_Collider.radius = 0.012f;
m_Collider.height = 0.1f;
break;
case JointType.Index_Joint1:
m_Collider.center = new Vector3(0f, 0f, 0.01f);
m_Collider.radius = 0.01f;
m_Collider.height = 0.04f;
break;
case JointType.Index_Joint2:
m_Collider.center = Vector3.zero;
m_Collider.radius = 0.01f;
m_Collider.height = 0.01f;
break;
case JointType.Index_Joint3:
m_Collider.center = Vector3.zero;
m_Collider.radius = 0.01f;
m_Collider.height = 0.01f;
break;
case JointType.Index_Tip:
m_Collider.center = new Vector3(0f, -0.001f, -0.0025f);
m_Collider.radius = 0.008f;
m_Collider.height = 0.015f;
break;
case JointType.Middle_Joint0:
m_Collider.center = new Vector3(0f, 0f, 0.02f);
m_Collider.radius = 0.012f;
m_Collider.height = 0.1f;
break;
case JointType.Middle_Joint1:
m_Collider.center = new Vector3(0f, 0f, 0.01f);
m_Collider.radius = 0.01f;
m_Collider.height = 0.04f;
break;
case JointType.Middle_Joint2:
m_Collider.center = Vector3.zero;
m_Collider.radius = 0.01f;
m_Collider.height = 0.01f;
break;
case JointType.Middle_Joint3:
m_Collider.center = Vector3.zero;
m_Collider.radius = 0.01f;
m_Collider.height = 0.01f;
break;
case JointType.Middle_Tip:
m_Collider.center = new Vector3(0f, -0.002f, -0.0025f);
m_Collider.radius = 0.008f;
m_Collider.height = 0.015f;
break;
case JointType.Ring_Joint0:
m_Collider.center = new Vector3(0f, 0f, 0.02f);
m_Collider.radius = 0.012f;
m_Collider.height = 0.1f;
break;
case JointType.Ring_Joint1:
m_Collider.center = new Vector3(0f, 0f, 0.01f);
m_Collider.radius = 0.01f;
m_Collider.height = 0.04f;
break;
case JointType.Ring_Joint2:
m_Collider.center = Vector3.zero;
m_Collider.radius = 0.01f;
m_Collider.height = 0.01f;
break;
case JointType.Ring_Joint3:
m_Collider.center = Vector3.zero;
m_Collider.radius = 0.01f;
m_Collider.height = 0.01f;
break;
case JointType.Ring_Tip:
m_Collider.center = new Vector3(0f, -0.002f, -0.0025f);
m_Collider.radius = 0.008f;
m_Collider.height = 0.015f;
break;
case JointType.Pinky_Joint0:
m_Collider.center = new Vector3(0f, 0f, 0.02f);
m_Collider.radius = 0.012f;
m_Collider.height = 0.1f;
break;
case JointType.Pinky_Joint1:
m_Collider.center = new Vector3(0f, 0f, 0.01f);
m_Collider.radius = 0.01f;
m_Collider.height = 0.04f;
break;
case JointType.Pinky_Joint2:
m_Collider.center = Vector3.zero;
m_Collider.radius = 0.01f;
m_Collider.height = 0.01f;
break;
case JointType.Pinky_Joint3:
m_Collider.center = Vector3.zero;
m_Collider.radius = 0.01f;
m_Collider.height = 0.01f;
break;
case JointType.Pinky_Tip:
m_Collider.center = new Vector3(0f, -0.002f, -0.0025f);
m_Collider.radius = 0.006f;
m_Collider.height = 0.015f;
break;
}
}
public Rigidbody InitRigidbody(float mass)
{
Rigidbody rigidbody = gameObject.AddComponent<Rigidbody>();
rigidbody.mass = mass;
rigidbody.useGravity = false;
rigidbody.interpolation = RigidbodyInterpolation.Interpolate;
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
rigidbody.solverIterations = 100;
rigidbody.solverVelocityIterations = 15;
return rigidbody;
}
private void InitCollider()
{
if (m_Collider == null)
{
m_Collider = gameObject.AddComponent<CapsuleCollider>();
m_Collider.radius = k_ColliderRadius;
m_Collider.height = k_ColliderHeight;
m_Collider.direction = 2;
}
}
private void OnCollisionEnter(Collision collision)
{
if (!IsJointCollider(collision.collider))
{
onJointCollision?.Invoke(collision, CollisionState.Enter);
}
}
private void OnCollisionStay(Collision collision)
{
if (!IsJointCollider(collision.collider))
{
onJointCollision?.Invoke(collision, CollisionState.Stay);
}
}
private void OnCollisionExit(Collision collision)
{
if (!IsJointCollider(collision.collider))
{
onJointCollision?.Invoke(collision, CollisionState.Exit);
}
}
private bool IsJointCollider(Collider collider)
{
JointCollider jointCollider = collider.GetComponent<JointCollider>();
return jointCollider != null;
}
public void AddJointCollisionListener(OnJointCollision handler)
{
onJointCollision += handler;
}
public void RemoveJointCollisionListener(OnJointCollision handler)
{
onJointCollision -= handler;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ca9a78f9ae24b3f4a92881fa9a680081
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,162 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
public class PhysicsInteractable : MonoBehaviour
{
[SerializeField]
private float forceMultiplier = 1.0f;
private readonly int MIN_POSE_SAMPLES = 2;
private readonly int MAX_POSE_SAMPLES = 10;
private readonly float MIN_VELOCITY = 0.5f;
private Rigidbody interactableRigidbody;
private List<Pose> movementPoses = new List<Pose>();
private List<float> timestamps = new List<float>();
private bool isBegin = false;
private bool isEnd = false;
private object lockVel = new object();
private void Update()
{
if (interactableRigidbody == null) { return; }
if (isBegin)
{
RecordMovement();
}
}
private void FixedUpdate()
{
if (interactableRigidbody == null) { return; }
if (isEnd)
{
interactableRigidbody.velocity = Vector3.zero;
interactableRigidbody.angularVelocity = Vector3.zero;
Vector3 velocity = CalculateVelocity();
if (velocity.magnitude > MIN_VELOCITY)
{
interactableRigidbody.AddForce(velocity * forceMultiplier, ForceMode.Impulse);
}
interactableRigidbody = null;
movementPoses.Clear();
timestamps.Clear();
isEnd = false;
}
}
private void RecordMovement()
{
float time = Time.time;
if (movementPoses.Count == 0 ||
timestamps[movementPoses.Count - 1] != time)
{
movementPoses.Add(new Pose(interactableRigidbody.position, interactableRigidbody.rotation));
timestamps.Add(time);
}
if (movementPoses.Count > MAX_POSE_SAMPLES)
{
movementPoses.RemoveAt(0);
timestamps.RemoveAt(0);
}
}
private Vector3 CalculateVelocity()
{
if (movementPoses.Count >= MIN_POSE_SAMPLES)
{
List<Vector3> velocities = new List<Vector3>();
for (int i = 0; i < movementPoses.Count - 1; i++)
{
for (int j = i + 1; j < movementPoses.Count; j++)
{
velocities.Add(GetVelocity(i, j));
}
}
Vector3 finalVelocity = FindBestVelocity(velocities);
return finalVelocity;
}
return Vector3.zero;
}
private Vector3 GetVelocity(int idx1, int idx2)
{
if (idx1 < 0 || idx1 >= movementPoses.Count
|| idx2 < 0 || idx2 >= movementPoses.Count
|| movementPoses.Count < MIN_POSE_SAMPLES)
{
return Vector3.zero;
}
if (idx2 < idx1)
{
(idx1, idx2) = (idx2, idx1);
}
Vector3 currentPos = movementPoses[idx2].position;
Vector3 previousPos = movementPoses[idx1].position;
float currentTime = timestamps[idx2];
float previousTime = timestamps[idx1];
float timeDelta = currentTime - previousTime;
if (currentPos == null || previousPos == null || timeDelta == 0)
{
return Vector3.zero;
}
Vector3 velocity = (currentPos - previousPos) / timeDelta;
return velocity;
}
private Vector3 FindBestVelocity(List<Vector3> velocities)
{
Vector3 bestVelocity = Vector3.zero;
float bestScore = float.PositiveInfinity;
Parallel.For(0, velocities.Count, i =>
{
float score = 0f;
for (int j = 0; j < velocities.Count; j++)
{
if (i != j)
{
score += (velocities[i] - velocities[j]).magnitude;
}
}
lock (lockVel)
{
if (score < bestScore)
{
bestVelocity = velocities[i];
bestScore = score;
}
}
});
return bestVelocity;
}
public void OnBeginInteractabled(IGrabbable grabbable)
{
if (grabbable is HandGrabInteractable handGrabbable)
{
interactableRigidbody = handGrabbable.rigidbody;
}
isBegin = true;
}
public void OnEndInteractabled(IGrabbable grabbable)
{
isBegin = false;
isEnd = true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 23de72f64d9c93540b53c64f183e4efa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,246 +0,0 @@
// "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 UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditorInternal;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
[CustomEditor(typeof(HandGrabInteractable))]
public class HandGrabInteractableEditor : UnityEditor.Editor
{
private SerializedProperty m_IsGrabbable, m_ForceMovable, m_FingerRequirement, m_GrabPoses, m_ShowAllIndicator;
private SerializedProperty grabPoseName, gestureThumbPose, gestureIndexPose, gestureMiddlePose, gestureRingPose, gesturePinkyPose,
recordedGrabRotations, isLeft, enableIndicator, autoIndicator, indicatorObject, grabOffset;
private ReorderableList grabPoses;
private bool showGrabPoses = false;
private void OnEnable()
{
m_IsGrabbable = serializedObject.FindProperty("m_IsGrabbable");
m_ForceMovable = serializedObject.FindProperty("m_ForceMovable");
m_FingerRequirement = serializedObject.FindProperty("m_FingerRequirement");
m_GrabPoses = serializedObject.FindProperty("m_GrabPoses");
m_ShowAllIndicator = serializedObject.FindProperty("m_ShowAllIndicator");
#region ReorderableList
grabPoses = new ReorderableList(serializedObject, m_GrabPoses, true, true, true, true);
grabPoses.drawHeaderCallback = (Rect rect) =>
{
EditorGUI.LabelField(rect, "Grab Pose List");
};
grabPoses.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
if (!UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(index))) { return; }
if (string.IsNullOrEmpty(grabPoseName.stringValue))
{
grabPoseName.stringValue = $"Grab Pose {index + 1}";
}
Rect gestureRect = new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight);
grabPoseName.stringValue = EditorGUI.TextField(gestureRect, grabPoseName.stringValue);
if (recordedGrabRotations.arraySize == 0)
{
// Draw GrabGesture fields
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(gestureRect, gestureThumbPose);
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(gestureRect, gestureIndexPose);
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(gestureRect, gestureMiddlePose);
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(gestureRect, gestureRingPose);
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(gestureRect, gesturePinkyPose);
}
// Draw Handness fields
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
bool isToggle = EditorGUI.Toggle(gestureRect, "Is Left", isLeft.boolValue);
if (isToggle != isLeft.boolValue)
{
isLeft.boolValue = isToggle;
SwitchRotations(ref recordedGrabRotations);
}
// Draw Indicator fields
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
enableIndicator.boolValue = EditorGUI.Toggle(gestureRect, "Show Indicator", enableIndicator.boolValue);
if (enableIndicator.boolValue)
{
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
autoIndicator.boolValue = EditorGUI.Toggle(gestureRect, "Auto Generator Indicator", autoIndicator.boolValue);
if (!autoIndicator.boolValue)
{
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
indicatorObject.objectReferenceValue = (GameObject)EditorGUI.ObjectField(gestureRect, "Indicator", (GameObject)indicatorObject.objectReferenceValue, typeof(GameObject), true);
}
}
else
{
m_ShowAllIndicator.boolValue = false;
}
// Draw Mirror Pose fields
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
Rect labelRect = new Rect(gestureRect.x, gestureRect.y, EditorGUIUtility.labelWidth, gestureRect.height);
EditorGUI.PrefixLabel(labelRect, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Mirror Pose"));
Rect buttonRect1 = new Rect(gestureRect.x + EditorGUIUtility.labelWidth + EditorGUIUtility.standardVerticalSpacing, gestureRect.y, (gestureRect.width - EditorGUIUtility.labelWidth - EditorGUIUtility.standardVerticalSpacing * 4) / 3, gestureRect.height);
Rect buttonRect2 = new Rect(buttonRect1.x + buttonRect1.width + EditorGUIUtility.standardVerticalSpacing, gestureRect.y, buttonRect1.width, gestureRect.height);
Rect buttonRect3 = new Rect(buttonRect2.x + buttonRect2.width + EditorGUIUtility.standardVerticalSpacing, gestureRect.y, buttonRect1.width, gestureRect.height);
if (GUI.Button(buttonRect1, "Align X axis"))
{
MirrorPose(ref grabOffset, Vector3.right);
}
if (GUI.Button(buttonRect2, "Align Y axis"))
{
MirrorPose(ref grabOffset, Vector3.up);
}
if (GUI.Button(buttonRect3, "Align Z axis"))
{
MirrorPose(ref grabOffset, Vector3.forward);
}
// Draw Position fields
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(gestureRect, grabOffset.FindPropertyRelative("position"));
// Draw Rotation fields
SerializedProperty rotationProperty = grabOffset.FindPropertyRelative("rotation");
Vector4 rotationVector = new Vector4(rotationProperty.quaternionValue.x, rotationProperty.quaternionValue.y, rotationProperty.quaternionValue.z, rotationProperty.quaternionValue.w);
gestureRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
rotationVector = EditorGUI.Vector4Field(gestureRect, "Rotation", rotationVector);
rotationProperty.quaternionValue = new Quaternion(rotationVector.x, rotationVector.y, rotationVector.z, rotationVector.w);
};
grabPoses.elementHeightCallback = (int index) =>
{
if (!UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(index))) { return EditorGUIUtility.singleLineHeight; }
// Including Title, Handness, Show Indicator, Mirror Pose, Position, Rotation
int minHeight = 6;
// To Show GrabGesture
if (recordedGrabRotations.arraySize == 0)
{
minHeight += 5;
}
if (enableIndicator.boolValue)
{
// To Show Auto Indicator
minHeight += 1;
// To Show Indicator Gameobject
if (!autoIndicator.boolValue)
{
minHeight += 1;
}
}
return EditorGUIUtility.singleLineHeight * minHeight + EditorGUIUtility.standardVerticalSpacing * (minHeight + 2);
};
grabPoses.onAddCallback = (ReorderableList list) =>
{
m_GrabPoses.arraySize++;
if (UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(list.count - 1)))
{
grabPoseName.stringValue = $"Grab Pose {list.count}";
}
};
#endregion
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_IsGrabbable);
EditorGUILayout.PropertyField(m_ForceMovable);
EditorGUILayout.PropertyField(m_FingerRequirement);
showGrabPoses = EditorGUILayout.Foldout(showGrabPoses, "Grab Pose Settings");
if (showGrabPoses)
{
if(m_GrabPoses.arraySize == 0)
{
grabPoses.elementHeight = EditorGUIUtility.singleLineHeight;
}
grabPoses.DoLayoutList();
bool isToggle = EditorGUILayout.Toggle("Show All Indicator", m_ShowAllIndicator.boolValue);
if (isToggle != m_ShowAllIndicator.boolValue)
{
m_ShowAllIndicator.boolValue = isToggle;
for (int i = 0; i < m_GrabPoses.arraySize; i++)
{
if (UpdateGrabPose(m_GrabPoses.GetArrayElementAtIndex(i)))
{
enableIndicator.boolValue = m_ShowAllIndicator.boolValue;
}
}
}
}
serializedObject.ApplyModifiedProperties();
}
private bool UpdateGrabPose(SerializedProperty grabPose)
{
SerializedProperty indicator = grabPose.FindPropertyRelative("indicator");
if (grabPose == null || indicator == null) { return false; }
grabPoseName = grabPose.FindPropertyRelative("grabPoseName");
gestureThumbPose = grabPose.FindPropertyRelative("handGrabGesture.thumbPose");
gestureIndexPose = grabPose.FindPropertyRelative("handGrabGesture.indexPose");
gestureMiddlePose = grabPose.FindPropertyRelative("handGrabGesture.middlePose");
gestureRingPose = grabPose.FindPropertyRelative("handGrabGesture.ringPose");
gesturePinkyPose = grabPose.FindPropertyRelative("handGrabGesture.pinkyPose");
recordedGrabRotations = grabPose.FindPropertyRelative("recordedGrabRotations");
isLeft = grabPose.FindPropertyRelative("isLeft");
enableIndicator = indicator.FindPropertyRelative("enableIndicator");
autoIndicator = indicator.FindPropertyRelative("autoIndicator");
indicatorObject = indicator.FindPropertyRelative("target");
grabOffset = grabPose.FindPropertyRelative("grabOffset");
return true;
}
/// <summary>
/// Convert the rotation of joints of the current hand into those of another hand.
/// </summary>
/// <param name="rotations">Rotation of joints of the current hand.</param>
private void SwitchRotations(ref SerializedProperty rotations)
{
for (int i = 0; i < rotations.arraySize; i++)
{
Quaternion rotation = rotations.GetArrayElementAtIndex(i).quaternionValue;
Quaternion newRotation = Quaternion.Euler(rotation.eulerAngles.x, -rotation.eulerAngles.y, -rotation.eulerAngles.z);
rotations.GetArrayElementAtIndex(i).quaternionValue = newRotation;
}
}
/// <summary>
/// Mirrors the pose properties (position and rotation) of a serialized object along a specified mirror axis.
/// </summary>
/// <param name="pose">The serialized property representing the pose to be mirrored.</param>
/// <param name="mirrorAxis">The axis along which the mirroring should occur.</param>
private void MirrorPose(ref SerializedProperty pose, Vector3 mirrorAxis)
{
Vector3 sourcePos = pose.FindPropertyRelative("position").vector3Value;
Quaternion sourceRot = pose.FindPropertyRelative("rotation").quaternionValue;
Vector3 sourceFwd = sourceRot * Vector3.forward;
Vector3 sourceUp = sourceRot * Vector3.up;
// Calculate the mirrored position using Vector3.Reflect along the specified mirror axis.
Vector3 mirroredPosition = Vector3.Reflect(sourcePos, mirrorAxis);
// Calculate the mirrored rotation using Quaternion.LookRotation and Vector3.Reflect for the forward and up vectors.
Quaternion mirroredRotation = Quaternion.LookRotation(Vector3.Reflect(sourceFwd, mirrorAxis), Vector3.Reflect(sourceUp, mirrorAxis));
pose.FindPropertyRelative("position").vector3Value = mirroredPosition;
pose.FindPropertyRelative("rotation").quaternionValue = mirroredRotation;
}
}
}
#endif

View File

@@ -1,69 +0,0 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
[CustomEditor(typeof(HandMeshManager))]
public class HandMeshManagerEditor : Editor
{
private HandMeshManager m_HandJointManager;
private SerializedProperty m_Handedness, m_HandGrabber, m_RootJointType, m_HandRootJoint, m_HandJoints;
private bool showJoints = false;
public static readonly GUIContent findJoints = EditorGUIUtility.TrTextContent("Find Joints");
public static readonly GUIContent clearJoints = EditorGUIUtility.TrTextContent("Clear");
private void OnEnable()
{
m_Handedness = serializedObject.FindProperty("m_Handedness");
m_HandGrabber = serializedObject.FindProperty("m_HandGrabber");
m_RootJointType = serializedObject.FindProperty("m_RootJointType");
m_HandRootJoint = serializedObject.FindProperty("m_HandRootJoint");
m_HandJoints = serializedObject.FindProperty("m_HandJoints");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_Handedness);
EditorGUILayout.PropertyField(m_HandGrabber);
EditorGUILayout.HelpBox("Without HandGrabber, it still works but won't stop when colliding with Immovable objects.", MessageType.Info);
EditorGUILayout.PropertyField(m_RootJointType);
EditorGUILayout.PropertyField(m_HandRootJoint);
showJoints = EditorGUILayout.Foldout(showJoints, "Hand Joints");
if (showJoints)
{
for (int i = 0; i < m_HandJoints.arraySize; i++)
{
SerializedProperty joint = m_HandJoints.GetArrayElementAtIndex(i);
JointType jointType = (JointType)i;
EditorGUILayout.PropertyField(joint, new GUIContent(jointType.ToString()));
}
using (new EditorGUILayout.HorizontalScope())
{
m_HandJointManager = target as HandMeshManager;
using (new EditorGUI.DisabledScope())
{
if (GUILayout.Button(findJoints))
{
m_HandJointManager.FindJoints();
}
}
using (new EditorGUI.DisabledScope())
{
if (GUILayout.Button(clearJoints))
{
m_HandJointManager.ClearJoints();
}
}
}
}
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

@@ -1,93 +0,0 @@
// "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 UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
/// <summary>
/// This class is designed to edit grab gestures.
/// </summary>
[RequireComponent(typeof(HandMeshManager))]
public class CustomGrabPose : MonoBehaviour
{
#if UNITY_EDITOR
[SerializeField]
private HandGrabGesture m_GrabGesture;
private HandMeshManager jointManager;
private Transform palmTransform;
private HandGrabGesture currentGesture;
private HandGrabInteractable candidate = null;
private readonly float k_GrabDistance = 0.1f;
private Pose[] fingerTipPoses => new Pose[5];
private void OnEnable()
{
if (jointManager == null)
{
jointManager = transform.GetComponent<HandMeshManager>();
}
}
private void Update()
{
// Non-DirectPreview mode.
if (m_GrabGesture != currentGesture)
{
currentGesture = m_GrabGesture;
jointManager.SetJointsFromGrabGesture(currentGesture);
}
}
/// <summary>
/// Finds the nearest interactable object to the hand.
/// </summary>
public void FindNearInteractable()
{
if (jointManager.GetJointTransform(JointType.Palm, out palmTransform))
{
float maxScore = 0;
foreach (HandGrabInteractable interactable in GrabManager.handGrabbables)
{
float distanceScore = interactable.CalculateDistanceScore(palmTransform.position, k_GrabDistance);
if (distanceScore > maxScore)
{
maxScore = distanceScore;
candidate = interactable;
}
}
}
}
/// <summary>
/// Save the position and rotation offset with the candidate.
/// </summary>
public void SavePoseWithCandidate()
{
if (candidate != null &&
jointManager.GetJointTransform(JointType.Palm, out palmTransform))
{
Vector3 posOffset = candidate.transform.position - palmTransform.position;
Quaternion rotOffset = palmTransform.rotation;
GrabPose grabPose = GrabPose.Identity;
grabPose.Update($"Grab Pose {candidate.grabPoses.Count + 1}", currentGesture, jointManager.IsLeft);
grabPose.grabOffset = new GrabOffset(candidate.transform.position, candidate.transform.rotation, posOffset, rotOffset);
if (!candidate.grabPoses.Contains(grabPose))
{
candidate.grabPoses.Add(grabPose);
}
GrabbablePoseRecorder.SaveChanges();
}
}
#endif
}
}

View File

@@ -1,141 +0,0 @@
// "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 UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
/// <summary>
/// This class is designed to generate appropriately sized colliders for each joint.
/// </summary>
public class GrabCollider : MonoBehaviour
{
public enum CollisionState
{
start = 0,
keep = 1,
end = 2,
}
private CapsuleCollider m_Collider = null;
public Collider Collider => m_Collider;
private bool m_IsCollision = false;
public bool IsCollision { get { return m_IsCollision; } set { m_IsCollision = value; } }
private const float k_ColliderRadius = 0.01f;
private const float k_ColliderHeight = 0.01f;
private JointType jointType = JointType.Count;
public delegate void CollisionHandler(JointType joint, Collision collision, CollisionState state);
private CollisionHandler m_CollisionHandler;
private void OnEnable()
{
m_Collider = transform.GetComponent<CapsuleCollider>();
if (m_Collider == null)
{
m_Collider = transform.gameObject.AddComponent<CapsuleCollider>();
}
m_Collider.radius = k_ColliderRadius;
m_Collider.height = k_ColliderHeight;
m_Collider.direction = 2;
Rigidbody rigidbody = transform.GetComponent<Rigidbody>();
if (rigidbody == null)
{
rigidbody = transform.gameObject.AddComponent<Rigidbody>();
}
rigidbody.useGravity = false;
rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
rigidbody.constraints = RigidbodyConstraints.FreezeAll;
}
/// <summary>
/// Set the joint id and adjust collider size..
/// </summary>
/// <param name="id">JointType of joint.</param>
public void SetJointId(int id)
{
jointType = (JointType)id;
if (m_Collider)
{
// Adjust the size and position of the collider based on jointId.
switch (jointType)
{
case JointType.Thumb_Joint0:
case JointType.Thumb_Joint1:
m_Collider.height = 0.03f;
break;
case JointType.Index_Joint0:
case JointType.Middle_Joint0:
case JointType.Ring_Joint0:
case JointType.Pinky_Joint0:
m_Collider.height = 0.08f;
m_Collider.center = new Vector3(0f, 0f, 0.02f);
break;
case JointType.Index_Joint1:
case JointType.Middle_Joint1:
case JointType.Ring_Joint1:
case JointType.Pinky_Joint1:
m_Collider.height = 0.05f;
m_Collider.center = new Vector3(0f, 0f, 0.02f);
break;
case JointType.Index_Tip:
case JointType.Middle_Tip:
case JointType.Ring_Tip:
case JointType.Pinky_Tip:
m_Collider.radius = 0.005f;
break;
}
}
}
public void AddListener(CollisionHandler handler)
{
m_CollisionHandler += handler;
}
public void RemoveListener(CollisionHandler handler)
{
m_CollisionHandler -= handler;
}
private void OnCollisionEnter(Collision collision)
{
if (!IsGrabCollider(collision.collider) && m_CollisionHandler != null)
{
m_CollisionHandler.Invoke(jointType, collision, CollisionState.start);
}
}
private void OnCollisionStay(Collision collision)
{
if (!IsGrabCollider(collision.collider) && m_CollisionHandler != null)
{
m_CollisionHandler.Invoke(jointType, collision, CollisionState.keep);
}
}
private void OnCollisionExit(Collision collision)
{
if (!IsGrabCollider(collision.collider) && m_CollisionHandler != null)
{
m_CollisionHandler.Invoke(jointType, collision, CollisionState.end);
}
}
private bool IsGrabCollider(Collider collider)
{
GrabCollider grabCollider = collider.gameObject.GetComponent<GrabCollider>();
return grabCollider != null;
}
}
}

View File

@@ -1,486 +0,0 @@
// "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.Linq;
using UnityEngine;
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
{
public class GrabColliderManager : MonoBehaviour
{
const string LOG_TAG = "VIVE.OpenXR.Toolkits.RealisticHandInteraction.GrabColliderManager";
private void DEBUG(string msg) { Debug.Log($"{LOG_TAG}, {msg}"); }
private void WARNING(string msg) { Debug.LogWarning($"{LOG_TAG}, {msg}"); }
private void ERROR(string msg) { Debug.LogError($"{LOG_TAG}, {msg}"); }
/// <summary>
/// The struct is designed to record movable colliders,
/// including which grabbable they belong to, which joints collisioned with, and whether they have been grabbed.
/// </summary>
private struct MovableHitInfo
{
public struct JointHitInfo
{
public JointType joint;
public Vector3 hitOffset;
public int hitTime { get; private set; }
public JointHitInfo(JointType in_JointType, Vector3 in_HitOffset)
{
joint = in_JointType;
hitOffset = in_HitOffset;
hitTime = Time.frameCount;
}
public void Update()
{
hitTime = Time.frameCount;
}
}
public HandGrabInteractable grabbable;
public List<JointHitInfo> jointHitInfos;
public bool grabbed;
public bool stopMove;
public MovableHitInfo(HandGrabInteractable in_Grabbable, JointType in_Joint, Vector3 in_Offset)
{
grabbable = in_Grabbable;
jointHitInfos = new List<JointHitInfo>()
{
new JointHitInfo(in_Joint, in_Offset)
};
grabbed = false;
stopMove = false;
}
public void Update(bool in_Grabbed, bool in_StopMove)
{
grabbed = in_Grabbed;
stopMove = in_StopMove;
}
public void Reset()
{
grabbed = false;
stopMove = false;
}
/// <summary>
/// Add a JointType. If it's already in the dictionary, update the time.
/// </summary>
/// <param name="joint">The joint which needs to be added.</param>
public void AddJoint(JointType joint, Vector3 offset)
{
int hitId = jointHitInfos.FindIndex(x => x.joint == joint);
if (hitId == -1)
{
jointHitInfos.Add(new JointHitInfo(joint, offset));
}
else
{
JointHitInfo jointHitInfo = jointHitInfos[hitId];
jointHitInfo.Update();
jointHitInfos[hitId] = jointHitInfo;
}
}
/// <summary>
/// Remove a JointType and check if it has been grabbed.
/// </summary>
/// <param name="joint">The joint which needs to be removed.</param>
public void RemoveJoint(JointType joint)
{
int hitId = jointHitInfos.FindIndex(x => x.joint == joint);
if (hitId != -1)
{
jointHitInfos.RemoveAt(hitId);
}
}
}
[SerializeField]
private HandMeshManager jointManager;
private GrabCollider[] jointsCollider = new GrabCollider[(int)JointType.Count];
private Pose[] jointsPrevFramePose = new Pose[(int)JointType.Count];
private bool isImmovableCollision = false;
private bool isGrabbing = false;
private List<MovableHitInfo> movableHits = new List<MovableHitInfo>();
private Dictionary<GrabCollider, Collider> immovableHits = new Dictionary<GrabCollider, Collider>();
public delegate void OnImmovableCollision(bool enable);
private OnImmovableCollision immovableCollisionHandler;
private void Awake()
{
if (jointManager == null)
{
jointManager = transform.GetComponent<HandMeshManager>();
if (jointManager == null)
{
ERROR("Failed to find HandJointManager.");
}
}
}
private void OnEnable()
{
if (jointManager != null)
{
jointManager.HandGrabber.AddBeginGrabListener(OnGrabberBeginGrab);
jointManager.HandGrabber.AddEndGrabListener(OnGrabberEndGrab);
}
CreateJointsCollider();
}
private void OnDisable()
{
if (jointManager != null)
{
jointManager.HandGrabber.RemoveBeginGrabListener(OnGrabberBeginGrab);
jointManager.HandGrabber.RemoveEndGrabListener(OnGrabberEndGrab);
}
foreach (var collider in jointsCollider)
{
collider.RemoveListener(CollisionEvent);
Destroy(collider);
}
Array.Clear(jointsCollider, 0, jointsCollider.Length);
}
private void Update()
{
if (jointManager == null) { return; }
UpdateColliderPose();
if (!isGrabbing)
{
UpdateImmovable();
UpdateMovable();
}
for (int i = 0; i < jointsCollider.Length; i++)
{
jointsPrevFramePose[i] = jointsCollider[i] == null ? Pose.identity : new Pose(jointsCollider[i].transform.position, jointsCollider[i].transform.rotation);
}
}
/// <summary>
/// Create colliders for each joint and set them do not collide with each other.
/// </summary>
private void CreateJointsCollider()
{
if (jointManager != null)
{
var cloneRoot = Instantiate(jointManager.HandRootJoint, jointManager.HandRootJoint.parent);
cloneRoot.name = jointManager.HandRootJoint.name;
List<GameObject> children = new List<GameObject>() { cloneRoot.gameObject };
GetChildren(cloneRoot, children);
foreach (var child in children)
{
Transform target = jointManager.HandJoints.FirstOrDefault(x => x.name == child.name);
if (target != null)
{
int index = Array.IndexOf(jointManager.HandJoints, target);
GrabCollider grabCollider = child.AddComponent<GrabCollider>();
grabCollider.AddListener(CollisionEvent);
grabCollider.SetJointId(index);
jointsCollider[index] = grabCollider;
}
}
}
for (int i = 0; i < jointsCollider.Length; i++)
{
if (jointsCollider[i] == null)
{
continue;
}
for (int j = i + 1; j < jointsCollider.Length; j++)
{
if (jointsCollider[j] != null)
{
Physics.IgnoreCollision(jointsCollider[i].Collider, jointsCollider[j].Collider, true);
}
}
}
}
private void GetChildren(Transform parent, List<GameObject> children)
{
foreach (Transform child in parent)
{
children.Add(child.gameObject);
GetChildren(child, children);
}
}
/// <summary>
/// Update the position of the collider using the position of the joint.
/// </summary>
private void UpdateColliderPose()
{
HandData hand = CachedHand.Get(jointManager.IsLeft);
bool isTracked = hand.isTracked;
if (!isTracked)
{
return;
}
var parentTransform = jointManager.HandRootJoint.parent;
var parentRotation = Matrix4x4.Rotate(parentTransform.rotation);
Vector3 jointPosition = Vector3.zero;
Quaternion jointRotation = Quaternion.identity;
for (int i = 0; i < jointsCollider.Length; i++)
{
if (jointsCollider[i] == null) { continue; }
hand.GetJointPosition((JointType)i, ref jointPosition);
hand.GetJointRotation((JointType)i, ref jointRotation);
if ((JointType)i == JointType.Wrist)
{
jointsCollider[i].transform.localPosition = jointPosition;
jointsCollider[i].transform.localRotation = jointRotation;
}
jointsCollider[i].transform.rotation = (parentRotation * Matrix4x4.Rotate(jointRotation)).rotation;
}
}
/// <summary>
/// Save the hand pose if a collision has already occurred with a joint.
/// </summary>
private void UpdateImmovable()
{
bool isCollision = jointsCollider.Any(x => x != null && x.IsCollision);
foreach (var jointCollider in jointsCollider)
{
jointCollider.Collider.enabled = isCollision ? jointCollider.IsCollision : true;
}
if (isImmovableCollision != isCollision)
{
isImmovableCollision = isCollision;
immovableCollisionHandler?.Invoke(isImmovableCollision);
}
}
/// <summary>
/// Check all movableHits and move the object relative to the movement of the collisioned joint.
/// </summary>
private void UpdateMovable()
{
if (isImmovableCollision) { return; }
const int k_MinCollisionTimeDiff = 5;
const int k_MaxCollisionTimeDiff = 50;
for (int i = movableHits.Count - 1; i >= 0; i--)
{
MovableHitInfo hit = movableHits[i];
if (hit.stopMove) { continue; }
Vector3 totalPosition = Vector3.zero;
Vector3 totalOffset = Vector3.zero;
int validCount = 0;
for (int j = hit.jointHitInfos.Count - 1; j >= 0; j--)
{
MovableHitInfo.JointHitInfo jointHit = hit.jointHitInfos[j];
int frameCountDiff = Time.frameCount - jointHit.hitTime;
if (frameCountDiff > k_MinCollisionTimeDiff)
{
if (frameCountDiff > k_MaxCollisionTimeDiff)
{
hit.RemoveJoint(jointHit.joint);
}
continue;
}
int jointId = (int)jointHit.joint;
Vector3 currentPose = jointsCollider[jointId].transform.position;
Vector3 prevPose = jointsPrevFramePose[jointId].position;
// Condition 1: Calculate the displacement between consecutive frames of joints, it should greater than 1E-6f as significant.
// Condition 2: Calculate distance score relative to grabbable; the score of current pose should be greater than the previous pose.
// Condition 3: The dot product of the vector between the current pose and the grabbable object,
// and the vector representing finger movement direction should be less than 0.
if (Vector3.Distance(prevPose, currentPose) > 1E-6f &&
movableHits[i].grabbable.CalculateDistanceScore(currentPose) >= movableHits[i].grabbable.CalculateDistanceScore(prevPose) &&
Vector3.Dot((currentPose - prevPose).normalized, (movableHits[i].grabbable.transform.position - prevPose).normalized) > 0)
{
validCount++;
totalPosition += currentPose;
totalOffset += jointHit.hitOffset;
}
}
if (validCount > 0)
{
movableHits[i].grabbable.transform.position = (totalPosition - totalOffset) / validCount;
}
if (hit.jointHitInfos.Count == 0)
{
movableHits.RemoveAt(i);
}
else
{
movableHits[i] = hit;
}
}
}
/// <summary>
/// Enable or disable the collider of joints.
/// </summary>
/// <param name="enable">Enable (true) or disable (false) the colliders.</param>
public void EnableCollider(bool enable)
{
for (int i = 0; i < jointsCollider.Length; i++)
{
if (jointsCollider[i] != null)
{
jointsCollider[i].gameObject.SetActive(enable);
}
}
}
#region Collision Event
/// <summary>
/// Adds a listener for immovable collision events.
/// </summary>
/// <param name="handler">The method to be called when an immovable collision occurs.</param>
public void AddImmovableCollisionListener(OnImmovableCollision handler)
{
immovableCollisionHandler += handler;
}
/// <summary>
/// Removes a listener for immovable collision events.
/// </summary>
/// <param name="handler">The method to be removed from the immovable collision event listeners.</param>
public void RemoveImmovableCollisionListener(OnImmovableCollision handler)
{
immovableCollisionHandler -= handler;
}
/// <summary>
/// Event handler for when the grabber begins grabbing.
/// </summary>
/// <param name="grabber">The grabber of IGrabber.</param>
private void OnGrabberBeginGrab(IGrabber grabber)
{
isGrabbing = true;
for (int i = 0; i < movableHits.Count; i++)
{
if (grabber.grabbable is HandGrabInteractable &&
(HandGrabInteractable)grabber.grabbable == movableHits[i].grabbable)
{
MovableHitInfo movableHit = movableHits[i];
movableHit.Update(true, true);
movableHits[i] = movableHit;
}
}
}
private void OnGrabberEndGrab(IGrabber grabber)
{
isGrabbing = false;
}
/// <summary>
/// Filter all collision events, check for grabbables, and update collision data.
/// </summary>
/// <param name="joint">The joint which has been collision.</param>
/// <param name="collision">The data of Collision.</param>
/// <param name="isColliding">True when the collision event is OnCollisionEnter or OnCollisionStay.</param>
private void CollisionEvent(JointType joint, Collision collision, GrabCollider.CollisionState state)
{
bool isCollision = state != GrabCollider.CollisionState.end;
Rigidbody rigidbody = collision.rigidbody;
GrabManager.GetFirstHandGrabbableFromParent(collision.collider.gameObject, out HandGrabInteractable grabbable);
if (collision.rigidbody == null && (grabbable == null || grabbable != null && !grabbable.enabled)) { return; }
if ((rigidbody == null || rigidbody.isKinematic) && grabbable != null && grabbable.forceMovable)
{
if (isCollision)
{
UpdateMovableHits(joint, grabbable);
}
else
{
RemoveMovableHits(joint, grabbable);
}
}
else if ((rigidbody != null && rigidbody.isKinematic) || (grabbable != null && !grabbable.forceMovable))
{
UpdateImmovableHIts(joint, collision.collider, isCollision);
}
}
private void UpdateMovableHits(JointType joint, HandGrabInteractable grabbable)
{
int index = movableHits.FindIndex(x => x.grabbable == grabbable);
if (index != -1)
{
MovableHitInfo moveable = movableHits[index];
moveable.AddJoint(joint, jointsCollider[(int)joint].transform.position - grabbable.transform.position);
movableHits[index] = moveable;
}
else
{
MovableHitInfo moveable = new MovableHitInfo(grabbable, joint, jointsCollider[(int)joint].transform.position - grabbable.transform.position);
movableHits.Add(moveable);
}
}
private void RemoveMovableHits(JointType joint, HandGrabInteractable grabbable)
{
int index = movableHits.FindIndex(x => x.grabbable == grabbable);
if (index != -1)
{
MovableHitInfo movable = movableHits[index];
movable.RemoveJoint(joint);
if (movable.jointHitInfos.Count == 0)
{
movableHits.Remove(movable);
}
else
{
movableHits[index] = movable;
}
}
}
private void UpdateImmovableHIts(JointType joint, Collider collider, bool isCollision)
{
GrabCollider grabCollider = jointsCollider[(int)joint];
grabCollider.IsCollision = isCollision;
if (isCollision && !immovableHits.ContainsKey(grabCollider))
{
immovableHits.Add(grabCollider, collider);
}
else if (!isCollision && immovableHits.ContainsKey(grabCollider))
{
immovableHits.Remove(grabCollider);
}
}
#endregion
}
}

View File

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