using System; using System.Collections.Generic; using System.Linq; using UnityEngine.Scripting; using UnityEngine.XR.OpenXR.Input; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.XR; #if UNITY_EDITOR using UnityEditor; #endif #if USE_INPUT_SYSTEM_POSE_CONTROL using PoseControl = UnityEngine.InputSystem.XR.PoseControl; #else using PoseControl = UnityEngine.XR.OpenXR.Input.PoseControl; #endif namespace UnityEngine.XR.OpenXR.Features.Interactions { /// /// This enables the use of hand common poses profiles in OpenXR. /// #if UNITY_EDITOR [UnityEditor.XR.OpenXR.Features.OpenXRFeature(UiName = "Hand Interaction Poses", BuildTargetGroups = new[] { BuildTargetGroup.Standalone, BuildTargetGroup.WSA, BuildTargetGroup.Android}, Company = "Unity", Desc = "Add hand common interaction poses feature, if enabled, four additional commonly used poses will be supported.", DocumentationLink = Constants.k_DocumentationManualURL + "features/handcommonposesinteraction.html", OpenxrExtensionStrings = extensionString, Version = "0.0.1", FeatureId = featureId)] #endif public class HandCommonPosesInteraction : OpenXRInteractionFeature { /// /// The feature id string. This is used to give the feature a well known id for reference. /// public const string featureId = "com.unity.openxr.feature.input.handinteractionposes"; /// /// A flag to mark this hand interaction feature is potentially additive. /// internal override bool IsAdditive => true; /// /// An interaction feature that supports commonly used hand poses for hand interactions across motion controller and hand tracking devices. /// [Preserve, InputControlLayout(displayName = "Hand Interaction Poses (OpenXR)", commonUsages = new[] { "LeftHand", "RightHand" }, isGenericTypeOfDevice = true)] public class HandInteractionPoses : OpenXRDevice { /// /// A that represents the OpenXR binding. /// [Preserve, InputControl(offset = 0, aliases = new[] { "device", "gripPose" }, usage = "Device")] public PoseControl devicePose { get; private set; } /// /// A that represents the OpenXR binding. /// [Preserve, InputControl(offset = 0, alias = "aimPose", usage = "Pointer")] public PoseControl pointer { get; private set; } /// /// A that represents the OpenXR binding. /// [Preserve, InputControl(offset = 0)] public PoseControl pokePose { get; private set; } /// /// A that represents the OpenXR binding. /// [Preserve, InputControl(offset = 0)] public PoseControl pinchPose { get; private set; } /// /// Internal call used to assign controls to the the correct element. /// protected override void FinishSetup() { base.FinishSetup(); devicePose = GetChildControl("devicePose"); pointer = GetChildControl("pointer"); pokePose = GetChildControl("pokePose"); pinchPose = GetChildControl("pinchPose"); } } /// /// The interaction profile string used to reference Hand Common Poses feature. /// public const string profile = "/interaction_profiles/unity/hand_interaction_poses"; // Available Bindings // Both Hands /// /// 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 grip = "/input/grip/pose"; /// /// 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 aim = "/input/aim/pose"; /// /// Constant for a pose interaction binding '.../input/poke_ext/pose' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string poke = "/input/poke_ext/pose"; /// /// Constant for a pose interaction binding '.../input/pinch_ext/pose' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs. /// public const string pinch = "/input/pinch_ext/pose"; private const string kDeviceLocalizedName = "Hand Interaction Poses OpenXR"; /// /// The OpenXR Extension string. This is used by OpenXR to check if this extension is available or enabled. /// public const string extensionString = "XR_EXT_hand_interaction"; #if UNITY_EDITOR protected internal override void GetValidationChecks(List results, BuildTargetGroup target) { results.Add( new ValidationRule(this){ message = "Additive Interaction feature requires a valid controller or hand interaction profile selected within Interaction Profiles.", error = true, errorEnteringPlaymode = true, checkPredicate = () => { var settings = OpenXRSettings.GetSettingsForBuildTargetGroup(target); if (null == settings) return false; bool handCommonPosesFeatureEnabled = false; bool otherNonAdditiveInteractionFeatureEnabled = false; foreach (var feature in settings.GetFeatures()) { if (feature.enabled) { if (feature is HandCommonPosesInteraction) handCommonPosesFeatureEnabled = true; else if (!(feature as OpenXRInteractionFeature).IsAdditive && !(feature is EyeGazeInteraction)) otherNonAdditiveInteractionFeatureEnabled = true; } } return handCommonPosesFeatureEnabled && otherNonAdditiveInteractionFeatureEnabled; }, fixIt = () => SettingsService.OpenProjectSettings("Project/XR Plug-in Management/OpenXR"), fixItAutomatic = false, fixItMessage = "Open Project Settings to select one or more non Additive interaction profiles." }); } #endif /// protected internal override bool OnInstanceCreate(ulong instance) { // Requires hand tracking extension if (!OpenXRRuntime.IsExtensionEnabled(extensionString)) return false; return base.OnInstanceCreate(instance); } /// /// Registers the layout with the Input System. /// protected override void RegisterDeviceLayout() { #if UNITY_EDITOR if (!OpenXRLoaderEnabledForSelectedBuildTarget(EditorUserBuildSettings.selectedBuildTargetGroup)) return; #endif InputSystem.InputSystem.RegisterLayout(typeof(HandInteractionPoses), matches: new InputDeviceMatcher() .WithInterface(XRUtilities.InterfaceMatchAnyVersion) .WithProduct(kDeviceLocalizedName)); } /// /// Removes the layout with the Input System. /// protected override void UnregisterDeviceLayout() { #if UNITY_EDITOR if (!OpenXRLoaderEnabledForSelectedBuildTarget(EditorUserBuildSettings.selectedBuildTargetGroup)) return; #endif InputSystem.InputSystem.RemoveLayout(nameof(HandInteractionPoses)); } /// /// Return Interaction profile type. Hand common poses profile is Device type. /// /// Interaction profile type. protected override InteractionProfileType GetInteractionProfileType() { return typeof(HandInteractionPoses).IsSubclassOf(typeof(XRController)) ? InteractionProfileType.XRController : InteractionProfileType.Device; } /// /// Return device layout string that used to register device in InputSystem. /// /// Device layout string. protected override string GetDeviceLayoutName() { return nameof(HandInteractionPoses); } /// protected override void RegisterActionMapsWithRuntime() { ActionMapConfig actionMap = new ActionMapConfig() { name = "handinteractionposes", localizedName = kDeviceLocalizedName, desiredInteractionProfile = profile, manufacturer = "", serialNumber = "", deviceInfos = new List() { new DeviceConfig() { characteristics = (InputDeviceCharacteristics)(InputDeviceCharacteristics.HandTracking | InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.TrackedDevice | InputDeviceCharacteristics.Controller | InputDeviceCharacteristics.Left), userPath = UserPaths.leftHand }, new DeviceConfig() { characteristics = (InputDeviceCharacteristics)(InputDeviceCharacteristics.HandTracking | InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.TrackedDevice | InputDeviceCharacteristics.Controller | InputDeviceCharacteristics.Right), userPath = UserPaths.rightHand } }, actions = new List() { // Device Pose new ActionConfig() { name = "devicePose", localizedName = "Device Pose", type = ActionType.Pose, usages = new List() { "Device" }, bindings = new List() { new ActionBinding() { interactionPath = grip, interactionProfileName = profile, } }, isAdditive = true }, // 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, } }, isAdditive = true }, //Poke Pose new ActionConfig() { name = "PokePose", localizedName = "Poke Pose", type = ActionType.Pose, bindings = new List() { new ActionBinding() { interactionPath = poke, interactionProfileName = profile, } }, isAdditive = true }, //Pinch Pose new ActionConfig() { name = "PinchPose", localizedName = "Pinch Pose", type = ActionType.Pose, bindings = new List() { new ActionBinding() { interactionPath = pinch, interactionProfileName = profile, } }, isAdditive = true } } }; AddActionMap(actionMap); } //Process additive actions: add additional supported additive actions to existing controller or hand interaction profiles internal override void AddAdditiveActions(List actionMaps, ActionMapConfig additiveMap) { foreach (var actionMap in actionMaps) { //valid userPath is user/hand/left or user/hand/right var validUserPath = actionMap.deviceInfos.Where(d => d.userPath != null && ((String.CompareOrdinal(d.userPath, OpenXRInteractionFeature.UserPaths.leftHand) == 0) || (String.CompareOrdinal(d.userPath, OpenXRInteractionFeature.UserPaths.rightHand) == 0))); if (validUserPath.Any()) { foreach (var additiveAction in additiveMap.actions.Where(a => a.isAdditive)) { bool duplicateFound = false; var poseActions = actionMap.actions.Where(m => m.type == ActionType.Pose).Distinct().ToList(); foreach (var poseAction in poseActions) { if ((poseAction.bindings.Where(b => b.interactionPath != null && (String.CompareOrdinal(b.interactionPath, additiveAction.bindings[0].interactionPath) == 0))).Any()) { poseAction.isAdditive = true; duplicateFound = true; } } if (!duplicateFound) actionMap.actions.Add(additiveAction); } } } } } }