//======= Copyright (c) Valve Corporation, All rights reserved. =============== // // Purpose: Adds SteamVR render support to existing camera objects // //============================================================================= using UnityEngine; using System.Collections; using System.Reflection; using Valve.VR; #if UNITY_2017_2_OR_NEWER using UnityEngine.XR; #else using XRSettings = UnityEngine.VR.VRSettings; using XRDevice = UnityEngine.VR.VRDevice; #endif namespace Valve.VR { [RequireComponent(typeof(Camera))] public class SteamVR_Camera : MonoBehaviour { [SerializeField] private Transform _head; public Transform head { get { return _head; } } public Transform offset { get { return _head; } } // legacy public Transform origin { get { return _head.parent; } } public new Camera camera { get; private set; } [SerializeField] private Transform _ears; public Transform ears { get { return _ears; } } public Ray GetRay() { return new Ray(_head.position, _head.forward); } public bool wireframe = false; #if UNITY_2017_2_OR_NEWER static public float sceneResolutionScale { get { return XRSettings.eyeTextureResolutionScale; } set { XRSettings.eyeTextureResolutionScale = value; } } #else static public float sceneResolutionScale { get { return XRSettings.renderScale; } set { if (value == 0) return; XRSettings.renderScale = value; } } #endif #region Enable / Disable void OnDisable() { SteamVR_Render.Remove(this); } void OnEnable() { // Bail if no hmd is connected var vr = SteamVR.instance; if (vr == null) { if (head != null) { head.GetComponent().enabled = false; } enabled = false; return; } // Convert camera rig for native OpenVR integration. var t = transform; if (head != t) { Expand(); t.parent = origin; while (head.childCount > 0) head.GetChild(0).parent = t; // Keep the head around, but parent to the camera now since it moves with the hmd // but existing content may still have references to this object. head.parent = t; head.localPosition = Vector3.zero; head.localRotation = Quaternion.identity; head.localScale = Vector3.one; head.gameObject.SetActive(false); _head = t; } if (ears == null) { var e = transform.GetComponentInChildren(); if (e != null) _ears = e.transform; } if (ears != null) ears.GetComponent().vrcam = this; SteamVR_Render.Add(this); } #endregion #region Functionality to ensure SteamVR_Camera component is always the last component on an object void Awake() { camera = GetComponent(); // cached to avoid runtime lookup ForceLast(); } static Hashtable values; public void ForceLast() { if (values != null) { // Restore values on new instance foreach (DictionaryEntry entry in values) { var f = entry.Key as FieldInfo; f.SetValue(this, entry.Value); } values = null; } else { // Make sure it's the last component var components = GetComponents(); // But first make sure there aren't any other SteamVR_Cameras on this object. for (int i = 0; i < components.Length; i++) { var c = components[i] as SteamVR_Camera; if (c != null && c != this) { DestroyImmediate(c); } } components = GetComponents(); if (this != components[components.Length - 1]) { // Store off values to be restored on new instance values = new Hashtable(); var fields = GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); foreach (var f in fields) if (f.IsPublic || f.IsDefined(typeof(SerializeField), true)) values[f] = f.GetValue(this); var go = gameObject; DestroyImmediate(this); go.AddComponent().ForceLast(); } } } #endregion #region Expand / Collapse object hierarchy #if UNITY_EDITOR public bool isExpanded { get { return head != null && transform.parent == head; } } #endif const string eyeSuffix = " (eye)"; const string earsSuffix = " (ears)"; const string headSuffix = " (head)"; const string originSuffix = " (origin)"; public string baseName { get { return name.EndsWith(eyeSuffix) ? name.Substring(0, name.Length - eyeSuffix.Length) : name; } } // Object hierarchy creation to make it easy to parent other objects appropriately, // otherwise this gets called on demand at runtime. Remaining initialization is // performed at startup, once the hmd has been identified. public void Expand() { var _origin = transform.parent; if (_origin == null) { _origin = new GameObject(name + originSuffix).transform; _origin.localPosition = transform.localPosition; _origin.localRotation = transform.localRotation; _origin.localScale = transform.localScale; } if (head == null) { _head = new GameObject(name + headSuffix, typeof(SteamVR_TrackedObject)).transform; head.parent = _origin; head.position = transform.position; head.rotation = transform.rotation; head.localScale = Vector3.one; head.tag = tag; } if (transform.parent != head) { transform.parent = head; transform.localPosition = Vector3.zero; transform.localRotation = Quaternion.identity; transform.localScale = Vector3.one; while (transform.childCount > 0) transform.GetChild(0).parent = head; #if !UNITY_2017_2_OR_NEWER var guiLayer = GetComponent(); if (guiLayer != null) { DestroyImmediate(guiLayer); head.gameObject.AddComponent(); } #endif var audioListener = GetComponent(); if (audioListener != null) { DestroyImmediate(audioListener); _ears = new GameObject(name + earsSuffix, typeof(SteamVR_Ears)).transform; ears.parent = _head; ears.localPosition = Vector3.zero; ears.localRotation = Quaternion.identity; ears.localScale = Vector3.one; } } if (!name.EndsWith(eyeSuffix)) name += eyeSuffix; } public void Collapse() { transform.parent = null; // Move children and components from head back to camera. while (head.childCount > 0) head.GetChild(0).parent = transform; #if !UNITY_2017_2_OR_NEWER var guiLayer = head.GetComponent(); if (guiLayer != null) { DestroyImmediate(guiLayer); gameObject.AddComponent(); } #endif if (ears != null) { while (ears.childCount > 0) ears.GetChild(0).parent = transform; DestroyImmediate(ears.gameObject); _ears = null; gameObject.AddComponent(typeof(AudioListener)); } if (origin != null) { // If we created the origin originally, destroy it now. if (origin.name.EndsWith(originSuffix)) { // Reparent any children so we don't accidentally delete them. var _origin = origin; while (_origin.childCount > 0) _origin.GetChild(0).parent = _origin.parent; DestroyImmediate(_origin.gameObject); } else { transform.parent = origin; } } DestroyImmediate(head.gameObject); _head = null; if (name.EndsWith(eyeSuffix)) name = name.Substring(0, name.Length - eyeSuffix.Length); } #endregion } }