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;
        }
    }
}