UnityGame/Library/PackageCache/com.unity.inputsystem/InputSystem/Plugins/XR/XRLayoutBuilder.cs
2024-10-27 10:53:47 +03:00

345 lines
13 KiB
C#

// ENABLE_VR is not defined on Game Core but the assembly is available with limited features when the XR module is enabled.
#if UNITY_INPUT_SYSTEM_ENABLE_XR && (ENABLE_VR || UNITY_GAMECORE) && !UNITY_FORCE_INPUTSYSTEM_XR_OFF
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 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")
.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();
}
}
}
#endif