//======= Copyright (c) Valve Corporation, All rights reserved. =============== // // Purpose: Access to SteamVR system (hmd) and compositor (distort) interfaces. // //============================================================================= using UnityEngine; using Valve.VR; public class SteamVR : System.IDisposable { // Use this to check if SteamVR is currently active without attempting // to activate it in the process. public static bool active { get { return _instance != null; } } // Set this to false to keep from auto-initializing when calling SteamVR.instance. private static bool _enabled = true; public static bool enabled { get { if (!UnityEngine.XR.XRSettings.enabled) enabled = false; return _enabled; } set { _enabled = value; if (!_enabled) SafeDispose(); } } private static SteamVR _instance; public static SteamVR instance { get { #if UNITY_EDITOR if (!Application.isPlaying) return null; #endif if (!enabled) return null; if (_instance == null) { _instance = CreateInstance(); // If init failed, then auto-disable so scripts don't continue trying to re-initialize things. if (_instance == null) _enabled = false; } return _instance; } } public static bool usingNativeSupport { get { return UnityEngine.XR.XRDevice.GetNativePtr() != System.IntPtr.Zero; } } static SteamVR CreateInstance() { try { var error = EVRInitError.None; if (!SteamVR.usingNativeSupport) { Debug.Log("OpenVR initialization failed. Ensure 'Virtual Reality Supported' is checked in Player Settings, and OpenVR is added to the list of Virtual Reality SDKs."); return null; } // Verify common interfaces are valid. OpenVR.GetGenericInterface(OpenVR.IVRCompositor_Version, ref error); if (error != EVRInitError.None) { ReportError(error); return null; } OpenVR.GetGenericInterface(OpenVR.IVROverlay_Version, ref error); if (error != EVRInitError.None) { ReportError(error); return null; } } catch (System.Exception e) { Debug.LogError(e); return null; } return new SteamVR(); } static void ReportError(EVRInitError error) { switch (error) { case EVRInitError.None: break; case EVRInitError.VendorSpecific_UnableToConnectToOculusRuntime: Debug.Log("SteamVR Initialization Failed! Make sure device is on, Oculus runtime is installed, and OVRService_*.exe is running."); break; case EVRInitError.Init_VRClientDLLNotFound: Debug.Log("SteamVR drivers not found! They can be installed via Steam under Library > Tools. Visit http://steampowered.com to install Steam."); break; case EVRInitError.Driver_RuntimeOutOfDate: Debug.Log("SteamVR Initialization Failed! Make sure device's runtime is up to date."); break; default: Debug.Log(OpenVR.GetStringForHmdError(error)); break; } } // native interfaces public CVRSystem hmd { get; private set; } public CVRCompositor compositor { get; private set; } public CVROverlay overlay { get; private set; } // tracking status static public bool initializing { get; private set; } static public bool calibrating { get; private set; } static public bool outOfRange { get; private set; } static public bool[] connected = new bool[OpenVR.k_unMaxTrackedDeviceCount]; // render values public float sceneWidth { get; private set; } public float sceneHeight { get; private set; } public float aspect { get; private set; } public float fieldOfView { get; private set; } public Vector2 tanHalfFov { get; private set; } public VRTextureBounds_t[] textureBounds { get; private set; } public SteamVR_Utils.RigidTransform[] eyes { get; private set; } public ETextureType textureType; // hmd properties public string hmd_TrackingSystemName { get { return GetStringProperty(ETrackedDeviceProperty.Prop_TrackingSystemName_String); } } public string hmd_ModelNumber { get { return GetStringProperty(ETrackedDeviceProperty.Prop_ModelNumber_String); } } public string hmd_SerialNumber { get { return GetStringProperty(ETrackedDeviceProperty.Prop_SerialNumber_String); } } public float hmd_SecondsFromVsyncToPhotons { get { return GetFloatProperty(ETrackedDeviceProperty.Prop_SecondsFromVsyncToPhotons_Float); } } public float hmd_DisplayFrequency { get { return GetFloatProperty(ETrackedDeviceProperty.Prop_DisplayFrequency_Float); } } public string GetTrackedDeviceString(uint deviceId) { var error = ETrackedPropertyError.TrackedProp_Success; var capacity = hmd.GetStringTrackedDeviceProperty(deviceId, ETrackedDeviceProperty.Prop_AttachedDeviceId_String, null, 0, ref error); if (capacity > 1) { var result = new System.Text.StringBuilder((int)capacity); hmd.GetStringTrackedDeviceProperty(deviceId, ETrackedDeviceProperty.Prop_AttachedDeviceId_String, result, capacity, ref error); return result.ToString(); } return null; } public string GetStringProperty(ETrackedDeviceProperty prop, uint deviceId = OpenVR.k_unTrackedDeviceIndex_Hmd) { var error = ETrackedPropertyError.TrackedProp_Success; var capactiy = hmd.GetStringTrackedDeviceProperty(deviceId, prop, null, 0, ref error); if (capactiy > 1) { var result = new System.Text.StringBuilder((int)capactiy); hmd.GetStringTrackedDeviceProperty(deviceId, prop, result, capactiy, ref error); return result.ToString(); } return (error != ETrackedPropertyError.TrackedProp_Success) ? error.ToString() : ""; } public float GetFloatProperty(ETrackedDeviceProperty prop, uint deviceId = OpenVR.k_unTrackedDeviceIndex_Hmd) { var error = ETrackedPropertyError.TrackedProp_Success; return hmd.GetFloatTrackedDeviceProperty(deviceId, prop, ref error); } #region Event callbacks private void OnInitializing(bool initializing) { SteamVR.initializing = initializing; } private void OnCalibrating(bool calibrating) { SteamVR.calibrating = calibrating; } private void OnOutOfRange(bool outOfRange) { SteamVR.outOfRange = outOfRange; } private void OnDeviceConnected(int i, bool connected) { SteamVR.connected[i] = connected; } private void OnNewPoses(TrackedDevicePose_t[] poses) { // Update eye offsets to account for IPD changes. eyes[0] = new SteamVR_Utils.RigidTransform(hmd.GetEyeToHeadTransform(EVREye.Eye_Left)); eyes[1] = new SteamVR_Utils.RigidTransform(hmd.GetEyeToHeadTransform(EVREye.Eye_Right)); for (int i = 0; i < poses.Length; i++) { var connected = poses[i].bDeviceIsConnected; if (connected != SteamVR.connected[i]) { SteamVR_Events.DeviceConnected.Send(i, connected); } } if (poses.Length > OpenVR.k_unTrackedDeviceIndex_Hmd) { var result = poses[OpenVR.k_unTrackedDeviceIndex_Hmd].eTrackingResult; var initializing = result == ETrackingResult.Uninitialized; if (initializing != SteamVR.initializing) { SteamVR_Events.Initializing.Send(initializing); } var calibrating = result == ETrackingResult.Calibrating_InProgress || result == ETrackingResult.Calibrating_OutOfRange; if (calibrating != SteamVR.calibrating) { SteamVR_Events.Calibrating.Send(calibrating); } var outOfRange = result == ETrackingResult.Running_OutOfRange || result == ETrackingResult.Calibrating_OutOfRange; if (outOfRange != SteamVR.outOfRange) { SteamVR_Events.OutOfRange.Send(outOfRange); } } } #endregion private SteamVR() { hmd = OpenVR.System; Debug.Log("Connected to " + hmd_TrackingSystemName + ":" + hmd_SerialNumber); compositor = OpenVR.Compositor; overlay = OpenVR.Overlay; // Setup render values uint w = 0, h = 0; hmd.GetRecommendedRenderTargetSize(ref w, ref h); sceneWidth = (float)w; sceneHeight = (float)h; float l_left = 0.0f, l_right = 0.0f, l_top = 0.0f, l_bottom = 0.0f; hmd.GetProjectionRaw(EVREye.Eye_Left, ref l_left, ref l_right, ref l_top, ref l_bottom); float r_left = 0.0f, r_right = 0.0f, r_top = 0.0f, r_bottom = 0.0f; hmd.GetProjectionRaw(EVREye.Eye_Right, ref r_left, ref r_right, ref r_top, ref r_bottom); tanHalfFov = new Vector2( Mathf.Max(-l_left, l_right, -r_left, r_right), Mathf.Max(-l_top, l_bottom, -r_top, r_bottom)); textureBounds = new VRTextureBounds_t[2]; textureBounds[0].uMin = 0.5f + 0.5f * l_left / tanHalfFov.x; textureBounds[0].uMax = 0.5f + 0.5f * l_right / tanHalfFov.x; textureBounds[0].vMin = 0.5f - 0.5f * l_bottom / tanHalfFov.y; textureBounds[0].vMax = 0.5f - 0.5f * l_top / tanHalfFov.y; textureBounds[1].uMin = 0.5f + 0.5f * r_left / tanHalfFov.x; textureBounds[1].uMax = 0.5f + 0.5f * r_right / tanHalfFov.x; textureBounds[1].vMin = 0.5f - 0.5f * r_bottom / tanHalfFov.y; textureBounds[1].vMax = 0.5f - 0.5f * r_top / tanHalfFov.y; // Grow the recommended size to account for the overlapping fov sceneWidth = sceneWidth / Mathf.Max(textureBounds[0].uMax - textureBounds[0].uMin, textureBounds[1].uMax - textureBounds[1].uMin); sceneHeight = sceneHeight / Mathf.Max(textureBounds[0].vMax - textureBounds[0].vMin, textureBounds[1].vMax - textureBounds[1].vMin); aspect = tanHalfFov.x / tanHalfFov.y; fieldOfView = 2.0f * Mathf.Atan(tanHalfFov.y) * Mathf.Rad2Deg; eyes = new SteamVR_Utils.RigidTransform[] { new SteamVR_Utils.RigidTransform(hmd.GetEyeToHeadTransform(EVREye.Eye_Left)), new SteamVR_Utils.RigidTransform(hmd.GetEyeToHeadTransform(EVREye.Eye_Right)) }; switch (SystemInfo.graphicsDeviceType) { #if (UNITY_5_4) case UnityEngine.Rendering.GraphicsDeviceType.OpenGL2: #endif case UnityEngine.Rendering.GraphicsDeviceType.OpenGLCore: case UnityEngine.Rendering.GraphicsDeviceType.OpenGLES2: case UnityEngine.Rendering.GraphicsDeviceType.OpenGLES3: textureType = ETextureType.OpenGL; break; #if !(UNITY_5_4) case UnityEngine.Rendering.GraphicsDeviceType.Vulkan: textureType = ETextureType.Vulkan; break; #endif default: textureType = ETextureType.DirectX; break; } SteamVR_Events.Initializing.Listen(OnInitializing); SteamVR_Events.Calibrating.Listen(OnCalibrating); SteamVR_Events.OutOfRange.Listen(OnOutOfRange); SteamVR_Events.DeviceConnected.Listen(OnDeviceConnected); SteamVR_Events.NewPoses.Listen(OnNewPoses); } ~SteamVR() { Dispose(false); } public void Dispose() { Dispose(true); System.GC.SuppressFinalize(this); } private void Dispose(bool disposing) { SteamVR_Events.Initializing.Remove(OnInitializing); SteamVR_Events.Calibrating.Remove(OnCalibrating); SteamVR_Events.OutOfRange.Remove(OnOutOfRange); SteamVR_Events.DeviceConnected.Remove(OnDeviceConnected); SteamVR_Events.NewPoses.Remove(OnNewPoses); _instance = null; } // Use this interface to avoid accidentally creating the instance in the process of attempting to dispose of it. public static void SafeDispose() { if (_instance != null) _instance.Dispose(); } }