version 2.4.0
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using 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
|
||||
@@ -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
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba0bbb9482b5a5d479cf11f2253cdc3e
|
||||
guid: a8e7c213fed939e4b859638cde97a31c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3744e08fd384e154c82d53686f0c82a6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44fadaef2720de846af62a7bbb2ec370
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1504c5149524a8a44a3854dfd4f94d1e
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a10ddbfcbce64f84787e026f4fc003a4
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 950573398b7eb5149bea536bfa6107ca
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea1b2b1a3faba024aa3df5391529aec0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using System.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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9b8d210c92da6a49ac85755f7b15cbb
|
||||
guid: 6d521c71dbb8287409daf6ef81005d79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f25051021522804d90dc5448b7657a2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using System.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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 199fd8ec06e6e7e49a3fab9a9b7e7988
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e199f8a96e0fdf7498bbb0bf45e5b11a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ced5448f0990b040b82475106e1b1d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 059d07a57fa09e543bf2f2b6d5788bb7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e78e2d8c82d5584faaf4b66d40d1057
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52790aba0e3d55f4fb27aded6c698d8b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca9a78f9ae24b3f4a92881fa9a680081
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23de72f64d9c93540b53c64f183e4efa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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 HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using 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
|
||||
@@ -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
|
||||
@@ -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 HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using 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
|
||||
}
|
||||
}
|
||||
@@ -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 HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user