502 lines
No EOL
20 KiB
C#
502 lines
No EOL
20 KiB
C#
//======= 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;
|
|
}
|
|
}
|
|
} |