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