// Copyright HTC Corporation All Rights Reserved. using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using UnityEngine.InputSystem.LowLevel; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.XR; using UnityEngine.Scripting; using UnityEngine.XR; using UnityEngine.XR.OpenXR; using UnityEngine.XR.OpenXR.Features; using UnityEngine.XR.OpenXR.Input; #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 using VIVE.OpenXR.Hand; namespace VIVE.OpenXR { /// /// This enables the use of HTC VIVE Focus 3 interaction profiles in OpenXR. /// #if UNITY_EDITOR [OpenXRFeature(UiName = "VIVE Focus 3 Controller Interaction", BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone }, Company = "HTC", Desc = "Allows for mapping input to the VIVE Focus 3 interaction profile.", DocumentationLink = "https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XR_HTC_vive_focus3_controller_interaction", OpenxrExtensionStrings = kOpenxrExtensionString, Version = "1.0.0", Category = FeatureCategory.Interaction, FeatureId = featureId)] #endif public class VIVEFocus3Profile : OpenXRInteractionFeature { #region Log const string LOG_TAG = "VIVE.OpenXR.VIVEFocus3Profile"; 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 ERROR(StringBuilder msg) { Debug.LogErrorFormat("{0} {1}", LOG_TAG, msg); } #endregion private static VIVEFocus3Profile m_Instance = null; public const string kOpenxrExtensionString = "XR_HTC_vive_focus3_controller_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.focus3controller"; private static bool HandInteractionExtEnabled { get { return OpenXRRuntime.IsExtensionEnabled(ViveHandInteractionExt.kOpenxrExtensionString); } } /// /// An Input System device based on the hand interaction profile in the Interaction Profile. /// [Preserve, InputControlLayout(displayName = "VIVE Focus 3 Controller (OpenXR)", commonUsages = new[] { "LeftHand", "RightHand" })] public class VIVEFocus3Controller : XRControllerWithRumble, IInputUpdateCallbackReceiver { #region Log const string LOG_TAG = "VIVE.OpenXR.VIVEFocus3Profile.VIVEFocus3Controller"; 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 ERROR(StringBuilder msg) { Debug.LogErrorFormat("{0} {1}", LOG_TAG, msg); } #endregion #region Action Path /// /// A [Vector2Control](xref:UnityEngine.InputSystem.Controls.Vector2Control) that represents the OpenXR binding. /// [Preserve, InputControl(aliases = new[] { "Joystick", "primary2DAxis", "joystickAxis", "thumbstickAxis" }, usage = "Primary2DAxis")] public Vector2Control thumbstick { get; private set; } /// /// A [AxisControl](xref:UnityEngine.InputSystem.Controls.AxisControl) that represents the OpenXR binding. /// [Preserve, InputControl(aliases = new[] { "GripAxis", "squeeze" }, usage = "Grip")] public AxisControl grip { get; private set; } /// /// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) that represents the OpenXR binding. /// [Preserve, InputControl(aliases = new[] { "gripButton", "squeezeClicked" }, usage = "GripButton")] public ButtonControl gripPressed { get; private set; } /// /// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) that represents the OpenXR binding. /// [Preserve, InputControl(aliases = new[] { "GripTouch", "squeezeTouched" }, usage = "GripTouch")] public ButtonControl gripTouched { get; private set; } /// /// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) that represents the OpenXR bindings, depending on handedness. /// [Preserve, InputControl(aliases = new[] { "Primary", "menubutton" }, usage = "MenuButton")] public ButtonControl menu { get; private set; } /// /// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) that represents the OpenXR bindings, depending on handedness. /// [Preserve, InputControl(aliases = new[] { "A", "X", "buttonA", "buttonX" }, usage = "PrimaryButton")] public ButtonControl primaryButton { get; private set; } /// /// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) that represents the OpenXR bindings, depending on handedness. /// [Preserve, InputControl(aliases = new[] { "B", "Y", "buttonB", "buttonY" }, usage = "SecondaryButton")] public ButtonControl secondaryButton { get; private set; } /// /// A [AxisControl](xref:UnityEngine.InputSystem.Controls.AxisControl) that represents the OpenXR binding. /// [Preserve, InputControl(aliases = new[] { "triggerAxis" }, usage = "Trigger")] public AxisControl trigger { get; private set; } /// /// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) that represents the OpenXR binding. /// [Preserve, InputControl(aliases = new[] { "indexButton", "triggerButton" }, usage = "TriggerButton")] public ButtonControl triggerPressed { get; private set; } /// /// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) that represents the OpenXR binding. /// [Preserve, InputControl(aliases = new[] { "indexTouch", "indexNearTouched" }, usage = "TriggerTouch")] public ButtonControl triggerTouched { get; private set; } /// /// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) that represents the OpenXR binding. /// [Preserve, InputControl(aliases = new[] { "JoystickOrPadPressed", "thumbstickClick", "joystickClicked", "primary2DAxisClick" }, usage = "Primary2DAxisClick")] public ButtonControl thumbstickClicked { get; private set; } /// /// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) that represents the OpenXR binding. /// [Preserve, InputControl(aliases = new[] { "JoystickOrPadTouched", "thumbstickTouch", "joystickTouched", "primary2DAxisTouch" }, usage = "Primary2DAxisTouch")] public ButtonControl thumbstickTouched { get; private set; } /// /// A [ButtonControl](xref:UnityEngine.InputSystem.Controls.ButtonControl) that represents the OpenXR binding. /// [Preserve, InputControl(aliases = new[] { "ParkingTouched", "parkingTouched" })] public ButtonControl thumbrestTouched { get; private set; } /// /// A that represents the OpenXR binding. The grip pose represents the location of the user's palm or holding a motion controller. /// [Preserve, InputControl(offset = 0, aliases = new[] { "device", "gripPose" }, usage = "Device")] public PoseControl devicePose { get; private set; } /// /// A that represents the OpenXR binding. The pointer pose represents the tip of the controller pointing forward. /// [Preserve, InputControl(offset = 0, aliases = new[] { "aimPose", "pointerPose" }, usage = "Pointer")] public PoseControl pointer { get; private set; } #if UNITY_ANDROID /// /// A representing the OpenXR binding. /// [Preserve, InputControl(offset = 0, alias = "indexTip", usage = "Poke")] public PoseControl pokePose { get; private set; } /// /// A representing the OpenXR binding. /// [Preserve, InputControl(offset = 0, usage = "Pinch")] public PoseControl pinchPose { get; private set; } #endif /// /// 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 = 24)] new 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 = 28)] new 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 = 32, noisy = true, alias = "gripPosition")] new 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 = 44, noisy = true, alias = "gripOrientation")] new public QuaternionControl deviceRotation { get; private set; } /// /// A [Vector3Control](xref:UnityEngine.InputSystem.Controls.Vector3Control) required for back compatibility with the XRSDK layouts. This is the pointer position. This value is equivalent to mapping pointer/position. /// [Preserve, InputControl(offset = 92, noisy = true)] public Vector3Control pointerPosition { get; private set; } /// /// A [QuaternionControl](xref:UnityEngine.InputSystem.Controls.QuaternionControl) required for backwards compatibility with the XRSDK layouts. This is the pointer rotation. This value is equivalent to mapping pointer/rotation. /// [Preserve, InputControl(offset = 104, noisy = true, alias = "pointerOrientation")] public QuaternionControl pointerRotation { get; private set; } #if UNITY_ANDROID /// /// 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. /// [Preserve, InputControl(offset = 152, noisy = true)] public Vector3Control pokePosition { get; private set; } /// /// 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. /// [Preserve, InputControl(offset = 164, noisy = true)] public QuaternionControl pokeRotation { get; private set; } /// /// 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. /// [Preserve, InputControl(offset = 212, noisy = true)] public Vector3Control pinchPosition { get; private set; } /// /// 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. /// [Preserve, InputControl(offset = 224, noisy = true)] public QuaternionControl pinchRotation { get; private set; } #endif /// /// A that represents the binding. /// [Preserve, InputControl(usage = "Haptic")] public HapticControl haptic { get; private set; } #endregion private bool UpdateInputDeviceInRuntime = false; /// /// Internal call used to assign controls to the the correct element. /// protected override void FinishSetup() { base.FinishSetup(); thumbstick = GetChildControl("thumbstick"); trigger = GetChildControl("trigger"); triggerPressed = GetChildControl("triggerPressed"); triggerTouched = GetChildControl("triggerTouched"); grip = GetChildControl("grip"); gripPressed = GetChildControl("gripPressed"); gripTouched = GetChildControl("gripTouched"); menu = GetChildControl("menu"); primaryButton = GetChildControl("primaryButton"); secondaryButton = GetChildControl("secondaryButton"); thumbstickClicked = GetChildControl("thumbstickClicked"); thumbstickTouched = GetChildControl("thumbstickTouched"); thumbrestTouched = GetChildControl("thumbrestTouched"); devicePose = GetChildControl("devicePose"); pointer = GetChildControl("pointer"); #if UNITY_ANDROID if (HandInteractionExtEnabled) { pinchPose = GetChildControl("pinchPose"); pokePose = GetChildControl("pokePose"); } #endif isTracked = GetChildControl("isTracked"); trackingState = GetChildControl("trackingState"); devicePosition = GetChildControl("devicePosition"); deviceRotation = GetChildControl("deviceRotation"); pointerPosition = GetChildControl("pointerPosition"); pointerRotation = GetChildControl("pointerRotation"); haptic = GetChildControl("haptic"); sb.Clear() .Append("FinishSetup() device interfaceName: ").Append(description.interfaceName) .Append(", deviceClass: ").Append(description.deviceClass) .Append(", product: ").Append(description.product) .Append(", serial: ").Append(description.serial) .Append(", capabilities: ").Append(description.capabilities); DEBUG(sb); } private bool bRoleUpdatedLeft = false, bRoleUpdatedRight = false; public void OnUpdate() { if (!UpdateInputDeviceInRuntime) { return; } if (m_Instance == null) { return; } string func = "OnUpdate() "; if (leftHand.isTracked.ReadValue() > 0 && !bRoleUpdatedLeft) { sb.Clear().Append(func) .Append("product: ").Append(description.product) .Append(" with user path: ").Append(UserPaths.leftHand).Append(" is_tracked."); DEBUG(sb); XrPath path = StringToPath(UserPaths.leftHand); if (m_Instance.GetInputSourceName(path, XrInputSourceLocalizedNameFlags.XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT, out string role) != XrResult.XR_SUCCESS) { sb.Clear().Append(func) .Append("GetInputSourceName XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT failed."); ERROR(sb); } else { sb.Clear().Append(func) .Append("product: ").Append(description.product) .Append(" with user path: ").Append(UserPaths.leftHand).Append(" has role: ").Append(role); DEBUG(sb); } if (m_Instance.GetInputSourceName(path, XrInputSourceLocalizedNameFlags.XR_INPUT_SOURCE_LOCALIZED_NAME_SERIAL_NUMBER_BIT_HTC, out string sn) != XrResult.XR_SUCCESS) { sb.Clear().Append(func) .Append("GetInputSourceName XR_INPUT_SOURCE_LOCALIZED_NAME_SERIAL_NUMBER_BIT_HTC failed."); ERROR(sb); } else { sb.Clear().Append(func) .Append("product: ").Append(description.product) .Append(" with user path: ").Append(UserPaths.leftHand).Append(" has serial number: ").Append(role); DEBUG(sb); } bRoleUpdatedLeft = true; } if (rightHand.isTracked.ReadValue() > 0 && !bRoleUpdatedRight) { sb.Clear().Append(func) .Append("product: ").Append(description.product) .Append(" with user path: ").Append(UserPaths.rightHand).Append(" is_tracked."); DEBUG(sb); XrPath path = StringToPath(UserPaths.rightHand); if (m_Instance.GetInputSourceName(path, XrInputSourceLocalizedNameFlags.XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT, out string role) != XrResult.XR_SUCCESS) { sb.Clear().Append(func) .Append("GetInputSourceName XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT failed."); ERROR(sb); } else { sb.Clear().Append(func) .Append("product: ").Append(description.product) .Append(" with user path: ").Append(UserPaths.rightHand).Append(" has role: ").Append(role); DEBUG(sb); } if (m_Instance.GetInputSourceName(path, XrInputSourceLocalizedNameFlags.XR_INPUT_SOURCE_LOCALIZED_NAME_SERIAL_NUMBER_BIT_HTC, out string sn) != XrResult.XR_SUCCESS) { sb.Clear().Append(func) .Append("GetInputSourceName XR_INPUT_SOURCE_LOCALIZED_NAME_SERIAL_NUMBER_BIT_HTC failed."); ERROR(sb); } else { sb.Clear().Append(func) .Append("product: ").Append(description.product) .Append(" with user path: ").Append(UserPaths.leftHand).Append(" has serial number: ").Append(role); DEBUG(sb); } bRoleUpdatedRight = true; } } } /// /// The interaction profile string used to reference the Interaction Profile. /// public const string profile = "/interaction_profiles/htc/vive_focus3_controller"; #region Supported component paths // Available Bindings // Left Hand Only /// /// Constant for a boolean interaction binding '.../input/x/click' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. This binding is only available for the user path. /// public const string buttonX = "/input/x/click"; /// /// Constant for a boolean interaction binding '.../input/y/click' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. This binding is only available for the user path. /// public const string buttonY = "/input/y/click"; /// /// Constant for a boolean interaction binding '.../input/menu/click' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. This binding is only available for the user path. /// public const string menu = "/input/menu/click"; // Right Hand Only /// /// Constant for a boolean interaction binding '.../input/a/click' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. This binding is only available for the user path. /// public const string buttonA = "/input/a/click"; /// /// Constant for a boolean interaction binding '..."/input/b/click' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. This binding is only available for the user path. /// public const string buttonB = "/input/b/click"; /// /// Constant for a boolean interaction binding '.../input/system/click' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. This binding is only available for the user path. /// public const string system = "/input/system/click"; // Both Hands /// /// 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 grip = "/input/squeeze/value"; /// /// Constant for a boolean interaction binding '.../input/squeeze/click' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string gripPress = "/input/squeeze/click"; /// /// Constant for a boolean interaction binding '.../input/squeeze/touch' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string gripTouch = "/input/squeeze/touch"; /// /// Constant for a float interaction binding '.../input/trigger/value' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string trigger = "/input/trigger/value"; /// /// Constant for a boolean interaction binding '.../input/trigger/click' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string triggerClick = "/input/trigger/click"; /// /// Constant for a boolean interaction binding '.../input/trigger/touch' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string triggerTouch = "/input/trigger/touch"; /// /// Constant for a Vector2 interaction binding '.../input/thumbstick' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string thumbstick = "/input/thumbstick"; /// /// Constant for a boolean interaction binding '.../input/thumbstick/click' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string thumbstickClick = "/input/thumbstick/click"; /// /// Constant for a boolean interaction binding '.../input/thumbstick/touch' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string thumbstickTouch = "/input/thumbstick/touch"; /// /// Constant for a boolean interaction binding '.../input/thumbrest/touch' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string thumbrest = "/input/thumbrest/touch"; /// /// Constant for a hand grip pose interaction binding '.../input/grip/pose' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string gripPose = "/input/grip/pose"; /// /// Constant for a hand point pose interaction binding '.../input/aim/pose' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string aim = "/input/aim/pose"; #if UNITY_ANDROID /// /// Constant for a pose interaction binding '.../input/pinch_ext/pose' OpenXR Input Binding.

