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