上传YomovSDK

This commit is contained in:
Sora丶kong
2026-03-03 03:15:46 +08:00
parent 9096da7e6c
commit eb97f31065
6477 changed files with 1932208 additions and 3 deletions

View File

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

View File

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

View File

@@ -0,0 +1,97 @@
# 12.67 XR_HTC_hand_interaction
## Name String
XR_HTC_hand_interaction
## Revision
1
## Hand Interaction Profile
### Interaction profile path:
- /interaction_profiles/htc/hand_interaction
### Valid for user paths:
- /user/hand_htc/left
- /user/hand_htc/right
### Supported input source
- <20>K/input/select/value
- <20>K/input/aim/pose
The application should use <20>K/input/aim/pose path to aim at objects in the world and use <20>K/input/select/value path to decide user selection from pinch shape strength which the range of value is 0.0f to 1.0f, with 1.0f meaning pinch fingers touched.
## VIVE Plugin
After adding the "VIVE XR Hand Interaction" to "Project Settings > XR Plugin-in Management > OpenXR > Android Tab > Interaction Profiles", you can use the following Input Action Pathes.
### Left Hand
- <ViveHandInteraction>{LeftHand}/selectValue: Presents the left hand pinch strength.
- <ViveHandInteraction>{LeftHand}/pointerPose: Presents the left hand pinch pose.
### Right Hand
- <ViveHandInteraction>{RightHand}/selectValue: Presents the right hand pinch strength.
- <ViveHandInteraction>{RightHand}/pointerPose: Presents the right hand pinch pose.
Refer to the <VIVE OpenXR sample path>/Samples/Commons/ActionMap/InputActions.inputActions about the "Input Action Path" usage in the sample <VIVE OpenXR sample path>/Samples/Input/OpenXRInput.unity.
--------------------
# 12.31. XR_EXT_hand_interaction
## Name String
XR_EXT_hand_interaction
## Revision
1
## Hand Interaction Profile
### Interaction profile path:
- /interaction_profiles/ext/hand_interaction_ext
### Valid for user paths:
- /user/hand/left
- /user/hand/right
### Supported input source
- <20>K/input/aim/pose
- <20>K/input/aim_activate_ext/value: a 1D analog input component indicating that the user activated the action on the target that the user is pointing at with the aim pose.
- <20>K/input/aim_activate_ext/ready_ext: a boolean input, where the value XR_TRUE indicates that the fingers to perform the "aim_activate" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing an "aim_activate" gesture.
- <20>K/input/grip/pose
- <20>K/input/grasp_ext/value: a 1D analog input component indicating that the user is making a fist.
- <20>K/input/grasp_ext/ready_ext: a boolean input, where the value XR_TRUE indicates that the hand performing the grasp action is properly tracked by the hand tracking device and it is observed to be ready to perform or is performing the grasp action.
- <20>K/input/pinch_ext/pose
- <20>K/input/pinch_ext/value: a 1D analog input component indicating the extent which the user is bringing their finger and thumb together to perform a "pinch" gesture.
- <20>K/input/pinch_ext/ready_ext: a boolean input, where the value XR_TRUE indicates that the fingers used to perform the "pinch" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing a "pinch" gesture.
- <20>K/input/poke_ext/pose
The <20>K/input/aim/pose is typically used for aiming at objects out of arm<72><6D>s reach. When using a hand interaction profile, it is typically paired with <20>K/input/aim_activate_ext/value to optimize aiming ray stability while performing the gesture. When using a controller interaction profile, the "aim" pose is typically paired with a trigger or a button for aim and fire operations.
The <20>K/input/grip/pose is typically used for holding a large object in the user<65><72>s hand. When using a hand interaction profile, it is typically paired with <20>K/input/grasp_ext/value for the user to directly manipulate an object held in a hand. When using a controller interaction profile, the "grip" pose is typically paired with a "squeeze" button or trigger that gives the user the sense of tightly holding an object.
The <20>K/input/pinch_ext/pose is typically used for directly manipulating a small object using the pinch gesture. When using a hand interaction profile, it is typically paired with the <20>K/input/pinch_ext/value gesture. When using a controller interaction profile, it is typically paired with a trigger manipulated with the index finger, which typically requires curling the index finger and applying pressure with the fingertip.
The <20>K/input/poke_ext/pose is typically used for contact-based interactions using the motion of the hand or fingertip. It typically does not pair with other hand gestures or buttons on the controller. The application typically uses a sphere collider with the "poke" pose to visualize the pose and detect touch with a virtual object.
## VIVE Plugin
After adding the "VIVE XR Hand Interaction Ext" to "Project Settings > XR Plugin-in Management > OpenXR > Android Tab > Interaction Profiles", you can use the following Input Action Pathes.
### Left Hand
- <ViveHandInteraction>{LeftHand}/pointerPose: Presents the left hand aim pose used for aiming at objects out of arm<72><6D>s reach.
- <ViveHandInteraction>{LeftHand}/pointerValue: Can be used as either a boolean or float action type, where the value XR_TRUE or 1.0f represents that the aimed-at target is being fully interacted with left hand.
- <ViveHandInteraction>{LeftHand}/pointerReady: XR_TRUE indicates that the left fingers to perform the "aim_activate" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing an "aim_activate" gesture.
- <ViveHandInteraction>{LeftHand}/gripPose: Presents the left hand grip pose used for holding a large object in the user<65><72>s hand.
- <ViveHandInteraction>{LeftHand}/gripValue: Can be used as either a boolean or float action type, where the value XR_TRUE or 1.0f represents that the left fist is tightly closed.
- <ViveHandInteraction>{LeftHand}/gripReady: XR_TRUE indicates that the left hand performing the grasp action is properly tracked by the hand tracking device and it is observed to be ready to perform or is performing the grasp action.
- <ViveHandInteraction>{LeftHand}/pinchPose: Presents the left hand pinch pose used for directly manipulating a small object using the pinch gesture.
- <ViveHandInteraction>{LeftHand}/pinchValue: Can be used as either a boolean or float action type, where the value XR_TRUE or 1.0f represents that the left finger and thumb are touching each other.
- <ViveHandInteraction>{LeftHand}/pinchReady: XR_TRUE indicates that the left fingers used to perform the "pinch" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing a "pinch" gesture.
- <ViveHandInteraction>{LeftHand}/pokePose: Presents the left hand poke pose used for contact-based interactions using the motion of the hand or fingertip.
### Right Hand
- <ViveHandInteraction>{RightHand}/pointerPose: Presents the right hand aim pose used for aiming at objects out of arm<72><6D>s reach.
- <ViveHandInteraction>{RightHand}/pointerValue: Can be used as either a boolean or float action type, where the value XR_TRUE or 1.0f represents that the aimed-at target is being fully interacted with right hand.
- <ViveHandInteraction>{RightHand}/pointerReady: XR_TRUE indicates that the right fingers to perform the "aim_activate" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing an "aim_activate" gesture.
- <ViveHandInteraction>{RightHand}/gripPose: Presents the right hand grip pose used for holding a large object in the user<65><72>s hand.
- <ViveHandInteraction>{RightHand}/gripValue: Can be used as either a boolean or float action type, where the value XR_TRUE or 1.0f represents that the right fist is tightly closed.
- <ViveHandInteraction>{RightHand}/gripReady: XR_TRUE indicates that the right hand performing the grasp action is properly tracked by the hand tracking device and it is observed to be ready to perform or is performing the grasp action.
- <ViveHandInteraction>{RightHand}/pinchPose: Presents the right hand pinch pose used for directly manipulating a small object using the pinch gesture.
- <ViveHandInteraction>{RightHand}/pinchValue: Can be used as either a boolean or float action type, where the value XR_TRUE or 1.0f represents that the right finger and thumb are touching each other.
- <ViveHandInteraction>{RightHand}/pinchReady: XR_TRUE indicates that the right fingers used to perform the "pinch" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing a "pinch" gesture.
- <ViveHandInteraction>{RightHand}/pokePose: Presents the right hand poke pose used for contact-based interactions using the motion of the hand or fingertip.
Refer to the <VIVE OpenXR sample path>/Samples/HandInteractionExt/HandInteractionExt.inputActions about the "Input Action Path" usage in the sample <VIVE OpenXR sample path>/Samples/HandInteractionExt/HandInteractionExt.unity.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f1983ec0f3f6f1841b9824ef68235039
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,363 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEngine.Scripting;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.XR;
using UnityEngine.InputSystem.Controls;
using UnityEngine.XR.OpenXR;
using UnityEngine;
using UnityEngine.InputSystem;
using System.Collections.Generic;
using UnityEngine.XR;
using UnityEngine.XR.OpenXR.Input;
using System.Text;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
using PoseControl = UnityEngine.InputSystem.XR.PoseControl;
#else
using PoseControl = UnityEngine.XR.OpenXR.Input.PoseControl;
#endif
namespace VIVE.OpenXR.Hand
{
/// <summary>
/// This <see cref="OpenXRInteractionFeature"/> enables the use of hand interaction profiles in OpenXR. It enables <see cref="ViveHandInteraction.kOpenxrExtensionString">XR_HTC_hand_interaction</see> in the underyling runtime.
/// </summary>
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Hand Interaction",
Hidden = true,
BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone },
Company = "HTC",
Desc = "Support for enabling the VIVE hand interaction profile. Will register the controller map for hand interaction if enabled.",
DocumentationLink = "https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_HTC_hand_interaction",
Version = "1.0.0",
OpenxrExtensionStrings = kOpenxrExtensionString,
Category = FeatureCategory.Interaction,
FeatureId = featureId)]
#endif
public class ViveHandInteraction : OpenXRInteractionFeature
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Hand.ViveHandInteraction ";
StringBuilder m_sb = null;
StringBuilder sb {
get {
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
void WARNING(StringBuilder msg) { Debug.LogWarningFormat("{0} {1}", LOG_TAG, msg); }
#endregion
/// <summary>
/// OpenXR specification <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_HTC_hand_interaction">12.69. XR_HTC_hand_interaction</see>.
/// </summary>
public const string kOpenxrExtensionString = "XR_HTC_hand_interaction";
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string featureId = "vive.openxr.feature.hand.interaction";
/// <summary>
/// The interaction profile string used to reference the hand interaction input device.
/// </summary>
private const string profile = "/interaction_profiles/htc/hand_interaction";
#region Supported component paths
private const string leftHand = "/user/hand_htc/left";
private const string rightHand = "/user/hand_htc/right";
/// <summary>
/// Constant for a float interaction binding '.../input/select/value' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs.
/// </summary>
public const string selectValue = "/input/select/value";
/// <summary>
/// Constant for a float interaction binding '.../input/squeeze/value' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs.
/// </summary>
public const string gripValue = "/input/squeeze/value";
/// <summary>
/// Constant for a pose interaction binding '.../input/aim/pose' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs.
/// </summary>
public const string pointerPose = "/input/aim/pose";
/// <summary>
/// Constant for a pose interaction binding '.../input/grip/pose' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs.
/// </summary>
public const string devicePose = "/input/grip/pose";
#endregion
[Preserve, InputControlLayout(displayName = "VIVE Hand Interaction (OpenXR)", commonUsages = new[] { "LeftHand", "RightHand" }, isGenericTypeOfDevice = true)]
public class HandInteractionDevice : OpenXRDevice
{
const string LOG_TAG = "VIVE.OpenXR.Hand.ViveHandInteraction.HandInteractionDevice";
void DEBUG(string msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
/// <summary>
/// A <see cref="AxisControl"/> representing the <see cref="ViveHandInteraction.selectValue"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(aliases = new[] { "selectAxis, pinchStrength" }, usage = "Select")]
public AxisControl selectValue { get; private set; }
/// <summary>
/// A <see cref="AxisControl"/> representing information from the <see cref="ViveHandInteraction.squeeze"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(aliases = new[] { "GripAxis" }, usage = "Grip")]
public AxisControl gripValue { get; private set; }
/// <summary>
/// A <see cref="PoseControl"/> representing information from the <see cref="ViveHandInteraction.devicePose"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(offset = 0, aliases = new[] { "device", "gripPose" }, usage = "Device")]
public PoseControl devicePose { get; private set; }
/// <summary>
/// A <see cref="PoseControl"/> representing the <see cref="ViveHandInteraction.pointerPose"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(offset = 0, alias = "aimPose", usage = "Pointer")]
public PoseControl pointerPose { get; private set; }
/// <summary>
/// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) required for backwards compatibility with the XRSDK layouts. This represents the overall tracking state of the device. This value is equivalent to mapping devicePose/isTracked.
/// </summary>
[Preserve, InputControl(offset = 8)]
public ButtonControl isTracked { get; private set; }
/// <summary>
/// A [IntegerControl](xref:UnityEngine.InputSystem.Controls.IntegerControl) required for backwards compatibility with the XRSDK layouts. This represents the bit flag set to indicate what data is valid. This value is equivalent to mapping devicePose/trackingState.
/// </summary>
[Preserve, InputControl(offset = 12)]
public IntegerControl trackingState { get; private set; }
/// <summary>
/// A [Vector3Control](xref:UnityEngine.InputSystem.Controls.Vector3Control) required for backwards compatibility with the XRSDK layouts. This is the device position. This value is equivalent to mapping devicePose/position.
/// </summary>
[Preserve, InputControl(offset = 16, alias = "gripPosition")]
public Vector3Control devicePosition { get; private set; }
/// <summary>
/// A [QuaternionControl](xref:UnityEngine.InputSystem.Controls.QuaternionControl) required for backwards compatibility with the XRSDK layouts. This is the device orientation. This value is equivalent to mapping devicePose/rotation.
/// </summary>
[Preserve, InputControl(offset = 28, alias = "gripOrientation")]
public QuaternionControl deviceRotation { get; private set; }
/// <summary>
/// Internal call used to assign controls to the the correct element.
/// </summary>
protected override void FinishSetup()
{
DEBUG("FinishSetup() interfaceName: " + description.interfaceName
+ ", deviceClass: " + description.deviceClass
+ ", product: " + description.product
+ ", serial: " + description.serial
+ ", version: " + description.version);
base.FinishSetup();
selectValue = GetChildControl<AxisControl>("selectValue");
gripValue = GetChildControl<AxisControl>("gripValue");
devicePose = GetChildControl<PoseControl>("devicePose");
pointerPose = GetChildControl<PoseControl>("pointerPose");
isTracked = GetChildControl<ButtonControl>("isTracked");
trackingState = GetChildControl<IntegerControl>("trackingState");
devicePosition = GetChildControl<Vector3Control>("devicePosition");
deviceRotation = GetChildControl<QuaternionControl>("deviceRotation");
}
}
#pragma warning disable
private bool m_XrInstanceCreated = false;
#pragma warning restore
private XrInstance m_XrInstance = 0;
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateInstance">xrCreateInstance</see> is done.
/// </summary>
/// <param name="xrInstance">The created instance.</param>
/// <returns>True for valid <see cref="XrInstance">XrInstance</see></returns>
protected override bool OnInstanceCreate(ulong xrInstance)
{
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
{
sb.Clear().Append("OnInstanceCreate() ").Append(kOpenxrExtensionString).Append(" is NOT enabled."); WARNING(sb);
return false;
}
m_XrInstanceCreated = true;
m_XrInstance = xrInstance;
sb.Clear().Append("OnInstanceCreate() " + m_XrInstance); DEBUG(sb);
return base.OnInstanceCreate(xrInstance);
}
private const string kLayoutName = "ViveHandInteraction";
private const string kDeviceLocalizedName = "Vive Hand Interaction OpenXR";
/// <summary>
/// Registers the <see cref="HandInteractionDevice"/> layout with the Input System.
/// </summary>
protected override void RegisterDeviceLayout()
{
sb.Clear().Append("RegisterDeviceLayout() ").Append(kLayoutName).Append(", product: ").Append(kDeviceLocalizedName); DEBUG(sb);
InputSystem.RegisterLayout(typeof(HandInteractionDevice),
kLayoutName,
matches: new InputDeviceMatcher()
.WithInterface(XRUtilities.InterfaceMatchAnyVersion)
.WithProduct(kDeviceLocalizedName));
}
/// <summary>
/// Removes the <see cref="HandInteractionDevice"/> layout from the Input System.
/// </summary>
protected override void UnregisterDeviceLayout()
{
sb.Clear().Append("UnregisterDeviceLayout() ").Append(kLayoutName); DEBUG(sb);
InputSystem.RemoveLayout(kLayoutName);
}
#if UNITY_XR_OPENXR_1_9_1
/// <summary>
/// Return interaction profile type. HandInteractionDevice profile is Device type.
/// </summary>
/// <returns>Interaction profile type.</returns>
protected override InteractionProfileType GetInteractionProfileType()
{
return typeof(HandInteractionDevice).IsSubclassOf(typeof(XRController)) ? InteractionProfileType.XRController : InteractionProfileType.Device;
}
/// <summary>
/// Return device layer out string used for registering device HandInteractionDevice in InputSystem.
/// </summary>
/// <returns>Device layout string.</returns>
protected override string GetDeviceLayoutName()
{
return kLayoutName;
}
#endif
/// <summary>
/// Registers action maps to Unity XR.
/// </summary>
protected override void RegisterActionMapsWithRuntime()
{
sb.Clear().Append("RegisterActionMapsWithRuntime() Action map vivehandinteraction")
.Append(", localizedName: ").Append(kDeviceLocalizedName)
.Append(", desiredInteractionProfile").Append(profile);
DEBUG(sb);
ActionMapConfig actionMap = new ActionMapConfig()
{
name = "vivehandinteraction",
localizedName = kDeviceLocalizedName,
desiredInteractionProfile = profile,
manufacturer = "HTC",
serialNumber = "",
deviceInfos = new List<DeviceConfig>()
{
new DeviceConfig()
{
characteristics = InputDeviceCharacteristics.TrackedDevice | InputDeviceCharacteristics.HandTracking | InputDeviceCharacteristics.Left,
userPath = leftHand // "/user/hand_htc/left"
},
new DeviceConfig()
{
characteristics = InputDeviceCharacteristics.TrackedDevice | InputDeviceCharacteristics.HandTracking | InputDeviceCharacteristics.Right,
userPath = rightHand // "/user/hand_htc/right"
}
},
actions = new List<ActionConfig>()
{
// Grip Axis
new ActionConfig()
{
name = "gripValue",
localizedName = "Grip Axis",
type = ActionType.Axis1D,
usages = new List<string>()
{
"Grip"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = gripValue,
interactionProfileName = profile,
}
}
},
// Select Axis
new ActionConfig()
{
name = "selectValue",
localizedName = "Select Axis",
type = ActionType.Axis1D,
usages = new List<string>()
{
"Select"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = selectValue,
interactionProfileName = profile,
}
}
},
// Grip pose
new ActionConfig()
{
name = "devicePose",
localizedName = "Device Pose",
type = ActionType.Pose,
usages = new List<string>()
{
"Device"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = devicePose,
interactionProfileName = profile,
}
}
},
// Pointer Pose
new ActionConfig()
{
name = "pointerPose",
localizedName = "Pointer Pose",
type = ActionType.Pose,
usages = new List<string>()
{
"Pointer"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = pointerPose,
interactionProfileName = profile,
}
}
}
}
};
AddActionMap(actionMap);
}
}
}

