// 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 { /// /// This enables the use of hand interaction profiles in OpenXR. It enables XR_HTC_hand_interaction in the underyling runtime. /// #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 /// /// OpenXR specification 12.69. XR_HTC_hand_interaction. /// public const string kOpenxrExtensionString = "XR_HTC_hand_interaction"; /// /// The feature id string. This is used to give the feature a well known id for reference. /// public const string featureId = "vive.openxr.feature.hand.interaction"; /// /// The interaction profile string used to reference the hand interaction input device. /// 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"; /// /// Constant for a float interaction binding '.../input/select/value' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string selectValue = "/input/select/value"; /// /// Constant for a float interaction binding '.../input/squeeze/value' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string gripValue = "/input/squeeze/value"; /// /// Constant for a pose interaction binding '.../input/aim/pose' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string pointerPose = "/input/aim/pose"; /// /// Constant for a pose interaction binding '.../input/grip/pose' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// 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); } /// /// A representing the OpenXR binding. /// [Preserve, InputControl(aliases = new[] { "selectAxis, pinchStrength" }, usage = "Select")] public AxisControl selectValue { get; private set; } /// /// A representing information from the OpenXR binding. /// [Preserve, InputControl(aliases = new[] { "GripAxis" }, usage = "Grip")] public AxisControl gripValue { get; private set; } /// /// A representing information from the OpenXR binding. /// [Preserve, InputControl(offset = 0, aliases = new[] { "device", "gripPose" }, usage = "Device")] public PoseControl devicePose { get; private set; } /// /// A representing the OpenXR binding. /// [Preserve, InputControl(offset = 0, alias = "aimPose", usage = "Pointer")] public PoseControl pointerPose { get; private set; } /// /// 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. /// [Preserve, InputControl(offset = 8)] public ButtonControl isTracked { get; private set; } /// /// 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. /// [Preserve, InputControl(offset = 12)] public IntegerControl trackingState { get; private set; } /// /// 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. /// [Preserve, InputControl(offset = 16, alias = "gripPosition")] public Vector3Control devicePosition { get; private set; } /// /// 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. /// [Preserve, InputControl(offset = 28, alias = "gripOrientation")] public QuaternionControl deviceRotation { get; private set; } /// /// Internal call used to assign controls to the the correct element. /// 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("selectValue"); gripValue = GetChildControl("gripValue"); devicePose = GetChildControl("devicePose"); pointerPose = GetChildControl("pointerPose"); isTracked = GetChildControl("isTracked"); trackingState = GetChildControl("trackingState"); devicePosition = GetChildControl("devicePosition"); deviceRotation = GetChildControl("deviceRotation"); } } #pragma warning disable private bool m_XrInstanceCreated = false; #pragma warning restore private XrInstance m_XrInstance = 0; /// /// Called when xrCreateInstance is done. /// /// The created instance. /// True for valid XrInstance 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"; /// /// Registers the layout with the Input System. /// 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)); } /// /// Removes the layout from the Input System. /// protected override void UnregisterDeviceLayout() { sb.Clear().Append("UnregisterDeviceLayout() ").Append(kLayoutName); DEBUG(sb); InputSystem.RemoveLayout(kLayoutName); } #if UNITY_XR_OPENXR_1_9_1 /// /// Return interaction profile type. HandInteractionDevice profile is Device type. /// /// Interaction profile type. protected override InteractionProfileType GetInteractionProfileType() { return typeof(HandInteractionDevice).IsSubclassOf(typeof(XRController)) ? InteractionProfileType.XRController : InteractionProfileType.Device; } /// /// Return device layer out string used for registering device HandInteractionDevice in InputSystem. /// /// Device layout string. protected override string GetDeviceLayoutName() { return kLayoutName; } #endif /// /// Registers action maps to Unity XR. /// 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() { 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() { // Grip Axis new ActionConfig() { name = "gripValue", localizedName = "Grip Axis", type = ActionType.Axis1D, usages = new List() { "Grip" }, bindings = new List() { new ActionBinding() { interactionPath = gripValue, interactionProfileName = profile, } } }, // Select Axis new ActionConfig() { name = "selectValue", localizedName = "Select Axis", type = ActionType.Axis1D, usages = new List() { "Select" }, bindings = new List() { new ActionBinding() { interactionPath = selectValue, interactionProfileName = profile, } } }, // Grip pose new ActionConfig() { name = "devicePose", localizedName = "Device Pose", type = ActionType.Pose, usages = new List() { "Device" }, bindings = new List() { new ActionBinding() { interactionPath = devicePose, interactionProfileName = profile, } } }, // Pointer Pose new ActionConfig() { name = "pointerPose", localizedName = "Pointer Pose", type = ActionType.Pose, usages = new List() { "Pointer" }, bindings = new List() { new ActionBinding() { interactionPath = pointerPose, interactionProfileName = profile, } } } } }; AddActionMap(actionMap); } } }