#if AR_FOUNDATION_5_2_OR_NEWER && (UNITY_EDITOR || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX) #define XR_SIMULATION_AVAILABLE #endif using System; using UnityEngine.Assertions; using UnityEngine.InputSystem; using UnityEngine.InputSystem.LowLevel; using UnityEngine.XR.Interaction.Toolkit.Inputs.Simulation.Hands; using UnityEngine.XR.Interaction.Toolkit.Utilities; #if XR_SIMULATION_AVAILABLE #if XR_MANAGEMENT_4_0_OR_NEWER using UnityEngine.XR.Management; #endif using UnityEngine.XR.Simulation; #endif namespace UnityEngine.XR.Interaction.Toolkit.Inputs.Simulation { /// /// The target device or devices to update from input. /// /// /// to support updating multiple controls from one input /// (e.g. to drive a controller and the head from the same input). /// [Flags] public enum TargetedDevices { /// /// No target device to update. /// None = 0, /// /// No target device, behaving as an FPS controller. /// FPS = 1 << 0, /// /// Update left controller or hand position and rotation. /// LeftDevice = 1 << 1, /// /// Update right controller or hand position and rotation. /// RightDevice = 1 << 2, /// /// Update HMD position and rotation. /// HMD = 1 << 3, } /// /// The target device control(s) to update from input. /// /// /// to support updating multiple controls from input /// (e.g. to drive the primary and secondary 2D axis on a controller from the same input). /// /// [Flags] public enum Axis2DTargets { /// /// Do not update device state from input. /// None = 0, /// /// Update device position from input. /// Position = 1 << 0, /// /// Update the primary touchpad or joystick on a controller device from input. /// Primary2DAxis = 1 << 1, /// /// Update the secondary touchpad or joystick on a controller device from input. /// Secondary2DAxis = 1 << 2, } /// /// The device input mode of the left and right controller. /// public enum ControllerInputMode { /// /// No current active mode. /// None, /// /// Mode for simulating controller trigger input. /// Trigger, /// /// Mode for simulating controller grip input. /// Grip, /// /// Mode for simulating controller primary button input. /// PrimaryButton, /// /// Mode for simulating controller secondary button input. /// SecondaryButton, /// /// Mode for simulating controller menu button input. /// Menu, /// /// Mode for simulating controller primary axis 2D click input. /// Primary2DAxisClick, /// /// Mode for simulating controller secondary axis 2D click input. /// Secondary2DAxisClick, /// /// Mode for simulating controller primary axis 2D touch input. /// Primary2DAxisTouch, /// /// Mode for simulating controller secondary axis 2D touch input. /// Secondary2DAxisTouch, /// /// Mode for simulating controller primary touch input. /// PrimaryTouch, /// /// Mode for simulating controller secondary touch input. /// SecondaryTouch, } /// /// The coordinate space in which to operate. /// public enum Space { /// /// Applies translations of a controller or HMD relative to its own coordinate space, considering its own rotations. /// Will translate a controller relative to itself, independent of the camera. /// Local, /// /// Applies translations of a controller or HMD relative to its parent. If the object does not have a parent, meaning /// it is a root object, the parent coordinate space is the same as the world coordinate space. This is the same /// as but without considering its own rotations. /// Parent, /// /// Applies translations of a controller or HMD relative to the screen. /// Will translate a controller relative to the camera, independent of the controller's orientation. /// Screen, } /// /// Utility functions for simulation classes. /// static class XRSimulatorUtility { /// /// The maximum angle the XR Camera can have around the x-axis. /// internal static readonly float cameraMaxXAngle = 80f; internal static readonly Vector3 leftDeviceDefaultInitialPosition = new Vector3(-0.1f, -0.05f, 0.3f); internal static readonly Vector3 rightDeviceDefaultInitialPosition = new Vector3(0.1f, -0.05f, 0.3f); internal static SimulatedDeviceLifecycleManager FindCreateSimulatedDeviceLifecycleManager(GameObject simulator) { if (ComponentLocatorUtility.TryFindComponent(out var simulatedDeviceLifecycleManager)) { return simulatedDeviceLifecycleManager; } else { simulatedDeviceLifecycleManager = simulator.AddComponent(); return simulatedDeviceLifecycleManager; } } internal static SimulatedHandExpressionManager FindCreateSimulatedHandExpressionManager(GameObject simulator) { if (ComponentLocatorUtility.TryFindComponent(out var simulatedHandExpressionManager)) { return simulatedHandExpressionManager; } else { simulatedHandExpressionManager = simulator.AddComponent(); return simulatedHandExpressionManager; } } internal static void Subscribe(InputActionReference reference, Action performed = null, Action canceled = null) { var action = GetInputAction(reference); if (action != null) { if (performed != null) action.performed += performed; if (canceled != null) action.canceled += canceled; } } internal static void Unsubscribe(InputActionReference reference, Action performed = null, Action canceled = null) { var action = GetInputAction(reference); if (action != null) { if (performed != null) action.performed -= performed; if (canceled != null) action.canceled -= canceled; } } static InputAction GetInputAction(InputActionReference actionReference) { #pragma warning disable IDE0031 // Use null propagation -- Do not use for UnityEngine.Object types return actionReference != null ? actionReference.action : null; #pragma warning restore IDE0031 } #if ENABLE_VR || UNITY_GAMECORE internal static Quaternion GetDeltaRotation(Space translateSpace, in XRSimulatedControllerState state, in Quaternion inverseCameraParentRotation) => GetDeltaRotation(translateSpace, state.deviceRotation, inverseCameraParentRotation); internal static Quaternion GetDeltaRotation(Space translateSpace, in XRSimulatedHandState state, in Quaternion inverseCameraParentRotation) => GetDeltaRotation(translateSpace, state.rotation, inverseCameraParentRotation); internal static Quaternion GetDeltaRotation(Space translateSpace, in XRSimulatedHMDState state, in Quaternion inverseCameraParentRotation) => GetDeltaRotation(translateSpace, state.centerEyeRotation, inverseCameraParentRotation); internal static void GetAxes(Space translateSpace, Transform cameraTransform, out Vector3 right, out Vector3 up, out Vector3 forward) { if (cameraTransform == null) throw new ArgumentNullException(nameof(cameraTransform)); switch (translateSpace) { case Space.Local: // Makes the assumption that the Camera and the Controllers are siblings // (meaning they share a parent GameObject). var cameraParent = cameraTransform.parent; if (cameraParent != null) { right = cameraParent.TransformDirection(Vector3.right); up = cameraParent.TransformDirection(Vector3.up); forward = cameraParent.TransformDirection(Vector3.forward); } else { right = Vector3.right; up = Vector3.up; forward = Vector3.forward; } break; case Space.Parent: right = Vector3.right; up = Vector3.up; forward = Vector3.forward; break; case Space.Screen: right = cameraTransform.TransformDirection(Vector3.right); up = cameraTransform.TransformDirection(Vector3.up); forward = cameraTransform.TransformDirection(Vector3.forward); break; default: right = Vector3.right; up = Vector3.up; forward = Vector3.forward; Assert.IsTrue(false, $"Unhandled {nameof(translateSpace)}={translateSpace}."); return; } } internal static Quaternion GetDeltaRotation(Space translateSpace, Quaternion rotation, in Quaternion inverseCameraParentRotation) { switch (translateSpace) { case Space.Local: return rotation * inverseCameraParentRotation; case Space.Parent: return Quaternion.identity; case Space.Screen: return inverseCameraParentRotation; default: Assert.IsTrue(false, $"Unhandled {nameof(translateSpace)}={translateSpace}."); return Quaternion.identity; } } #endif #if XR_SIMULATION_AVAILABLE && XR_MANAGEMENT_4_0_OR_NEWER internal static bool XRSimulationLoaderEnabledForEditorPlayMode() { if (XRGeneralSettings.Instance != null && XRGeneralSettings.Instance.Manager != null) return XRGeneralSettings.Instance.Manager.activeLoader is SimulationLoader simulationLoader && simulationLoader != null; return false; } #endif /// /// Finds and sets camera transform if necessary. /// /// Returns if the camera reference is valid. Otherwise, returns . internal static bool FindCameraTransform(ref (Transform transform, Camera camera) cachedCamera, ref Transform cameraTransform) { // Sync the cache tuple if necessary if (cachedCamera.transform != cameraTransform) cachedCamera = (cameraTransform, cameraTransform != null ? cameraTransform.GetComponent() : null); // Camera.main returns the first active and enabled main camera, so if the cached one // is no longer enabled, find the new main camera. This is to support, for example, // toggling between different XROrigin rigs each with their own main camera. if (cachedCamera.transform == null || (cachedCamera.camera != null && !cachedCamera.camera.isActiveAndEnabled)) { var mainCamera = Camera.main; if (mainCamera == null) return false; cameraTransform = mainCamera.transform; cachedCamera = (cameraTransform, cameraTransform.GetComponent()); } return true; } internal static unsafe bool TryExecuteCommand(InputDeviceCommand* commandPtr, out long result) { // This is a utility method called by XRSimulatedHMD and XRSimulatedController // since both devices share the same command handling. // This replicates the logic in XRToISXDevice::IOCTL (XRInputToISX.cpp). var type = commandPtr->type; if (type == RequestSyncCommand.Type) { // The state is maintained by XRDeviceSimulator, so no need for any change // when focus is regained. Returning success instructs Input System to not // reset the device. result = InputDeviceCommand.GenericSuccess; return true; } if (type == QueryCanRunInBackground.Type) { ((QueryCanRunInBackground*)commandPtr)->canRunInBackground = true; result = InputDeviceCommand.GenericSuccess; return true; } result = default; return false; } internal static Vector3 GetTranslationInDeviceSpace(float xTranslateInput, float yTranslateInput, float zTranslateInput, Transform cameraTransform, Quaternion cameraParentRotation, Quaternion inverseCameraParentRotation) { var translationInWorldSpace = GetTranslationInWorldSpace(xTranslateInput, yTranslateInput, zTranslateInput, cameraTransform, cameraParentRotation); var translationInDeviceSpace = inverseCameraParentRotation * translationInWorldSpace; return translationInDeviceSpace; } internal static Vector3 GetTranslationInWorldSpace(float xTranslateInput, float yTranslateInput, float zTranslateInput, Transform cameraTransform, Quaternion cameraParentRotation) { var scaledKeyboardTranslateInput = new Vector3( xTranslateInput, yTranslateInput, zTranslateInput); var forward = cameraTransform.forward; var cameraParentUp = cameraParentRotation * Vector3.up; if (Mathf.Approximately(Mathf.Abs(Vector3.Dot(forward, cameraParentUp)), 1f)) { forward = -cameraTransform.up; } var forwardProjected = Vector3.ProjectOnPlane(forward, cameraParentUp); var forwardRotation = Quaternion.LookRotation(forwardProjected, cameraParentUp); var translationInWorldSpace = forwardRotation * scaledKeyboardTranslateInput; return translationInWorldSpace; } } /// /// Extension methods for . /// static class TargetedDeviceExtensions { /// /// Returns the flags enum with the given flag set. /// /// The flags enum instance. /// The flag to also set in the returned instance. /// Returns the flags enum with the given flag set. public static TargetedDevices WithDevice(this TargetedDevices devices, TargetedDevices device) { return devices | device; } /// /// Returns the flags enum with the given flag not set. /// /// The flags enum instance. /// The flag to clear in the returned instance. /// Returns the flags enum with the given flag not set. public static TargetedDevices WithoutDevice(this TargetedDevices devices, TargetedDevices device) { return devices & ~device; } /// /// Determines whether one or more bit fields are set in the flags /// Non-boxing version of HasFlag for . /// /// The flags enum instance. /// The flag to check if set. /// Returns if the bit field or bit fields are set, otherwise returns . public static bool HasDevice(this TargetedDevices devices, TargetedDevices device) { return (devices & device) == device; } } }