View File

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

View File

@@ -0,0 +1,587 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEngine.Scripting;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.XR;
using UnityEngine.InputSystem.Controls;
using UnityEngine.XR.OpenXR;
using UnityEngine;
using UnityEngine.InputSystem;
using System.Collections.Generic;
using UnityEngine.XR;
using UnityEngine.XR.OpenXR.Input;
using System;
using System.Text;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
#if USE_INPUT_SYSTEM_POSE_CONTROL // Scripting Define Symbol added by using OpenXR Plugin 1.6.0.
using PoseControl = UnityEngine.InputSystem.XR.PoseControl;
#else
using PoseControl = UnityEngine.XR.OpenXR.Input.PoseControl;
#endif
namespace VIVE.OpenXR.Hand
{
/// <summary>
/// This <see cref="OpenXRInteractionFeature"/> enables the use of hand interaction profiles in OpenXR. It enables <see cref="ViveHandInteractionExt.kOpenxrExtensionString">XR_EXT_hand_interaction</see> in the underyling runtime.
/// </summary>
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Hand Interaction Ext (Deprecated)",
Hidden = true,
BuildTargetGroups = new[] { BuildTargetGroup.Android },
Company = "HTC",
Desc = "Support for enabling the KHR hand interaction profile. Will register the controller map for hand interaction if enabled.",
DocumentationLink = "https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_EXT_hand_interaction",
Version = "1.0.0",
OpenxrExtensionStrings = kOpenxrExtensionString,
Category = FeatureCategory.Interaction,
FeatureId = featureId)]
#endif
[Obsolete("This class is deprecated. Please use Unity Hand Interaction Profile instead.")]
public class ViveHandInteractionExt : OpenXRInteractionFeature
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Hand.ViveHandInteractionExt";
StringBuilder m_sb = null;
StringBuilder sb {
get {
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
void WARNING(StringBuilder msg) { Debug.LogWarningFormat("{0} {1}", LOG_TAG, msg); }
#endregion
/// <summary>
/// OpenXR specification <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_HTC_hand_interaction">12.69. XR_HTC_hand_interaction</see>.
/// </summary>
public const string kOpenxrExtensionString = "XR_EXT_hand_interaction";
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string featureId = "vive.openxr.feature.hand.interaction.ext";
[Preserve, InputControlLayout(displayName = "VIVE Hand Interaction Ext (OpenXR)", commonUsages = new[] { "LeftHand", "RightHand" })]
public class HandInteractionExtDevice : XRController
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Hand.ViveHandInteractionExt.HandInteractionExtDevice";
void DEBUG(string msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); }
#endregion
#region Action Path
/// <summary>
/// A <see cref="PoseControl"/> representing the <see cref="ViveHandInteractionExt.grip"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(offset = 0, aliases = new[] { "device", "gripPose" }, usage = "Device")]
public PoseControl devicePose { get; private set; }
/// <summary>
/// A <see cref="PoseControl"/> representing the <see cref="ViveHandInteractionExt.aim"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(offset = 0, alias = "aimPose", usage = "Pointer")]
public PoseControl pointer { get; private set; }
/// <summary>
/// A <see cref="PoseControl"/> representing the <see cref="ViveHandInteractionExt.pinchPose"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(offset = 0, usage = "Pinch")]
public PoseControl pinchPose { get; private set; }
/// <summary>
/// A <see cref="PoseControl"/> representing the <see cref="ViveHandInteractionExt.poke"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(offset = 0, alias = "indexTip", usage = "Poke")]
public PoseControl pokePose { get; private set; }
/// <summary>
/// A <see cref="AxisControl"/> representing information from the <see cref="ViveHandInteractionExt.graspValue"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(aliases = new[] { "gripValue" }, usage = "GraspValue")]
public AxisControl graspValue { get; private set; }
/// <summary>
/// A <see cref="ButtonControl"/> representing the <see cref="ViveHandInteractionExt.graspReady"/> OpenXR bindings, depending on handedness.
/// </summary>
[Preserve, InputControl(aliases = new[] { "isGrasped", "isGripped" }, usage = "GraspReady")]
public ButtonControl graspReady { get; private set; }
/// <summary>
/// A <see cref="AxisControl"/> representing information from the <see cref="ViveHandInteractionExt.pointerActivateValue"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(aliases = new[] { "pointerValue" }, usage = "PointerActivateValue")]
public AxisControl pointerActivateValue { get; private set; }
/// <summary>
/// A <see cref="ButtonControl"/> representing the <see cref="ViveHandInteractionExt.pointerActivateReady"/> OpenXR bindings, depending on handedness.
/// </summary>
[Preserve, InputControl(aliases = new[] { "isPointed", "pointerReady" }, usage = "PointerActivateReady")]
public ButtonControl pointerActivateReady { get; private set; }
/// <summary>
/// A <see cref="AxisControl"/> representing information from the <see cref="ViveHandInteractionExt.pinchValue"/> OpenXR binding.
/// </summary>
[Preserve, InputControl(usage = "PinchValue")]
public AxisControl pinchValue { get; private set; }
/// <summary>
/// A <see cref="ButtonControl"/> representing the <see cref="ViveHandInteractionExt.pinchReady"/> OpenXR bindings, depending on handedness.
/// </summary>
[Preserve, InputControl(aliases = new[] { "isPinched" }, usage = "PinchReady")]
public ButtonControl pinchReady { get; private set; }
/// <summary>
/// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) required for backwards compatibility with the XRSDK layouts. This represents the overall tracking state of the device. This value is equivalent to mapping devicePose/isTracked.
/// </summary>
[Preserve, InputControl(offset = 2)]
new public ButtonControl isTracked { get; private set; }
/// <summary>
/// A [IntegerControl](xref:UnityEngine.InputSystem.Controls.IntegerControl) required for backwards compatibility with the XRSDK layouts. This represents the bit flag set to indicate what data is valid. This value is equivalent to mapping devicePose/trackingState.
/// </summary>
[Preserve, InputControl(offset = 4)]
new public IntegerControl trackingState { get; private set; }
/// <summary>
/// A [Vector3Control](xref:UnityEngine.InputSystem.Controls.Vector3Control) required for backwards compatibility with the XRSDK layouts. This is the device position. This value is equivalent to mapping devicePose/position.
/// </summary>
[Preserve, InputControl(offset = 8, noisy = true, alias = "gripPosition")]
new public Vector3Control devicePosition { get; private set; }
/// <summary>
/// A [QuaternionControl](xref:UnityEngine.InputSystem.Controls.QuaternionControl) required for backwards compatibility with the XRSDK layouts. This is the device orientation. This value is equivalent to mapping devicePose/rotation.
/// </summary>
[Preserve, InputControl(offset = 20, noisy = true, alias = "gripRotation")]
new public QuaternionControl deviceRotation { get; private set; }
/// <summary>
/// A [Vector3Control](xref:UnityEngine.InputSystem.Controls.Vector3Control) required for backwards compatibility with the XRSDK layouts. This is the aim position. This value is equivalent to mapping pointer/position.
/// </summary>
[Preserve, InputControl(offset = 72, noisy = true)]
public Vector3Control pointerPosition { get; private set; }
/// <summary>
/// A [QuaternionControl](xref:UnityEngine.InputSystem.Controls.QuaternionControl) required for backwards compatibility with the XRSDK layouts. This is the aim orientation. This value is equivalent to mapping pointer/rotation.
/// </summary>
[Preserve, InputControl(offset = 84, noisy = true)]
public QuaternionControl pointerRotation { get; private set; }
/// <summary>
/// A [Vector3Control](xref:UnityEngine.InputSystem.Controls.Vector3Control) required for backwards compatibility with the XRSDK layouts. This is the pinch position. This value is equivalent to mapping pinchPose/position.
/// </summary>
[Preserve, InputControl(offset = 136, noisy = true)]
public Vector3Control pinchPosition { get; private set; }
/// <summary>
/// A [QuaternionControl](xref:UnityEngine.InputSystem.Controls.QuaternionControl) required for backwards compatibility with the XRSDK layouts. This is the pinch orientation. This value is equivalent to mapping pinchPose/rotation.
/// </summary>
[Preserve, InputControl(offset = 148, noisy = true)]
public QuaternionControl pinchRotation { get; private set; }
/// <summary>
/// A [Vector3Control](xref:UnityEngine.InputSystem.Controls.Vector3Control) required for backwards compatibility with the XRSDK layouts. This is the poke position. This value is equivalent to mapping pokePose/position.
/// </summary>
[Preserve, InputControl(offset = 200, noisy = true)]
public Vector3Control pokePosition { get; private set; }
/// <summary>
/// A [QuaternionControl](xref:UnityEngine.InputSystem.Controls.QuaternionControl) required for backwards compatibility with the XRSDK layouts. This is the poke orientation. This value is equivalent to mapping pokePose/rotation.
/// </summary>
[Preserve, InputControl(offset = 212, noisy = true)]
public QuaternionControl pokeRotation { get; private set; }
#endregion
/// <summary>
/// Internal call used to assign controls to the the correct element.
/// </summary>
protected override void FinishSetup()
{
DEBUG("FinishSetup() interfaceName: " + description.interfaceName
+ ", deviceClass: " + description.deviceClass
+ ", product: " + description.product
+ ", serial: " + description.serial
+ ", version: " + description.version);
base.FinishSetup();
pointer = GetChildControl<PoseControl>("pointer");
pointerActivateValue = GetChildControl<AxisControl>("pointerActivateValue");
pointerActivateReady = GetChildControl<ButtonControl>("pointerActivateReady");
devicePose = GetChildControl<PoseControl>("devicePose");
graspValue = GetChildControl<AxisControl>("graspValue");
graspReady = GetChildControl<ButtonControl>("graspReady");
pinchPose = GetChildControl<PoseControl>("pinchPose");
pinchValue = GetChildControl<AxisControl>("pinchValue");
pinchReady = GetChildControl<ButtonControl>("pinchReady");
pokePose = GetChildControl<PoseControl>("pokePose");
}
}
/// <summary>
/// The interaction profile string used to reference the hand interaction input device.
/// </summary>
public const string profile = "/interaction_profiles/ext/hand_interaction_ext";
#region Supported component paths
/// <summary>
/// Constant for a pose interaction binding '.../input/aim/pose' OpenXR Input Binding.<br></br>
/// Typically used for aiming at objects out of arm¡¦s reach. When using a hand interaction profile, it is typically paired with <see cref="pointerActivateValue"/> to optimize aiming ray stability while performing the gesture.<br></br>
/// When using a controller interaction profile, the "aim" pose is typically paired with a trigger or a button for aim and fire operations.
/// </summary>
public const string aim = "/input/aim/pose";
/// <summary>
/// Constant for a float interaction binding '.../input/aim_activate_ext/value' OpenXR Input Binding.<br></br>
/// A 1D analog input component indicating that the user activated the action on the target that the user is pointing at with the aim pose.
/// </summary>
public const string pointerActivateValue = "/input/aim_activate_ext/value";
/// <summary>
/// Constant for a boolean interaction binding '.../input/aim_activate_ext/ready_ext' OpenXR Input Binding.<br></br>
/// A boolean input, where the value XR_TRUE indicates that the fingers to perform the "aim_activate" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing an "aim_activate" gesture.
/// </summary>
public const string pointerActivateReady = "/input/aim_activate_ext/ready_ext";
/// <summary>
/// Constant for a pose interaction binding '.../input/grip/pose' OpenXR Input Binding.<br></br>
/// Typically used for holding a large object in the user¡¦s hand. When using a hand interaction profile, it is typically paired with <see cref="graspValue"/> for the user to directly manipulate an object held in a hand.<br></br>
/// When using a controller interaction profile, the "grip" pose is typically paired with a "squeeze" button or trigger that gives the user the sense of tightly holding an object.
/// </summary>
public const string grip = "/input/grip/pose";
/// <summary>
/// Constant for a float interaction binding '.../input/grasp_ext/value' OpenXR Input Binding.<br></br>
/// A 1D analog input component indicating that the user is making a fist.
/// </summary>
public const string graspValue = "/input/grasp_ext/value";
/// <summary>
/// Constant for a boolean interaction binding '.../input/grasp_ext/ready_ext' OpenXR Input Binding.<br></br>
/// A boolean input, where the value XR_TRUE indicates that the hand performing the grasp action is properly tracked by the hand tracking device and it is observed to be ready to perform or is performing the grasp action.
/// </summary>
public const string graspReady = "/input/grasp_ext/ready_ext";
/// <summary>
/// Constant for a pose interaction binding '.../input/pinch_ext/pose' OpenXR Input Binding.<br></br>
/// Typically used for directly manipulating a small object using the pinch gesture. When using a hand interaction profile, it is typically paired with the <see cref="pinchValue"/>.<br></br>
/// When using a controller interaction profile, it is typically paired with a trigger manipulated with the index finger, which typically requires curling the index finger and applying pressure with the fingertip.
/// </summary>
public const string pinchPose = "/input/pinch_ext/pose";
/// <summary>
/// Constant for a float interaction binding '.../input/pinch_ext/value' OpenXR Input Binding.<br></br>
/// A 1D analog input component indicating the extent which the user is bringing their finger and thumb together to perform a "pinch" gesture.
/// </summary>
public const string pinchValue = "/input/pinch_ext/value";
/// <summary>
/// Constant for a boolean interaction binding '.../input/pinch_ext/ready_ext' OpenXR Input Binding.<br></br>
/// A boolean input, where the value XR_TRUE indicates that the fingers used to perform the "pinch" gesture are properly tracked by the hand tracking device and the hand shape is observed to be ready to perform or is performing a "pinch" gesture.
/// </summary>
public const string pinchReady = "/input/pinch_ext/ready_ext";
/// <summary>
/// Constant for a pose interaction binding '.../input/poke_ext/pose' OpenXR Input Binding.<br></br>
/// Typically used for contact-based interactions using the motion of the hand or fingertip. It typically does not pair with other hand gestures or buttons on the controller. The application typically uses a sphere collider with the "poke" pose to visualize the pose and detect touch with a virtual object.
/// </summary>
public const string poke = "/input/poke_ext/pose";
#endregion
#pragma warning disable
private bool m_XrInstanceCreated = false;
#pragma warning restore
private XrInstance m_XrInstance = 0;
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateInstance">xrCreateInstance</see> is done.
/// </summary>
/// <param name="xrInstance">The created instance.</param>
/// <returns>True for valid <see cref="XrInstance">XrInstance</see></returns>
protected override bool OnInstanceCreate(ulong xrInstance)
{
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
{
sb.Clear().Append("OnInstanceCreate() ").Append(kOpenxrExtensionString).Append(" is NOT enabled."); WARNING(sb);
return false;
}
m_XrInstanceCreated = true;
m_XrInstance = xrInstance;
sb.Clear().Append("OnInstanceCreate() " + m_XrInstance); DEBUG(sb);
return base.OnInstanceCreate(xrInstance);
}
private const string kLayoutName = "ViveHandInteractionExt";
private const string kDeviceLocalizedName = "Vive Hand Interaction Ext OpenXR";
/// <summary>
/// Registers the <see cref="HandInteractionExtDevice"/> layout with the Input System.
/// </summary>
protected override void RegisterDeviceLayout()
{
sb.Clear().Append("RegisterDeviceLayout() ").Append(kLayoutName).Append(", product: ").Append(kDeviceLocalizedName); DEBUG(sb);
InputSystem.RegisterLayout(typeof(HandInteractionExtDevice),
kLayoutName,
matches: new InputDeviceMatcher()
.WithInterface(XRUtilities.InterfaceMatchAnyVersion)
.WithProduct(kDeviceLocalizedName));
}
/// <summary>
/// Removes the <see cref="HandInteractionExtDevice"/> layout from the Input System.
/// </summary>
protected override void UnregisterDeviceLayout()
{
sb.Clear().Append("UnregisterDeviceLayout() ").Append(kLayoutName); DEBUG(sb);
InputSystem.RemoveLayout(kLayoutName);
}
#if UNITY_XR_OPENXR_1_9_1
/// <summary>
/// Return interaction profile type. HandInteractionExtDevice profile is Device type.
/// </summary>
/// <returns>Interaction profile type.</returns>
protected override InteractionProfileType GetInteractionProfileType()
{
return typeof(HandInteractionExtDevice).IsSubclassOf(typeof(XRController)) ? InteractionProfileType.XRController : InteractionProfileType.Device;
}
/// <summary>
/// Return device layer out string used for registering device HandInteractionExtDevice in InputSystem.
/// </summary>
/// <returns>Device layout string.</returns>
protected override string GetDeviceLayoutName()
{
return kLayoutName;
}
#endif
/// <summary>
/// Registers action maps to Unity XR.
/// </summary>
protected override void RegisterActionMapsWithRuntime()
{
sb.Clear().Append("RegisterActionMapsWithRuntime() Action map vivehandinteractionext")
.Append(", localizedName: ").Append(kDeviceLocalizedName)
.Append(", desiredInteractionProfile").Append(profile);
DEBUG(sb);
ActionMapConfig actionMap = new ActionMapConfig()
{
name = "vivehandinteractionext",
localizedName = kDeviceLocalizedName,
desiredInteractionProfile = profile,
manufacturer = "HTC",
serialNumber = "",
deviceInfos = new List<DeviceConfig>()
{
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics)(InputDeviceCharacteristics.HandTracking | InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.TrackedDevice | InputDeviceCharacteristics.Left),
userPath = UserPaths.leftHand
},
new DeviceConfig()
{
characteristics = (InputDeviceCharacteristics)(InputDeviceCharacteristics.HandTracking | InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.TrackedDevice | InputDeviceCharacteristics.Right),
userPath = UserPaths.rightHand
}
},
actions = new List<ActionConfig>()
{
// Grip Pose
new ActionConfig()
{
name = "devicePose",
localizedName = "Grasp Pose",
type = ActionType.Pose,
usages = new List<string>()
{
"Device"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = grip,
interactionProfileName = profile,
}
}
},
// Grip Value
new ActionConfig()
{
name = "graspValue",
localizedName = "Grip Axis",
type = ActionType.Axis1D,
usages = new List<string>()
{
"GraspValue"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = graspValue,
interactionProfileName = profile,
}
}
},
// Grip Ready
new ActionConfig()
{
name = "graspReady",
localizedName = "Is Grasped",
type = ActionType.Binary,
usages = new List<string>()
{
"GraspReady"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = graspReady,
interactionProfileName = profile,
},
}
},
// Aim Pose
new ActionConfig()
{
name = "pointer",
localizedName = "Aim Pose",
type = ActionType.Pose,
usages = new List<string>()
{
"Pointer"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = aim,
interactionProfileName = profile,
}
}
},
// Aim Value
new ActionConfig()
{
name = "pointerActivateValue",
localizedName = "Pointer Axis",
type = ActionType.Axis1D,
usages = new List<string>()
{
"PointerActivateValue"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = pointerActivateValue,
interactionProfileName = profile,
}
}
},
// Aim Ready
new ActionConfig()
{
name = "pointerActivateReady",
localizedName = "Is Pointed",
type = ActionType.Binary,
usages = new List<string>()
{
"PointerActivateReady"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = pointerActivateReady,
interactionProfileName = profile,
},
}
},
// Pinch Pose
new ActionConfig()
{
name = "pinchPose",
localizedName = "Pinch Pose",
type = ActionType.Pose,
usages = new List<string>()
{
"Pinch"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = pinchPose,
interactionProfileName = profile,
}
}
},
// Pinch Value
new ActionConfig()
{
name = "pinchValue",
localizedName = "Pinch Axis",
type = ActionType.Axis1D,
usages = new List<string>()
{
"PinchValue"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = pinchValue,
interactionProfileName = profile,
}
}
},
// Pinch Ready
new ActionConfig()
{
name = "pinchReady",
localizedName = "Is Pinched",
type = ActionType.Binary,
usages = new List<string>()
{
"PinchReady"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = pinchReady,
interactionProfileName = profile,
},
}
},
// Poke Pose
new ActionConfig()
{
name = "pokePose",
localizedName = "Index Tip",
type = ActionType.Pose,
usages = new List<string>()
{
"Poke"
},
bindings = new List<ActionBinding>()
{
new ActionBinding()
{
interactionPath = poke,
interactionProfileName = profile,
}
}
},
}
};
AddActionMap(actionMap);
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,48 @@
# 12.28 XR_EXT_hand_tracking
## Name String
XR_EXT_hand_tracking
## Revision
4
## New Object Types
- [XrHandTrackerEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrHandTrackerEXT)
## New Enum Constants
- [XR_HAND_JOINT_COUNT_EXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XR_HAND_JOINT_COUNT_EXT)
[XrObjectType](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrObjectType) enumeration is extended with:
- XR_OBJECT_TYPE_HAND_TRACKER_EXT
[XrStructureType](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrStructureType) enumeration is extended with:
- XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT
- XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT
- XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT
- XR_TYPE_HAND_JOINT_LOCATIONS_EXT
- XR_TYPE_HAND_JOINT_VELOCITIES_EXT
## New Enums
- [XrHandEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrHandEXT)
- [XrHandJointEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrHandJointEXT)
- [XrHandJointSetEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrHandJointSetEXT)
## New Structures
- [XrSystemHandTrackingPropertiesEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrSystemHandTrackingPropertiesEXT)
- [XrHandTrackerCreateInfoEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrHandTrackerCreateInfoEXT)
- [XrHandJointsLocateInfoEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrHandJointsLocateInfoEXT)
- [XrHandJointLocationEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrHandJointLocationEXT)
- [XrHandJointVelocityEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrHandJointVelocityEXT)
- [XrHandJointLocationsEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrHandJointLocationsEXT)
- [XrHandJointVelocitiesEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrHandJointVelocitiesEXT)
## New Functions
- [xrCreateHandTrackerEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#xrCreateHandTrackerEXT)
- [xrDestroyHandTrackerEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#xrDestroyHandTrackerEXT)
- [xrLocateHandJointsEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#xrLocateHandJointsEXT)
## VIVE Plugin
After enabling the "VIVE Focus3 Hand Tracking" from "Project Settings > XR Plugin-in Management > OpenXR > Android Tab", you can retrieve the [XrHandJointLocationEXT](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XrHandJointLocationEXT) by using the following code.
using VIVE.OpenXR.Hand;
XrHandJointLocationEXT[] HandjointLocations = new XrHandJointLocationEXT[(int)XrHandJointEXT.XR_HAND_JOINT_MAX_ENUM_EXT];
var feature = OpenXRSettings.Instance.GetFeature<ViveHandTracking>();
if (feature && feature.GetJointLocations(isLeft, out HandjointLocations))
{
// now you have the hand joint data
}
Refer to <VIVE OpenXR sample path>/Plugin/Input/Scripts/VIVE/RenderHand.cs about the sample code.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 05f3bff8b2a4d0a4ea96ffedf619e30c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,458 @@
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

View File

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

View File

@@ -0,0 +1,102 @@
using System.Collections.Generic;
using UnityEngine;
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 ViveHandSubsystem : XRHandSubsystem
{
public const string featureId = "vive.openxr.feature.xrhandsubsystem";
private static XRHandSubsystem subsystem = null;
private XRHandProviderUtility.SubsystemUpdater subsystemUpdater = null;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void RegisterDescriptor()
{
if (!ViveHandTrackingSupport()) { return; }
bool handInteractionSupport = HandInteractionSupport();
var handsSubsystemCinfo = new XRHandSubsystemDescriptor.Cinfo
{
id = featureId,
providerType = typeof(ViveHandProvider),
subsystemTypeOverride = typeof(ViveHandSubsystem),
#if UNITY_XR_HANDS_1_5_0
supportsAimPose = handInteractionSupport,
supportsAimActivateValue = handInteractionSupport,
supportsGraspValue = handInteractionSupport,
supportsGripPose = handInteractionSupport,
supportsPinchPose = handInteractionSupport,
supportsPinchValue = handInteractionSupport,
supportsPokePose = handInteractionSupport,
#endif
};
XRHandSubsystemDescriptor.Register(handsSubsystemCinfo);
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void StartSubsystem()
{
List<XRHandSubsystemDescriptor> descriptors = new List<XRHandSubsystemDescriptor>();
if (subsystem == null || !subsystem.running)
{
descriptors.Clear();
SubsystemManager.GetSubsystemDescriptors(descriptors);
for (int i = 0; i < descriptors.Count; i++)
{
XRHandSubsystemDescriptor descriptor = descriptors[i];
if (descriptor.id == featureId)
{
subsystem = descriptor.Create();
subsystem.Start();
}
}
}
}
protected override void OnStart()
{
base.OnStart();
if (subsystemUpdater == null)
{
subsystemUpdater = new XRHandProviderUtility.SubsystemUpdater(subsystem);
}
subsystemUpdater.Start();
}
protected override void OnStop()
{
base.OnStop();
subsystemUpdater.Stop();
}
protected override void OnDestroy()
{
base.OnDestroy();
subsystemUpdater.Destroy();
subsystemUpdater = null;
}
private static bool ViveHandTrackingSupport()
{
ViveHandTracking viveHand = OpenXRSettings.Instance.GetFeature<ViveHandTracking>();
return viveHand.enabled;
}
private static bool HandInteractionSupport()
{
ViveInteractions viveInteractions = OpenXRSettings.Instance.GetFeature<ViveInteractions>();
if (viveInteractions.enabled)
{
return viveInteractions.UseKhrHandInteraction();
}
return false;
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,945 @@
// Copyright HTC Corporation All Rights Reserved.
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
namespace VIVE.OpenXR.Hand
{
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Hand Tracking",
BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone },
Company = "HTC",
Desc = "Support the Hand Tracking extension.",
DocumentationLink = "..\\Documentation",
OpenxrExtensionStrings = kOpenxrExtensionString,
Version = "4.0.0",
FeatureId = featureId)]
#endif
public class ViveHandTracking : OpenXRFeature
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Hand.ViveHandTracking ";
StringBuilder m_sb = null;
StringBuilder sb
{
get
{
if (m_sb == null) { m_sb = new StringBuilder(); }
return m_sb;
}
}
void DEBUG(String msg) { Debug.Log(LOG_TAG + msg); }
void DEBUG(StringBuilder msg) { Debug.Log(msg); }
void WARNING(StringBuilder msg) { Debug.LogWarning(msg); }
void ERROR(String msg) { Debug.LogError(LOG_TAG + msg); }
void ERROR(StringBuilder msg) { Debug.LogError(msg); }
#endregion
/// <summary>
/// OpenXR specification <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_hand_tracking">12.29 XR_EXT_hand_tracking</see>.
/// </summary>
public const string kOpenxrExtensionString = "XR_EXT_hand_tracking";
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string featureId = "vive.openxr.feature.hand.tracking";
#region OpenXR Life Cycle
private bool m_XrInstanceCreated = false;
private XrInstance m_XrInstance = 0;
private static IntPtr xrGetInstanceProcAddr_prev;
private static XrTime m_predictedDisplayTime;
private static XrDuration m_predictedDisplayDuration;
private static int sizeOfXrHandJointLocationEXT = Marshal.SizeOf(typeof(XrHandJointLocationEXT));
private static IntPtr handJointLocationsNativeBuffer = IntPtr.Zero;
private static byte[] handJointLocationsByteBuffer = null;
private static int handJointLocationsNativeBufferLength = 0; // Not byte size, it is the number of XrHandJointLocationEXT.
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
{
ViveInterceptors.Instance.AddRequiredFunction("xrWaitFrame");
if (ViveInterceptors.Instance.BeforeOriginalWaitFrame == null)
{
ViveInterceptors.Instance.BeforeOriginalWaitFrame = new ViveInterceptors.DelegateXrWaitFrameInterceptor(BeforeWaitFrame);
}
else
{
ViveInterceptors.Instance.BeforeOriginalWaitFrame += BeforeWaitFrame;
}
if (ViveInterceptors.Instance.AfterOriginalWaitFrame == null)
{
ViveInterceptors.Instance.AfterOriginalWaitFrame = new ViveInterceptors.DelegateXrWaitFrameInterceptor(AfterWaitFrame);
}
else
{
ViveInterceptors.Instance.AfterOriginalWaitFrame += AfterWaitFrame;
}
return ViveInterceptors.Instance.HookGetInstanceProcAddr(func);
}
private bool BeforeWaitFrame(XrSession session, ref ViveInterceptors.XrFrameWaitInfo frameWaitInfo, ref ViveInterceptors.XrFrameState frameState, ref XrResult result)
{
ViveInterceptors.XrFrameState nextFrameState = new ViveInterceptors.XrFrameState
{
type = XrStructureType.XR_TYPE_PASSTHROUGH_HAND_TRACKER_FRAME_STATE_HTC,
next = frameState.next,
predictedDisplayPeriod = 0,
predictedDisplayTime = 0,
shouldRender = false
};
frameState.next = MemoryTools.ToIntPtr(nextFrameState);
return true;
}
private bool AfterWaitFrame(XrSession session, ref ViveInterceptors.XrFrameWaitInfo frameWaitInfo, ref ViveInterceptors.XrFrameState frameState, ref XrResult result)
{
m_predictedDisplayTime = frameState.predictedDisplayTime;
m_predictedDisplayDuration = frameState.predictedDisplayPeriod;
IntPtr next = frameState.next;
HashSet<IntPtr> visited = new HashSet<IntPtr>();
int iterationCount = 0;
int maxIterations = 10;
while (next != IntPtr.Zero && !visited.Contains(next))
{
if (iterationCount++ > maxIterations) { break; }
visited.Add(next);
ViveInterceptors.XrFrameState nextFrameState = Marshal.PtrToStructure<ViveInterceptors.XrFrameState>(next);
if (nextFrameState.type == XrStructureType.XR_TYPE_PASSTHROUGH_HAND_TRACKER_FRAME_STATE_HTC &&
nextFrameState.predictedDisplayTime != 0)
{
m_predictedDisplayTime = nextFrameState.predictedDisplayTime;
break;
}
next = nextFrameState.next;
}
return true;
}
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateInstance">xrCreateInstance</see> is done.
/// </summary>
/// <param name="xrInstance">The created instance.</param>
/// <returns>True for valid <see cref="XrInstance">XrInstance</see></returns>
protected override bool OnInstanceCreate(ulong xrInstance)
{
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
{
sb.Clear().Append(LOG_TAG).Append("OnInstanceCreate() ").Append(kOpenxrExtensionString).Append(" is NOT enabled."); WARNING(sb);
return false;
}
m_XrInstanceCreated = true;
m_XrInstance = xrInstance;
InputSystem.onAfterUpdate += UpdateCallback;
sb.Clear().Append(LOG_TAG).Append("OnInstanceCreate() ").Append(m_XrInstance); DEBUG(sb);
return GetXrFunctionDelegates(m_XrInstance);
}
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroyInstance">xrDestroyInstance</see> is done.
/// </summary>
/// <param name="xrInstance">The instance to destroy.</param>
protected override void OnInstanceDestroy(ulong xrInstance)
{
if (m_XrInstance == xrInstance)
{
m_XrInstanceCreated = false;
m_XrInstance = 0;
InputSystem.onAfterUpdate -= UpdateCallback;
}
sb.Clear().Append(LOG_TAG).Append("OnInstanceDestroy() ").Append(xrInstance); DEBUG(sb);
// release buffer
if (handJointLocationsNativeBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(handJointLocationsNativeBuffer);
handJointLocationsNativeBuffer = IntPtr.Zero;
handJointLocationsByteBuffer = null;
handJointLocationsNativeBufferLength = 0;
}
}
private XrSystemId m_XrSystemId = 0;
/// <summary>
/// Called when the <see cref="XrSystemId">XrSystemId</see> retrieved by <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetSystem">xrGetSystem</see> is changed.
/// </summary>
/// <param name="xrSystem">The system id.</param>
protected override void OnSystemChange(ulong xrSystem)
{
m_XrSystemId = xrSystem;
sb.Clear().Append(LOG_TAG).Append("OnSystemChange() ").Append(m_XrSystemId); DEBUG(sb);
}
private bool m_XrSessionCreated = false;
private XrSession m_XrSession = 0;
private bool hasReferenceSpaceLocal = false, hasReferenceSpaceStage = false;
private XrSpace m_ReferenceSpaceLocal = 0, m_ReferenceSpaceStage = 0;
private bool hasLeftHandTracker = false, hasRightHandTracker = false;
private XrHandTrackerEXT leftHandTracker = 0, rightHandTracker = 0;
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateSession">xrCreateSession</see> is done.
/// </summary>
/// <param name="xrSession">The created session ID.</param>
protected override void OnSessionCreate(ulong xrSession)
{
m_XrSession = xrSession;
m_XrSessionCreated = true;
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() ").Append(m_XrSession); DEBUG(sb);
// Enumerate supported reference space types and create the XrSpace.
XrReferenceSpaceType[] spaces = new XrReferenceSpaceType[Enum.GetNames(typeof(XrReferenceSpaceType)).Count()];
UInt32 spaceCountOutput;
#pragma warning disable 0618
if (EnumerateReferenceSpaces(
spaceCapacityInput: 0,
spaceCountOutput: out spaceCountOutput,
spaces: out spaces[0]) == XrResult.XR_SUCCESS)
#pragma warning restore 0618
{
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() spaceCountOutput: ").Append(spaceCountOutput); DEBUG(sb);
Array.Resize(ref spaces, (int)spaceCountOutput);
#pragma warning disable 0618
if (EnumerateReferenceSpaces(
spaceCapacityInput: spaceCountOutput,
spaceCountOutput: out spaceCountOutput,
spaces: out spaces[0]) == XrResult.XR_SUCCESS)
#pragma warning restore 0618
{
XrReferenceSpaceCreateInfo createInfo;
/// Create m_ReferenceSpaceLocal
if (IsReferenceSpaceTypeSupported(spaceCountOutput, spaces, XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_LOCAL))
{
createInfo.type = XrStructureType.XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
createInfo.next = IntPtr.Zero;
createInfo.referenceSpaceType = XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_LOCAL;//referenceSpaceType;
createInfo.poseInReferenceSpace.orientation = new XrQuaternionf(0, 0, 0, 1);
createInfo.poseInReferenceSpace.position = new XrVector3f(0, 0, 0);
#pragma warning disable 0618
if (CreateReferenceSpace(
createInfo: ref createInfo,
space: out m_ReferenceSpaceLocal) == XrResult.XR_SUCCESS)
#pragma warning restore 0618
{
hasReferenceSpaceLocal = true;
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() CreateReferenceSpace LOCAL: ").Append(m_ReferenceSpaceLocal); DEBUG(sb);
}
else
{
ERROR("OnSessionCreate() CreateReferenceSpace LOCAL failed.");
}
}
/// Create m_ReferenceSpaceStage
if (IsReferenceSpaceTypeSupported(spaceCountOutput, spaces, XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_STAGE))
{
createInfo.type = XrStructureType.XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
createInfo.next = IntPtr.Zero;
createInfo.referenceSpaceType = XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_STAGE;
createInfo.poseInReferenceSpace.orientation = new XrQuaternionf(0, 0, 0, 1);
createInfo.poseInReferenceSpace.position = new XrVector3f(0, 0, 0);
#pragma warning disable 0618
if (CreateReferenceSpace(
createInfo: ref createInfo,
space: out m_ReferenceSpaceStage) == XrResult.XR_SUCCESS)
#pragma warning restore 0618
{
hasReferenceSpaceStage = true;
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() CreateReferenceSpace STAGE: ").Append(m_ReferenceSpaceStage); DEBUG(sb);
}
else
{
ERROR("OnSessionCreate() CreateReferenceSpace STAGE failed.");
}
}
}
else
{
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() EnumerateReferenceSpaces(").Append(spaceCountOutput).Append(") failed."); ERROR(sb);
}
}
else
{
ERROR("OnSessionCreate() EnumerateReferenceSpaces(0) failed.");
}
{ // left hand tracker
if (CreateHandTrackers(true, out XrHandTrackerEXT value))
{
hasLeftHandTracker = true;
leftHandTracker = value;
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() leftHandTracker ").Append(leftHandTracker); DEBUG(sb);
}
}
{ // right hand tracker
if (CreateHandTrackers(false, out XrHandTrackerEXT value))
{
hasRightHandTracker = true;
rightHandTracker = value;
sb.Clear().Append(LOG_TAG).Append("OnSessionCreate() rightHandTracker ").Append(rightHandTracker); DEBUG(sb);
}
}
}
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroySession">xrDestroySession</see> is done.
/// </summary>
/// <param name="xrSession">The session ID to destroy.</param>
protected override void OnSessionDestroy(ulong xrSession)
{
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() ").Append(xrSession); DEBUG(sb);
// Reference Space is binding with xrSession so we destroy the xrSpace when xrSession is destroyed.
if (hasReferenceSpaceLocal)
{
#pragma warning disable 0618
if (DestroySpace(m_ReferenceSpaceLocal) == XrResult.XR_SUCCESS)
#pragma warning restore 0618
{
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() DestroySpace LOCAL ").Append(m_ReferenceSpaceLocal); DEBUG(sb);
m_ReferenceSpaceLocal = 0;
}
else
{
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() DestroySpace LOCAL ").Append(m_ReferenceSpaceLocal).Append(" failed."); ERROR(sb);
}
hasReferenceSpaceLocal = false;
}
if (hasReferenceSpaceStage)
{
#pragma warning disable 0618
if (DestroySpace(m_ReferenceSpaceStage) == XrResult.XR_SUCCESS)
#pragma warning restore 0618
{
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() DestroySpace STAGE ").Append(m_ReferenceSpaceStage); DEBUG(sb);
m_ReferenceSpaceStage = 0;
}
else
{
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() DestroySpace STAGE ").Append(m_ReferenceSpaceStage).Append(" failed."); ERROR(sb);
}
hasReferenceSpaceStage = false;
}
// Hand Tracking is binding with xrSession so we destroy the hand trackers when xrSession is destroyed.
if (hasLeftHandTracker)
{
if (DestroyHandTrackerEXT(leftHandTracker) == XrResult.XR_SUCCESS)
{
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() Left DestroyHandTrackerEXT ").Append(leftHandTracker); DEBUG(sb);
}
else
{
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() Left DestroyHandTrackerEXT ").Append(leftHandTracker).Append(" failed."); ERROR(sb);
}
hasLeftHandTracker = false;
}
if (hasRightHandTracker)
{
if (DestroyHandTrackerEXT(rightHandTracker) == XrResult.XR_SUCCESS)
{
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() Right DestroyHandTrackerEXT ").Append(rightHandTracker); DEBUG(sb);
}
else
{
sb.Clear().Append(LOG_TAG).Append("OnSessionDestroy() Right DestroyHandTrackerEXT ").Append(rightHandTracker).Append(" failed."); ERROR(sb);
}
hasRightHandTracker = false;
}
if (m_XrSession == xrSession)
{
m_XrSession = 0;
m_XrSessionCreated = false;
}
}
#endregion
#region OpenXR function delegates
/// xrGetInstanceProcAddr
OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
/// xrGetSystemProperties
OpenXRHelper.xrGetSystemPropertiesDelegate xrGetSystemProperties;
/// <summary>
/// An application can call GetSystemProperties to retrieve information about the system such as vendor ID, system name, and graphics and tracking properties.
/// </summary>
/// <param name="properties">Points to an instance of the XrSystemProperties structure, that will be filled with returned information.</param>
/// <returns>XR_SUCCESS for success.</returns>
private XrResult GetSystemProperties(ref XrSystemProperties properties)
{
if (!m_XrSessionCreated)
{
ERROR("GetSystemProperties() XR_ERROR_SESSION_LOST.");
return XrResult.XR_ERROR_SESSION_LOST;
}
if (!m_XrInstanceCreated)
{
ERROR("GetSystemProperties() XR_ERROR_INSTANCE_LOST.");
return XrResult.XR_ERROR_INSTANCE_LOST;
}
return xrGetSystemProperties(m_XrInstance, m_XrSystemId, ref properties);
}
/// xrEnumerateReferenceSpaces
OpenXRHelper.xrEnumerateReferenceSpacesDelegate xrEnumerateReferenceSpaces;
/// <summary>
/// Enumerates the set of reference space types that this runtime supports for a given session. Runtimes must always return identical buffer contents from this enumeration for the lifetime of the session.
/// </summary>
/// <param name="spaceCapacityInput">The capacity of the spaces array, or 0 to indicate a request to retrieve the required capacity.</param>
/// <param name="spaceCountOutput">A pointer to the count of spaces written, or a pointer to the required capacity in the case that spaceCapacityInput is insufficient.</param>
/// <param name="spaces">A pointer to an application-allocated array that will be filled with the enumerant of each supported reference space. It can be NULL if spaceCapacityInput is 0.</param>
/// <returns>XR_SUCCESS for success.</returns>
private XrResult EnumerateReferenceSpaces(UInt32 spaceCapacityInput, out UInt32 spaceCountOutput, out XrReferenceSpaceType spaces)
{
if (!m_XrSessionCreated)
{
ERROR("EnumerateReferenceSpaces() XR_ERROR_SESSION_LOST.");
spaceCountOutput = 0;
spaces = XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT;
return XrResult.XR_ERROR_SESSION_LOST;
}
if (!m_XrInstanceCreated)
{
ERROR("EnumerateReferenceSpaces() XR_ERROR_SESSION_LOST.");
spaceCountOutput = 0;
spaces = XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT;
return XrResult.XR_ERROR_INSTANCE_LOST;
}
return xrEnumerateReferenceSpaces(m_XrSession, spaceCapacityInput, out spaceCountOutput, out spaces);
}
/// xrCreateReferenceSpace
OpenXRHelper.xrCreateReferenceSpaceDelegate xrCreateReferenceSpace;
/// <summary>
/// Creates an <see cref="XrSpace">XrSpace</see> handle based on a chosen reference space. Application can provide an <see cref="XrPosef">XrPosef</see> to define the position and orientation of the new spaces origin within the natural reference frame of the reference space.
/// </summary>
/// <param name="createInfo">The XrReferenceSpaceCreateInfo used to specify the space.</param>
/// <param name="space">The returned XrSpace handle.</param>
/// <returns>XR_SUCCESS for success.</returns>
private XrResult CreateReferenceSpace(ref XrReferenceSpaceCreateInfo createInfo, out XrSpace space)
{
if (!m_XrSessionCreated)
{
ERROR("CreateReferenceSpace() XR_ERROR_SESSION_LOST.");
space = 0;
return XrResult.XR_ERROR_SESSION_LOST;
}
if (!m_XrInstanceCreated)
{
ERROR("CreateReferenceSpace() XR_ERROR_INSTANCE_LOST.");
space = 0;
return XrResult.XR_ERROR_INSTANCE_LOST;
}
return xrCreateReferenceSpace(m_XrSession, ref createInfo, out space);
}
/// xrDestroySpace
OpenXRHelper.xrDestroySpaceDelegate xrDestroySpace;
/// <summary>
/// <see cref="XrSpace">XrSpace</see> handles are destroyed using DestroySpace. The runtime may still use this space if there are active dependencies (e.g, compositions in progress).
/// </summary>
/// <param name="space">Must be a valid XrSpace handle.</param>
/// <returns>XR_SUCCESS for success.</returns>
private XrResult DestroySpace(XrSpace space)
{
if (!m_XrSessionCreated)
{
ERROR("DestroySpace() XR_ERROR_SESSION_LOST.");
return XrResult.XR_ERROR_SESSION_LOST;
}
if (!m_XrInstanceCreated)
{
ERROR("DestroySpace() XR_ERROR_INSTANCE_LOST.");
return XrResult.XR_ERROR_INSTANCE_LOST;
}
return xrDestroySpace(space);
}
/// xrCreateHandTrackerEXT
ViveHandTrackingHelper.xrCreateHandTrackerEXTDelegate xrCreateHandTrackerEXT;
/// <summary>
/// An application can create an <see cref="XrHandTrackerEXT">XrHandTrackerEXT</see> handle using CreateHandTrackerEXT function.
/// </summary>
/// <param name="createInfo">The XrHandTrackerCreateInfoEXT used to specify the hand tracker.</param>
/// <param name="handTracker">The returned XrHandTrackerEXT handle.</param>
/// <returns>XR_SUCCESS for success.</returns>
public XrResult CreateHandTrackerEXT(ref XrHandTrackerCreateInfoEXT createInfo, out XrHandTrackerEXT handTracker)
{
if (!m_XrSessionCreated)
{
ERROR("CreateHandTrackerEXT() XR_ERROR_SESSION_LOST.");
handTracker = 0;
return XrResult.XR_ERROR_SESSION_LOST;
}
if (!m_XrInstanceCreated)
{
ERROR("CreateHandTrackerEXT() XR_ERROR_INSTANCE_LOST.");
handTracker = 0;
return XrResult.XR_ERROR_INSTANCE_LOST;
}
if (createInfo.hand == XrHandEXT.XR_HAND_LEFT_EXT && hasLeftHandTracker)
{
sb.Clear().Append(LOG_TAG).Append("CreateHandTrackerEXT() Left tracker ").Append(leftHandTracker).Append(" already created."); DEBUG(sb);
handTracker = leftHandTracker;
return XrResult.XR_SUCCESS;
}
if (createInfo.hand == XrHandEXT.XR_HAND_RIGHT_EXT && hasRightHandTracker)
{
sb.Clear().Append(LOG_TAG).Append("CreateHandTrackerEXT() Right tracker ").Append(rightHandTracker).Append(" already created."); DEBUG(sb);
handTracker = rightHandTracker;
return XrResult.XR_SUCCESS;
}
return xrCreateHandTrackerEXT(m_XrSession, ref createInfo, out handTracker);
}
/// xrDestroyHandTrackerEXT
ViveHandTrackingHelper.xrDestroyHandTrackerEXTDelegate xrDestroyHandTrackerEXT;
/// <summary>
/// Releases the handTracker and the underlying resources when finished with hand tracking experiences.
/// </summary>
/// <param name="handTracker">An XrHandTrackerEXT previously created by <see cref="CreateHandTrackerEXT">CreateHandTrackerEXT</see>.</param>
/// <returns>XR_SUCCESS for success.</returns>
public XrResult DestroyHandTrackerEXT(XrHandTrackerEXT handTracker)
{
if (!m_XrSessionCreated)
{
ERROR("DestroyHandTrackerEXT() XR_ERROR_SESSION_LOST.");
return XrResult.XR_ERROR_SESSION_LOST;
}
if (!m_XrInstanceCreated)
{
ERROR("DestroyHandTrackerEXT() XR_ERROR_INSTANCE_LOST.");
return XrResult.XR_ERROR_INSTANCE_LOST;
}
return xrDestroyHandTrackerEXT(handTracker);
}
/// xrLocateHandJointsEXT
ViveHandTrackingHelper.xrLocateHandJointsEXTDelegate xrLocateHandJointsEXT;
/// <summary>
/// The LocateHandJointsEXT function locates an array of hand joints to a base space at given time.
/// </summary>
/// <param name="handTracker">An <see cref="XrHandTrackerEXT">XrHandTrackerEXT</see> previously created by <see cref="ViveHandTracking.CreateHandTrackerEXT(ref XrHandTrackerCreateInfoEXT, out XrHandTrackerEXT)">CreateHandTrackerEXT</see>.</param>
/// <param name="locateInfo">A pointer to <see cref="XrHandJointsLocateInfoEXT">XrHandJointsLocateInfoEXT</see> describing information to locate hand joints.</param>
/// <param name="locations">A pointer to <see cref="XrHandJointLocationsEXT">XrHandJointLocationsEXT</see> receiving the returned hand joint locations.</param>
/// <returns></returns>
public XrResult LocateHandJointsEXT(XrHandTrackerEXT handTracker, XrHandJointsLocateInfoEXT locateInfo, ref XrHandJointLocationsEXT locations)
{
if (!m_XrSessionCreated)
{
ERROR("LocateHandJointsEXT() XR_ERROR_SESSION_LOST.");
return XrResult.XR_ERROR_SESSION_LOST;
}
if (!m_XrInstanceCreated)
{
ERROR("LocateHandJointsEXT() XR_ERROR_INSTANCE_LOST.");
return XrResult.XR_ERROR_INSTANCE_LOST;
}
return xrLocateHandJointsEXT(handTracker, locateInfo, ref locations);
}
private bool GetXrFunctionDelegates(XrInstance xrInstance)
{
/// xrGetInstanceProcAddr
if (xrGetInstanceProcAddr != null && xrGetInstanceProcAddr != IntPtr.Zero)
{
DEBUG("Get function pointer of xrGetInstanceProcAddr.");
XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer(
xrGetInstanceProcAddr,
typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate;
}
else
{
ERROR("xrGetInstanceProcAddr");
return false;
}
IntPtr funcPtr = IntPtr.Zero;
/// xrGetSystemProperties
if (XrGetInstanceProcAddr(xrInstance, "xrGetSystemProperties", out funcPtr) == XrResult.XR_SUCCESS)
{
if (funcPtr != IntPtr.Zero)
{
DEBUG("Get function pointer of xrGetSystemProperties.");
xrGetSystemProperties = Marshal.GetDelegateForFunctionPointer(
funcPtr,
typeof(OpenXRHelper.xrGetSystemPropertiesDelegate)) as OpenXRHelper.xrGetSystemPropertiesDelegate;
}
}
else
{
ERROR("xrGetSystemProperties");
return false;
}
/// xrEnumerateReferenceSpaces
if (XrGetInstanceProcAddr(xrInstance, "xrEnumerateReferenceSpaces", out funcPtr) == XrResult.XR_SUCCESS)
{
if (funcPtr != IntPtr.Zero)
{
DEBUG("Get function pointer of xrEnumerateReferenceSpaces.");
xrEnumerateReferenceSpaces = Marshal.GetDelegateForFunctionPointer(
funcPtr,
typeof(OpenXRHelper.xrEnumerateReferenceSpacesDelegate)) as OpenXRHelper.xrEnumerateReferenceSpacesDelegate;
}
}
else
{
ERROR("xrEnumerateReferenceSpaces");
return false;
}
/// xrCreateReferenceSpace
if (XrGetInstanceProcAddr(xrInstance, "xrCreateReferenceSpace", out funcPtr) == XrResult.XR_SUCCESS)
{
if (funcPtr != IntPtr.Zero)
{
DEBUG("Get function pointer of xrCreateReferenceSpace.");
xrCreateReferenceSpace = Marshal.GetDelegateForFunctionPointer(
funcPtr,
typeof(OpenXRHelper.xrCreateReferenceSpaceDelegate)) as OpenXRHelper.xrCreateReferenceSpaceDelegate;
}
}
else
{
ERROR("xrCreateReferenceSpace");
return false;
}
/// xrDestroySpace
if (XrGetInstanceProcAddr(xrInstance, "xrDestroySpace", out funcPtr) == XrResult.XR_SUCCESS)
{
if (funcPtr != IntPtr.Zero)
{
DEBUG("Get function pointer of xrDestroySpace.");
xrDestroySpace = Marshal.GetDelegateForFunctionPointer(
funcPtr,
typeof(OpenXRHelper.xrDestroySpaceDelegate)) as OpenXRHelper.xrDestroySpaceDelegate;
}
}
else
{
ERROR("xrDestroySpace");
return false;
}
/// xrCreateHandTrackerEXT
if (XrGetInstanceProcAddr(xrInstance, "xrCreateHandTrackerEXT", out funcPtr) == XrResult.XR_SUCCESS)
{
if (funcPtr != IntPtr.Zero)
{
DEBUG("Get function pointer of xrCreateHandTrackerEXT.");
xrCreateHandTrackerEXT = Marshal.GetDelegateForFunctionPointer(
funcPtr,
typeof(ViveHandTrackingHelper.xrCreateHandTrackerEXTDelegate)) as ViveHandTrackingHelper.xrCreateHandTrackerEXTDelegate;
}
}
else
{
ERROR("xrCreateHandTrackerEXT");
return false;
}
/// xrDestroyHandTrackerEXT
if (XrGetInstanceProcAddr(xrInstance, "xrDestroyHandTrackerEXT", out funcPtr) == XrResult.XR_SUCCESS)
{
if (funcPtr != IntPtr.Zero)
{
DEBUG("Get function pointer of xrDestroyHandTrackerEXT.");
xrDestroyHandTrackerEXT = Marshal.GetDelegateForFunctionPointer(
funcPtr,
typeof(ViveHandTrackingHelper.xrDestroyHandTrackerEXTDelegate)) as ViveHandTrackingHelper.xrDestroyHandTrackerEXTDelegate;
}
}
else
{
ERROR("xrDestroyHandTrackerEXT");
return false;
}
/// xrLocateHandJointsEXT
if (XrGetInstanceProcAddr(xrInstance, "xrLocateHandJointsEXT", out funcPtr) == XrResult.XR_SUCCESS)
{
if (funcPtr != IntPtr.Zero)
{
DEBUG("Get function pointer of xrLocateHandJointsEXT.");
xrLocateHandJointsEXT = Marshal.GetDelegateForFunctionPointer(
funcPtr,
typeof(ViveHandTrackingHelper.xrLocateHandJointsEXTDelegate)) as ViveHandTrackingHelper.xrLocateHandJointsEXTDelegate;
}
}
else
{
ERROR("xrLocateHandJointsEXT");
return false;
}
return true;
}
#endregion
static List<XRInputSubsystem> s_InputSubsystems = new List<XRInputSubsystem>();
/// <summary>
/// Retrieves the current tracking origin in Unity XR.
/// </summary>
/// <returns>The tracking origin in <see href="https://docs.unity3d.com/ScriptReference/XR.TrackingOriginModeFlags.html">TrackingOriginModeFlags</see></returns>
public TrackingOriginModeFlags GetTrackingOriginMode()
{
XRInputSubsystem subsystem = null;
SubsystemManager.GetSubsystems(s_InputSubsystems);
if (s_InputSubsystems.Count > 0)
{
subsystem = s_InputSubsystems[0];
}
if (subsystem != null)
{
return subsystem.GetTrackingOriginMode();
}
return TrackingOriginModeFlags.Unknown;
}
private bool IsReferenceSpaceTypeSupported(UInt32 spaceCountOutput, XrReferenceSpaceType[] spaces, XrReferenceSpaceType space)
{
bool support = false;
for (int i = 0; i < spaceCountOutput; i++)
{
sb.Clear().Append(LOG_TAG).Append("IsReferenceSpaceTypeSupported() supported space[").Append(i).Append("]: ").Append(spaces[i]); DEBUG(sb);
if (spaces[i] == space) { support = true; }
}
return support;
}
XrSystemHandTrackingPropertiesEXT handTrackingSystemProperties;
XrSystemProperties systemProperties;
private bool IsHandTrackingSupported()
{
bool ret = false;
if (!m_XrSessionCreated)
{
ERROR("IsHandTrackingSupported() session is not created.");
return ret;
}
handTrackingSystemProperties.type = XrStructureType.XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT;
systemProperties.type = XrStructureType.XR_TYPE_SYSTEM_PROPERTIES;
systemProperties.next = Marshal.AllocHGlobal(Marshal.SizeOf(handTrackingSystemProperties));
long offset = 0;
if (IntPtr.Size == 4)
offset = systemProperties.next.ToInt32();
else
offset = systemProperties.next.ToInt64();
IntPtr sys_hand_tracking_prop_ptr = new IntPtr(offset);
Marshal.StructureToPtr(handTrackingSystemProperties, sys_hand_tracking_prop_ptr, false);
#pragma warning disable 0618
if (GetSystemProperties(ref systemProperties) == XrResult.XR_SUCCESS)
#pragma warning restore 0618
{
if (IntPtr.Size == 4)
offset = systemProperties.next.ToInt32();
else
offset = systemProperties.next.ToInt64();
sys_hand_tracking_prop_ptr = new IntPtr(offset);
handTrackingSystemProperties = (XrSystemHandTrackingPropertiesEXT)Marshal.PtrToStructure(sys_hand_tracking_prop_ptr, typeof(XrSystemHandTrackingPropertiesEXT));
sb.Clear().Append(LOG_TAG).Append("IsHandTrackingSupported() XrSystemHandTrackingPropertiesEXT.supportsHandTracking: ").Append((UInt32)handTrackingSystemProperties.supportsHandTracking); DEBUG(sb);
ret = handTrackingSystemProperties.supportsHandTracking > 0;
}
else
{
ERROR("IsHandTrackingSupported() GetSystemProperties failed.");
}
Marshal.FreeHGlobal(systemProperties.next);
return ret;
}
private bool CreateHandTrackers(bool isLeft, out XrHandTrackerEXT handTracker)
{
if (!IsHandTrackingSupported())
{
sb.Clear().Append(LOG_TAG).Append("CreateHandTrackers() ").Append((isLeft ? "Left" : "Right")).Append(" hand tracking is NOT supported."); ERROR(sb);
handTracker = 0;
return false;
}
XrHandTrackerCreateInfoEXT createInfo;
createInfo.type = XrStructureType.XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT;
createInfo.next = IntPtr.Zero;
createInfo.hand = isLeft ? XrHandEXT.XR_HAND_LEFT_EXT : XrHandEXT.XR_HAND_RIGHT_EXT;
createInfo.handJointSet = XrHandJointSetEXT.XR_HAND_JOINT_SET_DEFAULT_EXT;
var ret = CreateHandTrackerEXT(ref createInfo, out handTracker);
sb.Clear().Append(LOG_TAG).Append("CreateHandTrackers() ").Append((isLeft ? "Left" : "Right")).Append(" CreateHandTrackerEXT = ").Append(ret); DEBUG(sb);
return ret == XrResult.XR_SUCCESS;
}
private XrHandJointLocationEXT[] jointLocationsL = new XrHandJointLocationEXT[(int)XrHandJointEXT.XR_HAND_JOINT_MAX_ENUM_EXT];
private XrHandJointLocationEXT[] jointLocationsR = new XrHandJointLocationEXT[(int)XrHandJointEXT.XR_HAND_JOINT_MAX_ENUM_EXT];
private XrHandJointLocationsEXT locations = new XrHandJointLocationsEXT(XrStructureType.XR_TYPE_HAND_JOINT_LOCATIONS_EXT, IntPtr.Zero, false, 0, IntPtr.Zero);
/// <summary>
/// Retrieves the <see cref="XrSpace"> XrSpace </see> used in Hand Tracking.
/// </summary>
/// <param name="space">Tracking space in <see cref="XrSpace"> XrSpace </see>.</param>
/// <returns>True for valid data.</returns>
public bool GetHandTrackingSpace(out XrSpace space)
{
space = 0;
TrackingOriginModeFlags origin = GetTrackingOriginMode();
if (origin == TrackingOriginModeFlags.Unknown || origin == TrackingOriginModeFlags.Unbounded) { return false; }
space = (origin == TrackingOriginModeFlags.Device ? m_ReferenceSpaceLocal : m_ReferenceSpaceStage);
return true;
}
private int lastUpdateFrameL = -1, lastUpdateFrameR = -1, updateFrame = -1;
private void UpdateCallback()
{
// Only allow updating poses once at BeforeRender & Dynamic per frame.
if (InputState.currentUpdateType == InputUpdateType.BeforeRender ||
InputState.currentUpdateType == InputUpdateType.Dynamic)
{
lastUpdateFrameL = -1;
lastUpdateFrameR = -1;
}
if (InputState.currentUpdateType == InputUpdateType.BeforeRender)
{
updateFrame = Time.frameCount;
}
}
private bool AllowUpdate(bool isLeft)
{
bool allow;
if (isLeft)
{
allow = (lastUpdateFrameL != Time.frameCount);
lastUpdateFrameL = Time.frameCount;
}
else
{
allow = (lastUpdateFrameR != Time.frameCount);
lastUpdateFrameR = Time.frameCount;
}
return allow;
}
/// <summary>
/// Retrieves the <see cref="XrHandJointLocationEXT"> XrHandJointLocationEXT </see> data.
/// </summary>
/// <param name="isLeft">Left or right hand.</param>
/// <param name="handJointLocation">Output parameter to retrieve <see cref="XrHandJointLocationEXT"> XrHandJointLocationEXT </see> data.</param>
/// <param name="timestamp">The hand tracking data timestamp.</param>
/// <returns>True for valid data.</returns>
public bool GetJointLocations(bool isLeft, out XrHandJointLocationEXT[] handJointLocation, out XrTime timestamp)
{
handJointLocation = isLeft ? jointLocationsL : jointLocationsR;
long displayTime = m_predictedDisplayTime;
if (Time.frameCount > updateFrame)
{
displayTime += m_predictedDisplayDuration;
}
timestamp = displayTime;
if (!AllowUpdate(isLeft)) { return true; }
bool ret = false;
if (isLeft && !hasLeftHandTracker) { return ret; }
if (!isLeft && !hasRightHandTracker) { return ret; }
/// Configures XrHandJointsLocateInfoEXT
XrHandJointsLocateInfoEXT locateInfo = new XrHandJointsLocateInfoEXT(
in_type: XrStructureType.XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT,
in_next: IntPtr.Zero,
in_baseSpace: GetCurrentAppSpace(),
in_time: displayTime);
/// Configures XrHandJointLocationsEXT
locations.type = XrStructureType.XR_TYPE_HAND_JOINT_LOCATIONS_EXT;
locations.next = IntPtr.Zero;
locations.isActive = false;
locations.jointCount = (uint)(handJointLocation.Length);
int jointLocationsLength = handJointLocation.Length;
if (handJointLocationsNativeBuffer == null || handJointLocationsNativeBuffer == IntPtr.Zero)
{
int N = sizeOfXrHandJointLocationEXT * jointLocationsLength;
handJointLocationsNativeBuffer = Marshal.AllocHGlobal(N);
handJointLocationsByteBuffer = new byte[N];
handJointLocationsNativeBufferLength = jointLocationsLength;
DEBUG($"GetJointLocations() handJointLocationsNativeBuffer[{N}] is allocated.");
}
else if (handJointLocationsNativeBufferLength < jointLocationsLength)
{
Marshal.FreeHGlobal(handJointLocationsNativeBuffer);
int N = sizeOfXrHandJointLocationEXT * jointLocationsLength;
handJointLocationsNativeBuffer = Marshal.AllocHGlobal(N);
handJointLocationsByteBuffer = new byte[N];
handJointLocationsNativeBufferLength = jointLocationsLength;
DEBUG($"GetJointLocations() handJointLocationsNativeBuffer[{N}] is allocated.");
}
locations.jointLocations = handJointLocationsNativeBuffer;
var retX = LocateHandJointsEXT(
handTracker: (isLeft ? leftHandTracker : rightHandTracker),
locateInfo: locateInfo,
locations: ref locations);
if (retX == XrResult.XR_SUCCESS)
{
timestamp = locateInfo.time;
if (locations.isActive)
{
MemoryTools.CopyAllFromRawMemory(handJointLocation, handJointLocationsNativeBuffer);
ret = true;
}
}
return ret;
}
/// <summary>
/// Retrieves the <see cref="XrHandJointLocationEXT"> XrHandJointLocationEXT </see> data.
/// </summary>
/// <param name="isLeft">Left or right hand.</param>
/// <param name="handJointLocation">Output parameter to retrieve <see cref="XrHandJointLocationEXT"> XrHandJointLocationEXT </see> data.</param>
/// <returns>True for valid data.</returns>
public bool GetJointLocations(bool isLeft, out XrHandJointLocationEXT[] handJointLocation)
{
return GetJointLocations(isLeft, out handJointLocation, out XrTime timestamp);
}
}
}

View File

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

View File

@@ -0,0 +1,350 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
namespace VIVE.OpenXR.Hand
{
/// <summary>
/// The XrHandTrackerEXT handle represents the resources for hand tracking of the specific hand.
/// </summary>
public struct XrHandTrackerEXT : IEquatable<ulong>
{
private readonly ulong value;
public XrHandTrackerEXT(ulong u)
{
value = u;
}
public static implicit operator ulong(XrHandTrackerEXT xrInst)
{
return xrInst.value;
}
public static implicit operator XrHandTrackerEXT(ulong u)
{
return new XrHandTrackerEXT(u);
}
public bool Equals(XrHandTrackerEXT other)
{
return value == other.value;
}
public bool Equals(ulong other)
{
return value == other;
}
public override bool Equals(object obj)
{
return obj is XrHandTrackerEXT && Equals((XrHandTrackerEXT)obj);
}
public override int GetHashCode()
{
return value.GetHashCode();
}
public override string ToString()
{
return value.ToString();
}
public static bool operator ==(XrHandTrackerEXT a, XrHandTrackerEXT b) { return a.Equals(b); }
public static bool operator !=(XrHandTrackerEXT a, XrHandTrackerEXT b) { return !a.Equals(b); }
public static bool operator >=(XrHandTrackerEXT a, XrHandTrackerEXT b) { return a.value >= b.value; }
public static bool operator <=(XrHandTrackerEXT a, XrHandTrackerEXT b) { return a.value <= b.value; }
public static bool operator >(XrHandTrackerEXT a, XrHandTrackerEXT b) { return a.value > b.value; }
public static bool operator <(XrHandTrackerEXT a, XrHandTrackerEXT b) { return a.value < b.value; }
public static XrHandTrackerEXT operator +(XrHandTrackerEXT a, XrHandTrackerEXT b) { return a.value + b.value; }
public static XrHandTrackerEXT operator -(XrHandTrackerEXT a, XrHandTrackerEXT b) { return a.value - b.value; }
public static XrHandTrackerEXT operator *(XrHandTrackerEXT a, XrHandTrackerEXT b) { return a.value * b.value; }
public static XrHandTrackerEXT operator /(XrHandTrackerEXT a, XrHandTrackerEXT b)
{
if (b.value == 0)
{
throw new DivideByZeroException();
}
return a.value / b.value;
}
}
/// <summary>
/// The XrHandEXT describes which hand the <see cref="XrHandTrackerEXT">XrHandTrackerEXT</see> is tracking.
/// </summary>
public enum XrHandEXT
{
/// <summary>
/// Specifies the hand tracker will be tracking the users left hand.
/// </summary>
XR_HAND_LEFT_EXT = 1,
/// <summary>
/// Specifies the hand tracker will be tracking the users right hand.
/// </summary>
XR_HAND_RIGHT_EXT = 2,
XR_HAND_MAX_ENUM_EXT = 3
}
/// <summary>
/// Defines 26 joints for hand tracking: 4 joints for the thumb finger, 5 joints for the other four fingers, and the wrist and palm of the hands.
/// </summary>
public enum XrHandJointEXT
{
XR_HAND_JOINT_PALM_EXT = 0,
XR_HAND_JOINT_WRIST_EXT = 1,
XR_HAND_JOINT_THUMB_METACARPAL_EXT = 2,
XR_HAND_JOINT_THUMB_PROXIMAL_EXT = 3,
XR_HAND_JOINT_THUMB_DISTAL_EXT = 4,
XR_HAND_JOINT_THUMB_TIP_EXT = 5,
XR_HAND_JOINT_INDEX_METACARPAL_EXT = 6,
XR_HAND_JOINT_INDEX_PROXIMAL_EXT = 7,
XR_HAND_JOINT_INDEX_INTERMEDIATE_EXT = 8,
XR_HAND_JOINT_INDEX_DISTAL_EXT = 9,
XR_HAND_JOINT_INDEX_TIP_EXT = 10,
XR_HAND_JOINT_MIDDLE_METACARPAL_EXT = 11,
XR_HAND_JOINT_MIDDLE_PROXIMAL_EXT = 12,
XR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT = 13,
XR_HAND_JOINT_MIDDLE_DISTAL_EXT = 14,
XR_HAND_JOINT_MIDDLE_TIP_EXT = 15,
XR_HAND_JOINT_RING_METACARPAL_EXT = 16,
XR_HAND_JOINT_RING_PROXIMAL_EXT = 17,
XR_HAND_JOINT_RING_INTERMEDIATE_EXT = 18,
XR_HAND_JOINT_RING_DISTAL_EXT = 19,
XR_HAND_JOINT_RING_TIP_EXT = 20,
XR_HAND_JOINT_LITTLE_METACARPAL_EXT = 21,
XR_HAND_JOINT_LITTLE_PROXIMAL_EXT = 22,
XR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT = 23,
XR_HAND_JOINT_LITTLE_DISTAL_EXT = 24,
XR_HAND_JOINT_LITTLE_TIP_EXT = 25,
XR_HAND_JOINT_MAX_ENUM_EXT = 26
}
/// <summary>
/// The XrHandJointSetEXT enum describes the set of hand joints to track when creating an <see cref="XrHandTrackerEXT">XrHandTrackerEXT</see>.
/// </summary>
public enum XrHandJointSetEXT
{
/// <summary>
/// Indicates that the created <see cref="XrHandTrackerEXT">XrHandTrackerEXT</see> tracks the set of hand joints described by <see cref="XrHandJointEXT">XrHandJointEXT</see> enum, i.e. the <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrLocateHandJointsEXT">xrLocateHandJointsEXT</see> function returns an array of joint locations with the count of <see cref="ViveHandTrackingHelper.XR_HAND_JOINT_COUNT_EXT">XR_HAND_JOINT_COUNT_EXT</see> and can be indexed using <see cref="XrHandJointEXT">XrHandJointEXT</see>.
/// </summary>
XR_HAND_JOINT_SET_DEFAULT_EXT = 0,
XR_HAND_JOINT_SET_MAX_ENUM_EXT = 1
}
/// <summary>
/// An application can inspect whether the system is capable of hand tracking input by extending the <see cref="XrSystemProperties">XrSystemProperties</see> with XrSystemHandTrackingPropertiesEXT structure when calling <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetSystemProperties">xrGetSystemProperties</see>.
/// </summary>
public struct XrSystemHandTrackingPropertiesEXT
{
/// <summary>
/// The <see cref="XrStructureType">XrStructureType</see> of this structure.
/// </summary>
public XrStructureType type;
/// <summary>
/// NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.
/// </summary>
public IntPtr next;
/// <summary>
/// An <see cref="XrBool32">XrBool32</see>, indicating if current system is capable of hand tracking input.
/// </summary>
public XrBool32 supportsHandTracking;
};
/// <summary>
/// The XrHandTrackerCreateInfoEXT structure describes the information to create an <see cref="XrHandTrackerEXT">XrHandTrackerEXT</see> handle.
/// </summary>
public struct XrHandTrackerCreateInfoEXT
{
/// <summary>
/// The XrStructureType of this structure.
/// </summary>
public XrStructureType type;
/// <summary>
/// NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.
/// </summary>
public IntPtr next;
/// <summary>
/// An <see cref="XrHandEXT">XrHandEXT</see> which describes which hand the tracker is tracking.
/// </summary>
public XrHandEXT hand;
/// <summary>
/// An <see cref="XrHandJointSetEXT">XrHandJointSetEXT</see> describe the set of hand joints to retrieve.
/// </summary>
public XrHandJointSetEXT handJointSet;
/// <param name="in_type">The XrStructureType of this structure.</param>
/// <param name="in_next">NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.</param>
/// <param name="in_hand">An <see cref="XrHandEXT">XrHandEXT</see> which describes which hand the tracker is tracking.</param>
/// <param name="in_handJointSet">An <see cref="XrHandJointSetEXT">XrHandJointSetEXT</see> describe the set of hand joints to retrieve.</param>
public XrHandTrackerCreateInfoEXT(XrStructureType in_type, IntPtr in_next, XrHandEXT in_hand, XrHandJointSetEXT in_handJointSet)
{
type = in_type;
next = in_next;
hand = in_hand;
handJointSet = in_handJointSet;
}
}
/// <summary>
/// The XrHandJointsLocateInfoEXT structure describes the information to locate hand joints.
/// </summary>
public struct XrHandJointsLocateInfoEXT
{
/// <summary>
/// The <see cref="XrStructureType">XrStructureType</see> of this structure.
/// </summary>
public XrStructureType type;
/// <summary>
/// NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.
/// </summary>
public IntPtr next;
/// <summary>
/// An <see cref="XrSpace">XrSpace</see> within which the returned hand joint locations will be represented.
/// </summary>
public XrSpace baseSpace;
/// <summary>
/// An <see cref="XrTime">XrTime</see> at which to locate the hand joints.
/// </summary>
public XrTime time;
/// <param name="in_type">The <see cref="XrStructureType">XrStructureType</see> of this structure.</param>
/// <param name="in_next">NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.</param>
/// <param name="in_baseSpace">An <see cref="XrSpace">XrSpace</see> within which the returned hand joint locations will be represented.</param>
/// <param name="in_time">An <see cref="XrTime">XrTime</see> at which to locate the hand joints.</param>
public XrHandJointsLocateInfoEXT(XrStructureType in_type, IntPtr in_next, XrSpace in_baseSpace, XrTime in_time)
{
type = in_type;
next = in_next;
baseSpace = in_baseSpace;
time = in_time;
}
};
/// <summary>
/// XrHandJointLocationEXT structure describes the position, orientation, and radius of a hand joint.
/// </summary>
public struct XrHandJointLocationEXT
{
/// <summary>
/// A bitfield, with bit masks defined in <see cref="XrSpaceLocationFlags">XrSpaceLocationFlags</see>, to indicate which members contain valid data. If none of the bits are set, no other fields in this structure should be considered to be valid or meaningful.
/// </summary>
public XrSpaceLocationFlags locationFlags;
/// <summary>
/// An <see cref="XrPosef">XrPosef</see> defining the position and orientation of the origin of a hand joint within the reference frame of the corresponding <see cref="XrHandJointsLocateInfoEXT.baseSpace">XrHandJointsLocateInfoEXT::baseSpace</see>.
/// </summary>
public XrPosef pose;
/// <summary>
/// A float value radius of the corresponding joint in units of meters.
/// </summary>
public float radius;
}
/// <summary>
/// XrHandJointVelocityEXT structure describes the linear and angular velocity of a hand joint.
/// </summary>
public struct XrHandJointVelocityEXT
{
/// <summary>
/// A bitfield, with bit masks defined in <see cref="XrSpaceVelocityFlags">XrSpaceVelocityFlags</see>, to indicate which members contain valid data. If none of the bits are set, no other fields in this structure should be considered to be valid or meaningful.
/// </summary>
public XrSpaceVelocityFlags velocityFlags;
/// <summary>
/// The relative linear velocity of the hand joint with respect to and expressed in the reference frame of the corresponding <see cref="XrHandJointsLocateInfoEXT.baseSpace">XrHandJointsLocateInfoEXT::baseSpace</see>, in units of meters per second.
/// </summary>
public XrVector3f linearVelocity;
/// <summary>
/// The relative angular velocity of the hand joint with respect to the corresponding <see cref="XrHandJointsLocateInfoEXT.baseSpace">XrHandJointsLocateInfoEXT::baseSpace</see>. The vectors direction is expressed in the reference frame of the corresponding <see cref="XrHandJointsLocateInfoEXT.baseSpace">XrHandJointsLocateInfoEXT::baseSpace</see> and is parallel to the rotational axis of the hand joint. The vectors magnitude is the relative angular speed of the hand joint in radians per second. The vector follows the right-hand rule for torque/rotation.
/// </summary>
public XrVector3f angularVelocity;
}
/// <summary>
/// The application can chain an XrHandJointVelocitiesEXT structure to the next pointer of <see cref="XrHandJointLocationsEXT">XrHandJointLocationsEXT</see> when calling <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrLocateHandJointsEXT">xrLocateHandJointsEXT</see> to retrieve the hand joint velocities.
/// </summary>
public struct XrHandJointVelocitiesEXT
{
/// <summary>
/// The <see cref="XrStructureType">XrStructureType</see> of this structure.
/// </summary>
public XrStructureType type;
/// <summary>
/// NULL or a pointer to the next structure in a structure chain. No such structures are defined in core OpenXR or this extension.
/// </summary>
public IntPtr next;
/// <summary>
/// A uint32_t describing the number of elements in jointVelocities array.
/// </summary>
UInt32 jointCount;
/// <summary>
/// An array of <see cref="XrHandJointVelocityEXT">XrHandJointVelocityEXT</see> receiving the returned hand joint velocities.
/// </summary>
IntPtr jointVelocities; //XrHandJointVelocityEXT*
}
/// <summary>
/// XrHandJointLocationsEXT structure returns the state of the hand joint locations.
/// </summary>
public struct XrHandJointLocationsEXT
{
/// <summary>
/// The <see cref="XrStructureType">XrStructureType</see> of this structure.
/// </summary>
public XrStructureType type;
/// <summary>
/// NULL or a pointer to the next structure in a structure chain, such as <see cref="XrHandJointVelocitiesEXT">XrHandJointVelocitiesEXT</see>.
/// </summary>
public IntPtr next;
/// <summary>
/// An <see cref="XrBool32">XrBool32</see> indicating if the hand tracker is actively tracking.
/// </summary>
public XrBool32 isActive;
/// <summary>
/// A uint32_t describing the count of elements in jointLocations array.
/// </summary>
public UInt32 jointCount;
/// <summary>
/// An array of <see cref="XrHandJointLocationEXT">XrHandJointLocationEXT</see> receiving the returned hand joint locations.
/// </summary>
public IntPtr jointLocations; //XrHandJointLocationEXT*
/// <param name="in_type">The <see cref="XrStructureType">XrStructureType</see> of this structure.</param>
/// <param name="in_next">NULL or a pointer to the next structure in a structure chain, such as <see cref="XrHandJointVelocitiesEXT">XrHandJointVelocitiesEXT</see>.</param>
/// <param name="in_isActive">An <see cref="XrBool32">XrBool32</see> indicating if the hand tracker is actively tracking.</param>
/// <param name="in_jointCount">A uint32_t describing the count of elements in jointLocations array.</param>
/// <param name="in_jointLocations">An array of <see cref="XrHandJointLocationEXT">XrHandJointLocationEXT</see> receiving the returned hand joint locations.</param>
public XrHandJointLocationsEXT(XrStructureType in_type, IntPtr in_next, XrBool32 in_isActive, UInt32 in_jointCount, IntPtr in_jointLocations)
{
type = in_type;
next = in_next;
isActive = in_isActive;
jointCount = in_jointCount;
jointLocations = in_jointLocations;
}
}
public static class ViveHandTrackingHelper
{
/// <summary>
/// Defines the number of hand joint enumerants defined in <see cref="XrHandJointEXT">XrHandJointEXT</see>.
/// </summary>
public const int XR_HAND_JOINT_COUNT_EXT = 26;
/// <summary>
/// The function delegate of <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateHandTrackerEXT">xrCreateHandTrackerEXT</see>.
/// </summary>
/// <param name="session">An <see cref="XrSession">XrSession</see> in which the hand tracker will be active.</param>
/// <param name="createInfo">The <see cref="XrHandTrackerCreateInfoEXT">XrHandTrackerCreateInfoEXT</see> used to specify the hand tracker.</param>
/// <param name="handTracker">The returned <see cref="XrHandTrackerEXT">XrHandTrackerEXT</see> handle.</param>
/// <returns>XR_SUCCESS for success.</returns>
public delegate XrResult xrCreateHandTrackerEXTDelegate(
XrSession session,
ref XrHandTrackerCreateInfoEXT createInfo,
out XrHandTrackerEXT handTracker);
/// <summary>
/// The function delegate of <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroyHandTrackerEXT">xrDestroyHandTrackerEXT</see>.
/// </summary>
/// <param name="handTracker">An <see cref="XrHandTrackerEXT">XrHandTrackerEXT</see> previously created by <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateHandTrackerEXT">xrCreateHandTrackerEXT</see>.</param>
/// <returns>XR_SUCCESS for success.</returns>
public delegate XrResult xrDestroyHandTrackerEXTDelegate(
XrHandTrackerEXT handTracker);
/// <summary>
/// The function delegate of <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrLocateHandJointsEXT">xrLocateHandJointsEXT</see>.
/// </summary>
/// <param name="handTracker">An <see cref="XrHandTrackerEXT">XrHandTrackerEXT</see> previously created by <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateHandTrackerEXT">xrCreateHandTrackerEXT</see>.</param>
/// <param name="locateInfo">A pointer to <see cref="XrHandJointsLocateInfoEXT">XrHandJointsLocateInfoEXT</see> describing information to locate hand joints.</param>
/// <param name="locations">A pointer to <see cref="XrHandJointLocationsEXT">XrHandJointLocationsEXT</see> receiving the returned hand joint locations.</param>
/// <returns></returns>
public delegate XrResult xrLocateHandJointsEXTDelegate(
XrHandTrackerEXT handTracker,
XrHandJointsLocateInfoEXT locateInfo,
ref XrHandJointLocationsEXT locations);
}
}

View File

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