VR4Medical/ICI/Library/PackageCache/com.unity.xr.openxr@3903c1059bcf/Runtime/Features/OpenXRInteractionFeature.cs
2025-07-29 13:45:50 +03:00

418 lines
18 KiB
C#

using System;
using System.Collections.Generic;
#if UNITY_EDITOR
using System.Linq;
using UnityEditor;
using UnityEditor.XR.Management;
using UnityEngine.XR.Management;
#endif
namespace UnityEngine.XR.OpenXR.Features
{
/// <summary>
/// A Unity OpenXR Interaction feature.
/// This class can be inherited from to add a custom action mapping for OpenXR.
/// </summary>
[Serializable]
public abstract class OpenXRInteractionFeature : OpenXRFeature
{
/// <summary>
/// Temporary static list used for action map creation
/// </summary>
private static List<ActionMapConfig> m_CreatedActionMaps = null;
private static Dictionary<InteractionProfileType, Dictionary<string, bool>> m_InteractionProfileEnabledMaps = new Dictionary<InteractionProfileType, Dictionary<string, bool>>();
/// <summary>
/// Flag that indicates this feature or profile is additive and its binding paths will be added to other non-additive profiles if enabled.
/// </summary>
internal virtual bool IsAdditive => false;
/// <summary>
/// The underlying type of an OpenXR action. This enumeration contains all supported control types within OpenXR. This is used when declaring actions in OpenXR with XrAction/>.
/// </summary>
[Serializable]
protected internal enum ActionType
{
/// <summary>A binary (on/off) action type. Represented by ButtonControl in the Input System or Boolean in XR.InputDevice.</summary>
Binary,
/// <summary>A single Axis float action type. Represented by an AxisControl in the InputSystem or a float in XR.InputDevice.</summary>
Axis1D,
/// <summary>A two-dimensional float action type. Represented by a Vector2Control in the InputSystem or Vector2 in XR.InputDevice.</summary>
Axis2D,
/// <summary>A position and rotation in three-dimensional space. Represented by a PoseControl in the InputSystem, and a series of controls (boolean to represent if it's being tracked or not, unsigned integer for which fields are available, Vector3 for position, Quaternion for rotation) in XR.InputDevice.</summary>
Pose,
/// <summary>This control represents an output motor. Usable as sequential channels (first declared is channel 0, second is 1, etc...) in both the Input System and XR.InputDevice haptic APIs.</summary>
Vibrate,
/// <summary>A value representing the total number of ActionTypes available. This can be used to check if an ActionType value is a valid ActionType.</summary>
Count
}
/// <summary>
/// Information sent to OpenXR about specific, physical control on an input device. Used to identify what an action is bound to (that is, which physical control will trigger that action).
/// </summary>
[Serializable]
protected internal class ActionBinding
{
/// <summary>OpenXR interaction profile name</summary>
public string interactionProfileName;
/// <summary>OpenXR path for the interaction</summary>
public string interactionPath;
/// <summary>Optional OpenXR user paths <see cref="UserPaths"/></summary>
public List<string> userPaths;
}
/// <summary>
/// Declares an abstract input source bound to multiple physical input controls. XrActions are commonly contained within an ActionMapConfig as a grouped series of abstract, remappable inputs.
/// </summary>
[Serializable]
protected internal class ActionConfig
{
/// <summary>The name of the action, reported into the InputSystem as the name of the control that represents the input data for this action. This name can only contain a-z lower case letters.</summary>
public string name;
/// <summary>The type of data this action will report. <see cref="ActionType"/></summary>
public ActionType type;
/// <summary>Human readable name for the action</summary>
public string localizedName;
/// <summary>The underlying physical input controls to use as the value for this action</summary>
public List<ActionBinding> bindings;
/// <summary>These will be tagged onto <see cref="UnityEngine.XR.InputDevice"/> features. See <see cref="UnityEngine.XR.InputDevice.TryGetFeatureValue"/></summary>
public List<string> usages;
/// <summary>Tag to determine if certain action is additive and could be added to the existing profiles</summary>
public bool isAdditive;
}
/// <summary>
/// Information sent to the OpenXR runtime to identify how to map a <see cref="UnityEngine.XR.InputDevice"/> or <see cref="UnityEngine.InputSystem.InputDevice"/> to an underlying OpenXR user path.
/// </summary>
protected internal class DeviceConfig
{
/// <summary>The <see cref="InputDeviceCharacteristics"/> for the <see cref="UnityEngine.XR.InputDevice"/> that will represent this ActionMapConfig. See <see cref="UnityEngine.XR.InputDevice.characteristics"/></summary>
public InputDeviceCharacteristics characteristics;
/// <summary>OpenXR user path that this device maps to. <see cref="UserPaths"/></summary>
public string userPath;
}
/// <summary>
/// Defines a mapping of between the Unity input system and OpenXR.
/// </summary>
[Serializable]
protected internal class ActionMapConfig
{
/// <summary>
/// Name of the action map
/// </summary>
public string name;
/// <summary>
/// Human readable name of the OpenXR user path that this device maps to.
/// </summary>
public string localizedName;
/// <summary>
/// List of devices to configure
/// </summary>
public List<DeviceConfig> deviceInfos;
/// <summary>
/// List of actions to configure
/// </summary>
public List<ActionConfig> actions;
/// <summary>
/// OpenXR interaction profile to use for this action map
/// </summary>
public string desiredInteractionProfile;
/// <summary>
/// Name of the manufacturer providing this action map
/// </summary>
public string manufacturer;
/// <summary>
/// Serial number of the device
/// </summary>
public string serialNumber;
}
/// <summary>
/// Common OpenXR user path definitions.
/// See the [OpenXR Specification](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-user) for more information.
/// </summary>
public static class UserPaths
{
/// <summary>
/// Path for user left hand
/// </summary>
public const string leftHand = "/user/hand/left";
/// <summary>
/// Path for user right hand
/// </summary>
public const string rightHand = "/user/hand/right";
/// <summary>
/// Path for user head
/// </summary>
public const string head = "/user/head";
/// <summary>
/// Path for user gamepad
/// </summary>
public const string gamepad = "/user/gamepad";
/// <summary>
/// Path for user treadmill
/// </summary>
public const string treadmill = "/user/treadmill";
}
/// <summary>
/// Flags used to indicate Interaction profile type
/// </summary>
public enum InteractionProfileType
{
/// <summary>
/// Interaction profile derived from InputDevice class
/// </summary>
Device,
/// <summary>
/// Interaction profile derived from XRController class
/// </summary>
XRController
}
/// <summary>
/// Register a device layout with the Unity Input System.
/// Called whenever this interaction profile is enabled in the Editor.
/// </summary>
protected virtual void RegisterDeviceLayout()
{
}
/// <summary>
/// Remove a device layout from the Unity Input System.
/// Called whenever this interaction profile is disabled in the Editor.
/// </summary>
protected virtual void UnregisterDeviceLayout()
{
}
/// <summary>
/// Register action maps for this device with the OpenXR Runtime.
/// Called at runtime before Start.
/// </summary>
protected virtual void RegisterActionMapsWithRuntime()
{
}
/// <inheritdoc/>
protected internal override bool OnInstanceCreate(ulong xrSession)
{
RegisterDeviceLayout();
return true;
}
/// <summary>
/// Return interaction profile type. Default type is XRController. Override this if interactionProfile is not derived from XRController class.
/// </summary>
/// <returns>Interaction profile type.</returns>
protected virtual InteractionProfileType GetInteractionProfileType() => InteractionProfileType.XRController;
/// <summary>
/// Return device layout name string used for register layouts in inputSystem.
/// </summary>
/// <returns>Device layout string.</returns>
protected virtual string GetDeviceLayoutName() => "";
/// <summary>
/// Request the feature create its action maps
/// </summary>
/// <param name="configs">Target list for the action maps</param>
internal void CreateActionMaps(List<ActionMapConfig> configs)
{
m_CreatedActionMaps = configs;
RegisterActionMapsWithRuntime();
m_CreatedActionMaps = null;
}
/// <summary>
/// Add an action map to the Unity Input System.
///
/// This method must be called from within the RegisterActionMapsWithRuntime method.
/// </summary>
/// <param name="map">Action map to add</param>
protected void AddActionMap(ActionMapConfig map)
{
if (null == map)
throw new ArgumentNullException("map");
if (null == m_CreatedActionMaps)
throw new InvalidOperationException("ActionMap must be added from within the RegisterActionMapsWithRuntime method");
m_CreatedActionMaps.Add(map);
}
internal virtual void AddAdditiveActions(List<OpenXRInteractionFeature.ActionMapConfig> actionMaps, ActionMapConfig additiveMap)
{
}
/// <summary>
/// Handle enabled state change
/// </summary>
protected internal override void OnEnabledChange()
{
base.OnEnabledChange();
#if UNITY_EDITOR && INPUT_SYSTEM_BINDING_VALIDATOR
var packageSettings = OpenXRSettings.GetSettingsForBuildTargetGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
if (null == packageSettings)
return;
foreach (var feature in packageSettings.GetFeatures<OpenXRInteractionFeature>())
{
var profileType = ((OpenXRInteractionFeature) feature).GetInteractionProfileType();
string deviceLayoutName = ((OpenXRInteractionFeature) feature).GetDeviceLayoutName();
deviceLayoutName = "<" + deviceLayoutName + ">";
if (m_InteractionProfileEnabledMaps.ContainsKey(profileType) && m_InteractionProfileEnabledMaps[profileType].ContainsKey(deviceLayoutName))
m_InteractionProfileEnabledMaps[profileType][deviceLayoutName] = feature.enabled;
}
#endif
}
internal static void RegisterLayouts()
{
#if ENABLE_INPUT_SYSTEM
#if UNITY_EDITOR
var packageSettings = OpenXRSettings.GetSettingsForBuildTargetGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
if (null == packageSettings)
return;
#if INPUT_SYSTEM_BINDING_VALIDATOR
m_InteractionProfileEnabledMaps.Clear();
foreach (var feature in packageSettings.GetFeatures<OpenXRInteractionFeature>())
{
//Register all the available profiles
((OpenXRInteractionFeature) feature).RegisterDeviceLayout();
var profileType = ((OpenXRInteractionFeature) feature).GetInteractionProfileType();
string deviceLayoutName = ((OpenXRInteractionFeature) feature).GetDeviceLayoutName();
if (String.IsNullOrEmpty(deviceLayoutName))
{
Debug.LogWarningFormat("No GetDeviceLayoutName() override detected in {0}. Binding path validator for this interaction profile is not as effective. To fix, add GetDeviceLayoutName and GetInteractionProfileType override in this profile.", feature.nameUi);
continue;
}
deviceLayoutName = "<" + deviceLayoutName + ">";
if (!m_InteractionProfileEnabledMaps.ContainsKey(profileType))
m_InteractionProfileEnabledMaps[profileType] = new Dictionary<string, bool>();
m_InteractionProfileEnabledMaps[profileType].Add(deviceLayoutName, feature.enabled);
}
InputSystem.InputSystem.customBindingPathValidators -= PathValidator;
InputSystem.InputSystem.customBindingPathValidators += PathValidator;
#else //#if INPUT_SYSTEM_BINDING_VALIDATOR
foreach (var feature in packageSettings.GetFeatures<OpenXRInteractionFeature>())
{
//Register all the available profiles
((OpenXRInteractionFeature)feature).RegisterDeviceLayout();
}
#endif //#if INPUT_SYSTEM_BINDING_VALIDATOR
#else
foreach (var feature in OpenXRSettings.Instance.GetFeatures<OpenXRInteractionFeature>())
if (feature.enabled)
((OpenXRInteractionFeature)feature).RegisterDeviceLayout();
#endif //#if UNITY_EDITOR
#endif //#if ENABLE_INPUT_SYSTEM
}
#if UNITY_EDITOR && INPUT_SYSTEM_BINDING_VALIDATOR
internal static Action PathValidator(string bindingPath)
{
//case1: OpenXR plugin not enabled in XR management
if (!OpenXRLoaderEnabledForSelectedBuildTarget(EditorUserBuildSettings.selectedBuildTargetGroup))
return null;
string warningText = null;
//case2: current bindingPath maps to XRController.
if (bindingPath.StartsWith("<XRController>"))
{
if (!m_InteractionProfileEnabledMaps.ContainsKey(InteractionProfileType.XRController))
return null;
bool controllerProfileEnabled = false;
foreach (var profile in m_InteractionProfileEnabledMaps[InteractionProfileType.XRController])
{
if (profile.Value)
controllerProfileEnabled = true;
}
if (controllerProfileEnabled)
return null;
warningText = "This binding will be inactive because there are no enabled OpenXR interaction profiles.";
}
else
{
//case3: current bindingPath maps to specific OpenXR interaction profile
//Only check for bindings that belongs to OpenXRInteractionFeature
bool checkXRInteractionBinding = false;
bool profileEnabled = false;
foreach (var map in m_InteractionProfileEnabledMaps)
{
foreach (var profile in map.Value)
{
if (bindingPath.StartsWith(profile.Key, StringComparison.OrdinalIgnoreCase))
{
checkXRInteractionBinding = true;
profileEnabled = profile.Value;
break;
}
}
if (checkXRInteractionBinding)
break;
}
if (!checkXRInteractionBinding || profileEnabled)
return null;
warningText = "This binding will be inactive because it refers to a disabled OpenXR interaction profile.";
}
// Draw the warning information in the Binding Properties panel
return () =>
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
GUILayout.Label(EditorGUIUtility.IconContent("Warning@2x"), new GUIStyle(EditorStyles.label));
GUILayout.Label(warningText, EditorStyles.wordWrappedLabel);
EditorGUILayout.EndHorizontal();
if(GUILayout.Button("Manage Interaction Profiles"))
SettingsService.OpenProjectSettings("Project/XR Plug-in Management/OpenXR");
EditorGUILayout.Space();
EditorGUILayout.EndVertical();
};
}
#endif
#if UNITY_EDITOR
internal static bool OpenXRLoaderEnabledForSelectedBuildTarget(BuildTargetGroup targetGroup)
{
var settings =XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(targetGroup)?.AssignedSettings;
if (!settings)
return false;
bool loaderFound = false;
foreach (var activeLoader in settings.activeLoaders)
{
if (activeLoader as OpenXRLoader != null)
{
loaderFound = true;
break;
}
}
return loaderFound;
}
#endif
}
}