2023-05-08 15:56:10 +03:00

313 lines
12 KiB
C#

using System.Collections.Generic;
using System.Linq;
namespace UnityEngine.XR.Content.Rendering
{
/// <summary>
/// The HighlightController manages scripts that highlight objects in some way - those that inherit from IMaterialHighlight
/// It is in charge of locating all applicable renderers, and swapping/doing additional drawing passes as needed to represent the highlights
/// </summary>
[System.Serializable]
public class HighlightController
{
// Local method use only -- created here to reduce garbage collection. Collections must be cleared before use
static readonly List<Renderer> k_RendererComponents = new List<Renderer>();
/// <summary>
/// Holds additional data for renderers that need additional drawing passes
/// This is specically MeshRenderers with more than one submesh - we can't just extend the material array
/// to get additional drawing passes, so we use this data to draw them manually.
/// </summary>
class CustomHighlightLayer
{
internal Material[] m_HighlightMaterials;
internal Mesh m_SharedMesh;
internal Transform m_Transform;
}
[SerializeField]
[Tooltip("Used to set the mode of capturing renderers on an object or to use only manually set renderers.")]
RendererCaptureDepth m_RendererCaptureDepth = RendererCaptureDepth.AllChildRenderers;
[SerializeField]
[Tooltip("Manually set renderers to be affected by the highlight")]
protected Renderer[] m_ManuallySetRenderers = new Renderer[0];
// Cached data about any renderers that will be highlighted, and materials that are swapped in and out
int m_MaterialAdditions = 0;
List<IMaterialHighlight> m_CacheUsers = new List<IMaterialHighlight>();
HashSet<Renderer> m_Renderers = new HashSet<Renderer>();
Dictionary<int, Material[]> m_OriginalMaterials = new Dictionary<int, Material[]>();
Dictionary<int, Material[]> m_HighlightMaterials = new Dictionary<int, Material[]>();
Dictionary<int, CustomHighlightLayer> m_CustomLayerMaterials = new Dictionary<int, CustomHighlightLayer>();
bool m_DelayedUnhighlight = false;
bool m_Highlighting = false;
float m_UnhighlightTimer = 0.0f;
/// <summary>
/// The transform that will be highlighted - it is searched for any child renderers
/// </summary>
public Transform rendererSource { get; set; }
/// <summary>
/// Registers a highlight script - this will provide materials to replace or layer when highlighting an object
/// </summary>
/// <param name="cacheUser">The highlight script to apply to the cached child renderers</param>
public void RegisterCacheUser(IMaterialHighlight cacheUser)
{
if (cacheUser.highlightMode == MaterialHighlightMode.Layer)
m_MaterialAdditions++;
// Set cache user to know about this
m_CacheUsers.Add(cacheUser);
}
/// <summary>
/// Unregisters a highlight script so that it will no longer influence the cached renderers
/// </summary>
/// <param name="cacheUser">The highlight script to remove from influencing renderers</param>
public void UnregisterCacheUser(IMaterialHighlight cacheUser)
{
if (cacheUser.highlightMode == MaterialHighlightMode.Layer)
m_MaterialAdditions--;
m_CacheUsers.Remove(cacheUser);
}
/// <summary>
/// Ensures that all renderers, materials, and highlight scripts have their materials ready
/// </summary>
public void Initialize()
{
if (rendererSource == null)
{
Debug.LogError("Trying to use a Highlight Controller before setting the root gameobject!");
return;
}
foreach (var cacheUser in m_CacheUsers)
{
cacheUser.Initialize();
}
// Cache the renderers
UpdateRendererCache();
// Generate the original material list and implement the materials from the included highlights
UpdateMaterialCache();
}
/// <summary>
/// Ensures that any materials or other objects allocated by highlight scripts can be cleaned up
/// </summary>
public void Deinitialize()
{
foreach (var cacheUser in m_CacheUsers)
{
if (cacheUser != null)
cacheUser.Deinitialize();
}
}
/// <summary>
/// Handles fading out materials when highlights are disabled and also manually drawing layered highlights as needed
/// </summary>
public void Update()
{
if (m_DelayedUnhighlight)
{
m_UnhighlightTimer -= Time.deltaTime;
if (m_UnhighlightTimer <= 0.0f)
{
m_DelayedUnhighlight = false;
m_Highlighting = false;
UpdateMaterialCache();
foreach (var renderer in m_Renderers)
{
var rendererID = renderer.GetInstanceID();
renderer.materials = m_OriginalMaterials[rendererID];
}
}
}
if (m_Highlighting && m_CustomLayerMaterials.Count > 0)
{
foreach (var customLayer in m_CustomLayerMaterials.Values)
{
for (var matIndex = 0; matIndex < customLayer.m_HighlightMaterials.Length; ++matIndex)
{
for (var submeshIndex = 0; submeshIndex < customLayer.m_SharedMesh.subMeshCount; ++submeshIndex)
{
Graphics.DrawMesh(
customLayer.m_SharedMesh,
customLayer.m_Transform.localToWorldMatrix,
customLayer.m_HighlightMaterials[matIndex],
customLayer.m_Transform.gameObject.layer,
null,
submeshIndex);
}
}
}
}
}
/// <summary>
/// Applies highlight materials to all the targeted renderers
/// </summary>
public void Highlight()
{
m_DelayedUnhighlight = false;
m_Highlighting = true;
UpdateMaterialCache();
foreach (var renderer in m_Renderers)
{
var rendererID = renderer.GetInstanceID();
renderer.materials = m_HighlightMaterials[rendererID];
}
foreach (var cacheUser in m_CacheUsers)
{
if (cacheUser != null)
cacheUser.OnHighlight();
}
}
/// <summary>
/// Restores the original materials to all the targeted renderers
/// </summary>
/// <param name="force">If true, the original materials are restored instantly. Otherwise, a fade can occur.</param>
public void Unhighlight(bool force = false)
{
UpdateMaterialCache();
var maxDelay = 0.0f;
foreach (var cacheUser in m_CacheUsers)
{
if (cacheUser != null)
maxDelay = Mathf.Max(cacheUser.OnUnhighlight());
}
if (maxDelay <= 0.0f)
{
foreach (var renderer in m_Renderers)
{
var rendererID = renderer.GetInstanceID();
renderer.materials = m_OriginalMaterials[rendererID];
}
m_Highlighting = false;
}
else
{
m_DelayedUnhighlight = true;
m_UnhighlightTimer = maxDelay;
}
}
void UpdateRendererCache()
{
m_Renderers.Clear();
m_Renderers.UnionWith(m_ManuallySetRenderers.Where(r => r != null));
switch (m_RendererCaptureDepth)
{
case RendererCaptureDepth.AllChildRenderers:
rendererSource.GetComponentsInChildren(true, k_RendererComponents);
foreach (var renderer in k_RendererComponents)
{
var textMesh = renderer.GetComponent<TextMesh>();
var meshFilter = renderer.GetComponent<MeshFilter>();
if (textMesh == null)
m_Renderers.Add(renderer);
if (meshFilter != null && meshFilter.mesh.subMeshCount > 1)
m_CustomLayerMaterials.Add(renderer.GetInstanceID(), new CustomHighlightLayer { m_SharedMesh = meshFilter.sharedMesh, m_Transform = renderer.transform });
}
k_RendererComponents.Clear();
break;
case RendererCaptureDepth.CurrentRenderer:
rendererSource.GetComponents(k_RendererComponents);
foreach (var renderer in k_RendererComponents)
{
var textMesh = renderer.GetComponent<TextMesh>();
var meshFilter = renderer.GetComponent<MeshFilter>();
if (textMesh == null)
m_Renderers.Add(renderer);
if (meshFilter != null && meshFilter.mesh.subMeshCount > 1)
m_CustomLayerMaterials.Add(renderer.GetInstanceID(), new CustomHighlightLayer { m_SharedMesh = meshFilter.sharedMesh, m_Transform = renderer.transform });
}
k_RendererComponents.Clear();
break;
case RendererCaptureDepth.ManualOnly:
break;
default:
Debug.LogError($"{rendererSource.name} highlight has an invalid renderer capture mode {m_RendererCaptureDepth}.", rendererSource);
break;
}
if (m_Renderers.Count == 0)
Debug.LogWarning($"{rendererSource.name} highlight has no renderers set.", rendererSource);
}
void UpdateMaterialCache()
{
foreach (var renderer in m_Renderers)
{
var rendererID = renderer.GetInstanceID();
if (m_OriginalMaterials.ContainsKey(rendererID))
continue;
var sharedMaterials = renderer.sharedMaterials;
var sharedLength = sharedMaterials.Length;
m_OriginalMaterials[rendererID] = sharedMaterials;
CustomHighlightLayer highlightLayer = null;
Material[] highlightMaterials;
Material[] layerMaterials;
var addOffset = sharedLength;
if (m_CustomLayerMaterials.TryGetValue(rendererID, out highlightLayer))
{
highlightMaterials = new Material[sharedLength];
highlightLayer.m_HighlightMaterials = new Material[m_MaterialAdditions];
layerMaterials = highlightLayer.m_HighlightMaterials;
addOffset = 0;
}
else
{
highlightMaterials = new Material[sharedLength + m_MaterialAdditions];
layerMaterials = highlightMaterials;
}
for (var matIndex = 0; matIndex < sharedLength; matIndex++)
{
highlightMaterials[matIndex] = sharedMaterials[matIndex];
}
for (var i = 0; i < m_CacheUsers.Count; i++)
{
var cacheUser = m_CacheUsers[i];
if (cacheUser.highlightMode == MaterialHighlightMode.Replace)
{
for (var matIndex = 0; matIndex < sharedLength; matIndex++)
{
highlightMaterials[matIndex] = cacheUser.highlightMaterial;
}
}
else
{
layerMaterials[addOffset] = cacheUser.highlightMaterial;
addOffset++;
}
}
m_HighlightMaterials[rendererID] = highlightMaterials;
}
}
}
}