Files
VIVE-OpenXR-Unity/com.htc.upm.vive.openxr/Runtime/Features/Hand/Tracking/Scripts/ViveHandProvider.cs
2025-01-10 17:31:06 +08:00

459 lines
18 KiB
C#

using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.OpenXR;
using VIVE.OpenXR.Interaction;
#if UNITY_XR_HANDS
using UnityEngine.XR.Hands;
using UnityEngine.XR.Hands.ProviderImplementation;
namespace VIVE.OpenXR.Hand
{
public class ViveHandProvider : XRHandSubsystemProvider
{
#region Hand Interaction
private const string kFeatureAimPos = "PointerPosition";
private const string kFeatureAimRot = "PointerRotation";
private const string kFeatureAimValue = "PointerActivateValue";
private const string kFeatureGripPos = "DevicePosition";
private const string kFeatureGripRot = "DeviceRotation";
private const string kFeatureGripValue = "GraspValue";
private const string kFeaturePinchPos = "PinchPosition";
private const string kFeaturePinchRot = "PinchRotation";
private const string kFeaturePinchValue = "PinchValue";
private const string kFeaturePokePos = "PokePosition";
private const string kFeaturePokeRot = "PokeRotation";
private class HandDevice
{
public Pose aimPose => m_AimPose;
public Pose gripPose => m_GripPose;
public Pose pinchPose => m_PinchPose;
public Pose pokePose => m_PokePose;
public float aimActivateValue => m_AimActivateValue;
public float graspValue => m_GraspValue;
public float pinchValue => m_PinchValue;
private Pose m_AimPose = Pose.identity;
private Pose m_GripPose = Pose.identity;
private Pose m_PinchPose = Pose.identity;
private Pose m_PokePose = Pose.identity;
private float m_AimActivateValue = 0;
private float m_GraspValue = 0;
private float m_PinchValue = 0;
private InputDevice device = default(InputDevice);
private Dictionary<string, InputFeatureUsage<Vector3>> posUsageMapping = new Dictionary<string, InputFeatureUsage<Vector3>>();
private Dictionary<string, InputFeatureUsage<Quaternion>> rotUsageMapping = new Dictionary<string, InputFeatureUsage<Quaternion>>();
private Dictionary<string, InputFeatureUsage<float>> valueUsageMapping = new Dictionary<string, InputFeatureUsage<float>>();
public HandDevice(InputDevice device)
{
this.device = device;
List<InputFeatureUsage> inputFeatures = new List<InputFeatureUsage>();
device.TryGetFeatureUsages(inputFeatures);
for (int i = 0; i < inputFeatures.Count; i++)
{
InputFeatureUsage feature = inputFeatures[i];
switch (feature.name)
{
case kFeatureAimPos:
case kFeatureGripPos:
case kFeaturePinchPos:
case kFeaturePokePos:
posUsageMapping.Add(feature.name, feature.As<Vector3>());
break;
case kFeatureAimRot:
case kFeatureGripRot:
case kFeaturePinchRot:
case kFeaturePokeRot:
rotUsageMapping.Add(feature.name, feature.As<Quaternion>());
break;
case kFeatureAimValue:
case kFeatureGripValue:
case kFeaturePinchValue:
valueUsageMapping.Add(feature.name, feature.As<float>());
break;
default:
break;
}
}
}
public void UpdateInputValue()
{
UpdatePosition();
UpdateRotation();
UpdateValue();
}
private void UpdatePosition()
{
var enumerator = posUsageMapping.GetEnumerator();
while (enumerator.MoveNext())
{
var feature = enumerator.Current;
string featureName = feature.Key;
InputFeatureUsage<Vector3> featureUsage = feature.Value;
if (device.TryGetFeatureValue(featureUsage, out Vector3 position))
{
switch (featureName)
{
case kFeatureAimPos:
m_AimPose.position = position;
break;
case kFeatureGripPos:
m_GripPose.position = position;
break;
case kFeaturePinchPos:
m_PinchPose.position = position;
break;
case kFeaturePokePos:
m_PokePose.position = position;
break;
}
}
}
}
private void UpdateRotation()
{
var enumerator = rotUsageMapping.GetEnumerator();
while (enumerator.MoveNext())
{
var feature = enumerator.Current;
string featureName = feature.Key;
InputFeatureUsage<Quaternion> featureUsage = feature.Value;
if (device.TryGetFeatureValue(featureUsage, out Quaternion rotation))
{
switch (featureName)
{
case kFeatureAimRot:
m_AimPose.rotation = rotation;
break;
case kFeatureGripRot:
m_GripPose.rotation = rotation;
break;
case kFeaturePinchRot:
m_PinchPose.rotation = rotation;
break;
case kFeaturePokeRot:
m_PokePose.rotation = rotation;
break;
}
}
}
}
private void UpdateValue()
{
var enumerator = valueUsageMapping.GetEnumerator();
while (enumerator.MoveNext())
{
var feature = enumerator.Current;
string featureName = feature.Key;
InputFeatureUsage<float> featureUsage = feature.Value;
if (device.TryGetFeatureValue(featureUsage, out float value))
{
switch (featureName)
{
case kFeatureAimValue:
m_AimActivateValue = value;
break;
case kFeatureGripValue:
m_GraspValue = value;
break;
case kFeaturePinchValue:
m_PinchValue = value;
break;
}
}
}
}
}
private static HandDevice leftHandDevice = null;
private static HandDevice rightHandDevice = null;
private const string kInteractionDeviceName = "Vive Hand Interaction Ext OpenXR";
#endregion
private ViveHandTracking viveHand;
public override void Destroy() { }
public override void GetHandLayout(NativeArray<bool> handJointsInLayout)
{
handJointsInLayout[XRHandJointID.Palm.ToIndex()] = true;
handJointsInLayout[XRHandJointID.Wrist.ToIndex()] = true;
handJointsInLayout[XRHandJointID.ThumbMetacarpal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.ThumbProximal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.ThumbDistal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.ThumbTip.ToIndex()] = true;
handJointsInLayout[XRHandJointID.IndexMetacarpal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.IndexProximal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.IndexIntermediate.ToIndex()] = true;
handJointsInLayout[XRHandJointID.IndexDistal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.IndexTip.ToIndex()] = true;
handJointsInLayout[XRHandJointID.MiddleMetacarpal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.MiddleProximal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.MiddleIntermediate.ToIndex()] = true;
handJointsInLayout[XRHandJointID.MiddleDistal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.MiddleTip.ToIndex()] = true;
handJointsInLayout[XRHandJointID.RingMetacarpal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.RingProximal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.RingIntermediate.ToIndex()] = true;
handJointsInLayout[XRHandJointID.RingDistal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.RingTip.ToIndex()] = true;
handJointsInLayout[XRHandJointID.LittleMetacarpal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.LittleProximal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.LittleIntermediate.ToIndex()] = true;
handJointsInLayout[XRHandJointID.LittleDistal.ToIndex()] = true;
handJointsInLayout[XRHandJointID.LittleTip.ToIndex()] = true;
}
public override void Start()
{
Initialize();
#if UNITY_XR_HANDS_1_5_0
InitHandInteractionDevices();
InputDevices.deviceConnected += DeviceConnected;
InputDevices.deviceDisconnected += DeviceDisconnected;
#endif
}
public override void Stop()
{
#if UNITY_XR_HANDS_1_5_0
InputDevices.deviceConnected -= DeviceConnected;
InputDevices.deviceDisconnected -= DeviceDisconnected;
#endif
}
public override XRHandSubsystem.UpdateSuccessFlags TryUpdateHands(XRHandSubsystem.UpdateType updateType, ref Pose leftHandRootPose, NativeArray<XRHandJoint> leftHandJoints, ref Pose rightHandRootPose, NativeArray<XRHandJoint> rightHandJoints)
{
XRHandSubsystem.UpdateSuccessFlags flags = XRHandSubsystem.UpdateSuccessFlags.None;
if (UpdateHand(true, ref leftHandRootPose, ref leftHandJoints))
{
flags |= XRHandSubsystem.UpdateSuccessFlags.LeftHandRootPose | XRHandSubsystem.UpdateSuccessFlags.LeftHandJoints;
}
if (UpdateHand(false, ref rightHandRootPose, ref rightHandJoints))
{
flags |= XRHandSubsystem.UpdateSuccessFlags.RightHandRootPose | XRHandSubsystem.UpdateSuccessFlags.RightHandJoints;
}
#if UNITY_XR_HANDS_1_5_0
if (updateType == XRHandSubsystem.UpdateType.Dynamic && canSurfaceCommonPoseData)
{
UpdateHandInteraction();
}
#endif
return flags;
}
#if UNITY_XR_HANDS_1_5_0
public override bool canSurfaceCommonPoseData => HandInteractionSupport();
public override bool TryGetAimPose(Handedness handedness, out Pose aimPose)
{
aimPose = Pose.identity;
HandDevice handDevice = GetHandDevice(handedness);
if (handDevice != null)
{
aimPose = handDevice.aimPose;
return true;
}
return false;
}
public override bool TryGetAimActivateValue(Handedness handedness, out float aimActivateValue)
{
aimActivateValue = 0;
HandDevice handDevice = GetHandDevice(handedness);
if (handDevice != null)
{
aimActivateValue = handDevice.aimActivateValue;
return true;
}
return false;
}
public override bool TryGetGripPose(Handedness handedness, out Pose gripPose)
{
gripPose = Pose.identity;
HandDevice handDevice = GetHandDevice(handedness);
if (handDevice != null)
{
gripPose = handDevice.gripPose;
return true;
}
return false;
}
public override bool TryGetGraspValue(Handedness handedness, out float graspValue)
{
graspValue = 0;
HandDevice handDevice = GetHandDevice(handedness);
if (handDevice != null)
{
graspValue = handDevice.graspValue;
return true;
}
return false;
}
public override bool TryGetPinchPose(Handedness handedness, out Pose pinchPose)
{
pinchPose = Pose.identity;
HandDevice handDevice = GetHandDevice(handedness);
if (handDevice != null)
{
pinchPose = handDevice.pinchPose;
return true;
}
return false;
}
public override bool TryGetPinchValue(Handedness handedness, out float pinchValue)
{
pinchValue = 0;
HandDevice handDevice = GetHandDevice(handedness);
if (handDevice != null)
{
pinchValue = handDevice.pinchValue;
return true;
}
return false;
}
public override bool TryGetPokePose(Handedness handedness, out Pose pokePose)
{
pokePose = Pose.identity;
HandDevice handDevice = GetHandDevice(handedness);
if (handDevice != null)
{
pokePose = handDevice.pokePose;
return true;
}
return false;
}
private void DeviceConnected(InputDevice inputDevice)
{
if (inputDevice.characteristics.HasFlag(InputDeviceCharacteristics.Left) &&
inputDevice.name == kInteractionDeviceName)
{
leftHandDevice = new HandDevice(inputDevice);
}
if (inputDevice.characteristics.HasFlag(InputDeviceCharacteristics.Right) &&
inputDevice.name == kInteractionDeviceName)
{
rightHandDevice = new HandDevice(inputDevice);
}
}
private void DeviceDisconnected(InputDevice inputDevice)
{
if (inputDevice.characteristics.HasFlag(InputDeviceCharacteristics.Left) &&
inputDevice.name == kInteractionDeviceName)
{
leftHandDevice = default;
}
if (inputDevice.characteristics.HasFlag(InputDeviceCharacteristics.Right) &&
inputDevice.name == kInteractionDeviceName)
{
rightHandDevice = default;
}
}
private void InitHandInteractionDevices()
{
List<InputDevice> inputDevices = new List<InputDevice>();
InputDevices.GetDevicesWithCharacteristics(InputDeviceCharacteristics.HeldInHand |
InputDeviceCharacteristics.HandTracking |
InputDeviceCharacteristics.TrackedDevice, inputDevices);
for (int i = 0; i < inputDevices.Count; i++)
{
InputDevice inputDevice = inputDevices[i];
DeviceConnected(inputDevice);
}
}
private void UpdateHandInteraction()
{
if (leftHandDevice != null)
{
leftHandDevice.UpdateInputValue();
}
if (rightHandDevice != null)
{
rightHandDevice.UpdateInputValue();
}
}
private HandDevice GetHandDevice(Handedness handedness) => handedness == Handedness.Left ? leftHandDevice : rightHandDevice;
private bool HandInteractionSupport()
{
ViveInteractions viveInteractions = OpenXRSettings.Instance.GetFeature<ViveInteractions>();
if (viveInteractions.enabled)
{
return viveInteractions.UseKhrHandInteraction();
}
return false;
}
#endif
private void Initialize()
{
viveHand = OpenXRSettings.Instance.GetFeature<ViveHandTracking>();
}
private bool UpdateHand(bool isLeft, ref Pose handRootPose, ref NativeArray<XRHandJoint> handJoints)
{
if (!viveHand) { return false; }
bool isValid = viveHand.GetJointLocations(isLeft, out XrHandJointLocationEXT[] viveJoints);
Handedness handedness = isLeft ? Handedness.Left : Handedness.Right;
XRHandJointTrackingState trackingState = XRHandJointTrackingState.None;
for (int jointIndex = XRHandJointID.BeginMarker.ToIndex(); jointIndex < XRHandJointID.EndMarker.ToIndex(); ++jointIndex)
{
XRHandJointID jointID = XRHandJointIDUtility.FromIndex(jointIndex);
int viveIndex = XRHandJointIDToIndex(jointID);
Pose pose = Pose.identity;
if (isValid)
{
pose.position = viveJoints[viveIndex].pose.position.ToUnityVector();
pose.rotation = viveJoints[viveIndex].pose.orientation.ToUnityQuaternion();
trackingState = XRHandJointTrackingState.Pose;
}
handJoints[jointIndex] = XRHandProviderUtility.CreateJoint(handedness, trackingState, jointID, pose);
}
handJoints[XRHandJointID.Wrist.ToIndex()].TryGetPose(out handRootPose);
return isValid;
}
private int XRHandJointIDToIndex(XRHandJointID id)
{
switch (id)
{
case XRHandJointID.Palm:
return 0;
case XRHandJointID.Wrist:
return 1;
default:
return (int)id - 1;
}
}
}
}
#endif