// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System; using System.Collections; using System.Threading; using System.Threading.Tasks; using UltimateXR.Animation.Interpolation; using UltimateXR.Avatar; using UltimateXR.Core; using UltimateXR.Core.Components.Composite; using UltimateXR.Core.Settings; using UltimateXR.Extensions.System.Threading; using UltimateXR.Extensions.Unity; using UltimateXR.Extensions.Unity.Render; using UnityEngine; namespace UltimateXR.CameraUtils { /// /// Component added to a camera that allows to fade the rendered content to and from a color /// by using a fullscreen quad. /// public class UxrCameraFade : UxrAvatarComponent { #region Public Types & Data /// /// Gets whether the component is currently fading. /// public bool IsFading => DrawFade; /// /// Gets or sets the fade color used. The alpha is determined by the fade itself. /// public Color FadeColor { get => _fadeColor; set => _fadeColor = value; } /// /// Gets or sets the layer value of the quad that is used to render the fade. /// public int QuadLayer { get => _quadLayer; set { _quadLayer = value; if (_quadObject != null) { _quadObject.layer = value; } } } #endregion #region Public Methods /// /// Checks if the given camera has a component. If not it is added to the camera. /// /// Camera to check /// The component which may have been added or was already present public static UxrCameraFade CheckAddToCamera(Camera camera) { UxrCameraFade cameraFade = camera.gameObject.GetOrAddComponent(); cameraFade._fadeColor = Color.black; return cameraFade; } /// /// Checks if the given camera has a component and a fade is currently active. /// /// Camera to check /// /// True if the camera has a component attached AND a fade /// currently running, false otherwise /// public static bool HasCameraFadeActive(Camera camera) { UxrCameraFade cameraFade = camera.gameObject.GetComponent(); return cameraFade != null && cameraFade.DrawFade; } /// /// Starts a fade over time on the given camera. The camera will fade out to a given color and /// then fade in from that color again. /// This is the static helper method that can be used to perform everything in just a single static call. /// /// The camera to perform the fade on /// Number of seconds of the initial fade-out /// Number of seconds of the fade-in /// The color the component fades out to and fades in from /// Optional callback executed right after the fade out finished /// Optional callback executed right after the fade in finished public static void StartFade(Camera camera, float fadeOutDurationSeconds, float fadeInDurationSeconds, Color fadeColor, Action fadeOutFinishedCallback = null, Action fadeInFinishedCallback = null) { UxrCameraFade cameraFade = CheckAddToCamera(camera); cameraFade.StartFade(fadeOutDurationSeconds, fadeInDurationSeconds, fadeColor, fadeOutFinishedCallback, fadeInFinishedCallback); } /// /// Starts a fade over time on the camera that has this component. The camera will fade out to a given color and /// then fade in from that color again. /// For a coroutine-friendly way of fading check StartFadeCoroutine(). /// /// Number of seconds of the initial fade-out /// Number of seconds of the fade-in /// The color the component fades out to and fades in from /// Optional callback that is called just after the fade out finished /// Optional callback that is called just after the fade in finished public void StartFade(float fadeOutDurationSeconds, float fadeInDurationSeconds, Color fadeColor, Action fadeOutFinishedCallback = null, Action fadeInFinishedCallback = null) { if (DrawFade) { if (UxrGlobalSettings.Instance.LogLevelAvatar >= UxrLogLevel.Warnings) { Debug.LogWarning($"{UxrConstants.AvatarModule} A fade was requested while one already being active. Some callbacks may not be called correctly. "); } } _fadeColor = fadeColor; DrawFade = true; _fadeOutFinished = false; _fadeTimer = fadeOutDurationSeconds + fadeInDurationSeconds; _fadeOutDuration = fadeOutDurationSeconds; _fadeInDuration = fadeInDurationSeconds; _fadeOutFinishedCallback = fadeOutFinishedCallback; _fadeInFinishedCallback = fadeInFinishedCallback; _fadeCurrentColor = _fadeColor; _fadeCurrentColor.a = 0.0f; FadeMaterial.color = _fadeCurrentColor; } /// /// Enables the camera fade color. It will draw an overlay with the given color until /// is called. /// /// The color to draw the overlay with /// The quantity [0.0, 1.0] of the fade public void EnableFadeColor(Color color, float quantity) { DrawFade = true; _fadeTimer = -1.0f; _fadeCurrentColor = color; _fadeCurrentColor.a *= quantity; FadeMaterial.color = _fadeCurrentColor; } /// /// Disables the camera fade rendering. /// public void DisableFadeColor() { DrawFade = false; _fadeTimer = -1.0f; } /// /// Starts a fade over time using an async operation. /// /// The cancellation token to cancel the async operation /// The fade duration in seconds /// The fade start color /// The fade end color public async Task FadeAsync(CancellationToken ct, float fadeSeconds, Color startColor, Color endColor) { await TaskExt.Loop(ct, fadeSeconds, t => { DrawFade = true; _fadeCurrentColor = Color.Lerp(startColor, endColor, t); FadeMaterial.color = _fadeCurrentColor; }, UxrEasing.Linear, true); if (Mathf.Approximately(endColor.a, 0.0f)) { DrawFade = false; } } #endregion #region Unity /// /// Initializes all internal data. /// protected override void Awake() { base.Awake(); CheckInitialize(); } /// /// Subscribes to events. /// protected override void OnEnable() { base.OnEnable(); UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated; } /// /// Unsubscribes from events. /// protected override void OnDisable() { base.OnDisable(); UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated; DrawFade = false; } #endregion #region Coroutines /// /// Coroutine that fades the screen over time. It can be used to be yielded externally from another coroutine. /// is provided as the async alternative. /// /// Seconds it will take to execute the fade /// Start color value /// End color value /// Coroutine IEnumerator public IEnumerator StartFadeCoroutine(float fadeSeconds, Color startColor, Color endColor) { if (DrawFade) { // Debug.LogWarning($"{UxrConstants.AvatarModule} A fade coroutine was requested while a fade already being active"); } yield return this.LoopCoroutine(fadeSeconds, t => { DrawFade = true; _fadeCurrentColor = Color.Lerp(startColor, endColor, t); FadeMaterial.color = _fadeCurrentColor; }, UxrEasing.Linear, true); if (Mathf.Approximately(endColor.a, 0.0f)) { DrawFade = false; } } #endregion #region Event Handling Methods /// /// Updates the fade (StartFade version) and calls all callbacks when they need to be triggered. /// private void UxrManager_AvatarsUpdated() { if (Avatar.AvatarMode == UxrAvatarMode.UpdateExternally) { DrawFade = false; return; } if (_fadeTimer > 0.0f) { _fadeTimer -= Time.deltaTime; if (_fadeTimer <= 0.0f) { DrawFade = false; _fadeInFinishedCallback?.Invoke(); } else if (_fadeTimer < _fadeInDuration && _fadeOutFinished == false) { _fadeOutFinished = true; _fadeCurrentColor.a = 1.0f * _fadeColor.a; _fadeOutFinishedCallback?.Invoke(); } else { if (_fadeTimer < _fadeInDuration) { _fadeCurrentColor.a = Mathf.Clamp01(_fadeTimer / _fadeInDuration) * _fadeColor.a; } else { _fadeCurrentColor.a = Mathf.Clamp01(1.0f - (_fadeTimer - _fadeInDuration) / _fadeOutDuration) * _fadeColor.a; } } FadeMaterial.color = _fadeCurrentColor; } } #endregion #region Private Methods /// /// Initializes the component if necessary. /// private void CheckInitialize() { if (!_initialized) { _initialized = true; _camera = Avatar.CameraComponent; _fadeTimer = -1.0f; DrawFade = false; CreateCameraQuad(); } } /// /// Creates the quad to render in front of the camera. /// private void CreateCameraQuad() { _quadObject = new GameObject("Fade"); _quadObject.transform.SetParent(transform); _quadObject.transform.localPosition = Vector3.forward * (_camera.nearClipPlane + 0.01f); _quadObject.transform.localEulerAngles = new Vector3(0.0f, 180.0f, 0.0f); _quadObject.layer = _quadLayer; MeshFilter meshFilter = _quadObject.AddComponent(); MeshRenderer meshRenderer = _quadObject.AddComponent(); meshFilter.mesh = MeshExt.CreateQuad(2.0f); _fadeMaterial = new Material(ShaderExt.UnlitOverlayFade); meshRenderer.sharedMaterial = _fadeMaterial; _quadObject.SetActive(_drawFade); } #endregion #region Private Types & Data /// /// Gets the material used to draw the fade. /// private Material FadeMaterial { get { if (_fadeMaterial == null) { _fadeMaterial = new Material(ShaderExt.UnlitTransparentColorNoDepthTest); } return _fadeMaterial; } } /// /// Gets or sets whether to draw the fade. /// private bool DrawFade { get => _drawFade; set { _drawFade = value; if (_quadObject != null) { _quadObject.SetActive(_drawFade); } } } private Color _fadeColor = Color.black; private bool _initialized; private Camera _camera; private Material _fadeMaterial; private bool _drawFade; private bool _fadeOutFinished; private float _fadeTimer; private float _fadeInDuration; private float _fadeOutDuration; private Color _fadeCurrentColor; private GameObject _quadObject; private int _quadLayer = 1; private Action _fadeOutFinishedCallback; private Action _fadeInFinishedCallback; #endregion } }