//========= Copyright 2016-2023, HTC Corporation. All rights reserved. =========== #if ENABLE_INPUT_SYSTEM using HTC.UnityPlugin.Utility; using HTC.UnityPlugin.VRModuleManagement; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.Utilities; using UnityEngine.InputSystem.XR; using TrackingState = UnityEngine.XR.InputTrackingState; #if VIU_UIS_POSE_CONTROL using UnityPoseControl = UnityEngine.InputSystem.XR.PoseControl; #endif #if VIU_OPENXR_PLUGIN_POSE_CONTROL using OpenXRPoseControl = UnityEngine.XR.OpenXR.Input.PoseControl; #endif namespace HTC.UnityPlugin.Vive { public struct VIUSyntheticDeviceState : IInputStateTypeInfo { public FourCC format => new FourCC('V', 'I', 'U', 'D'); [StructLayout(LayoutKind.Explicit, Size = kSizeInBytes)] public struct PoseState : IInputStateTypeInfo { internal const int kSizeInBytes = 60; public FourCC format => new FourCC('P', 'o', 's', 'e'); [FieldOffset(0), InputControl(displayName = "Is Tracked", layout = "Button")] public bool isTracked; [FieldOffset(4), InputControl(displayName = "Tracking State", layout = "Integer")] public TrackingState trackingState; [FieldOffset(8), InputControl(displayName = "Position", noisy = true)] public Vector3 position; [FieldOffset(20), InputControl(displayName = "Rotation", noisy = true)] public Quaternion rotation; [FieldOffset(36), InputControl(displayName = "Velocity", noisy = true)] public Vector3 velocity; [FieldOffset(48), InputControl(displayName = "Angular Velocity", noisy = true)] public Vector3 angularVelocity; } [InputControl(name = "pose", layout = "Pose")] public PoseState pose; /// align [InputControl(name = "System", layout = "Button", bit = (uint)ControllerButton.System)] [InputControl(name = "Menu", layout = "Button", bit = (uint)ControllerButton.Menu, aliases = new[] { "BKey", "OuterFaceButton" }, usage = "Menu")] [InputControl(name = "MenuTouch", layout = "Button", bit = (uint)ControllerButton.MenuTouch, aliases = new[] { "BkeyTouch", "OuterFaceButtonTouch" })] [InputControl(name = "Trigger", layout = "Button", bit = (uint)ControllerButton.Trigger, aliases = new[] { "Axis1" }, usages = new[] { "PrimaryTrigger", "PrimaryAction", "Submit" })] [InputControl(name = "TriggerTouch", layout = "Button", bit = (uint)ControllerButton.TriggerTouch, aliases = new[] { "Axis1Touch" })] [InputControl(name = "Pad", layout = "Button", bit = (uint)ControllerButton.Pad, aliases = new[] { "Axis0" }, usage = "SecondaryAction")] [InputControl(name = "PadTouch", layout = "Button", bit = (uint)ControllerButton.PadTouch, aliases = new[] { "Axis0Touch" })] [InputControl(name = "Joystick", layout = "Button", bit = (uint)ControllerButton.Joystick)] [InputControl(name = "JoystickTouch", layout = "Button", bit = (uint)ControllerButton.JoystickTouch)] [InputControl(name = "Grip", layout = "Button", bit = (uint)ControllerButton.Grip, usage = "Cancel")] [InputControl(name = "GripTouch", layout = "Button", bit = (uint)ControllerButton.GripTouch)] [InputControl(name = "CapSenseGrip", layout = "Button", bit = (uint)ControllerButton.CapSenseGrip, aliases = new[] { "Axis2" })] [InputControl(name = "CapSenseGripTouch", layout = "Button", bit = (uint)ControllerButton.CapSenseGripTouch, aliases = new[] { "Axis2Touch" })] [InputControl(name = "ProximitySensor", layout = "Button", bit = (uint)ControllerButton.ProximitySensor)] [InputControl(name = "Bumper", layout = "Button", bit = (uint)ControllerButton.Bumper, aliases = new[] { "Axis3" })] [InputControl(name = "BumperTouch", layout = "Button", bit = (uint)ControllerButton.BumperTouch, aliases = new[] { "Axis3Touch" })] [InputControl(name = "AKey", layout = "Button", bit = (uint)ControllerButton.AKey, aliases = new[] { "InnerFaceButton" })] [InputControl(name = "AKeyTouch", layout = "Button", bit = (uint)ControllerButton.AKeyTouch, aliases = new[] { "InnerFaceButtonTouch" })] [InputControl(name = "Axis4", layout = "Button", bit = (uint)ControllerButton.Axis4)] [InputControl(name = "Axis4Touch", layout = "Button", bit = (uint)ControllerButton.Axis4Touch)] [InputControl(name = "HairTrigger", layout = "Button", bit = (uint)ControllerButton.HairTrigger)] [InputControl(name = "FullTrigger", layout = "Button", bit = (uint)ControllerButton.FullTrigger)] [InputControl(name = "DPadLeft", layout = "Button", bit = (uint)ControllerButton.DPadLeft, usage = "Back")] [InputControl(name = "DPadUp", layout = "Button", bit = (uint)ControllerButton.DPadUp)] [InputControl(name = "DPadRight", layout = "Button", bit = (uint)ControllerButton.DPadRight, usage = "Forward")] [InputControl(name = "DPadDown", layout = "Button", bit = (uint)ControllerButton.DPadDown)] [InputControl(name = "DPadLeftTouch", layout = "Button", bit = (uint)ControllerButton.DPadLeftTouch)] [InputControl(name = "DPadUpTouch", layout = "Button", bit = (uint)ControllerButton.DPadUpTouch)] [InputControl(name = "DPadRightTouch", layout = "Button", bit = (uint)ControllerButton.DPadRightTouch)] [InputControl(name = "DPadDownTouch", layout = "Button", bit = (uint)ControllerButton.DPadDownTouch)] [InputControl(name = "DPadUpperLeft", layout = "Button", bit = (uint)ControllerButton.DPadUpperLeft)] [InputControl(name = "DPadUpperRight", layout = "Button", bit = (uint)ControllerButton.DPadUpperRight)] [InputControl(name = "DPadLowerRight", layout = "Button", bit = (uint)ControllerButton.DPadLowerRight)] [InputControl(name = "DPadLowerLeft", layout = "Button", bit = (uint)ControllerButton.DPadLowerLeft)] [InputControl(name = "DPadUpperLeftTouch", layout = "Button", bit = (uint)ControllerButton.DPadUpperLeftTouch)] [InputControl(name = "DPadUpperRightTouch", layout = "Button", bit = (uint)ControllerButton.DPadUpperRightTouch)] [InputControl(name = "DPadLowerRightTouch", layout = "Button", bit = (uint)ControllerButton.DPadLowerRightTouch)] [InputControl(name = "DPadLowerLeftTouch", layout = "Button", bit = (uint)ControllerButton.DPadLowerLeftTouch)] [InputControl(name = "DPadCenter", layout = "Button", bit = (uint)ControllerButton.DPadCenter)] [InputControl(name = "DPadCenterTouch", layout = "Button", bit = (uint)ControllerButton.DPadCenterTouch)] [InputControl(name = "IndexPinch", layout = "Button", bit = (uint)ControllerButton.IndexPinch)] [InputControl(name = "MiddlePinch", layout = "Button", bit = (uint)ControllerButton.MiddlePinch)] [InputControl(name = "RingPinch", layout = "Button", bit = (uint)ControllerButton.RingPinch)] [InputControl(name = "PinkyPinch", layout = "Button", bit = (uint)ControllerButton.PinkyPinch)] [InputControl(name = "Fist", layout = "Button", bit = (uint)ControllerButton.Fist)] [InputControl(name = "Five", layout = "Button", bit = (uint)ControllerButton.Five)] [InputControl(name = "Ok", layout = "Button", bit = (uint)ControllerButton.Ok)] [InputControl(name = "ThumbUp", layout = "Button", bit = (uint)ControllerButton.ThumbUp)] [InputControl(name = "IndexUp", layout = "Button", bit = (uint)ControllerButton.IndexUp)] public ulong buttons; /// align [InputControl(name = "PadAxis", layout = "Stick", usage = "Primary2DMotion")] [InputControl(name = "PadAxis/x", layout = "Axis", usages = new[] { "Horizontal", "ScrollHorizontal" })] [InputControl(name = "PadAxis/y", layout = "Axis", usages = new[] { "Vertical", "ScrollVertical" })] public Vector2 touchpad; [InputControl(name = "TriggerAxis", layout = "Axis")] public float trigger; [InputControl(name = "CapSenseGripAxis", layout = "Axis")] public float capSenseGrip; [InputControl(name = "IndexCurlAxis", layout = "Axis")] public float indexCurl; [InputControl(name = "MiddleCurlAxis", layout = "Axis")] public float middleCurl; [InputControl(name = "RingCurlAxis", layout = "Axis")] public float ringCurl; [InputControl(name = "PinkyCurlAxis", layout = "Axis")] public float pinkyCurl; [InputControl(name = "JoystickAxis", layout = "Stick", usage = "Secondary2DMotion")] [InputControl(name = "JoystickAxis/x", layout = "Axis", usages = new[] { "Horizontal", "ScrollHorizontal" })] [InputControl(name = "JoystickAxis/y", layout = "Axis", usages = new[] { "Vertical", "ScrollVertical" })] public Vector2 joystick; [InputControl(name = "IndexPinchAxis", layout = "Axis")] public float indexPinch; [InputControl(name = "MiddlePinchAxis", layout = "Axis")] public float middlePinch; [InputControl(name = "RingPinchAxis", layout = "Axis")] public float ringPinch; [InputControl(name = "PinkyPinchAxis", layout = "Axis")] public float pinkyPinch; } #if UNITY_EDITOR [UnityEditor.InitializeOnLoad] #endif [InputControlLayout(displayName = "VIU Synthetic Device", stateType = typeof(VIUSyntheticDeviceState), isGenericTypeOfDevice = true)] public class VIUSyntheticDevice : InputDevice, IInputUpdateCallbackReceiver { [Serializable] private struct LayoutStruct { public string name; public string displayName; public string extend; public string[] extendMultiple; public string[] commonUsages; } private class RoleDeviceCreator : IDisposable { private readonly string layoutName; private readonly int minRoleValue; private readonly ViveRole.IMap map; private VIUSyntheticDevice[] devices; private const RegexOptions REGEX_OPTIONS = RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled; private static readonly Regex leftRgx = new Regex("^.*(left).*$", REGEX_OPTIONS); private static readonly Regex rightRgx = new Regex("^.*(right).*$", REGEX_OPTIONS); public RoleDeviceCreator(ViveRole.IMap map, string layoutName) { var roleInfo = map.RoleValueInfo; devices = new VIUSyntheticDevice[roleInfo.ValidRoleLength]; this.layoutName = layoutName; this.minRoleValue = roleInfo.MinValidRoleValue; this.map = map; map.onRoleValueMappingChanged += OnRoleValueMappingChanged; for (int i = 0, imax = roleInfo.ValidRoleLength; i < imax; ++i) { var roleValue = i + minRoleValue; var deviceIndex = map.GetMappedDeviceByRoleValue(roleValue); if (VRModule.IsValidDeviceIndex(deviceIndex)) { devices[i] = AddDevice(map, roleValue); } } } public void Dispose() { RemoveAllDevices(); devices = null; map.onRoleValueMappingChanged -= OnRoleValueMappingChanged; } public void RemoveAllDevices() { for (int i = 0, imax = devices.Length; i < imax; ++i) { if (devices[i] != null) { InputSystem.RemoveDevice(devices[i]); } devices[i] = null; } } public VIUSyntheticDevice GetDevice(int roleValue) { var roleIndex = roleValue - minRoleValue; return roleIndex >= 0 && roleIndex < devices.Length ? devices[roleIndex] : null; } private VIUSyntheticDevice AddDevice(ViveRole.IMap map, int roleValue) { VIUSyntheticDevice device; try { device = InputSystem.AddDevice(new InputDeviceDescription() { interfaceName = layoutName, manufacturer = "HTC ViveSoftware", }) as VIUSyntheticDevice; InputSystem.AddDeviceUsage(device, map.RoleValueInfo.GetNameByRoleValue(roleValue)); if (leftRgx.IsMatch(layoutName)) { InputSystem.AddDeviceUsage(device, CommonUsages.LeftHand); } else if (rightRgx.IsMatch(layoutName)) { InputSystem.AddDeviceUsage(device, CommonUsages.RightHand); } } catch (Exception e) { Debug.LogException(e); return null; } device.ctrlState = ViveInput.GetState(map.RoleValueInfo.RoleEnumType, roleValue); return device; } private static void ResumeDevice(InputDevice device) { InputSystem.AddDevice(device); } private static void RemoveDevice(InputDevice device) { InputSystem.RemoveDevice(device); } private void OnRoleValueMappingChanged(ViveRole.IMap map, ViveRole.MappingChangedEventArg arg) { if (!VRModule.IsValidDeviceIndex(arg.previousDeviceIndex)) { if (VRModule.IsValidDeviceIndex(arg.currentDeviceIndex)) { // try create or add into InputSystem var roleIndex = arg.roleValue - minRoleValue; var device = devices[roleIndex]; if (device == null) { devices[roleIndex] = AddDevice(map, arg.roleValue); } else { ResumeDevice(device); } } } else { if (!VRModule.IsValidDeviceIndex(arg.currentDeviceIndex)) { // try remove device from InputSystem var roleIndex = arg.roleValue - minRoleValue; var device = devices[roleIndex]; if (device != null) { RemoveDevice(device); } } } } } private static Dictionary roleDeviceCreators = new Dictionary(); private ViveInput.ICtrlState ctrlState; private EnumArray _buttons; private EnumArray _axises; public EnumArray.IReadOnly buttons { get { return _buttons.ReadOnly; } } public EnumArray.IReadOnly axises { get { return _axises.ReadOnly; } } public StickControl pad { get; private set; } public StickControl joystick { get; private set; } #if VIU_UIS_POSE_CONTROL public UnityPoseControl pose { get; private set; } #endif #if VIU_OPENXR_PLUGIN_POSE_CONTROL public OpenXRPoseControl openxr_pose { get; private set; } #endif public ButtonControl isTracked { get; private set; } public IntegerControl trackingState { get; private set; } public Vector3Control position { get; private set; } public QuaternionControl rotation { get; private set; } public Vector3Control velocity { get; private set; } public Vector3Control angularVelocity { get; private set; } static VIUSyntheticDevice() { // RegisterLayout() adds a "Control layout" to the system. // These can be layouts for individual Controls (like sticks) // or layouts for entire Devices (which are themselves // Controls) like in our case. InputSystem.RegisterLayout(); // Duplicate layouts for each Vive role enum try { var currentAsm = typeof(ViveRole).Assembly; var currentAsmName = currentAsm.GetName().Name; foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { var referencingCurrentAsm = false; if (asm == currentAsm) { referencingCurrentAsm = true; } else { foreach (var asmref in asm.GetReferencedAssemblies()) { if (asmref.Name == currentAsmName) { referencingCurrentAsm = true; break; } } } if (referencingCurrentAsm) { foreach (var type in asm.GetTypes().Where(t => ViveRoleEnum.ValidateViveRoleEnum(t) == ViveRoleEnumValidateResult.Valid)) { TryRegisterLayoutForViveRoleEnum(type); } } } } catch (Exception e) { Debug.LogError(e); } #if UNITY_EDITOR if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) #endif { InputSystem.onLayoutChange += OnLayoutChange; } } [RuntimeInitializeOnLoadMethod] private static void InitializeInPlayer() { } /// /// Registry a new VIUSyntheticDevice Layout for a role so it can be shown in binding UI /// /// A enum type defined with /// public static bool TryRegisterLayoutForViveRoleEnum(Type viveRoleType) { var map = ViveRole.GetMap(viveRoleType); var info = map == null ? null : map.RoleValueInfo; if (info == null || info.RoleValueNames.Length == 0) { return false; } var layoutName = InternalViveRoleLayoutName(viveRoleType); var validNames = ListPool.Get(); try { for (int i = 0, imax = info.RoleValueNames.Length; i < imax; ++i) { if (info.IsValidRoleValue(info.RoleValues[i])) { validNames.Add(info.RoleValueNames[i]); } } InputSystem.RegisterLayout(JsonUtility.ToJson(new LayoutStruct() { name = layoutName, displayName = viveRoleType.Name, extend = typeof(VIUSyntheticDevice).Name, commonUsages = validNames.ToArray(), }), layoutName, new InputDeviceMatcher() .WithInterface(layoutName) .WithManufacturer("HTC ViveSoftware")); } catch (Exception e) { Debug.LogException(e); return false; } finally { ListPool.Release(validNames); } #if UNITY_EDITOR if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) #endif { RoleDeviceCreator creator; if (!roleDeviceCreators.TryGetValue(layoutName, out creator)) { creator = new RoleDeviceCreator(map, layoutName); roleDeviceCreators.Add(layoutName, creator); } else { Debug.LogWarning("[VIUSyntheticDevice] TryRegisterLayoutForViveRoleEnum device creator for " + layoutName + " already exist."); } } return true; } private static void OnLayoutChange(string name, InputControlLayoutChange change) { if (change == InputControlLayoutChange.Removed) { RoleDeviceCreator creator; if (roleDeviceCreators.TryGetValue(name, out creator)) { creator.Dispose(); roleDeviceCreators.Remove(name); } } } private static string InternalViveRoleLayoutName(Type viveRoleType) { return "VIUSyntheticDeviceLayout" + viveRoleType.Name; } void IInputUpdateCallbackReceiver.OnUpdate() { if (ctrlState == null) { return; } var deviceIndex = ctrlState.RoleMap.GetMappedDeviceByRoleValue(ctrlState.RoleValue); var deviceState = VRModule.GetCurrentDeviceState(deviceIndex); ctrlState.Update(); InputSystem.QueueStateEvent(this, new VIUSyntheticDeviceState() { buttons = ctrlState.CurrentButtonPressed, touchpad = new Vector2(ctrlState.GetAxis(ControllerAxis.PadX), ctrlState.GetAxis(ControllerAxis.PadY)), trigger = ctrlState.GetAxis(ControllerAxis.Trigger), capSenseGrip = ctrlState.GetAxis(ControllerAxis.CapSenseGrip), indexCurl = ctrlState.GetAxis(ControllerAxis.IndexCurl), middleCurl = ctrlState.GetAxis(ControllerAxis.MiddleCurl), ringCurl = ctrlState.GetAxis(ControllerAxis.RingCurl), pinkyCurl = ctrlState.GetAxis(ControllerAxis.PinkyCurl), joystick = new Vector2(ctrlState.GetAxis(ControllerAxis.JoystickX), ctrlState.GetAxis(ControllerAxis.JoystickY)), indexPinch = ctrlState.GetAxis(ControllerAxis.IndexPinch), middlePinch = ctrlState.GetAxis(ControllerAxis.MiddlePinch), ringPinch = ctrlState.GetAxis(ControllerAxis.RingPinch), pinkyPinch = ctrlState.GetAxis(ControllerAxis.PinkyPinch), pose = new VIUSyntheticDeviceState.PoseState() { isTracked = deviceState.isPoseValid, trackingState = TrackingState.Position | TrackingState.Rotation | TrackingState.Velocity | TrackingState.AngularVelocity, position = deviceState.position, rotation = deviceState.rotation, velocity = deviceState.velocity, angularVelocity = deviceState.angularVelocity, }, }); } // Query device by path public static VIUSyntheticDevice FindDeviceByRole(TRole role) #if CSHARP_7_OR_LATER where TRole : Enum #endif { return InputSystem.FindControl("<" + InternalViveRoleLayoutName(typeof(TRole)) + ">{" + role.ToString() + "}") as VIUSyntheticDevice; } // Query device by path public static VIUSyntheticDevice FindDeviceByRole(Type roleEnumType, int roleValue) { var info = ViveRoleEnum.GetInfo(roleEnumType); if (info == null) { return null; } return InputSystem.FindControl("<" + InternalViveRoleLayoutName(roleEnumType) + ">{" + info.GetNameByRoleValue(roleValue) + "}") as VIUSyntheticDevice; } public static VIUSyntheticDevice GetCreatedDeviceByRole(TRole role) #if CSHARP_7_OR_LATER where TRole : Enum #endif { var roleEnumType = typeof(TRole); if (ViveRoleEnum.ValidateViveRoleEnum(roleEnumType) == ViveRoleEnumValidateResult.Valid) { return InternalGetCreatedDeviceByRole(roleEnumType, EnumArrayBase.E2I(role)); } return null; } public static VIUSyntheticDevice GetCreatedDeviceByRole(Type roleEnumType, int roleValue) { if (ViveRoleEnum.ValidateViveRoleEnum(roleEnumType) == ViveRoleEnumValidateResult.Valid) { return InternalGetCreatedDeviceByRole(roleEnumType, roleValue); } return null; } public static VIUSyntheticDevice InternalGetCreatedDeviceByRole(Type roleEnumType, int roleValue) { var name = InternalViveRoleLayoutName(roleEnumType); RoleDeviceCreator creator; return roleDeviceCreators.TryGetValue(name, out creator) ? creator.GetDevice(roleValue) : null; } protected override void FinishSetup() { base.FinishSetup(); #if VIU_UIS_POSE_CONTROL pose = GetChildControl("pose") as UnityPoseControl; #endif #if VIU_OPENXR_PLUGIN_POSE_CONTROL openxr_pose = GetChildControl("pose") as OpenXRPoseControl; #endif isTracked = GetChildControl("pose/isTracked"); trackingState = GetChildControl("pose/trackingState"); position = GetChildControl("pose/position"); rotation = GetChildControl("pose/rotation"); velocity = GetChildControl("pose/velocity"); angularVelocity = GetChildControl("pose/angularVelocity"); pad = GetChildControl("PadAxis"); joystick = GetChildControl("JoystickAxis"); _buttons = new EnumArray(); foreach (var btn in EnumArrayBase.StaticEnums) { _buttons[(int)btn] = TryGetChildControl(EnumArrayBase.StaticEnumName(btn)); } _axises = new EnumArray(); foreach (var axis in EnumArrayBase.StaticEnums) { _axises[(int)axis] = TryGetChildControl(EnumArrayBase.StaticEnumName(axis) + "Axis"); } _axises[(int)ControllerAxis.PadX] = GetChildControl("PadAxis/x"); _axises[(int)ControllerAxis.PadY] = GetChildControl("PadAxis/y"); _axises[(int)ControllerAxis.JoystickX] = GetChildControl("JoystickAxis/x"); _axises[(int)ControllerAxis.JoystickY] = GetChildControl("JoystickAxis/y"); } } [StructLayout(LayoutKind.Explicit, Size = kSizeInBytes)] public struct VIUSyntheticXRHMDState : IInputStateTypeInfo { public const int kSizeInBytes = 120; public FourCC format => new FourCC('V', 'I', 'U', 'H'); [FieldOffset(0), InputControl(layout = "Button")] public bool isTracked; [FieldOffset(4), InputControl(layout = "Integer")] public TrackingState trackingState; [FieldOffset(8), InputControl(noisy = true)] public Vector3 devicePosition; [FieldOffset(20), InputControl(noisy = true)] public Quaternion deviceRotation; [FieldOffset(36), InputControl(noisy = true)] public Vector3 leftEyePosition; [FieldOffset(48), InputControl(noisy = true)] public Quaternion leftEyeRotation; [FieldOffset(64), InputControl(noisy = true)] public Vector3 rightEyePosition; [FieldOffset(76), InputControl(noisy = true)] public Quaternion rightEyeRotation; [FieldOffset(92), InputControl(noisy = true)] public Vector3 centerEyePosition; [FieldOffset(104), InputControl(noisy = true)] public Quaternion centerEyeRotation; } #if UNITY_EDITOR [UnityEditor.InitializeOnLoad] #endif [InputControlLayout(displayName = "VIU Synthetic XR HMD", stateType = typeof(VIUSyntheticXRHMDState), hideInUI = true)] public class VIUSyntheticXRHMD : XRHMD, IInputUpdateCallbackReceiver { private ViveInput.ICtrlState ctrlState; private static VIUSyntheticXRHMD hmdDevice; private static readonly RigidPose leftEyeLocalPose = new RigidPose(new Vector3(-0.033f, 0f, 0f), Quaternion.identity); private static readonly RigidPose rightEyeLocalPose = new RigidPose(new Vector3(0.033f, 0f, 0f), Quaternion.identity); static VIUSyntheticXRHMD() { InputSystem.RegisterLayout(); #if UNITY_EDITOR if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) #endif { var roleMap = ViveRole.GetMap(); HandleDevice(ref hmdDevice, VRModule.IsValidDeviceIndex(roleMap.GetMappedDeviceByRole(DeviceRole.Hmd))); roleMap.onRoleMappingChanged += (map, arg) => { if (arg.role == DeviceRole.Hmd) { HandleDevice(ref hmdDevice, VRModule.IsValidDeviceIndex(arg.currentDeviceIndex)); } }; } } private static void HandleDevice(ref VIUSyntheticXRHMD device, bool connected) { try { if (connected) { if (device == null) { device = InputSystem.AddDevice("VIUSyntheticXRHMD"); device.ctrlState = ViveInput.GetState(DeviceRole.Hmd); } else { InputSystem.AddDevice(device); } } else { if (device != null) { InputSystem.RemoveDevice(device); } } } catch (Exception e) { Debug.LogException(e); } } void IInputUpdateCallbackReceiver.OnUpdate() { if (ctrlState == null) { return; } var deviceIndex = ctrlState.RoleMap.GetMappedDeviceByRoleValue(ctrlState.RoleValue); var deviceState = VRModule.GetCurrentDeviceState(deviceIndex); var leftEyePose = deviceState.pose * leftEyeLocalPose; var rightEyePose = deviceState.pose * rightEyeLocalPose; InputSystem.QueueStateEvent(this, new VIUSyntheticXRHMDState() { isTracked = deviceState.isPoseValid, trackingState = TrackingState.Position | TrackingState.Rotation, devicePosition = deviceState.position, deviceRotation = deviceState.rotation, // TODO: should get eye pose from IPD property or eye tracking sdk leftEyePosition = leftEyePose.pos, leftEyeRotation = leftEyePose.rot, rightEyePosition = rightEyePose.pos, rightEyeRotation = rightEyePose.rot, centerEyePosition = deviceState.position, centerEyeRotation = deviceState.rotation, }); } } [StructLayout(LayoutKind.Explicit, Size = kSizeInBytes)] public struct VIUSyntheticXRControllerState : IInputStateTypeInfo { public const int kSizeInBytes = 36; public FourCC format => new FourCC('V', 'I', 'U', 'C'); [FieldOffset(0), InputControl(layout = "Button")] public bool isTracked; [FieldOffset(4), InputControl(layout = "Integer")] public TrackingState trackingState; [FieldOffset(8), InputControl(noisy = true)] public Vector3 devicePosition; [FieldOffset(20), InputControl(noisy = true)] public Quaternion deviceRotation; } #if UNITY_EDITOR [UnityEditor.InitializeOnLoad] #endif [InputControlLayout(displayName = "VIU Synthetic XR Controller", stateType = typeof(VIUSyntheticXRControllerState), hideInUI = true)] public class VIUSyntheticXRController : XRController, IInputUpdateCallbackReceiver { private ViveInput.ICtrlState ctrlState; private static VIUSyntheticXRController rightDevice; private static VIUSyntheticXRController leftDevice; static VIUSyntheticXRController() { InputSystem.RegisterLayout(); #if UNITY_EDITOR if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) #endif { var roleMap = ViveRole.GetMap(); HandleDevice(ref rightDevice, HandRole.RightHand, CommonUsages.RightHand, VRModule.IsValidDeviceIndex(roleMap.GetMappedDeviceByRole(HandRole.RightHand))); HandleDevice(ref leftDevice, HandRole.LeftHand, CommonUsages.LeftHand, VRModule.IsValidDeviceIndex(roleMap.GetMappedDeviceByRole(HandRole.LeftHand))); roleMap.onRoleMappingChanged += (map, arg) => { if (arg.role == HandRole.RightHand) { HandleDevice(ref rightDevice, HandRole.RightHand, CommonUsages.RightHand, VRModule.IsValidDeviceIndex(arg.currentDeviceIndex)); } else if (arg.role == HandRole.LeftHand) { HandleDevice(ref leftDevice, HandRole.LeftHand, CommonUsages.LeftHand, VRModule.IsValidDeviceIndex(arg.currentDeviceIndex)); } }; } } private static void HandleDevice(ref VIUSyntheticXRController device, HandRole hand, InternedString handStr, bool connected) { try { if (connected) { if (device == null) { device = InputSystem.AddDevice("VIUSyntheticXRController" + hand.ToString()); InputSystem.AddDeviceUsage(device, handStr); device.ctrlState = ViveInput.GetState(hand); } else { InputSystem.AddDevice(device); } } else { if (device != null) { InputSystem.RemoveDevice(device); } } } catch (Exception e) { Debug.LogException(e); } } void IInputUpdateCallbackReceiver.OnUpdate() { if (ctrlState == null) { return; } var deviceIndex = ctrlState.RoleMap.GetMappedDeviceByRoleValue(ctrlState.RoleValue); var deviceState = VRModule.GetCurrentDeviceState(deviceIndex); InputSystem.QueueStateEvent(this, new VIUSyntheticXRControllerState() { isTracked = deviceState.isPoseValid, trackingState = TrackingState.Position | TrackingState.Rotation, devicePosition = deviceState.position, deviceRotation = deviceState.rotation, }); } } } #endif