version 2.4.0
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff1e9b9f321387c4e86e6a5fa6cd65c9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
// "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;
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
[CustomEditor(typeof(CustomGrabPose))]
|
||||
public class CustomGrabPoseEditor : UnityEditor.Editor
|
||||
{
|
||||
private CustomGrabPose m_GrabPoseDesigner;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_GrabPoseDesigner = target as CustomGrabPose;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
GUILayout.Space(10f);
|
||||
ShowGrabPosesMenu();
|
||||
}
|
||||
|
||||
private void ShowGrabPosesMenu()
|
||||
{
|
||||
if (GUILayout.Button("Save HandGrab Pose"))
|
||||
{
|
||||
m_GrabPoseDesigner.FindNearInteractable();
|
||||
m_GrabPoseDesigner.SavePoseWithCandidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c01f88bc88bb27849a723888721c96f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ba007edb5befc349a95aa1cfa7cb6c9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8e7c213fed939e4b859638cde97a31c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d606d68d81c647241b90f1060786476c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e312e5246287a2546944bb77519270c4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32007394341a17c44859030ef5b809ee
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 010c0098d232cb0428f42a48488a6255
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9731229184277b54ba66bffd1633169b
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b7f1f69b8b23e9459efda715941fbee
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea1b2b1a3faba024aa3df5391529aec0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
// "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 automatically generate indicators.
|
||||
/// </summary>
|
||||
public class AutoGenIndicator : MonoBehaviour
|
||||
{
|
||||
private MeshRenderer meshRenderer;
|
||||
private MeshFilter meshFilter;
|
||||
|
||||
private readonly Color indicatorColor = new Color(1f, 0.7960785f, 0.09411766f, 1f);
|
||||
private const float k_Length = 0.05f;
|
||||
private const float k_Width = 0.05f;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
meshRenderer = transform.gameObject.AddComponent<MeshRenderer>();
|
||||
meshFilter = transform.gameObject.AddComponent<MeshFilter>();
|
||||
MeshInitialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the mesh for the indicator.
|
||||
/// </summary>
|
||||
private void MeshInitialize()
|
||||
{
|
||||
Shader shader = Shader.Find("Sprites/Default");
|
||||
meshRenderer.material = new Material(shader);
|
||||
meshRenderer.material.SetColor("_Color", indicatorColor);
|
||||
meshRenderer.sortingOrder = 1;
|
||||
|
||||
Mesh arrowMesh = new Mesh();
|
||||
Vector3[] vertices = new Vector3[4];
|
||||
int[] triangles = new int[3 * 2];
|
||||
|
||||
vertices[0] = new Vector3(0, 0f, 0f);
|
||||
vertices[1] = new Vector3(0f, k_Length * 0.8f, 0f);
|
||||
vertices[2] = new Vector3(-k_Width * 0.5f, k_Length, 0f);
|
||||
vertices[3] = new Vector3(k_Width * 0.5f, k_Length, 0f);
|
||||
|
||||
triangles[0] = 0;
|
||||
triangles[1] = 2;
|
||||
triangles[2] = 1;
|
||||
|
||||
triangles[3] = 0;
|
||||
triangles[4] = 1;
|
||||
triangles[5] = 3;
|
||||
|
||||
arrowMesh.vertices = vertices;
|
||||
arrowMesh.triangles = triangles;
|
||||
|
||||
arrowMesh.RecalculateNormals();
|
||||
|
||||
meshFilter.mesh = arrowMesh;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the pose of the indicator.
|
||||
/// </summary>
|
||||
/// <param name="position">The position vector to set.</param>
|
||||
/// <param name="direction">The direction vector to set.</param>
|
||||
public void SetPose(Vector3 position, Vector3 direction)
|
||||
{
|
||||
transform.position = position;
|
||||
transform.up = direction.normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4694bdada589dce4c9b7096c7169833f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd5957dc7b39bd249885b5bb53749b7a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,173 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is designed to manage all Grabbers and Grabbables.
|
||||
/// </summary>
|
||||
public static class GrabManager
|
||||
{
|
||||
private static List<IGrabber> m_GrabberRegistry = new List<IGrabber>();
|
||||
public static IReadOnlyList<HandGrabInteractor> handGrabbers => m_GrabberRegistry.OfType<HandGrabInteractor>().ToList().AsReadOnly();
|
||||
|
||||
private static List<IGrabbable> m_GrabbableRegistry = new List<IGrabbable>();
|
||||
public static IReadOnlyList<HandGrabInteractable> handGrabbables => m_GrabbableRegistry.OfType<HandGrabInteractable>().ToList().AsReadOnly();
|
||||
|
||||
#region IGrabber
|
||||
/// <summary>
|
||||
/// Register the grabber in the grabber registry.
|
||||
/// </summary>
|
||||
/// <param name="grabber">The grabber to register.</param>
|
||||
/// <returns>True if the grabber is successfully registered; otherwise, false.</returns>
|
||||
public static bool RegisterGrabber(IGrabber grabber)
|
||||
{
|
||||
if (!m_GrabberRegistry.Contains(grabber))
|
||||
{
|
||||
m_GrabberRegistry.Add(grabber);
|
||||
}
|
||||
return m_GrabberRegistry.Contains(grabber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the grabber from the grabber registry.
|
||||
/// </summary>
|
||||
/// <param name="grabber">The grabber to remove.</param>
|
||||
/// <returns>True if the grabber is successfully removed; otherwise, false.</returns>
|
||||
public static bool UnregisterGrabber(IGrabber grabber)
|
||||
{
|
||||
if (m_GrabberRegistry.Contains(grabber))
|
||||
{
|
||||
m_GrabberRegistry.Remove(grabber);
|
||||
}
|
||||
return !m_GrabberRegistry.Contains(grabber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the first hand grabber component found in the child hierarchy of the GameObject.
|
||||
/// </summary>
|
||||
/// <param name="target">The target whose child hierarchy to search.</param>
|
||||
/// <param name="grabber">The output parameter to store the first hand grabber component found.</param>
|
||||
/// <returns>True if a hand grabber component is found; otherwise, false.</returns>
|
||||
public static bool GetFirstHandGrabberFromChild(GameObject target, out HandGrabInteractor grabber)
|
||||
{
|
||||
grabber = TopDownFind<HandGrabInteractor>(target.transform);
|
||||
return grabber != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the first hand grabber component found in the parent hierarchy of the GameObject.
|
||||
/// </summary>
|
||||
/// <param name="target">The target whose parent hierarchy to search.</param>
|
||||
/// <param name="grabber">The output parameter to store the first hand grabber component found.</param>
|
||||
/// <returns>True if a hand grabber component is found; otherwise, false.</returns>
|
||||
public static bool GetFirstHandGrabberFromParent(GameObject target, out HandGrabInteractor grabber)
|
||||
{
|
||||
grabber = BottomUpFind<HandGrabInteractor>(target.transform);
|
||||
return grabber != null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region GrabInteractable
|
||||
/// <summary>
|
||||
/// Register the grabbable in the grabbable registry.
|
||||
/// </summary>
|
||||
/// <param name="grabbable">The grabbable to register.</param>
|
||||
/// <returns>True if the grabbable is successfully registered; otherwise, false.</returns>
|
||||
public static bool RegisterGrabbable(IGrabbable grabbable)
|
||||
{
|
||||
if (!m_GrabbableRegistry.Contains(grabbable))
|
||||
{
|
||||
m_GrabbableRegistry.Add(grabbable);
|
||||
}
|
||||
return m_GrabbableRegistry.Contains(grabbable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the grabbable from the grabbable registry.
|
||||
/// </summary>
|
||||
/// <param name="grabbable">The grabbable to remove.</param>
|
||||
/// <returns>True if the grabbable is successfully removed; otherwise, false.</returns>
|
||||
public static bool UnregisterGrabbable(IGrabbable grabbable)
|
||||
{
|
||||
if (m_GrabbableRegistry.Contains(grabbable))
|
||||
{
|
||||
m_GrabbableRegistry.Remove(grabbable);
|
||||
}
|
||||
return !m_GrabbableRegistry.Contains(grabbable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the first hand grabbable component found in the child hierarchy of the GameObject.
|
||||
/// </summary>
|
||||
/// <param name="target">The target whose child hierarchy to search.</param>
|
||||
/// <param name="grabbable">The output parameter to store the first hand grabbable component found.</param>
|
||||
/// <returns>True if a hand grabbable component is found; otherwise, false.</returns>
|
||||
public static bool GetFirstHandGrabbableFromChild(GameObject target, out HandGrabInteractable grabbable)
|
||||
{
|
||||
grabbable = TopDownFind<HandGrabInteractable>(target.transform);
|
||||
return grabbable != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the first hand grabbable component found in the parent hierarchy of the GameObject.
|
||||
/// </summary>
|
||||
/// <param name="target">The target whose parent hierarchy to search.</param>
|
||||
/// <param name="grabbable">The output parameter to store the first hand grabbable component found.</param>
|
||||
/// <returns>True if a hand grabbable component is found; otherwise, false.</returns>
|
||||
public static bool GetFirstHandGrabbableFromParent(GameObject target, out HandGrabInteractable grabbable)
|
||||
{
|
||||
grabbable = BottomUpFind<HandGrabInteractable>(target.transform);
|
||||
return grabbable != null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Find available components from self to children nodes.
|
||||
/// </summary>
|
||||
/// <param name="transform">The transform of the gameobject.</param>
|
||||
/// <returns>Value for available component.</returns>
|
||||
private static T TopDownFind<T>(Transform transform) where T : Component
|
||||
{
|
||||
T component = transform.GetComponent<T>();
|
||||
if (component != null)
|
||||
{
|
||||
return component;
|
||||
}
|
||||
|
||||
if (transform.childCount > 0)
|
||||
{
|
||||
for (int i = 0; i < transform.childCount; i++)
|
||||
{
|
||||
T childComponent = TopDownFind<T>(transform.GetChild(i));
|
||||
if (childComponent != null)
|
||||
{
|
||||
return childComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find available components from self to parent node.
|
||||
/// </summary>
|
||||
/// <param name="transform">The transform of the gameobject.</param>
|
||||
/// <returns>Value for available component.</returns>
|
||||
private static T BottomUpFind<T>(Transform transform) where T : Component
|
||||
{
|
||||
T component = transform.GetComponent<T>();
|
||||
if (component != null)
|
||||
{
|
||||
return component;
|
||||
}
|
||||
|
||||
if (transform.parent != null)
|
||||
{
|
||||
return BottomUpFind<T>(transform.parent);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 252950eac28fb1f4cb1eae8e653f92ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,120 @@
|
||||
// "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 WaveVR 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 UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// The class is designed to record all grab poses for all hand Grabbables.
|
||||
/// </summary>
|
||||
public class GrabPoseBinder : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// This struct records the grab pose for grabbable object.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
private struct GrabPoseBindFormat
|
||||
{
|
||||
[SerializeField]
|
||||
public string grabbableName;
|
||||
[SerializeField]
|
||||
public List<GrabPose> grabPoses;
|
||||
|
||||
public GrabPoseBindFormat(string in_GrabbableName, List<GrabPose> in_GrabPoses)
|
||||
{
|
||||
grabbableName = in_GrabbableName;
|
||||
grabPoses = in_GrabPoses;
|
||||
}
|
||||
|
||||
public GrabPoseBindFormat Identity => new GrabPoseBindFormat(string.Empty, new List<GrabPose>());
|
||||
|
||||
public void Update(List<GrabPose> grabPoses)
|
||||
{
|
||||
this.grabPoses.Clear();
|
||||
this.grabPoses.AddRange(grabPoses);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
grabbableName = string.Empty;
|
||||
grabPoses.Clear();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is GrabPoseBindFormat grabPoseBindFormat &&
|
||||
grabbableName == grabPoseBindFormat.grabbableName &&
|
||||
grabPoses == grabPoseBindFormat.grabPoses;
|
||||
}
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return grabbableName.GetHashCode() ^ grabPoses.GetHashCode();
|
||||
}
|
||||
public static bool operator ==(GrabPoseBindFormat source, GrabPoseBindFormat target) => source.Equals(target);
|
||||
public static bool operator !=(GrabPoseBindFormat source, GrabPoseBindFormat target) => !(source == target);
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private List<GrabPoseBindFormat> m_BindingInfos = new List<GrabPoseBindFormat>();
|
||||
|
||||
/// <summary>
|
||||
/// Update the binding information for each hand grabbable object.
|
||||
/// </summary>
|
||||
public void UpdateBindingInfos()
|
||||
{
|
||||
m_BindingInfos.Clear();
|
||||
foreach (HandGrabInteractable grabbable in GrabManager.handGrabbables)
|
||||
{
|
||||
m_BindingInfos.Add(new GrabPoseBindFormat(grabbable.name, grabbable.grabPoses));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the binding information.
|
||||
/// </summary>
|
||||
/// <returns>True if storage is successful; otherwise, false.</returns>
|
||||
public bool StorageData()
|
||||
{
|
||||
if (m_BindingInfos.Count == 0) { return false; }
|
||||
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
AssetDatabase.Refresh();
|
||||
EditorUtility.SetDirty(this);
|
||||
AssetDatabase.SaveAssets();
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds grab poses associated with the specified hand grabbable object.
|
||||
/// </summary>
|
||||
/// <param name="grabbable">The hand grabbable object to search for.</param>
|
||||
/// <param name="grabPoses">The output parameter to store the found grab poses.</param>
|
||||
/// <returns>True if grab poses are found for the grabbable object; otherwise, false.</returns>
|
||||
public bool FindGrabPosesWithGrabbable(HandGrabInteractable grabbable, out List<GrabPose> grabPoses)
|
||||
{
|
||||
grabPoses = new List<GrabPose>();
|
||||
GrabPoseBindFormat bindingInfo = m_BindingInfos.Find(x => x.grabbableName == grabbable.name);
|
||||
if (bindingInfo != null)
|
||||
{
|
||||
grabPoses = bindingInfo.grabPoses;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53503199deedf444e84f1714b700737d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,104 @@
|
||||
// "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 WaveVR 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.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// The class is designed to update the grab poses of all hand Grabbables.
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
public class GrabbablePoseRecorder
|
||||
{
|
||||
private static readonly string filepath = "Assets/GrabablePoseRecording.asset";
|
||||
private static readonly string metaFilepath = filepath + ".meta";
|
||||
private static bool IsFileExist => File.Exists(filepath);
|
||||
|
||||
static GrabbablePoseRecorder()
|
||||
{
|
||||
EditorApplication.playModeStateChanged += ApplyChanges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply changes to grab poses when entering edit mode.
|
||||
/// </summary>
|
||||
/// <param name="state">The state of the play mode.</param>
|
||||
private static void ApplyChanges(PlayModeStateChange state)
|
||||
{
|
||||
if (IsFileExist && state == PlayModeStateChange.EnteredEditMode)
|
||||
{
|
||||
GrabPoseBinder binder = AssetDatabase.LoadAssetAtPath<GrabPoseBinder>(filepath);
|
||||
if (binder != null)
|
||||
{
|
||||
HandGrabInteractable[] grabbables = Object.FindObjectsOfType<HandGrabInteractable>();
|
||||
foreach (var grabbable in grabbables)
|
||||
{
|
||||
if (binder.FindGrabPosesWithGrabbable(grabbable, out List<GrabPose> updatedGrabPose))
|
||||
{
|
||||
for (int i = 0; i < updatedGrabPose.Count; i++)
|
||||
{
|
||||
GrabPose grabPose = updatedGrabPose[i];
|
||||
GrabPose oldGrabPose = grabbable.grabPoses.Find(x => x.grabPoseName == grabPose.grabPoseName);
|
||||
if (oldGrabPose != null)
|
||||
{
|
||||
grabPose.indicator.target = oldGrabPose.indicator.target;
|
||||
}
|
||||
updatedGrabPose[i] = grabPose;
|
||||
}
|
||||
grabbable.grabPoses.Clear();
|
||||
grabbable.grabPoses.AddRange(updatedGrabPose);
|
||||
}
|
||||
}
|
||||
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
||||
}
|
||||
File.Delete(filepath);
|
||||
File.Delete(metaFilepath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves changes to grab pose bindings.
|
||||
/// </summary>
|
||||
public static void SaveChanges()
|
||||
{
|
||||
if (!IsFileExist)
|
||||
{
|
||||
GenerateAsset();
|
||||
}
|
||||
else
|
||||
{
|
||||
GrabPoseBinder binder = AssetDatabase.LoadAssetAtPath<GrabPoseBinder>(filepath);
|
||||
if (binder != null)
|
||||
{
|
||||
binder.UpdateBindingInfos();
|
||||
binder.StorageData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new asset for storing grab pose bindings.
|
||||
/// </summary>
|
||||
private static void GenerateAsset()
|
||||
{
|
||||
GrabPoseBinder binder = ScriptableObject.CreateInstance<GrabPoseBinder>();
|
||||
binder.UpdateBindingInfos();
|
||||
AssetDatabase.CreateAsset(binder, filepath);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9119682127e09314ca250470f13db0f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,452 @@
|
||||
// "Wave SDK
|
||||
// © 2020 HTC Corporation. All Rights Reserved.
|
||||
//
|
||||
// Unless otherwise required by copyright law and practice,
|
||||
// upon the execution of HTC SDK license agreement,
|
||||
// HTC grants you access to and use of the Wave SDK(s).
|
||||
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||||
// conditions signed by you and all SDK and API requirements,
|
||||
// specifications, and documentation provided by HTC to You."
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is designed to implement IHandGrabbable, allowing objects to be grabbed.
|
||||
/// </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;
|
||||
|
||||
public bool isGrabbed => m_Grabber != null;
|
||||
|
||||
[SerializeField]
|
||||
private bool m_IsGrabbable = true;
|
||||
public bool isGrabbable { get { return m_IsGrabbable; } set { m_IsGrabbable = 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;
|
||||
|
||||
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 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()
|
||||
{
|
||||
allColliders.AddRange(transform.GetComponentsInChildren<Collider>(true));
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
GrabManager.RegisterGrabbable(this);
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
GrabManager.UnregisterGrabbable(this);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Interface
|
||||
/// <summary>
|
||||
/// Set the grabber for the hand grabbable object.
|
||||
/// </summary>
|
||||
/// <param name="grabber">The grabber to set.</param>
|
||||
public void SetGrabber(IGrabber grabber)
|
||||
{
|
||||
if (grabber is HandGrabInteractor handGrabber)
|
||||
{
|
||||
m_Grabber = 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);
|
||||
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;
|
||||
m_BestGrabPose = GrabPose.Identity;
|
||||
endGrabbed?.Invoke(this);
|
||||
m_OnEndGrabbed?.Invoke(this);
|
||||
DEBUG($"{transform.name} is released.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable/Disable indicators. If enabled, display the closest indicator based on grabber position.
|
||||
/// </summary>
|
||||
/// <param name="enable">True to show the indicator, false to hide it.</param>
|
||||
/// <param name="grabber">The grabber for which to show or hide this indicator.</param>
|
||||
public void ShowIndicator(bool enable, HandGrabInteractor grabber)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
closestGrabber = grabber;
|
||||
if (m_ShowAllIndicator)
|
||||
{
|
||||
ShowAllIndicator(grabber.isLeft);
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (closestGrabber == grabber)
|
||||
{
|
||||
closestGrabber = null;
|
||||
ShowIndicatorByIndex(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the shortest distance between the grabber and the grabbable and convert it into a score based on grabDistance.
|
||||
/// </summary>
|
||||
/// <param name="grabberPos">The current pose of grabber.</param>
|
||||
/// <param name="grabDistance">The maximum grab distance between the grabber and the grabbable object.</param>
|
||||
/// <returns>The score represents the distance between the grabber and the grabbable.</returns>
|
||||
public float CalculateDistanceScore(Vector3 grabberPos, float grabDistance = 0.03f)
|
||||
{
|
||||
if (!isGrabbable || isGrabbed) { return 0; }
|
||||
Vector3 closestPoint = GetClosestPoint(grabberPos);
|
||||
float distacne = Vector3.Distance(grabberPos, closestPoint);
|
||||
return distacne > grabDistance ? 0 : 1 - (distacne / grabDistance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Generate all indicators and calculate grab offsets.
|
||||
/// </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)
|
||||
{
|
||||
if (m_GrabPoses[i].indicator.NeedGenerateIndicator())
|
||||
{
|
||||
AutoGenerateIndicator(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
GrabPose grabPose = m_GrabPoses[i];
|
||||
grabPose.indicator.CalculateGrabOffset(transform.position, transform.rotation);
|
||||
m_GrabPoses[i] = grabPose;
|
||||
}
|
||||
}
|
||||
}
|
||||
ShowIndicatorByIndex(-1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically generate an indicator by the index of the grab pose.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the grab pose.</param>
|
||||
private void AutoGenerateIndicator(int index)
|
||||
{
|
||||
AutoGenIndicator autoGenIndicator = new GameObject($"Indicator {index}", typeof(AutoGenIndicator)).GetComponent<AutoGenIndicator>();
|
||||
GrabPose grabPose = m_GrabPoses[index];
|
||||
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.position, transform.rotation);
|
||||
m_GrabPoses[index] = grabPose;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the point closest to the source position.
|
||||
/// </summary>
|
||||
/// <param name="sourcePos">The position of source.</param>
|
||||
/// <returns>The position which closest to the source position.</returns>
|
||||
private Vector3 GetClosestPoint(Vector3 sourcePos)
|
||||
{
|
||||
Vector3 closestPoint = Vector3.zero;
|
||||
float shortDistance = float.MaxValue;
|
||||
foreach (var collider in allColliders)
|
||||
{
|
||||
Vector3 closePoint = collider.ClosestPointOnBounds(sourcePos);
|
||||
float distance = Vector3.Distance(sourcePos, closePoint);
|
||||
if (collider.bounds.Contains(closePoint))
|
||||
{
|
||||
Vector3 direction = (closePoint - sourcePos).normalized;
|
||||
RaycastHit[] hits = Physics.RaycastAll(sourcePos, direction, distance);
|
||||
foreach (var hit in hits)
|
||||
{
|
||||
if (hit.collider == collider)
|
||||
{
|
||||
float hitDistance = Vector3.Distance(sourcePos, hit.point);
|
||||
if (distance > hitDistance)
|
||||
{
|
||||
distance = hitDistance;
|
||||
closePoint = hit.point;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shortDistance > distance)
|
||||
{
|
||||
shortDistance = distance;
|
||||
closestPoint = closePoint;
|
||||
}
|
||||
}
|
||||
return closestPoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the best grab pose for the grabber and updates the bestGrabPoseId.
|
||||
/// </summary>
|
||||
/// <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 void UpdateBestGrabPose(bool isLeft, Pose grabberPose)
|
||||
{
|
||||
int index = FindBestGrabPose(isLeft, grabberPose);
|
||||
if (index != -1 && index < m_GrabPoses.Count)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the best grab pose for the grabber.
|
||||
/// </summary>
|
||||
/// <param name="isLeft">Whether the grabber is the left hand.</param>
|
||||
/// <param name="grabberPose">The pose of the grabber.</param>
|
||||
/// <returns>The index of the best grab pose among the grab poses.</returns>
|
||||
private int FindBestGrabPose(bool isLeft, Pose grabberPose)
|
||||
{
|
||||
int index = -1;
|
||||
float maxDot = float.MinValue;
|
||||
Vector3 currentDirection = grabberPose.position - transform.position;
|
||||
for (int i = 0; i < m_GrabPoses.Count; i++)
|
||||
{
|
||||
if (m_GrabPoses[i].isLeft == isLeft)
|
||||
{
|
||||
Pose handPose = CalculateActualHandPose(m_GrabPoses[i].grabOffset);
|
||||
Vector3 grabDirection = handPose.position - transform.position;
|
||||
float dot = Vector3.Dot(currentDirection.normalized, grabDirection.normalized);
|
||||
if (dot > maxDot)
|
||||
{
|
||||
maxDot = dot;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show the indicator corresponding to the specified index and hides others.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the indicator to show.</param>
|
||||
private void ShowIndicatorByIndex(int index)
|
||||
{
|
||||
foreach (var grabPose in m_GrabPoses)
|
||||
{
|
||||
grabPose.indicator.SetActive(false);
|
||||
}
|
||||
if (index >= 0 && index < m_GrabPoses.Count &&
|
||||
m_GrabPoses[index].indicator.enableIndicator)
|
||||
{
|
||||
m_GrabPoses[index].indicator.UpdatePositionAndRotation(transform.position, transform.rotation);
|
||||
m_GrabPoses[index].indicator.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show all indicators corresponding to the specified hand side and hides others.
|
||||
/// </summary>
|
||||
/// <param name="isLeft">Whether the hand side is left.</param>
|
||||
private void ShowAllIndicator(bool isLeft)
|
||||
{
|
||||
foreach (var grabPose in m_GrabPoses)
|
||||
{
|
||||
grabPose.indicator.SetActive(false);
|
||||
}
|
||||
foreach (var grabPose in m_GrabPoses)
|
||||
{
|
||||
if (grabPose.isLeft == isLeft)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1c0e40da1ab9014c89d359be00fffb1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,309 @@
|
||||
// "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.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VIVE.OpenXR.Toolkits.RealisticHandInteraction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is designed to implement IHandGrabber, allowing objects to grab grabbable objects.
|
||||
/// </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,
|
||||
Hover,
|
||||
Grabbing,
|
||||
};
|
||||
|
||||
#region Public States
|
||||
private HandGrabInteractable m_Grabbable = null;
|
||||
public IGrabbable grabbable => m_Grabbable;
|
||||
public bool isGrabbing => m_Grabbable != null;
|
||||
|
||||
[SerializeField]
|
||||
private Handedness m_Handedness = Handedness.Left;
|
||||
public Handedness handedness => m_Handedness;
|
||||
|
||||
private HandGrabState m_HandGrabState = null;
|
||||
public HandGrabState handGrabState => m_HandGrabState;
|
||||
|
||||
[SerializeField]
|
||||
private float m_GrabDistance = 0.03f;
|
||||
public float grabDistance { get { return m_GrabDistance; } set { m_GrabDistance = value; } }
|
||||
|
||||
public bool isLeft => handedness == Handedness.Left;
|
||||
|
||||
[SerializeField]
|
||||
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 currentCandidate = null;
|
||||
private GrabState m_State = GrabState.None;
|
||||
private Pose wristPose = Pose.identity;
|
||||
private Vector3[] fingerTipPosition = new Vector3[(int)FingerId.Count];
|
||||
private OnBeginGrab beginGrabHandler;
|
||||
private OnEndGrab endGrabHandler;
|
||||
|
||||
#region MonoBehaviour
|
||||
private void Awake()
|
||||
{
|
||||
m_HandGrabState = new HandGrabState(isLeft);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
GrabManager.RegisterGrabber(this);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
GrabManager.UnregisterGrabber(this);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
m_HandGrabState.UpdateState();
|
||||
|
||||
HandPose handPose = HandPoseProvider.GetHandPose(isLeft ? HandPoseType.HAND_LEFT : HandPoseType.HAND_RIGHT);
|
||||
if (handPose != null)
|
||||
{
|
||||
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:
|
||||
NoneUpdate();
|
||||
break;
|
||||
case GrabState.Hover:
|
||||
HoverUpdate();
|
||||
break;
|
||||
case GrabState.Grabbing:
|
||||
GrabbingUpdate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Interface
|
||||
/// <summary>
|
||||
/// Checks if the specified joint type is required.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
HandData.GetJointIndex(joint, out int group, out _);
|
||||
switch (group)
|
||||
{
|
||||
case 2: return m_Grabbable.fingerRequirement.thumb == GrabRequirement.Required;
|
||||
case 3: return m_Grabbable.fingerRequirement.index == GrabRequirement.Required;
|
||||
case 4: return m_Grabbable.fingerRequirement.middle == GrabRequirement.Required;
|
||||
case 5: return m_Grabbable.fingerRequirement.ring == GrabRequirement.Required;
|
||||
case 6: return m_Grabbable.fingerRequirement.pinky == GrabRequirement.Required;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Find the candidate grabbable object for grabber.
|
||||
/// </summary>
|
||||
private void FindCandidate()
|
||||
{
|
||||
currentCandidate = null;
|
||||
float distanceScore = float.MinValue;
|
||||
if (GetClosestGrabbable(m_GrabDistance, out HandGrabInteractable grabbable, out float score) && score > distanceScore)
|
||||
{
|
||||
distanceScore = score;
|
||||
currentCandidate = grabbable;
|
||||
}
|
||||
|
||||
if (currentCandidate != null)
|
||||
{
|
||||
float grabScore = Grab.CalculateHandGrabScore(this, currentCandidate);
|
||||
if (distanceScore < MinDistanceScore || grabScore < MinGrabScore)
|
||||
{
|
||||
currentCandidate = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the closest grabbable object for grabber.
|
||||
/// </summary>
|
||||
/// <param name="grabDistance">The maximum grab distance between the grabber and the grabbable object.</param>
|
||||
/// <param name="grabbable">The closest grabbable object.</param>
|
||||
/// <param name="maxScore">The maximum score indicating the closeness of the grabbable object.</param>
|
||||
/// <returns>True if a grabbable object is found within the grab distance; otherwise, false.</returns>
|
||||
private bool GetClosestGrabbable(float grabDistance, out HandGrabInteractable grabbable, out float maxScore)
|
||||
{
|
||||
grabbable = null;
|
||||
maxScore = 0f;
|
||||
foreach (HandGrabInteractable interactable in GrabManager.handGrabbables)
|
||||
{
|
||||
interactable.ShowIndicator(false, this);
|
||||
|
||||
foreach (Vector3 tipPos in fingerTipPosition)
|
||||
{
|
||||
float distanceScore = interactable.CalculateDistanceScore(tipPos, grabDistance);
|
||||
if (distanceScore > maxScore)
|
||||
{
|
||||
maxScore = distanceScore;
|
||||
grabbable = interactable;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (grabbable != null)
|
||||
{
|
||||
grabbable.ShowIndicator(true, this);
|
||||
}
|
||||
return grabbable != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the state to GrabState.Hover if a candidate is found.
|
||||
/// </summary>
|
||||
private void NoneUpdate()
|
||||
{
|
||||
if (currentCandidate != null)
|
||||
{
|
||||
m_State = GrabState.Hover;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the state and related information when the grabber begins grabbing the grabbable.
|
||||
/// </summary>
|
||||
private void HoverUpdate()
|
||||
{
|
||||
if (currentCandidate == null)
|
||||
{
|
||||
m_State = GrabState.None;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Grab.HandBeginGrab(this, currentCandidate))
|
||||
{
|
||||
m_State = GrabState.Grabbing;
|
||||
m_Grabbable = currentCandidate;
|
||||
m_Grabbable.SetGrabber(this);
|
||||
m_Grabbable.ShowIndicator(false, this);
|
||||
beginGrabHandler?.Invoke(this);
|
||||
onBeginGrab?.Invoke(this);
|
||||
|
||||
DEBUG($"The {(m_Handedness == Handedness.Left ? "left" : "right")} hand begins to grab the {m_Grabbable.name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the position of grabbable object according to the movement of the grabber.
|
||||
/// </summary>
|
||||
private void GrabbingUpdate()
|
||||
{
|
||||
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;
|
||||
return;
|
||||
}
|
||||
m_Grabbable.UpdatePositionAndRotation(wristPose);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3155365f073fdb45ba7a61887f8cf06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d521c71dbb8287409daf6ef81005d79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35c93bc2a4bb4334e9fc24c4dbac1b63
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 062de99b5e677f34b8f4f6429d5178cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
Reference in New Issue
Block a user