using System.Collections.Generic;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.XR;
using UnityEngine.Scripting;
using UnityEngine.XR.OpenXR.Input;
#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 eye gaze interaction profiles in OpenXR. It enables XR_EXT_eye_gaze_interaction in the underlying runtime.
/// This creates a new with the characteristic. This new device has both and input features, as well as and usages to determine if the gaze is available.
///
#if UNITY_EDITOR
[UnityEditor.XR.OpenXR.Features.OpenXRFeature(UiName = "Eye Gaze Interaction Profile",
BuildTargetGroups = new[] { BuildTargetGroup.WSA, BuildTargetGroup.Standalone, BuildTargetGroup.Android },
Company = "Unity",
Desc = "Support for enabling the eye tracking interaction profile. Will register the controller map for eye tracking if enabled.",
DocumentationLink = Constants.k_DocumentationManualURL + "features/eyegazeinteraction.html",
Version = "0.0.1",
OpenxrExtensionStrings = extensionString,
Category = UnityEditor.XR.OpenXR.Features.FeatureCategory.Interaction,
FeatureId = featureId)]
#endif
public class EyeGazeInteraction : 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.eyetracking";
///
/// An Input System device based off the Eye Gaze Interaction Profile. Enabled through .
///
[Preserve, InputControlLayout(displayName = "Eye Gaze (OpenXR)", isGenericTypeOfDevice = true)]
public class EyeGazeDevice : OpenXRDevice
{
///
/// A representing the OpenXR binding.
///
[Preserve, InputControl(offset = 0, usages = new[] { "Device", "gaze" })]
public PoseControl pose { get; private set; }
///
protected override void FinishSetup()
{
base.FinishSetup();
pose = GetChildControl("pose");
}
}
///
/// The OpenXR constant that is used to reference an eye tracking supported input device. See OpenXR Specification 6.3.1 for more information on user paths.
///
private const string userPath = "/user/eyes_ext";
/// The interaction profile string used to reference the eye gaze input device.
private const string profile = "/interaction_profiles/ext/eye_gaze_interaction";
///
/// Constant for a pose interaction binding '.../input/gaze_ext/pose' OpenXR Input Binding. Used by input subsystem to bind actions to physical inputs.
///
private const string pose = "/input/gaze_ext/pose";
private const string kDeviceLocalizedName = "Eye Tracking OpenXR";
/// The OpenXR Extension string. This is used by OpenXR to check if this extension is available or enabled. See eye gaze interaction extension documentation for more information on this OpenXR extension.
public const string extensionString = "XR_EXT_eye_gaze_interaction";
private const string layoutName = "EyeGaze";
#if UNITY_EDITOR
protected internal override void GetValidationChecks(List results, BuildTargetGroup target)
{
if (target == BuildTargetGroup.WSA)
{
results.Add(new ValidationRule(this){
message = "Eye Gaze support requires the Gaze Input capability.",
error = false,
checkPredicate = () => PlayerSettings.WSA.GetCapability(PlayerSettings.WSACapability.GazeInput),
fixIt = () => PlayerSettings.WSA.SetCapability(PlayerSettings.WSACapability.GazeInput, true)
});
}
}
#endif
///
protected internal override bool OnInstanceCreate(ulong instance)
{
// Requires the eye 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(EyeGazeDevice),
layoutName,
matches: new InputDeviceMatcher()
.WithInterface(XRUtilities.InterfaceMatchAnyVersion)
.WithProduct(kDeviceLocalizedName));
}
///
/// Removes the layout from the Input System.
///
protected override void UnregisterDeviceLayout()
{
#if UNITY_EDITOR
if (!OpenXRLoaderEnabledForSelectedBuildTarget(EditorUserBuildSettings.selectedBuildTargetGroup))
return;
#endif
InputSystem.InputSystem.RemoveLayout(layoutName);
}
///
/// Return interaction profile type. EyeGaze profile is Device type.
///
/// Interaction profile type.
protected override InteractionProfileType GetInteractionProfileType()
{
return typeof(EyeGazeDevice).IsSubclassOf(typeof(XRController)) ? InteractionProfileType.XRController : InteractionProfileType.Device;
}
///
/// Return device layer out string used for registering device EyeGaze in InputSystem.
///
/// Device layout string.
protected override string GetDeviceLayoutName()
{
return layoutName;
}
///
protected override void RegisterActionMapsWithRuntime()
{
ActionMapConfig actionMap = new ActionMapConfig()
{
name = "eyegaze",
localizedName = kDeviceLocalizedName,
desiredInteractionProfile = profile,
manufacturer = "",
serialNumber = "",
deviceInfos = new List()
{
new DeviceConfig()
{
characteristics = InputDeviceCharacteristics.TrackedDevice | InputDeviceCharacteristics.EyeTracking | InputDeviceCharacteristics.HeadMounted,
userPath = userPath
}
},
actions = new List()
{
// Pointer Pose
new ActionConfig()
{
name = "pose",
localizedName = "Pose",
type = ActionType.Pose,
usages = new List()
{
"Device",
"gaze"
},
bindings = new List()
{
new ActionBinding()
{
interactionPath = pose,
interactionProfileName = profile,
}
}
}
}
};
AddActionMap(actionMap);
}
}
///
/// Tags that can be used with to get eye tracking related input features. See for additional usages.
///
public static class EyeTrackingUsages
{
/// The origin position for the gaze. The gaze represents where a user is looking, and represents the starting location, close to the eyes, from which to project a gaze ray from.
public static InputFeatureUsage gazePosition = new InputFeatureUsage("gazePosition");
/// The orientation of the gaze, such that the direction of the gaze is the same as * gazeRotation. Use with to create a gaze ray.
public static InputFeatureUsage gazeRotation = new InputFeatureUsage("gazeRotation");
}
}