version 2.4.0
This commit is contained in:
@@ -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:
|
||||
Reference in New Issue
Block a user