using System;
using UnityEngine;
namespace Unity.XRTools.Rendering
{
///
/// A unified base class for the XR Line Renderer and XR Trail Renderer
///
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
[ExecuteInEditMode]
[DisallowMultipleComponent]
public abstract class MeshChainRenderer : MonoBehaviour
{
static readonly GradientColorKey k_DefaultStartColor = new GradientColorKey(Color.white, 0);
static readonly GradientColorKey k_DefaultEndColor = new GradientColorKey(Color.white, 1);
static readonly GradientAlphaKey k_DefaultStartAlpha = new GradientAlphaKey(1, 0);
static readonly GradientAlphaKey k_DefaultEndAlpha = new GradientAlphaKey(1, 1);
///
/// Materials to use when rendering.
///
[SerializeField]
[Tooltip("Materials to use when rendering.")]
protected Material[] m_Materials;
///
/// The multiplier applied to the curve, describing the width (in world space) along the line.
///
[SerializeField]
[Tooltip("The multiplier applied to the curve, describing the width (in world space) along the line.")]
protected float m_Width = 1.0f;
///
/// The curve describing the width of the line at various points along its length.
///
[SerializeField]
[Tooltip("The curve describing the width of the line at various points along its length.")]
protected AnimationCurve m_WidthCurve = new AnimationCurve();
///
/// The gradient describing color along the line.
///
[SerializeField]
[Tooltip("The gradient describing color along the line.")]
protected Gradient m_Color = new Gradient();
///
/// The MeshRenderer used to render the line
///
[SerializeField]
[HideInInspector]
protected MeshRenderer m_MeshRenderer;
///
/// Cached mesh data
///
protected XRMeshChain m_XRMeshData;
///
/// Whether the mesh data needs to be refreshed
///
protected bool m_MeshNeedsRefreshing;
///
/// The step size
///
protected float m_StepSize = 1.0f;
///
/// Returns the first instantiated Material assigned to the renderer.
///
public virtual Material material
{
get { return m_MeshRenderer.material; }
set { m_MeshRenderer.material = value; }
}
///
/// Returns all the instantiated materials of this object.
///
public virtual Material[] materials
{
get { return m_MeshRenderer.materials; }
set { m_MeshRenderer.materials = value; }
}
///
/// Returns the shared material of this object.
///
public virtual Material sharedMaterial
{
get { return m_MeshRenderer.sharedMaterial; }
set { m_MeshRenderer.sharedMaterial = value; }
}
///
/// Returns all shared materials of this object.
///
public virtual Material[] SharedMaterials
{
get { return m_MeshRenderer.materials; }
set { m_MeshRenderer.sharedMaterials = value; }
}
///
/// Set the width at the start of the line.
///
public float widthStart
{
get { return m_WidthCurve.Evaluate(0) * m_Width; }
set
{
if (m_Width == 0 || float.IsNaN(m_Width))
return;
var keys = m_WidthCurve.keys;
keys[0].value = value / m_Width;
m_WidthCurve.keys = keys;
UpdateWidth();
}
}
///
/// Set the width at the end of the line.
///
public float widthEnd
{
get { return m_WidthCurve.Evaluate(1) * m_Width; }
set
{
if (m_Width == 0 || float.IsNaN(m_Width))
return;
var keys = m_WidthCurve.keys;
var lastIndex = keys.Length - 1;
keys[lastIndex].value = value / m_Width;
m_WidthCurve.keys = keys;
UpdateWidth();
}
}
///
/// Set an overall multiplier that is applied to the LineRenderer.widthCurve to get the final width of the line.
///
public float widthMultiplier
{
get { return m_Width; }
set
{
m_Width = value;
UpdateWidth();
}
}
///
/// Set the curve describing the width of the line at various points along its length.
///
public AnimationCurve widthCurve
{
get { return m_WidthCurve; }
set
{
m_WidthCurve = value ?? new AnimationCurve(new Keyframe(0, 1.0f));
UpdateWidth();
}
}
///
/// Set the color gradient describing the color of the line at various points along its length.
///
public Gradient colorGradient
{
get { return m_Color; }
set
{
if (m_Color == value)
{
return;
}
m_Color = value ?? new Gradient
{
alphaKeys = new[] { k_DefaultStartAlpha, k_DefaultEndAlpha },
colorKeys = new[] { k_DefaultStartColor, k_DefaultEndColor },
mode = GradientMode.Blend
};
UpdateColors();
}
}
///
/// Set the color at the start of the line.
///
public Color colorStart
{
get { return m_Color.Evaluate(0); }
set
{
var colorKeys = m_Color.colorKeys;
var alphaKeys = m_Color.alphaKeys;
var flatColor = value;
flatColor.a = 1.0f;
colorKeys[0].color = flatColor;
alphaKeys[0].alpha = value.a;
m_Color.colorKeys = colorKeys;
m_Color.alphaKeys = alphaKeys;
UpdateColors();
}
}
///
/// Set the color at the end of the line.
///
public Color colorEnd
{
get { return m_Color.Evaluate(1); }
set
{
var colorKeys = m_Color.colorKeys;
var alphaKeys = m_Color.alphaKeys;
var lastColorIndex = colorKeys.Length - 1;
var lastAlphaIndex = alphaKeys.Length - 1;
var flatColor = value;
flatColor.a = 1.0f;
colorKeys[lastColorIndex].color = flatColor;
alphaKeys[lastAlphaIndex].alpha = value.a;
m_Color.colorKeys = colorKeys;
m_Color.alphaKeys = alphaKeys;
UpdateColors();
}
}
///
/// Resets the width to a straight curve at the given value
///
/// The new width for the curve to display
public void SetTotalWidth(float newWidth)
{
m_Width = newWidth;
m_WidthCurve = new AnimationCurve(new Keyframe(0, 1.0f));
UpdateWidth();
}
///
/// Resets the color to a single value
///
/// The new color for the curve to display
public void SetTotalColor(Color newColor)
{
var flatColor = newColor;
flatColor.a = 1.0f;
m_Color = new Gradient
{
alphaKeys = new[] { new GradientAlphaKey(newColor.a, 0), new GradientAlphaKey(newColor.a, 1), },
colorKeys = new[] { new GradientColorKey(flatColor, 0), new GradientColorKey(flatColor, 1) },
mode = GradientMode.Blend
};
UpdateColors();
}
void OnValidate()
{
SetupMeshBackend();
if (NeedsReinitialize())
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.delayCall += EditorCheckForUpdate;
#endif
}
else
{
EditorCheckForUpdate();
}
}
///
/// Manually trigger the mesh data to be refreshed. This is automatically done in LateUpdate if anything has changed.
///
public void RefreshMesh()
{
m_XRMeshData.RefreshMesh();
m_MeshNeedsRefreshing = false;
}
///
/// Cleans up the visible interface of the MeshRenderer by hiding unneeded components
/// Also makes sure the animation curves are set up properly to defaults
///
void SetupMeshBackend()
{
if (m_MeshRenderer == null)
m_MeshRenderer = GetComponent();
if (m_MeshRenderer.hideFlags == HideFlags.None)
m_MeshRenderer.hideFlags = HideFlags.HideInInspector;
var meshFilter = GetComponent();
if (meshFilter.hideFlags == HideFlags.None)
meshFilter.hideFlags = HideFlags.HideInInspector;
if (m_Materials == null || m_Materials.Length == 0)
{
m_Materials = m_MeshRenderer.sharedMaterials;
}
var sharedMaterials = m_MeshRenderer.sharedMaterials;
var length = sharedMaterials.Length;
var materialsEqual = true;
for (var i = 0; i < length; i++)
{
if (sharedMaterials[i] != m_Materials[i])
{
materialsEqual = false;
break;
}
}
if (!materialsEqual)
{
m_MeshRenderer.sharedMaterials = m_Materials;
}
if (m_WidthCurve == null || m_WidthCurve.keys == null || m_WidthCurve.keys.Length == 0)
{
m_WidthCurve = new AnimationCurve(new Keyframe(0, 1.0f));
}
if (m_Color == null)
{
m_Color = new Gradient
{
alphaKeys = new[] { k_DefaultStartAlpha, k_DefaultEndAlpha },
colorKeys = new[] { k_DefaultStartColor, k_DefaultEndColor },
mode = GradientMode.Blend
};
}
}
///
/// Makes the sure mesh renderer reference is initialized before any functions try to access it
///
protected virtual void Awake()
{
SetupMeshBackend();
Initialize();
}
///
/// Makes the sure mesh renderer reference is initialized on reset of the component
///
void Reset()
{
SetupMeshBackend();
Initialize();
}
///
/// Ensures the lines have all their data precached upon loading
///
void Start()
{
Initialize();
}
#if UNITY_EDITOR
///
/// Ensures the hidden mesh/meshfilters are destroyed if users are messing with the components in edit mode
///
void OnDestroy()
{
if (!Application.isPlaying)
{
var rendererToDestroy = m_MeshRenderer;
var filterToDestroy = gameObject.GetComponent();
UnityEditor.EditorApplication.delayCall += () =>
{
if (!Application.isPlaying)
{
if (rendererToDestroy != null)
{
DestroyImmediate(rendererToDestroy);
}
if (filterToDestroy != null)
{
DestroyImmediate(filterToDestroy);
}
}
};
}
}
#endif
///
/// Does the actual internal mesh updating as late as possible so nothing ends up a frame behind
///
protected virtual void LateUpdate()
{
if (m_MeshNeedsRefreshing == true)
{
m_XRMeshData.RefreshMesh();
m_MeshNeedsRefreshing = false;
}
}
///
/// Allows the component to be referenced as a renderer, forwarding the MeshRenderer ahead
///
///
/// The MeshChainRenderer's MeshRenderer
public static implicit operator Renderer(MeshChainRenderer meshChainRenderer)
{
return meshChainRenderer.m_MeshRenderer;
}
///
/// Triggered by validation - forced initialization to make sure data changed
/// in the editor is reflected immediately to the user.
///
void EditorCheckForUpdate()
{
// Because this gets delay-called, it can be referring to a destroyed component when a scene starts
if (this != null)
{
// If we did not initialize, refresh all the properties instead
Initialize(false);
}
}
///
/// Updates any internal variables to represent the new color that has been applied
///
protected virtual void UpdateColors() { }
///
/// Updates any internal variables to represent the new width that has been applied
///
protected virtual void UpdateWidth() { }
///
/// Creates or updates the underlying mesh data
///
protected virtual void Initialize(bool generate = true) { }
///
/// Tests if the mesh data needs to be created or rebuilt
///
/// true if the mesh data needs recreation, false if it is already set up properly
protected virtual bool NeedsReinitialize()
{
return true;
}
///
/// Enables the internal mesh representing the line
///
protected virtual void OnEnable()
{
m_MeshRenderer.enabled = true;
}
///
/// Disables the internal mesh representing the line
///
protected virtual void OnDisable()
{
m_MeshRenderer.enabled = false;
}
}
}