#if UNITY_INPUT_SYSTEM_ENABLE_XR && ENABLE_VR 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) || (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)) { #if UNITY_2019_3_OR_NEWER const InputDeviceCharacteristics controllerCharacteristics = InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.Controller; if ((deviceDescriptor.characteristics & InputDeviceCharacteristics.HeadMounted) != 0) matchedLayout = "XRHMD"; else if ((deviceDescriptor.characteristics & controllerCharacteristics) == controllerCharacteristics) matchedLayout = "XRController"; #else //UNITY_2019_3_OR_NEWER if (deviceDescriptor.deviceRole == InputDeviceRole.LeftHanded || deviceDescriptor.deviceRole == InputDeviceRole.RightHanded) matchedLayout = "XRController"; else if (deviceDescriptor.deviceRole == InputDeviceRole.Generic) matchedLayout = "XRHMD"; #endif //UNITY_2019_3_OR_NEWER } 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 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 currentUsages = new List(); uint currentOffset = 0; foreach (var feature in descriptor.inputFeatures) { 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(); uint nextOffset = GetSizeOfFeature(feature); if (interfaceName == XRUtilities.InterfaceV1) { #if UNITY_ANDROID if (nextOffset < 4) nextOffset = 4; #endif } 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") .WithByteOffset(currentOffset) .WithFormat(InputStateBlock.FormatFloat) .WithUsages(currentUsages); break; } case FeatureType.Axis2D: { builder.AddControl(featureName) .WithLayout("Vector2") .WithByteOffset(currentOffset) .WithFormat(InputStateBlock.FormatVector2) .WithUsages(currentUsages); 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(); } } } #endif // UNITY_INPUT_SYSTEM_ENABLE_XR && ENABLE_VR