313 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			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;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |