Maze / Library /PackageCache /com.unity.inputsystem@1.6.1 /InputSystem /Plugins /XR /XRLayoutBuilder.cs
| // ENABLE_VR is not defined on Game Core but the assembly is available with limited features when the XR module is enabled. | |
| using System; | |
| using System.Collections.Generic; | |
| using UnityEngine.InputSystem.LowLevel; | |
| using UnityEngine.InputSystem.Utilities; | |
| using System.Text; | |
| using UnityEngine.InputSystem.Layouts; | |
| using UnityEngine.XR; | |
| namespace UnityEngine.InputSystem.XR | |
| { | |
| internal class XRLayoutBuilder | |
| { | |
| private string parentLayout; | |
| private string interfaceName; | |
| private XRDeviceDescriptor descriptor; | |
| private static uint GetSizeOfFeature(XRFeatureDescriptor featureDescriptor) | |
| { | |
| switch (featureDescriptor.featureType) | |
| { | |
| case FeatureType.Binary: | |
| return sizeof(byte); | |
| case FeatureType.DiscreteStates: | |
| return sizeof(int); | |
| case FeatureType.Axis1D: | |
| return sizeof(float); | |
| case FeatureType.Axis2D: | |
| return sizeof(float) * 2; | |
| case FeatureType.Axis3D: | |
| return sizeof(float) * 3; | |
| case FeatureType.Rotation: | |
| return sizeof(float) * 4; | |
| case FeatureType.Hand: | |
| return sizeof(uint) * 26; | |
| case FeatureType.Bone: | |
| return sizeof(uint) + (sizeof(float) * 3) + (sizeof(float) * 4); | |
| case FeatureType.Eyes: | |
| return (sizeof(float) * 3) * 3 + ((sizeof(float) * 4) * 2) + (sizeof(float) * 2); | |
| case FeatureType.Custom: | |
| return featureDescriptor.customSize; | |
| } | |
| return 0; | |
| } | |
| private static string SanitizeString(string original, bool allowPaths = false) | |
| { | |
| var stringLength = original.Length; | |
| var sanitizedName = new StringBuilder(stringLength); | |
| for (var i = 0; i < stringLength; i++) | |
| { | |
| var letter = original[i]; | |
| if (char.IsUpper(letter) || char.IsLower(letter) || char.IsDigit(letter) || letter == '_' || (allowPaths && (letter == '/'))) | |
| { | |
| sanitizedName.Append(letter); | |
| } | |
| } | |
| return sanitizedName.ToString(); | |
| } | |
| internal static string OnFindLayoutForDevice(ref InputDeviceDescription description, string matchedLayout, | |
| InputDeviceExecuteCommandDelegate executeCommandDelegate) | |
| { | |
| // If the device isn't a XRInput, we're not interested. | |
| if (description.interfaceName != XRUtilities.InterfaceCurrent && description.interfaceName != XRUtilities.InterfaceV1) | |
| { | |
| return null; | |
| } | |
| // If the description doesn't come with a XR SDK descriptor, we're not | |
| // interested either. | |
| if (string.IsNullOrEmpty(description.capabilities)) | |
| { | |
| return null; | |
| } | |
| // Try to parse the XR descriptor. | |
| XRDeviceDescriptor deviceDescriptor; | |
| try | |
| { | |
| deviceDescriptor = XRDeviceDescriptor.FromJson(description.capabilities); | |
| } | |
| catch (Exception) | |
| { | |
| return null; | |
| } | |
| if (deviceDescriptor == null) | |
| { | |
| return null; | |
| } | |
| if (string.IsNullOrEmpty(matchedLayout)) | |
| { | |
| const InputDeviceCharacteristics controllerCharacteristics = InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.Controller; | |
| if ((deviceDescriptor.characteristics & InputDeviceCharacteristics.HeadMounted) != 0) | |
| matchedLayout = "XRHMD"; | |
| else if ((deviceDescriptor.characteristics & controllerCharacteristics) == controllerCharacteristics) | |
| matchedLayout = "XRController"; | |
| } | |
| string layoutName; | |
| if (string.IsNullOrEmpty(description.manufacturer)) | |
| { | |
| layoutName = $"{SanitizeString(description.interfaceName)}::{SanitizeString(description.product)}"; | |
| } | |
| else | |
| { | |
| layoutName = | |
| $"{SanitizeString(description.interfaceName)}::{SanitizeString(description.manufacturer)}::{SanitizeString(description.product)}"; | |
| } | |
| var layout = new XRLayoutBuilder { descriptor = deviceDescriptor, parentLayout = matchedLayout, interfaceName = description.interfaceName }; | |
| InputSystem.RegisterLayoutBuilder(() => layout.Build(), layoutName, matchedLayout); | |
| return layoutName; | |
| } | |
| private static string ConvertPotentialAliasToName(InputControlLayout layout, string nameOrAlias) | |
| { | |
| var internedNameOrAlias = new InternedString(nameOrAlias); | |
| var controls = layout.controls; | |
| for (var i = 0; i < controls.Count; i++) | |
| { | |
| var controlItem = controls[i]; | |
| if (controlItem.name == internedNameOrAlias) | |
| return nameOrAlias; | |
| var aliases = controlItem.aliases; | |
| for (var j = 0; j < aliases.Count; j++) | |
| { | |
| if (aliases[j] == nameOrAlias) | |
| return controlItem.name.ToString(); | |
| } | |
| } | |
| return nameOrAlias; | |
| } | |
| private bool IsSubControl(string name) | |
| { | |
| return name.Contains('/'); | |
| } | |
| private string GetParentControlName(string name) | |
| { | |
| int idx = name.IndexOf('/'); | |
| return name.Substring(0, idx); | |
| } | |
| static readonly string[] poseSubControlNames = | |
| { | |
| "/isTracked", | |
| "/trackingState", | |
| "/position", | |
| "/rotation", | |
| "/velocity", | |
| "/angularVelocity" | |
| }; | |
| static readonly FeatureType[] poseSubControlTypes = | |
| { | |
| FeatureType.Binary, | |
| FeatureType.DiscreteStates, | |
| FeatureType.Axis3D, | |
| FeatureType.Rotation, | |
| FeatureType.Axis3D, | |
| FeatureType.Axis3D | |
| }; | |
| // A PoseControl consists of 6 subcontrols with specific names and types | |
| private bool IsPoseControl(List<XRFeatureDescriptor> features, int startIndex) | |
| { | |
| for (var i = 0; i < 6; i++) | |
| { | |
| if (!features[startIndex + i].name.EndsWith(poseSubControlNames[i]) || | |
| features[startIndex + i].featureType != poseSubControlTypes[i]) | |
| return false; | |
| } | |
| return true; | |
| } | |
| private InputControlLayout Build() | |
| { | |
| var builder = new InputControlLayout.Builder | |
| { | |
| stateFormat = new FourCC('X', 'R', 'S', '0'), | |
| extendsLayout = parentLayout, | |
| updateBeforeRender = true | |
| }; | |
| var inheritedLayout = !string.IsNullOrEmpty(parentLayout) | |
| ? InputSystem.LoadLayout(parentLayout) | |
| : null; | |
| var parentControls = new List<string>(); | |
| var currentUsages = new List<string>(); | |
| uint currentOffset = 0; | |
| for (var i = 0; i < descriptor.inputFeatures.Count; i++) | |
| { | |
| var feature = descriptor.inputFeatures[i]; | |
| currentUsages.Clear(); | |
| if (feature.usageHints != null) | |
| { | |
| foreach (var usageHint in feature.usageHints) | |
| { | |
| if (!string.IsNullOrEmpty(usageHint.content)) | |
| currentUsages.Add(usageHint.content); | |
| } | |
| } | |
| var featureName = feature.name; | |
| featureName = SanitizeString(featureName, true); | |
| if (inheritedLayout != null) | |
| featureName = ConvertPotentialAliasToName(inheritedLayout, featureName); | |
| featureName = featureName.ToLower(); | |
| if (IsSubControl(featureName)) | |
| { | |
| string parentControl = GetParentControlName(featureName); | |
| if (!parentControls.Contains(parentControl)) | |
| { | |
| if (IsPoseControl(descriptor.inputFeatures, i)) | |
| { | |
| builder.AddControl(parentControl) | |
| .WithLayout("Pose") | |
| .WithByteOffset(0); | |
| parentControls.Add(parentControl); | |
| } | |
| } | |
| } | |
| uint nextOffset = GetSizeOfFeature(feature); | |
| if (interfaceName == XRUtilities.InterfaceV1) | |
| { | |
| if (nextOffset < 4) | |
| nextOffset = 4; | |
| } | |
| else | |
| { | |
| if (nextOffset >= 4 && (currentOffset % 4 != 0)) | |
| currentOffset += (4 - (currentOffset % 4)); | |
| } | |
| switch (feature.featureType) | |
| { | |
| case FeatureType.Binary: | |
| { | |
| builder.AddControl(featureName) | |
| .WithLayout("Button") | |
| .WithByteOffset(currentOffset) | |
| .WithFormat(InputStateBlock.FormatBit) | |
| .WithUsages(currentUsages); | |
| break; | |
| } | |
| case FeatureType.DiscreteStates: | |
| { | |
| builder.AddControl(featureName) | |
| .WithLayout("Integer") | |
| .WithByteOffset(currentOffset) | |
| .WithFormat(InputStateBlock.FormatInt) | |
| .WithUsages(currentUsages); | |
| break; | |
| } | |
| case FeatureType.Axis1D: | |
| { | |
| builder.AddControl(featureName) | |
| .WithLayout("Analog") | |
| .WithRange(-1, 1) | |
| .WithByteOffset(currentOffset) | |
| .WithFormat(InputStateBlock.FormatFloat) | |
| .WithUsages(currentUsages); | |
| break; | |
| } | |
| case FeatureType.Axis2D: | |
| { | |
| builder.AddControl(featureName) | |
| .WithLayout("Stick") | |
| .WithByteOffset(currentOffset) | |
| .WithFormat(InputStateBlock.FormatVector2) | |
| .WithUsages(currentUsages); | |
| builder.AddControl(featureName + "/x") | |
| .WithLayout("Analog") | |
| .WithRange(-1, 1); | |
| builder.AddControl(featureName + "/y") | |
| .WithLayout("Analog") | |
| .WithRange(-1, 1); | |
| break; | |
| } | |
| case FeatureType.Axis3D: | |
| { | |
| builder.AddControl(featureName) | |
| .WithLayout("Vector3") | |
| .WithByteOffset(currentOffset) | |
| .WithFormat(InputStateBlock.FormatVector3) | |
| .WithUsages(currentUsages); | |
| break; | |
| } | |
| case FeatureType.Rotation: | |
| { | |
| builder.AddControl(featureName) | |
| .WithLayout("Quaternion") | |
| .WithByteOffset(currentOffset) | |
| .WithFormat(InputStateBlock.FormatQuaternion) | |
| .WithUsages(currentUsages); | |
| break; | |
| } | |
| case FeatureType.Hand: | |
| { | |
| break; | |
| } | |
| case FeatureType.Bone: | |
| { | |
| builder.AddControl(featureName) | |
| .WithLayout("Bone") | |
| .WithByteOffset(currentOffset) | |
| .WithUsages(currentUsages); | |
| break; | |
| } | |
| case FeatureType.Eyes: | |
| { | |
| builder.AddControl(featureName) | |
| .WithLayout("Eyes") | |
| .WithByteOffset(currentOffset) | |
| .WithUsages(currentUsages); | |
| break; | |
| } | |
| } | |
| currentOffset += nextOffset; | |
| } | |
| return builder.Build(); | |
| } | |
| } | |
| } | |