IMHOTEP Framework
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Pages
SteamVR_LoadLevel.cs
1 //======= Copyright (c) Valve Corporation, All rights reserved. ===============
2 //
3 // Purpose: Helper for smoothing over transitions between levels.
4 //
5 //=============================================================================
6 
7 using UnityEngine;
8 using System.Collections;
9 using Valve.VR;
10 using System.IO;
11 
12 public class SteamVR_LoadLevel : MonoBehaviour
13 {
14  private static SteamVR_LoadLevel _active = null;
15  public static bool loading { get { return _active != null; } }
16  public static float progress
17  {
18  get { return (_active != null && _active.async != null) ? _active.async.progress : 0.0f; }
19  }
20  public static Texture progressTexture
21  {
22  get { return (_active != null) ? _active.renderTexture : null; }
23  }
24 
25  // Name of level to load.
26  public string levelName;
27 
28  // Name of internal process to launch (instead of levelName).
29  public string internalProcessPath;
30 
31  // The command-line args for the internal process to launch.
32  public string internalProcessArgs;
33 
34  // If true, call LoadLevelAdditiveAsync instead of LoadLevelAsync.
35  public bool loadAdditive;
36 
37  // Async load causes crashes in some apps.
38  public bool loadAsync = true;
39 
40  // Optional logo texture.
41  public Texture loadingScreen;
42 
43  // Optional progress bar textures.
44  public Texture progressBarEmpty, progressBarFull;
45 
46  // Sizes of overlays.
47  public float loadingScreenWidthInMeters = 6.0f;
48  public float progressBarWidthInMeters = 3.0f;
49 
50  // If specified, the loading screen will be positioned in the player's view this far away.
51  public float loadingScreenDistance = 0.0f;
52 
53  // Optional overrides for where to display loading screen and progress bar overlays.
54  // Otherwise defaults to using this object's transform.
55  public Transform loadingScreenTransform, progressBarTransform;
56 
57  // Optional skybox override textures.
58  public Texture front, back, left, right, top, bottom;
59 
60  // Colors to use when dropping to the compositor between levels if no skybox is set.
61  public Color backgroundColor = Color.black;
62 
63  // If false, the background color above gets applied as the foreground color in the compositor.
64  // This does not have any effect when using a skybox instead.
65  public bool showGrid = false;
66 
67  // Time to fade from current scene to the compositor and back.
68  public float fadeOutTime = 0.5f;
69  public float fadeInTime = 0.5f;
70 
71  // Additional time to wait after finished loading before we start fading the new scene back in.
72  // This is to cover up any initial hitching that takes place right at the start of levels.
73  // Most scenes should hopefully not require this.
74  public float postLoadSettleTime = 0.0f;
75 
76  // Time to fade loading screen in and out (also used for progress bar).
77  public float loadingScreenFadeInTime = 1.0f;
78  public float loadingScreenFadeOutTime = 0.25f;
79 
80  float fadeRate = 1.0f;
81  float alpha = 0.0f;
82 
83  AsyncOperation async; // used to track level load progress
84  RenderTexture renderTexture; // used to render progress bar
85 
86  ulong loadingScreenOverlayHandle = OpenVR.k_ulOverlayHandleInvalid;
87  ulong progressBarOverlayHandle = OpenVR.k_ulOverlayHandleInvalid;
88 
89  public bool autoTriggerOnEnable = false;
90 
91  void OnEnable()
92  {
93  if (autoTriggerOnEnable)
94  Trigger();
95  }
96 
97  public void Trigger()
98  {
99  if (!loading && !string.IsNullOrEmpty(levelName))
100  StartCoroutine(LoadLevel());
101  }
102 
103  // Helper function to quickly and simply load a level from script.
104  public static void Begin(string levelName,
105  bool showGrid = false, float fadeOutTime = 0.5f,
106  float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 1.0f)
107  {
108  var loader = new GameObject("loader").AddComponent<SteamVR_LoadLevel>();
109  loader.levelName = levelName;
110  loader.showGrid = showGrid;
111  loader.fadeOutTime = fadeOutTime;
112  loader.backgroundColor = new Color(r, g, b, a);
113  loader.Trigger();
114  }
115 
116  // Updates progress bar.
117  void OnGUI()
118  {
119  if (_active != this)
120  return;
121 
122  // Optionally create an overlay for our progress bar to use, separate from the loading screen.
123  if (progressBarEmpty != null && progressBarFull != null)
124  {
125  if (progressBarOverlayHandle == OpenVR.k_ulOverlayHandleInvalid)
126  progressBarOverlayHandle = GetOverlayHandle("progressBar", progressBarTransform != null ? progressBarTransform : transform, progressBarWidthInMeters);
127 
128  if (progressBarOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
129  {
130  var progress = (async != null) ? async.progress : 0.0f;
131 
132  // Use the full bar size for everything.
133  var w = progressBarFull.width;
134  var h = progressBarFull.height;
135 
136  // Create a separate render texture so we can composite the full image on top of the empty one.
137  if (renderTexture == null)
138  {
139  renderTexture = new RenderTexture(w, h, 0);
140  renderTexture.Create();
141  }
142 
143  var prevActive = RenderTexture.active;
144  RenderTexture.active = renderTexture;
145 
146  if (Event.current.type == EventType.Repaint)
147  GL.Clear(false, true, Color.clear);
148 
149  GUILayout.BeginArea(new Rect(0, 0, w, h));
150 
151  GUI.DrawTexture(new Rect(0, 0, w, h), progressBarEmpty);
152 
153  // Reveal the full bar texture based on progress.
154  GUI.DrawTextureWithTexCoords(new Rect(0, 0, progress * w, h), progressBarFull, new Rect(0.0f, 0.0f, progress, 1.0f));
155 
156  GUILayout.EndArea();
157 
158  RenderTexture.active = prevActive;
159 
160  // Texture needs to be set every frame after it is updated since SteamVR makes a copy internally to a shared texture.
161  var overlay = OpenVR.Overlay;
162  if (overlay != null)
163  {
164  var texture = new Texture_t();
165  texture.handle = renderTexture.GetNativeTexturePtr();
166  texture.eType = SteamVR.instance.textureType;
167  texture.eColorSpace = EColorSpace.Auto;
168  overlay.SetOverlayTexture(progressBarOverlayHandle, ref texture);
169  }
170  }
171  }
172 
173  #if false
174  // Draw loading screen and progress bar to 2d companion window as well.
175  if (loadingScreen != null)
176  {
177  var screenAspect = (float)Screen.width / Screen.height;
178  var textureAspect = (float)loadingScreen.width / loadingScreen.height;
179 
180  float w, h;
181  if (screenAspect < textureAspect)
182  {
183  // Clamp horizontally
184  w = Screen.width * 0.9f;
185  h = w / textureAspect;
186  }
187  else
188  {
189  // Clamp vertically
190  h = Screen.height * 0.9f;
191  w = h * textureAspect;
192  }
193 
194  GUILayout.BeginArea(new Rect(0, 0, Screen.width, Screen.height));
195 
196  var x = Screen.width / 2 - w / 2;
197  var y = Screen.height / 2 - h / 2;
198  GUI.DrawTexture(new Rect(x, y, w, h), loadingScreen);
199 
200  GUILayout.EndArea();
201  }
202 
203  if (renderTexture != null)
204  {
205  var x = Screen.width / 2 - renderTexture.width / 2;
206  var y = Screen.height * 0.9f - renderTexture.height;
207  GUI.DrawTexture(new Rect(x, y, renderTexture.width, renderTexture.height), renderTexture);
208  }
209  #endif
210  }
211 
212  // Fade our overlays in/out over time.
213  void Update()
214  {
215  if (_active != this)
216  return;
217 
218  alpha = Mathf.Clamp01(alpha + fadeRate * Time.deltaTime);
219 
220  var overlay = OpenVR.Overlay;
221  if (overlay != null)
222  {
223  if (loadingScreenOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
224  overlay.SetOverlayAlpha(loadingScreenOverlayHandle, alpha);
225 
226  if (progressBarOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
227  overlay.SetOverlayAlpha(progressBarOverlayHandle, alpha);
228  }
229  }
230 
231  // Corourtine to handle all the steps across loading boundaries.
232  IEnumerator LoadLevel()
233  {
234  // Optionally rotate loading screen transform around the camera into view.
235  // We assume here that the loading screen is already facing toward the origin,
236  // and that the progress bar transform (if any) is a child and will follow along.
237  if (loadingScreen != null && loadingScreenDistance > 0.0f)
238  {
239  // Wait until we have tracking.
240  var hmd = SteamVR_Controller.Input((int)OpenVR.k_unTrackedDeviceIndex_Hmd);
241  while (!hmd.hasTracking)
242  yield return null;
243 
244  var tloading = hmd.transform;
245  tloading.rot = Quaternion.Euler(0.0f, tloading.rot.eulerAngles.y, 0.0f);
246  tloading.pos += tloading.rot * new Vector3(0.0f, 0.0f, loadingScreenDistance);
247 
248  var t = loadingScreenTransform != null ? loadingScreenTransform : transform;
249  t.position = tloading.pos;
250  t.rotation = tloading.rot;
251  }
252 
253  _active = this;
254 
255  SteamVR_Events.Loading.Send(true);
256 
257  // Calculate rate for fading in loading screen and progress bar.
258  if (loadingScreenFadeInTime > 0.0f)
259  {
260  fadeRate = 1.0f / loadingScreenFadeInTime;
261  }
262  else
263  {
264  alpha = 1.0f;
265  }
266 
267  var overlay = OpenVR.Overlay;
268 
269  // Optionally create our loading screen overlay.
270  if (loadingScreen != null && overlay != null)
271  {
272  loadingScreenOverlayHandle = GetOverlayHandle("loadingScreen", loadingScreenTransform != null ? loadingScreenTransform : transform, loadingScreenWidthInMeters);
273  if (loadingScreenOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
274  {
275  var texture = new Texture_t();
276  texture.handle = loadingScreen.GetNativeTexturePtr();
277  texture.eType = SteamVR.instance.textureType;
278  texture.eColorSpace = EColorSpace.Auto;
279  overlay.SetOverlayTexture(loadingScreenOverlayHandle, ref texture);
280  }
281  }
282 
283  bool fadedForeground = false;
284 
285  // Fade out to compositor
286  SteamVR_Events.LoadingFadeOut.Send(fadeOutTime);
287 
288  // Optionally set a skybox to use as a backdrop in the compositor.
289  var compositor = OpenVR.Compositor;
290  if (compositor != null)
291  {
292  if (front != null)
293  {
294  SteamVR_Skybox.SetOverride(front, back, left, right, top, bottom);
295 
296  // Explicitly fade to the compositor since loading will cause us to stop rendering.
297  compositor.FadeGrid(fadeOutTime, true);
298  yield return new WaitForSeconds(fadeOutTime);
299  }
300  else if (backgroundColor != Color.clear)
301  {
302  // Otherwise, use the specified background color.
303  if (showGrid)
304  {
305  // Set compositor background color immediately, and start fading to it.
306  compositor.FadeToColor(0.0f, backgroundColor.r, backgroundColor.g, backgroundColor.b, backgroundColor.a, true);
307  compositor.FadeGrid(fadeOutTime, true);
308  yield return new WaitForSeconds(fadeOutTime);
309  }
310  else
311  {
312  // Fade the foreground color in (which will blend on top of the scene), and then cut to the compositor.
313  compositor.FadeToColor(fadeOutTime, backgroundColor.r, backgroundColor.g, backgroundColor.b, backgroundColor.a, false);
314  yield return new WaitForSeconds(fadeOutTime + 0.1f);
315  compositor.FadeGrid(0.0f, true);
316  fadedForeground = true;
317  }
318  }
319  }
320 
321  // Now that we're fully faded out, we can stop submitting frames to the compositor.
322  SteamVR_Render.pauseRendering = true;
323 
324  // Continue waiting for the overlays to fully fade in before continuing.
325  while (alpha < 1.0f)
326  yield return null;
327 
328  // Keep us from getting destroyed when loading the new level, otherwise this coroutine will get stopped prematurely.
329  transform.parent = null;
330  DontDestroyOnLoad(gameObject);
331 
332  if (!string.IsNullOrEmpty(internalProcessPath))
333  {
334  Debug.Log("Launching external application...");
335  var applications = OpenVR.Applications;
336  if (applications == null)
337  {
338  Debug.Log("Failed to get OpenVR.Applications interface!");
339  }
340  else
341  {
342  var workingDirectory = Directory.GetCurrentDirectory();
343  var fullPath = Path.Combine(workingDirectory, internalProcessPath);
344  Debug.Log("LaunchingInternalProcess");
345  Debug.Log("ExternalAppPath = " + internalProcessPath);
346  Debug.Log("FullPath = " + fullPath);
347  Debug.Log("ExternalAppArgs = " + internalProcessArgs);
348  Debug.Log("WorkingDirectory = " + workingDirectory);
349  var error = applications.LaunchInternalProcess(fullPath, internalProcessArgs, workingDirectory);
350  Debug.Log("LaunchInternalProcessError: " + error);
351 #if UNITY_EDITOR
352  UnityEditor.EditorApplication.isPlaying = false;
353 #elif !UNITY_METRO
354  System.Diagnostics.Process.GetCurrentProcess().Kill();
355 #endif
356  }
357  }
358  else
359  {
360  var mode = loadAdditive ? UnityEngine.SceneManagement.LoadSceneMode.Additive : UnityEngine.SceneManagement.LoadSceneMode.Single;
361  if (loadAsync)
362  {
363  Application.backgroundLoadingPriority = ThreadPriority.Low;
364  async = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(levelName, mode);
365 
366  // Performing this in a while loop instead seems to help smooth things out.
367  //yield return async;
368  while (!async.isDone)
369  {
370  yield return null;
371  }
372  }
373  else
374  {
375  UnityEngine.SceneManagement.SceneManager.LoadScene(levelName, mode);
376  }
377  }
378 
379  yield return null;
380 
381  System.GC.Collect();
382 
383  yield return null;
384 
385  Shader.WarmupAllShaders();
386 
387  // Optionally wait a short period of time after loading everything back in, but before we start rendering again
388  // in order to give everything a change to settle down to avoid any hitching at the start of the new level.
389  yield return new WaitForSeconds(postLoadSettleTime);
390 
391  SteamVR_Render.pauseRendering = false;
392 
393  // Fade out loading screen.
394  if (loadingScreenFadeOutTime > 0.0f)
395  {
396  fadeRate = -1.0f / loadingScreenFadeOutTime;
397  }
398  else
399  {
400  alpha = 0.0f;
401  }
402 
403  // Fade out to compositor
404  SteamVR_Events.LoadingFadeIn.Send(fadeInTime);
405 
406  // Refresh compositor reference since loading scenes might have invalidated it.
407  compositor = OpenVR.Compositor;
408  if (compositor != null)
409  {
410  // Fade out foreground color if necessary.
411  if (fadedForeground)
412  {
413  compositor.FadeGrid(0.0f, false);
414  compositor.FadeToColor(fadeInTime, 0.0f, 0.0f, 0.0f, 0.0f, false);
415  yield return new WaitForSeconds(fadeInTime);
416  }
417  else
418  {
419  // Fade scene back in, and reset skybox once no longer visible.
420  compositor.FadeGrid(fadeInTime, false);
421  yield return new WaitForSeconds(fadeInTime);
422 
423  if (front != null)
424  {
425  SteamVR_Skybox.ClearOverride();
426  }
427  }
428  }
429 
430  // Finally, stick around long enough for our overlays to fully fade out.
431  while (alpha > 0.0f)
432  yield return null;
433 
434  if (overlay != null)
435  {
436  if (progressBarOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
437  overlay.HideOverlay(progressBarOverlayHandle);
438  if (loadingScreenOverlayHandle != OpenVR.k_ulOverlayHandleInvalid)
439  overlay.HideOverlay(loadingScreenOverlayHandle);
440  }
441 
442  Destroy(gameObject);
443 
444  _active = null;
445 
446  SteamVR_Events.Loading.Send(false);
447  }
448 
449  // Helper to create (or reuse if possible) each of our different overlay types.
450  ulong GetOverlayHandle(string overlayName, Transform transform, float widthInMeters = 1.0f)
451  {
452  ulong handle = OpenVR.k_ulOverlayHandleInvalid;
453 
454  var overlay = OpenVR.Overlay;
455  if (overlay == null)
456  return handle;
457 
458  var key = SteamVR_Overlay.key + "." + overlayName;
459 
460  var error = overlay.FindOverlay(key, ref handle);
461  if (error != EVROverlayError.None)
462  error = overlay.CreateOverlay(key, overlayName, ref handle);
463  if (error == EVROverlayError.None)
464  {
465  overlay.ShowOverlay(handle);
466  overlay.SetOverlayAlpha(handle, alpha);
467  overlay.SetOverlayWidthInMeters(handle, widthInMeters);
468 
469  // D3D textures are upside-down in Unity to match OpenGL.
470  if (SteamVR.instance.textureType == ETextureType.DirectX)
471  {
472  var textureBounds = new VRTextureBounds_t();
473  textureBounds.uMin = 0;
474  textureBounds.vMin = 1;
475  textureBounds.uMax = 1;
476  textureBounds.vMax = 0;
477  overlay.SetOverlayTextureBounds(handle, ref textureBounds);
478  }
479 
480  // Convert from world space to tracking space using the top-most camera.
481  var vrcam = (loadingScreenDistance == 0.0f) ? SteamVR_Render.Top() : null;
482  if (vrcam != null && vrcam.origin != null)
483  {
484  var offset = new SteamVR_Utils.RigidTransform(vrcam.origin, transform);
485  offset.pos.x /= vrcam.origin.localScale.x;
486  offset.pos.y /= vrcam.origin.localScale.y;
487  offset.pos.z /= vrcam.origin.localScale.z;
488 
489  var t = offset.ToHmdMatrix34();
490  overlay.SetOverlayTransformAbsolute(handle, SteamVR_Render.instance.trackingSpace, ref t);
491  }
492  else
493  {
494  var t = new SteamVR_Utils.RigidTransform(transform).ToHmdMatrix34();
495  overlay.SetOverlayTransformAbsolute(handle, SteamVR_Render.instance.trackingSpace, ref t);
496  }
497  }
498 
499  return handle;
500  }
501 }
502