/// Typically used for directly manipulating a small object using the pinch gesture. When using a hand interaction profile, it is typically paired with the .

/// 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. ///
public const string pinchPose = "/input/pinch_ext/pose"; /// /// Constant for a pose interaction binding '.../input/poke_ext/pose' OpenXR Input Binding.

/// 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. ///
public const string pokePose = "/input/poke_ext/pose"; #endif /// /// Constant for a haptic interaction binding '.../output/haptic' OpenXR Input Binding. Used by input subsystem to bind actions to physical outputs. /// public const string haptic = "/output/haptic"; #endregion private const string kLayoutName = "VIVEFocus3Controller"; private const string kDeviceLocalizedName = "VIVE Focus 3 Controller 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(VIVEFocus3Controller), 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. VIVEFocus3Controller profile is Device type. /// /// Interaction profile type. protected override InteractionProfileType GetInteractionProfileType() { return typeof(VIVEFocus3Controller).IsSubclassOf(typeof(XRController)) ? InteractionProfileType.XRController : InteractionProfileType.Device; } /// /// Return device layer out string used for registering device VIVEFocus3Controller in InputSystem. /// /// Device layout string. protected override string GetDeviceLayoutName() { return kLayoutName; } #endif private List RequestActionConfigs() { if (HandInteractionExtEnabled) { sb.Clear().Append("RequestActionConfigs() XR_EXT_hand_interaction is enabled."); DEBUG(sb); return new List() { // Thumbstick Axis new ActionConfig() { name = "thumbstick", localizedName = "Thumbstick Axis", type = ActionType.Axis2D, usages = new List() { "Primary2DAxis" }, bindings = new List() { new ActionBinding() { interactionPath = thumbstick, interactionProfileName = profile, } } }, // Grip Axis new ActionConfig() { name = "grip", localizedName = "Grip Axis", type = ActionType.Axis1D, usages = new List() { "Grip" }, bindings = new List() { new ActionBinding() { interactionPath = grip, interactionProfileName = profile, } } }, // Grip Press new ActionConfig() { name = "gripPressed", localizedName = "Grip Pressed", type = ActionType.Binary, usages = new List() { "GripButton" }, bindings = new List() { new ActionBinding() { interactionPath = gripPress, interactionProfileName = profile, } } }, // Grip Touch // Known issue: Registering gripTouched will cause Controller Interaction Profile not work. /*new ActionConfig() { name = "gripTouched", localizedName = "Grip Touched", type = ActionType.Binary, usages = new List() { "GripTouch" }, bindings = new List() { new ActionBinding() { interactionPath = gripTouch, interactionProfileName = profile, } } },*/ // Menu new ActionConfig() { name = "menu", localizedName = "Menu", type = ActionType.Binary, usages = new List() { "MenuButton" }, bindings = new List() { new ActionBinding() { interactionPath = menu, interactionProfileName = profile, userPaths = new List() { UserPaths.leftHand } }, new ActionBinding() { interactionPath = system, interactionProfileName = profile, userPaths = new List() { UserPaths.rightHand } }, } }, // X / A Press new ActionConfig() { name = "primaryButton", localizedName = "Primary Pressed", type = ActionType.Binary, usages = new List() { "PrimaryButton" }, bindings = new List() { new ActionBinding() { interactionPath = buttonX, interactionProfileName = profile, userPaths = new List() { UserPaths.leftHand } }, new ActionBinding() { interactionPath = buttonA, interactionProfileName = profile, userPaths = new List() { UserPaths.rightHand } }, } }, // Y / B Press new ActionConfig() { name = "secondaryButton", localizedName = "Secondary Pressed", type = ActionType.Binary, usages = new List() { "SecondaryButton" }, bindings = new List() { new ActionBinding() { interactionPath = buttonY, interactionProfileName = profile, userPaths = new List() { UserPaths.leftHand } }, new ActionBinding() { interactionPath = buttonB, interactionProfileName = profile, userPaths = new List() { UserPaths.rightHand } }, } }, // Trigger Axis new ActionConfig() { name = "trigger", localizedName = "Trigger Axis", type = ActionType.Axis1D, usages = new List() { "Trigger" }, bindings = new List() { new ActionBinding() { interactionPath = trigger, interactionProfileName = profile, } } }, // Trigger Press new ActionConfig() { name = "triggerPressed", localizedName = "Trigger Pressed", type = ActionType.Binary, usages = new List() { "TriggerButton" }, bindings = new List() { new ActionBinding() { interactionPath = triggerClick, interactionProfileName = profile, } } }, // Trigger Touch new ActionConfig() { name = "triggerTouched", localizedName = "Trigger Touched", type = ActionType.Binary, usages = new List() { "TriggerTouch" }, bindings = new List() { new ActionBinding() { interactionPath = triggerTouch, interactionProfileName = profile, } } }, // Thumbstick Click new ActionConfig() { name = "thumbstickClicked", localizedName = "Thumbstick Pressed", type = ActionType.Binary, usages = new List() { "Primary2DAxisClick" }, bindings = new List() { new ActionBinding() { interactionPath = thumbstickClick, interactionProfileName = profile, } } }, // Thumbstick Touch new ActionConfig() { name = "thumbstickTouched", localizedName = "Thumbstick Touched", type = ActionType.Binary, usages = new List() { "Primary2DAxisTouch" }, bindings = new List() { new ActionBinding() { interactionPath = thumbstickTouch, interactionProfileName = profile, } } }, // Thumbrest Touch new ActionConfig() { name = "thumbrestTouched", localizedName = "Parking Touch", type = ActionType.Binary, bindings = new List() { new ActionBinding() { interactionPath = thumbrest, interactionProfileName = profile, } } }, // Device Pose new ActionConfig() { name = "devicePose", localizedName = "Grip Pose", type = ActionType.Pose, usages = new List() { "Device" }, bindings = new List() { new ActionBinding() { interactionPath = gripPose, interactionProfileName = profile, } } }, // Pointer Pose new ActionConfig() { name = "pointer", localizedName = "Pointer Pose", type = ActionType.Pose, usages = new List() { "Pointer" }, bindings = new List() { new ActionBinding() { interactionPath = aim, interactionProfileName = profile, } } }, #if UNITY_ANDROID // Pinch Pose new ActionConfig() { name = "pinchPose", localizedName = "Pinch Pose", type = ActionType.Pose, usages = new List() { "Pinch" }, bindings = new List() { new ActionBinding() { interactionPath = pinchPose, interactionProfileName = profile, } } }, // Poke Pose new ActionConfig() { name = "pokePose", localizedName = "Index Tip", type = ActionType.Pose, usages = new List() { "Poke" }, bindings = new List() { new ActionBinding() { interactionPath = pokePose, interactionProfileName = profile, } } }, #endif // Haptics new ActionConfig() { name = "haptic", localizedName = "Haptic Output", type = ActionType.Vibrate, usages = new List() { "Haptic" }, bindings = new List() { new ActionBinding() { interactionPath = haptic, interactionProfileName = profile, } } } }; } else { sb.Clear().Append("RequestActionConfigs() XR_EXT_hand_interaction is disabled."); DEBUG(sb); return new List() { // Thumbstick Axis new ActionConfig() { name = "thumbstick", localizedName = "Thumbstick Axis", type = ActionType.Axis2D, usages = new List() { "Primary2DAxis" }, bindings = new List() { new ActionBinding() { interactionPath = thumbstick, interactionProfileName = profile, } } }, // Grip Axis new ActionConfig() { name = "grip", localizedName = "Grip Axis", type = ActionType.Axis1D, usages = new List() { "Grip" }, bindings = new List() { new ActionBinding() { interactionPath = grip, interactionProfileName = profile, } } }, // Grip Press new ActionConfig() { name = "gripPressed", localizedName = "Grip Pressed", type = ActionType.Binary, usages = new List() { "GripButton" }, bindings = new List() { new ActionBinding() { interactionPath = gripPress, interactionProfileName = profile, } } }, // Menu new ActionConfig() { name = "menu", localizedName = "Menu", type = ActionType.Binary, usages = new List() { "MenuButton" }, bindings = new List() { new ActionBinding() { interactionPath = menu, interactionProfileName = profile, userPaths = new List() { UserPaths.leftHand } }, new ActionBinding() { interactionPath = system, interactionProfileName = profile, userPaths = new List() { UserPaths.rightHand } }, } }, // X / A Press new ActionConfig() { name = "primaryButton", localizedName = "Primary Pressed", type = ActionType.Binary, usages = new List() { "PrimaryButton" }, bindings = new List() { new ActionBinding() { interactionPath = buttonX, interactionProfileName = profile, userPaths = new List() { UserPaths.leftHand } }, new ActionBinding() { interactionPath = buttonA, interactionProfileName = profile, userPaths = new List() { UserPaths.rightHand } }, } }, // Y / B Press new ActionConfig() { name = "secondaryButton", localizedName = "Secondary Pressed", type = ActionType.Binary, usages = new List() { "SecondaryButton" }, bindings = new List() { new ActionBinding() { interactionPath = buttonY, interactionProfileName = profile, userPaths = new List() { UserPaths.leftHand } }, new ActionBinding() { interactionPath = buttonB, interactionProfileName = profile, userPaths = new List() { UserPaths.rightHand } }, } }, // Trigger Axis new ActionConfig() { name = "trigger", localizedName = "Trigger Axis", type = ActionType.Axis1D, usages = new List() { "Trigger" }, bindings = new List() { new ActionBinding() { interactionPath = trigger, interactionProfileName = profile, } } }, // Trigger Press new ActionConfig() { name = "triggerPressed", localizedName = "Trigger Pressed", type = ActionType.Binary, usages = new List() { "TriggerButton" }, bindings = new List() { new ActionBinding() { interactionPath = triggerClick, interactionProfileName = profile, } } }, // Trigger Touch new ActionConfig() { name = "triggerTouched", localizedName = "Trigger Touched", type = ActionType.Binary, usages = new List() { "TriggerTouch" }, bindings = new List() { new ActionBinding() { interactionPath = triggerTouch, interactionProfileName = profile, } } }, // Thumbstick Click new ActionConfig() { name = "thumbstickClicked", localizedName = "Thumbstick Pressed", type = ActionType.Binary, usages = new List() { "Primary2DAxisClick" }, bindings = new List() { new ActionBinding() { interactionPath = thumbstickClick, interactionProfileName = profile, } } }, // Thumbstick Touch new ActionConfig() { name = "thumbstickTouched", localizedName = "Thumbstick Touched", type = ActionType.Binary, usages = new List() { "Primary2DAxisTouch" }, bindings = new List() { new ActionBinding() { interactionPath = thumbstickTouch, interactionProfileName = profile, } } }, // Thumbrest Touch new ActionConfig() { name = "thumbrestTouched", localizedName = "Parking Touch", type = ActionType.Binary, bindings = new List() { new ActionBinding() { interactionPath = thumbrest, interactionProfileName = profile, } } }, // Device Pose new ActionConfig() { name = "devicePose", localizedName = "Grip Pose", type = ActionType.Pose, usages = new List() { "Device" }, bindings = new List() { new ActionBinding() { interactionPath = gripPose, interactionProfileName = profile, } } }, // Pointer Pose new ActionConfig() { name = "pointer", localizedName = "Pointer Pose", type = ActionType.Pose, usages = new List() { "Pointer" }, bindings = new List() { new ActionBinding() { interactionPath = aim, interactionProfileName = profile, } } }, // Haptics new ActionConfig() { name = "haptic", localizedName = "Haptic Output", type = ActionType.Vibrate, usages = new List() { "Haptic" }, bindings = new List() { new ActionBinding() { interactionPath = haptic, interactionProfileName = profile, } } } }; } } /// /// Register action maps for this device with the OpenXR Runtime. Called at runtime before Start. /// protected override void RegisterActionMapsWithRuntime() { ActionMapConfig actionMap = new ActionMapConfig() { name = "vivefocus3controller", localizedName = kDeviceLocalizedName, desiredInteractionProfile = profile, manufacturer = "HTC", serialNumber = "", deviceInfos = new List() { new DeviceConfig() { characteristics = InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.TrackedDevice | InputDeviceCharacteristics.Controller | InputDeviceCharacteristics.Left, userPath = UserPaths.leftHand // "/user/hand/left" }, new DeviceConfig() { characteristics = InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.TrackedDevice | InputDeviceCharacteristics.Controller | InputDeviceCharacteristics.Right, userPath = UserPaths.rightHand // "/user/hand/right" } }, actions = RequestActionConfigs() }; AddActionMap(actionMap); } #region OpenXR function delegates /// xrGetInstanceProcAddr OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr; /// xrEnumerateDisplayRefreshRatesFB OpenXRHelper.xrGetInputSourceLocalizedNameDelegate xrGetInputSourceLocalizedName = null; private bool GetXrFunctionDelegates(XrInstance xrInstance) { /// xrGetInstanceProcAddr if (xrGetInstanceProcAddr != null && xrGetInstanceProcAddr != IntPtr.Zero) { sb.Clear().Append("Get function pointer of xrGetInstanceProcAddr."); DEBUG(sb); XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer( xrGetInstanceProcAddr, typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate; } else { sb.Clear().Append("No function pointer of xrGetInstanceProcAddr"); ERROR(sb); return false; } IntPtr funcPtr = IntPtr.Zero; /// xrGetInputSourceLocalizedName if (XrGetInstanceProcAddr(xrInstance, "xrGetInputSourceLocalizedName", out funcPtr) == XrResult.XR_SUCCESS) { if (funcPtr != IntPtr.Zero) { sb.Clear().Append("Get function pointer of xrGetInputSourceLocalizedName."); DEBUG(sb); xrGetInputSourceLocalizedName = Marshal.GetDelegateForFunctionPointer( funcPtr, typeof(OpenXRHelper.xrGetInputSourceLocalizedNameDelegate)) as OpenXRHelper.xrGetInputSourceLocalizedNameDelegate; } else { sb.Clear().Append("No function pointer of xrGetInputSourceLocalizedName."); ERROR(sb); } } else { sb.Clear().Append("No function pointer of xrGetInputSourceLocalizedName"); ERROR(sb); } return true; } #endregion private XrResult GetInputSourceName(XrPath path, XrInputSourceLocalizedNameFlags sourceType, out string sourceName) { string func = "GetInputSourceName() "; sourceName = ""; if (!m_XrSessionCreated || xrGetInputSourceLocalizedName == null) { return XrResult.XR_ERROR_VALIDATION_FAILURE; } string userPath = PathToString(path); sb.Clear().Append(func).Append("userPath: ").Append(userPath).Append(", flag: ").Append((UInt64)sourceType); DEBUG(sb); XrInputSourceLocalizedNameGetInfo nameInfo = new XrInputSourceLocalizedNameGetInfo( XrStructureType.XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO, IntPtr.Zero, path, (XrInputSourceLocalizedNameFlags)sourceType); UInt32 nameSizeIn = 0; UInt32 nameSizeOut = 0; char[] buffer = new char[0]; XrResult result = xrGetInputSourceLocalizedName(m_XrSession, ref nameInfo, nameSizeIn, ref nameSizeOut, buffer); if (result == XrResult.XR_SUCCESS) { if (nameSizeOut < 1) { sb.Clear().Append(func) .Append("xrGetInputSourceLocalizedName(").Append(userPath).Append(")") .Append(", flag: ").Append((UInt64)sourceType) .Append("bufferCountOutput size is invalid!"); ERROR(sb); return XrResult.XR_ERROR_VALIDATION_FAILURE; } nameSizeIn = nameSizeOut; buffer = new char[nameSizeIn]; result = xrGetInputSourceLocalizedName(m_XrSession, ref nameInfo, nameSizeIn, ref nameSizeOut, buffer); if (result == XrResult.XR_SUCCESS) { sourceName = new string(buffer).TrimEnd('\0'); sb.Clear().Append(func) .Append("xrGetInputSourceLocalizedName(").Append(userPath).Append(")") .Append(", flag: ").Append((UInt64)sourceType) .Append(", bufferCapacityInput: ").Append(nameSizeIn) .Append(", bufferCountOutput: ").Append(nameSizeOut) .Append(", sourceName: ").Append(sourceName); DEBUG(sb); } else { sb.Clear().Append(func) .Append("2.xrGetInputSourceLocalizedName(").Append(userPath).Append(")") .Append(", flag: ").Append((UInt64)sourceType) .Append(", bufferCapacityInput: ").Append(nameSizeIn) .Append(", bufferCountOutput: ").Append(nameSizeOut) .Append(" result: ").Append(result); ERROR(sb); } } else { sb.Clear().Append(func) .Append("1.xrGetInputSourceLocalizedName(").Append(userPath).Append(")") .Append(", flag: ").Append((UInt64)sourceType) .Append(", bufferCapacityInput: ").Append(nameSizeIn) .Append(", bufferCountOutput: ").Append(nameSizeOut) .Append(" result: ").Append(result); ERROR(sb); } return result; } #region OpenXR Life Cycle #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) { m_XrInstanceCreated = true; m_XrInstance = xrInstance; m_Instance = this; sb.Clear().Append("OnInstanceCreate() ").Append(m_XrInstance); DEBUG(sb); GetXrFunctionDelegates(m_XrInstance); return true; } /// /// Called when xrDestroyInstance is done. /// /// The instance to destroy. protected override void OnInstanceDestroy(ulong xrInstance) { if (m_XrInstance == xrInstance) { m_XrInstanceCreated = false; m_XrInstance = 0; } sb.Clear().Append("OnInstanceDestroy() ").Append(xrInstance); DEBUG(sb); } private bool m_XrSessionCreated = false; private XrSession m_XrSession = 0; /// /// Called when xrCreateSession is done. /// /// The created session ID. protected override void OnSessionCreate(ulong xrSession) { m_XrSession = xrSession; m_XrSessionCreated = true; sb.Clear().Append("OnSessionCreate() ").Append(m_XrSession); DEBUG(sb); } /// /// Called when xrDestroySession is done. /// /// The session ID to destroy. protected override void OnSessionDestroy(ulong xrSession) { sb.Clear().Append("OnSessionDestroy() ").Append(xrSession); DEBUG(sb); if (m_XrSession == xrSession) { m_XrSession = 0; m_XrSessionCreated = false; } } #endregion } }