/// OSVR-Unity Connection /// /// http://sensics.com/osvr /// /// /// Copyright 2014 Sensics, Inc. /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// using UnityEngine; using System.Collections; namespace OSVR { namespace Unity { [RequireComponent(typeof(Camera))] public class VRViewer : MonoBehaviour { // quick hack to show visual on either eye public enum EYE_DIRECTION { EYE_LEFT, EYE_RIGHT, EYE_BOTH }; public EYE_DIRECTION EyeDirection = EYE_DIRECTION.EYE_LEFT; private int __eyeIndex, __eyeCount; #region Public Variables public DisplayController DisplayController { get { return _displayController; } set { _displayController = value; } } public VREye[] Eyes { get { return _eyes; } } public uint EyeCount { get { return _eyeCount; } } public uint ViewerIndex { get { return _viewerIndex; } set { _viewerIndex = value; } } [HideInInspector] public Transform cachedTransform; public Camera Camera { get { if (_camera == null) { _camera = GetComponent(); } return _camera; } set { _camera = value; } } #endregion #region Private Variables private DisplayController _displayController; private VREye[] _eyes; private uint _eyeCount; private uint _viewerIndex; private Camera _camera; private bool _disabledCamera = true; private bool _hmdConnectionError = false; private Rect _emptyViewport = new Rect(0, 0, 0, 0); private IEnumerator _endOfFrameCoroutine; #endregion void Awake() { Init(); } void Init() { if (_camera == null) { _camera = GetComponent(); //cache: cachedTransform = transform; if (DisplayController == null) { DisplayController = FindObjectOfType(); } _endOfFrameCoroutine = EndOfFrame(); } } void OnEnable() { Init(); if (DisplayController != null) StartCoroutine(_endOfFrameCoroutine); } void OnDisable() { if (DisplayController != null) { StopCoroutine(_endOfFrameCoroutine); //@todo any cleanup of RenderTextures necessary here? } } //Creates the Eyes of this Viewer public void CreateEyes(uint eyeCount) { _eyeCount = eyeCount; //cache the number of eyes this viewer controls _eyes = new VREye[_eyeCount]; uint eyeIndex = 0; uint foundEyes = 0; //Check if there are already VREyes in the scene. //If so, use them instead of creating a new VREye[] eyesInScene = FindObjectsOfType(); foundEyes = (uint)eyesInScene.Length; if (eyesInScene != null && foundEyes > 0) { for (eyeIndex = 0; eyeIndex < eyesInScene.Length; eyeIndex++) { VREye eye = eyesInScene[eyeIndex]; // get the VREye gameobject GameObject eyeGameObject = eye.gameObject; eyeGameObject.name = "VREye" + eyeIndex; eye.Viewer = this; eye.EyeIndex = eyeIndex; //set the eye's index eyeGameObject.transform.parent = DisplayController.transform; //child of DisplayController eyeGameObject.transform.localPosition = Vector3.zero; eyeGameObject.transform.rotation = this.transform.rotation; _eyes[eyeIndex] = eye; uint eyeSurfaceCount = DisplayController.DisplayConfig.GetNumSurfacesForViewerEye(ViewerIndex, (byte)eyeIndex); eye.CreateSurfaces(eyeSurfaceCount); } } for (; eyeIndex < _eyeCount; eyeIndex++) { if(foundEyes == 0) { GameObject eyeGameObject = new GameObject("Eye" + eyeIndex); //add an eye gameobject to the scene VREye eye = eyeGameObject.AddComponent(); //add the VReye component eye.Viewer = this; //ASSUME THERE IS ONLY ONE VIEWER eye.EyeIndex = eyeIndex; //set the eye's index eyeGameObject.transform.parent = DisplayController.transform; //child of DisplayController eyeGameObject.transform.localPosition = Vector3.zero; eyeGameObject.transform.rotation = this.transform.rotation; _eyes[eyeIndex] = eye; //create the eye's rendering surface uint eyeSurfaceCount = DisplayController.DisplayConfig.GetNumSurfacesForViewerEye(ViewerIndex, (byte)eyeIndex); eye.CreateSurfaces(eyeSurfaceCount); } else { //if we need to create a new VREye, and there is already one in the scene (eyeIndex > 0), //duplicate the last eye found instead of creating new gameobjects GameObject eyeGameObject = (GameObject)Instantiate(_eyes[eyeIndex - 1].gameObject); VREye eye = eyeGameObject.GetComponent(); //add the VReye component eye.Viewer = this; //ASSUME THERE IS ONLY ONE VIEWER eye.EyeIndex = eyeIndex; //set the eye's index eyeGameObject.name = "VREye" + eyeIndex; eyeGameObject.transform.parent = DisplayController.transform; //child of DisplayController eyeGameObject.transform.localPosition = Vector3.zero; eyeGameObject.transform.rotation = this.transform.rotation; _eyes[eyeIndex] = eye; uint eyeSurfaceCount = DisplayController.DisplayConfig.GetNumSurfacesForViewerEye(ViewerIndex, (byte)eyeIndex); eye.CreateSurfaces(eyeSurfaceCount); } } } //Get an updated tracker position + orientation public OSVR.ClientKit.Pose3 GetViewerPose(uint viewerIndex) { return DisplayController.DisplayConfig.GetViewerPose(viewerIndex); } //Updates the position and rotation of the head public void UpdateViewerHeadPose(OSVR.ClientKit.Pose3 headPose) { cachedTransform.localPosition = Math.ConvertPosition(headPose.translation); cachedTransform.localRotation = Math.ConvertOrientation(headPose.rotation); } //Update the pose of each eye, then update and render each eye's surfaces public void UpdateEyes() { if (DisplayController.UseRenderManager) { //Update RenderInfo #if UNITY_5_2 || UNITY_5_3 || UNITY_5_4 || UNITY_5_5 GL.IssuePluginEvent(DisplayController.RenderManager.GetRenderEventFunction(), OsvrRenderManager.UPDATE_RENDERINFO_EVENT); #else Debug.LogError("[OSVR-Unity] GL.IssuePluginEvent failed. This version of Unity cannot support RenderManager."); DisplayController.UseRenderManager = false; #endif } else { DisplayController.UpdateClient(); } // try reading settings from menu, if any if (MenuController.mStimuliOption != null) { string _eyeDirection = MenuController.mStimuliOption.StimuliDirection; if (_eyeDirection.Equals("Left")) EyeDirection = EYE_DIRECTION.EYE_LEFT; else if (_eyeDirection.Equals("Right")) EyeDirection = EYE_DIRECTION.EYE_RIGHT; else if (_eyeDirection.Equals("Both")) EyeDirection = EYE_DIRECTION.EYE_BOTH; } // added to show stimuli on either eye switch (EyeDirection) { case EYE_DIRECTION.EYE_LEFT: __eyeIndex = 0; __eyeCount = 1; break; case EYE_DIRECTION.EYE_RIGHT: __eyeIndex = 1; __eyeCount = 2; break; case EYE_DIRECTION.EYE_BOTH: __eyeIndex = 0; __eyeCount = 2; break; } // for (uint eyeIndex = 0; eyeIndex < EyeCount; eyeIndex++) for ( ; __eyeIndex < __eyeCount; __eyeIndex++) { //update the eye pose VREye eye = Eyes[__eyeIndex]; if (DisplayController.UseRenderManager) { //get eye pose from RenderManager eye.UpdateEyePose(DisplayController.RenderManager.GetRenderManagerEyePose((byte)__eyeIndex)); } else { //get eye pose from DisplayConfig eye.UpdateEyePose(_displayController.DisplayConfig.GetViewerEyePose(ViewerIndex, (byte)__eyeIndex)); } // update the eye's surfaces, includes call to Render eye.UpdateSurfaces(); } } //helper method for updating the client context public void UpdateClient() { DisplayController.UpdateClient(); } // Culling determines which objects are visible to the camera. OnPreCull is called just before this process. // This gets called because we have a camera component, but we disable the camera here so it doesn't render. // We have the "dummy" camera so existing Unity game code can refer to a MainCamera object. // We update our viewer and eye transforms here because it is as late as possible before rendering happens. // OnPreRender is not called because we disable the camera here. void OnPreCull() { //leave the preview camera enabled if there is no display config _camera.enabled = !DisplayController.CheckDisplayStartup(); DoRendering(); // Flag that we disabled the camera _disabledCamera = true; } // The main rendering loop, should be called late in the pipeline, i.e. from OnPreCull // Set our viewer and eye poses and render to each surface. void DoRendering() { // update poses once DisplayConfig is ready if (DisplayController.CheckDisplayStartup()) { if(_hmdConnectionError) { _hmdConnectionError = false; Debug.Log("[OSVR-Unity] HMD connection established. You can ignore previous error messages indicating Display Startup failure."); } // update the viewer's head pose // currently getting viewer pose from DisplayConfig always UpdateViewerHeadPose(GetViewerPose(ViewerIndex)); // each viewer updates its eye poses, viewports, projection matrices UpdateEyes(); } else { if(!_hmdConnectionError) { //report an error message once if the HMD is not connected //it can take a few frames to connect under normal operation, so inidcate when this error has been resolved _hmdConnectionError = true; Debug.LogError("[OSVR-Unity] Display Startup failed. Check HMD connection."); } } } // This couroutine is called every frame. IEnumerator EndOfFrame() { while (true) { yield return new WaitForEndOfFrame(); if (DisplayController.UseRenderManager && DisplayController.CheckDisplayStartup()) { // Issue a RenderEvent, which copies Unity RenderTextures to RenderManager buffers #if UNITY_5_2 || UNITY_5_3 || UNITY_5_4 || UNITY_5_5 GL.Viewport(_emptyViewport); GL.Clear(false, true, Camera.backgroundColor); GL.IssuePluginEvent(DisplayController.RenderManager.GetRenderEventFunction(), OsvrRenderManager.RENDER_EVENT); if(DisplayController.showDirectModePreview) { Camera.Render(); } #else Debug.LogError("[OSVR-Unity] GL.IssuePluginEvent failed. This version of Unity cannot support RenderManager."); DisplayController.UseRenderManager = false; #endif } //if we disabled the dummy camera, enable it here if (_disabledCamera) { Camera.enabled = true; _disabledCamera = false; } } } } } }