/****************************************************************************** * Copyright (C) Ultraleap, Inc. 2011-2021. * * * * Use subject to the terms of the Apache License 2.0 available at * * http://www.apache.org/licenses/LICENSE-2.0, or another agreement * * between Ultraleap and you, your company or other organization. * ******************************************************************************/ using System.Collections.Generic; using UnityEngine; #if XR_MANAGEMENT_AVAILABLE using UnityEngine.XR.Management; #endif #if UNITY_2017_2_OR_NEWER using UnityEngine.XR; #else using UnityEngine.VR; #endif namespace Leap.Unity { /// /// Wraps various (but not all) "XR" calls with Unity 5.6-supporting "VR" calls /// via #ifdefs. /// public static class XRSupportUtil { #if UNITY_2019_2_OR_NEWER private static System.Collections.Generic.List nodeStates = new System.Collections.Generic.List(); #endif #if UNITY_2020_1_OR_NEWER static List _boundaryPoints = new List(); #endif public static bool IsXREnabled() { #if SVR if (SvrManager.Instance != null) { return SvrManager.Instance.Initialized; } #endif #if XR_MANAGEMENT_AVAILABLE return XRGeneralSettings.Instance.Manager != null; #elif UNITY_2017_2_OR_NEWER return XRSettings.enabled; #else return VRSettings.enabled; #endif } public static bool IsXRDevicePresent() { #if SVR if (SvrManager.Instance != null) { return SvrManager.Instance.status.running; } #endif #if XR_MANAGEMENT_AVAILABLE return XRGeneralSettings.Instance.Manager.activeLoader != null; #elif UNITY_2020_1_OR_NEWER return XRSettings.isDeviceActive; #elif UNITY_2017_2_OR_NEWER return XRDevice.isPresent; #else return VRDevice.isPresent; #endif } static bool outputPresenceWarning = false; public static bool IsUserPresent(bool defaultPresence = true) { #if SVR if (SvrManager.Instance != null) { return true; // TODO user presence can be queried with recent versions of the SVR SDK (>= v4.0.4) - a proximity detector is fitted to some devices. } #endif #if UNITY_2019_3_OR_NEWER var devices = new List(); InputDevices.GetDevicesWithCharacteristics(InputDeviceCharacteristics.HeadMounted, devices); if (devices.Count == 0 && !outputPresenceWarning) { Debug.LogWarning("No head-mounted devices found. Possibly no HMD is available to the XR system."); outputPresenceWarning = true; } if (devices.Count != 0) { var device = devices[0]; if (device.TryGetFeatureValue(CommonUsages.userPresence, out var userPresent)) { return userPresent; } } #elif UNITY_2017_2_OR_NEWER var userPresence = XRDevice.userPresence; if (userPresence == UserPresenceState.Present) { return true; } else if (!outputPresenceWarning && userPresence == UserPresenceState.Unsupported) { Debug.LogWarning("XR UserPresenceState unsupported (XR support is probably disabled)."); outputPresenceWarning = true; } #else if (!outputPresenceWarning) { Debug.LogWarning("XR UserPresenceState is only supported in 2017.2 and newer."); outputPresenceWarning = true; } #endif return defaultPresence; } public static Vector3 GetXRNodeCenterEyeLocalPosition() { #if SVR if (SvrManager.Instance != null) { if (XRSupportUtil.IsXREnabled()) { return SvrManager.Instance.head.transform.localPosition; } else { Debug.LogWarning("Cannot read XRNodeCenterEyeLocalPosition as XR is not enabled"); return Vector3.zero; } } #endif #if UNITY_2019_2_OR_NEWER InputTracking.GetNodeStates(nodeStates); Vector3 position; foreach (XRNodeState state in nodeStates) { if (state.nodeType == XRNode.CenterEye && state.TryGetPosition(out position)) { return position; } } return Vector3.zero; #elif UNITY_2017_2_OR_NEWER return InputTracking.GetLocalPosition(XRNode.CenterEye); #else return InputTracking.GetLocalPosition(VRNode.CenterEye); #endif } public static Quaternion GetXRNodeCenterEyeLocalRotation() { #if SVR if (SvrManager.Instance != null) { if (XRSupportUtil.IsXREnabled()) { return SvrManager.Instance.head.transform.localRotation; } else { Debug.LogWarning("Cannot read XRNodeCenterEyeLocalRotation as XR is not enabled"); return Quaternion.identity; } } #endif #if UNITY_2019_2_OR_NEWER InputTracking.GetNodeStates(nodeStates); Quaternion rotation; foreach (XRNodeState state in nodeStates) { if (state.nodeType == XRNode.CenterEye && state.TryGetRotation(out rotation)) { return rotation; } } return Quaternion.identity; #elif UNITY_2017_2_OR_NEWER return InputTracking.GetLocalRotation(XRNode.CenterEye); #else return InputTracking.GetLocalRotation(VRNode.CenterEye); #endif } public static Vector3 GetXRNodeHeadLocalPosition() { #if SVR if (SvrManager.Instance != null) { if (XRSupportUtil.IsXREnabled()) { return SvrManager.Instance.head.localPosition; } else { Debug.LogWarning("Cannot read XRNodeHeadLocalPosition as XR is not enabled"); return Vector3.zero; } } #endif #if UNITY_2019_2_OR_NEWER InputTracking.GetNodeStates(nodeStates); Vector3 position; foreach (XRNodeState state in nodeStates) { if (state.nodeType == XRNode.Head && state.TryGetPosition(out position)) { return position; } } return Vector3.zero; #elif UNITY_2017_2_OR_NEWER return InputTracking.GetLocalPosition(XRNode.Head); #else return InputTracking.GetLocalPosition(VRNode.Head); #endif } public static Quaternion GetXRNodeHeadLocalRotation() { #if SVR if (SvrManager.Instance != null) { if (XRSupportUtil.IsXREnabled()) { return SvrManager.Instance.head.transform.localRotation; } else { Debug.LogWarning("Cannot read XRNodeHeadLocalRotation as XR is not enabled"); return Quaternion.identity; } } #endif #if UNITY_2019_2_OR_NEWER InputTracking.GetNodeStates(nodeStates); Quaternion rotation; foreach (XRNodeState state in nodeStates) { if (state.nodeType == XRNode.Head && state.TryGetRotation(out rotation)) { return rotation; } } return Quaternion.identity; #elif UNITY_2017_2_OR_NEWER return InputTracking.GetLocalRotation(XRNode.Head); #else return InputTracking.GetLocalRotation(VRNode.Head); #endif } public static Vector3 GetXRNodeLocalPosition(int node) { #if SVR if (SvrManager.Instance != null) { Debug.LogWarning("Not implemented yet"); return Vector3.zero; } #endif #if UNITY_2019_2_OR_NEWER InputTracking.GetNodeStates(nodeStates); Vector3 position; foreach (XRNodeState state in nodeStates) { if (state.nodeType == (XRNode)node && state.TryGetPosition(out position)) { return position; } } return Vector3.zero; #elif UNITY_2017_2_OR_NEWER return InputTracking.GetLocalPosition((XRNode)node); #else return InputTracking.GetLocalPosition((VRNode)node); #endif } public static Quaternion GetXRNodeLocalRotation(int node) { #if SVR if (SvrManager.Instance != null) { Debug.LogWarning("Not implemented yet"); return Quaternion.identity; } #endif #if UNITY_2019_2_OR_NEWER InputTracking.GetNodeStates(nodeStates); Quaternion rotation; foreach (XRNodeState state in nodeStates) { if (state.nodeType == (XRNode)node && state.TryGetRotation(out rotation)) { return rotation; } } return Quaternion.identity; #elif UNITY_2017_2_OR_NEWER return InputTracking.GetLocalRotation((XRNode)node); #else return InputTracking.GetLocalRotation((VRNode)node); #endif } public static void Recenter() { #if SVR if (SvrInput.Instance != null) { SvrInput.Instance.HandleRecenter(); return; } #endif #if UNITY_2019_3_OR_NEWER var devices = new List(); InputDevices.GetDevicesWithCharacteristics(InputDeviceCharacteristics.HeadMounted, devices); if (devices.Count == 0) return; var hmdDevice = devices[0]; #if !UNITY_2020_1_OR_NEWER if (hmdDevice.subsystem != null) { #endif hmdDevice.subsystem.TryRecenter(); #if !UNITY_2020_1_OR_NEWER } else { #pragma warning disable 0618 InputTracking.Recenter(); #pragma warning restore 0618 } #endif #else InputTracking.Recenter(); #endif } public static float GetGPUTime() { #if SVR if (SvrManager.Instance != null) { Debug.LogWarning("Not implemented for android"); return 0; } #endif float gpuTime = 0f; #if UNITY_5_6_OR_NEWER #if UNITY_2017_2_OR_NEWER UnityEngine.XR.XRStats.TryGetGPUTimeLastFrame(out gpuTime); #else UnityEngine.VR.VRStats.TryGetGPUTimeLastFrame(out gpuTime); #endif #else gpuTime = UnityEngine.VR.VRStats.gpuTimeLastFrame; #endif return gpuTime; } public static string GetLoadedDeviceName() { #if SVR if (SvrManager.Instance != null) { return "XR2_Device"; } #endif #if UNITY_2017_2_OR_NEWER return XRSettings.loadedDeviceName; #else return VRSettings.loadedDeviceName; #endif } /// Returns whether there's a floor available. public static bool IsRoomScale() { #if SVR if (SvrManager.Instance != null) { return true; } #endif #if UNITY_2019_3_OR_NEWER var devices = new List(); InputDevices.GetDevicesWithCharacteristics(InputDeviceCharacteristics.HeadMounted, devices); if (devices.Count == 0) { return false; } var hmdDevice = devices[0]; #if !UNITY_2020_1_OR_NEWER if (hmdDevice.subsystem != null) { #endif return hmdDevice.subsystem.GetTrackingOriginMode().HasFlag(TrackingOriginModeFlags.Floor); #if !UNITY_2020_1_OR_NEWER } else { #pragma warning disable 0618 return XRDevice.GetTrackingSpaceType() == TrackingSpaceType.RoomScale; #pragma warning restore 0618 } #endif #elif UNITY_2017_2_OR_NEWER return XRDevice.GetTrackingSpaceType() == TrackingSpaceType.RoomScale; #else return VRDevice.GetTrackingSpaceType() == TrackingSpaceType.RoomScale; #endif } /// Returns whether the playspace is larger than 1m on its shortest side. public static bool IsLargePlayspace() { #if SVR if (SvrManager.Instance != null) { // The current SVR support for reading the playspace dimensions is crude. Likely to be improved on by 3rd parties // By default, we should consider the current playspace as 'large' return true; } #endif #if UNITY_2020_1_OR_NEWER // Oculus reports a floor centered space now... var devices = new List(); InputDevices.GetDevicesWithCharacteristics(InputDeviceCharacteristics.HeadMounted, devices); if (devices.Count == 0) { return false; } var hmdDevice = devices[0]; hmdDevice.subsystem.TryGetBoundaryPoints(_boundaryPoints); Bounds playspaceSize = new Bounds(); foreach (Vector3 boundaryPoint in _boundaryPoints) { playspaceSize.Encapsulate(boundaryPoint); } return playspaceSize.size.magnitude > 1f; // Playspace is greater than 1m on its shortest axis #else return IsRoomScale(); #endif } } }