holopy3/Assets/SteamVR/Scripts/SteamVR_LoadLevel.cs

502 lines
20 KiB
C#
Raw Permalink Normal View History

2020-12-10 14:25:12 +00:00
//======= 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;
}
}
}