// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#if GT_USE_UGUI
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
namespace Microsoft.MixedReality.GraphicsTools
{
///
/// Animates material properties for the Graphics Tools/Non-Canvas/Frontplate and
/// Graphics Tools/Canvas/Frontplate shaders to play a pulse ring effect at a point in 3D space.
///
[AddComponentMenu("Scripts/GraphicsTools/FrontPlatePulse")]
public class FrontPlatePulse : MonoBehaviour
{
///
/// The total duration of the pulse effect in seconds.
///
public float Duration
{
get => duration;
set => duration = value;
}
[SerializeField]
[Tooltip("The total duration of the pulse effect in seconds.")]
private float duration = 1.2f;
private bool isInitialized = false;
private Renderer _renderer = null;
private MaterialPropertyBlock materialProperty = null;
private CanvasMaterialAnimatorCanvasFrontplate animator = null;
private Graphic graphic = null;
private struct PulseState
{
public string useGlobalBlob, blobPosition, blobPulse, blobFade;
public bool pulseActive;
public float startTime;
public Vector3 initialPosition;
public PulseState(string useGlobalBlob, string blobPosition, string blobPulse, string blobFade)
{
pulseActive = false;
startTime = 0.0f;
initialPosition = Vector3.zero;
this.useGlobalBlob = useGlobalBlob;
this.blobPosition = blobPosition;
this.blobPulse = blobPulse;
this.blobFade = blobFade;
}
}
private PulseState[] states = new PulseState[2]
{
new PulseState("_Use_Global_Left_Index_", "_Blob_Position_", "_Blob_Pulse_", "_Blob_Fade_"),
new PulseState("_Use_Global_Right_Index_", "_Blob_Position_2_", "_Blob_Pulse_2_", "_Blob_Fade_2_")
};
#region MonoBehaviour Implementation
private void Start()
{
_renderer = gameObject.GetComponent();
if (_renderer != null)
{
materialProperty = new MaterialPropertyBlock();
}
else
{
animator = GetComponent();
if (animator == null)
{
graphic = GetComponent();
MaterialRestorer.Capture(graphic.material);
}
}
Debug.Assert(_renderer != null || animator != null || graphic != null,
"The FrontPlatePulse component must have a Renderer, CanvasMaterialAnimatorCanvasFrontplate, or Graphic component.");
isInitialized = true;
}
private void OnDestroy()
{
if (graphic != null)
{
// When in editor restore any material changes made during play.
MaterialRestorer.Restore(graphic.material);
}
}
private void OnEnable()
{
if (isInitialized)
{
for (int i = 0; i < states.Length; i++)
{
if (states[i].pulseActive)
{
ResetPulse(ref states[i], i);
}
}
}
}
#endregion MonoBehaviour Implementation
///
/// Starts the pulse animation at a position and index.
///
public void PulseAt(Vector3 position, int index)
{
if (isInitialized)
{
if (states[index].pulseActive)
{
StopAllCoroutines();
ResetPulse(ref states[index], index);
}
states[index].startTime = Time.time;
states[index].pulseActive = true;
states[index].initialPosition = position;
if (_renderer != null)
{
_renderer.GetPropertyBlock(materialProperty);
materialProperty.SetFloat(states[index].useGlobalBlob, 0.0f);
_renderer.SetPropertyBlock(materialProperty);
}
else if (animator != null)
{
if (index == 0)
{
animator._Use_Global_Left_Index_ = 0.0f;
}
else
{
animator._Use_Global_Right_Index_ = 0.0f;
}
animator.ApplyToMaterial();
}
else if (graphic != null)
{
graphic.material.SetFloat(states[index].useGlobalBlob, 0.0f);
}
StartCoroutine(UpdatePulse());
}
}
///
/// Overloaded method to specify if this is a left or right pulse.
///
public void Pulse(Vector3 position, bool left)
{
PulseAt(position, left ? 0 : 1);
}
///
/// Pulse without a spatial input source to define position or index.
///
public void PulseNonSpatial()
{
PulseAt(transform.position, 0);
}
///
/// Returns true of if any PulseState is active.
///
public bool IsPulsing()
{
if (!isInitialized)
{
return false;
}
return states[0].pulseActive || states[1].pulseActive;
}
private IEnumerator UpdatePulse()
{
if (isInitialized)
{
while (IsPulsing())
{
for (int i = 0; i < states.Length; ++i)
{
UpdateState(ref states[i], i);
}
yield return null;
}
}
}
private void UpdateState(ref PulseState state, int index)
{
if (state.pulseActive)
{
if (_renderer != null)
{
_renderer.GetPropertyBlock(materialProperty);
}
// The front plate plane may change position frame to frame, so we need to update it.
Vector3 position = new Plane(transform.forward, transform.position).ClosestPointOnPlane(state.initialPosition);
if (_renderer != null)
{
materialProperty.SetVector(states[index].blobPosition, position);
}
else if (animator != null)
{
if (index == 0)
{
animator._Blob_Position_ = position;
}
else
{
animator._Blob_Position_2_ = position;
}
}
else if (graphic != null)
{
graphic.material.SetVector(states[index].blobPosition, position);
}
float t = (Time.time - state.startTime) / Duration * 2.0f;
if (t < 0.5f)
{
if (_renderer != null)
{
materialProperty.SetFloat(state.blobPulse, t * 2.0f);
}
else if (animator != null)
{
if (index == 0)
{
animator._Blob_Pulse_ = t * 2.0f;
}
else
{
animator._Blob_Pulse_2_ = t * 2.0f;
}
}
else if (graphic != null)
{
graphic.material.SetFloat(state.blobPulse, t * 2.0f);
}
}
else if (t < 1.0f)
{
if (_renderer != null)
{
materialProperty.SetFloat(state.useGlobalBlob, 1.0f);
materialProperty.SetFloat(state.blobPulse, 0.0f);
materialProperty.SetFloat(state.blobFade, 2.0f * t - 1.0f);
}
else if (animator != null)
{
if (index == 0)
{
animator._Use_Global_Left_Index_ = 1.0f;
animator._Blob_Pulse_ = 0.0f;
animator._Blob_Fade_ = 2.0f * t - 1.0f;
}
else
{
animator._Use_Global_Right_Index_ = 1.0f;
animator._Blob_Pulse_2_ = 0.0f;
animator._Blob_Fade_2_ = 2.0f * t - 1.0f;
}
}
else if (graphic != null)
{
graphic.material.SetFloat(state.useGlobalBlob, 1.0f);
graphic.material.SetFloat(state.blobPulse, 0.0f);
graphic.material.SetFloat(state.blobFade, 2.0f * t - 1.0f);
}
}
else
{
state.pulseActive = false;
if (_renderer != null)
{
materialProperty.SetFloat(state.blobFade, 1.0f);
}
else if (animator != null)
{
if (index == 0)
{
animator._Blob_Fade_ = 1.0f;
}
else
{
animator._Blob_Fade_2_ = 1.0f;
}
}
else if (graphic != null)
{
graphic.material.SetFloat(state.blobFade, 1.0f);
}
}
if (_renderer != null)
{
_renderer.SetPropertyBlock(materialProperty);
}
if (animator != null)
{
animator.ApplyToMaterial();
}
}
}
private void ResetPulse(ref PulseState state, int index)
{
if (isInitialized)
{
state.pulseActive = false;
if (_renderer != null)
{
_renderer.GetPropertyBlock(materialProperty);
materialProperty.SetFloat(state.blobFade, 1.0f);
materialProperty.SetFloat(state.useGlobalBlob, 1.0f);
materialProperty.SetFloat(state.blobPulse, 0.0f);
_renderer.SetPropertyBlock(materialProperty);
}
else if (animator != null)
{
if (index == 0)
{
animator._Blob_Fade_ = 1.0f;
animator._Use_Global_Left_Index_ = 1.0f;
animator._Blob_Pulse_ = 0.0f;
}
else
{
animator._Blob_Fade_2_ = 1.0f;
animator._Use_Global_Right_Index_ = 1.0f;
animator._Blob_Pulse_2_ = 0.0f;
}
animator.ApplyToMaterial();
}
else if (graphic != null)
{
graphic.material.SetFloat(state.blobFade, 1.0f);
graphic.material.SetFloat(state.useGlobalBlob, 1.0f);
graphic.material.SetFloat(state.blobPulse, 0.0f);
}
}
}
}
}
#endif // GT_USE_UGUI