324 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			324 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| namespace UnityEngine.XR.Content.Rendering
 | |
| {
 | |
|     /// <summary>
 | |
|     /// Draws an outline on an object when highlighting. Can either transition the color and or size of the out line
 | |
|     /// as selected or be instant on.
 | |
|     /// </summary>
 | |
|     public class OutlineHighlight : MonoBehaviour, IMaterialHighlight
 | |
|     {
 | |
|         enum OutlineSource
 | |
|         {
 | |
|             Shader = 0,
 | |
|             Material
 | |
|         }
 | |
| 
 | |
|         const float k_OutlineWidth = 0.005f;
 | |
|         const string k_ShaderColorParameter = "_Color";
 | |
|         const string k_ShaderWidthParameter = "g_flOutlineWidth";
 | |
|         static readonly int k_GFlOutlineWidth = Shader.PropertyToID(k_ShaderWidthParameter);
 | |
|         static readonly int k_Color = Shader.PropertyToID(k_ShaderColorParameter);
 | |
| 
 | |
| #pragma warning disable 649
 | |
|         [SerializeField]
 | |
|         [Tooltip("How the highlight material will be applied to the renderer's material array.")]
 | |
|         MaterialHighlightMode m_HighlightMode = MaterialHighlightMode.Replace;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Selects source for the highlight material. Either using a shader or material.")]
 | |
|         OutlineSource m_OutlineSource = OutlineSource.Shader;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Outline highlight shader to use for highlight material.")]
 | |
|         Shader m_Shader;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Material used for drawing the outline highlight.")]
 | |
|         Material m_HighlightMaterial;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Transition outline width over time")]
 | |
|         bool m_TransitionWidth;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("The outline width used if no transition or the end value for transition width of outline")]
 | |
|         [Range(0f, 1f)]
 | |
|         float m_OutlineScale = 1f;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Starting value for transition width of outline")]
 | |
|         [Range(0f, 1f)]
 | |
|         float m_StartingOutlineScale;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Transition outline color over time")]
 | |
|         bool m_TransitionColor;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("The outline color used if no transition or the end value for transition color of outline")]
 | |
|         Color m_OutlineColor = new Color(0.3f, 0.6f, 1f, 1f);
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Starting value for transition color of outline")]
 | |
|         Color m_StartingOutlineColor = Color.black;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Time it takes to transition from start to end on highlight")]
 | |
|         float m_TransitionDuration = 0.3f;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Use material values for starting color and width")]
 | |
|         bool m_StartWithMaterialValues;
 | |
| #pragma warning restore 649
 | |
| 
 | |
|         Material m_InstanceOutlineMaterial;
 | |
| 
 | |
|         bool m_Animating = false;
 | |
|         bool m_AnimatingIn = false;
 | |
|         float m_TransitionTimer = 0.0f;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Time it takes to transition from start to end on highlight
 | |
|         /// </summary>
 | |
|         public float transitionDuration
 | |
|         {
 | |
|             get => m_TransitionDuration;
 | |
|             set => m_TransitionDuration = value;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Transition outline width over time
 | |
|         /// </summary>
 | |
|         public bool transitionWidth
 | |
|         {
 | |
|             get => m_TransitionWidth;
 | |
|             set => m_TransitionWidth = value;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Transition outline color over time
 | |
|         /// </summary>
 | |
|         public bool transitionColor
 | |
|         {
 | |
|             get => m_TransitionColor;
 | |
|             set => m_TransitionColor = value;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// The outline color used if no transition or the end value for transition color of outline
 | |
|         /// </summary>
 | |
|         public Color outlineColor
 | |
|         {
 | |
|             get => m_OutlineColor;
 | |
|             set => m_OutlineColor = value;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Starting value for transition color of outline
 | |
|         /// </summary>
 | |
|         public Color startingOutlineColor
 | |
|         {
 | |
|             get => m_StartingOutlineColor;
 | |
|             set => m_StartingOutlineColor = value;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// The outline width used if no transition or the end value for transition width of outline
 | |
|         /// </summary>
 | |
|         public float outlineScale
 | |
|         {
 | |
|             get => m_OutlineScale;
 | |
|             set => m_OutlineScale = value;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Starting value for transition width of outline
 | |
|         /// </summary>
 | |
|         public float startingOutlineScale
 | |
|         {
 | |
|             get => m_StartingOutlineScale;
 | |
|             set => m_StartingOutlineScale = value;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// A 0-1 relative outline scale that takes into account the ideal base outline width,
 | |
|         /// multiplied by the user specified value. This allows for more intuitive adjustment of the value.
 | |
|         /// This is the value used if there is no transition otherwise this is the end value of a transition.
 | |
|         /// </summary>
 | |
|         float relativeOutlineScale => outlineScale * k_OutlineWidth;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// A 0-1 relative outline scale that takes into account the ideal base outline width,
 | |
|         /// multiplied by the user specified value. This allows for more intuitive adjustment of the value.
 | |
|         /// This is the start value of a transition otherwise this value is not used.
 | |
|         /// </summary>
 | |
|         float startingRelativeOutlineScale => startingOutlineScale * k_OutlineWidth;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// How the highlight material will be applied to the renderer's material array.
 | |
|         /// </summary>
 | |
|         public MaterialHighlightMode highlightMode
 | |
|         {
 | |
|             get => m_HighlightMode;
 | |
|             set => m_HighlightMode = value;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Material to use for highlighting
 | |
|         /// </summary>
 | |
|         public Material highlightMaterial => m_InstanceOutlineMaterial;
 | |
| 
 | |
|         void IMaterialHighlight.Initialize()
 | |
|         {
 | |
|             InstantiateHighlightMaterial();
 | |
| 
 | |
|             if (m_StartWithMaterialValues)
 | |
|             {
 | |
|                 startingOutlineScale = m_HighlightMaterial.GetFloat(k_GFlOutlineWidth) / k_OutlineWidth;
 | |
|                 startingOutlineColor = m_HighlightMaterial.GetColor(k_Color);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void IMaterialHighlight.Deinitialize()
 | |
|         {
 | |
|             if (m_InstanceOutlineMaterial)
 | |
|             {
 | |
|                 Destroy(m_InstanceOutlineMaterial);
 | |
|                 m_InstanceOutlineMaterial = null;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         protected void OnDestroy()
 | |
|         {
 | |
|             ((IMaterialHighlight)(this)).Deinitialize();
 | |
|         }
 | |
| 
 | |
|         protected void Update()
 | |
|         {
 | |
|             if (m_Animating)
 | |
|             {
 | |
|                 m_TransitionTimer += Time.unscaledDeltaTime;
 | |
| 
 | |
|                 var transitionPercent = Mathf.Clamp01(m_TransitionTimer / m_TransitionDuration);
 | |
|                 var alpha = m_AnimatingIn ? transitionPercent : (1.0f - transitionPercent);
 | |
| 
 | |
|                 if (m_TransitionWidth)
 | |
|                 {
 | |
|                     var size = Mathf.Lerp(startingRelativeOutlineScale, relativeOutlineScale, alpha);
 | |
|                     m_InstanceOutlineMaterial.SetFloat(k_GFlOutlineWidth, size);
 | |
|                 }
 | |
| 
 | |
|                 if (m_TransitionColor)
 | |
|                 {
 | |
|                     var color = Color.Lerp(startingOutlineColor, outlineColor, alpha);
 | |
|                     m_InstanceOutlineMaterial.SetColor(k_Color, color);
 | |
|                 }
 | |
| 
 | |
|                 if (transitionPercent >= 1.0f)
 | |
|                 {
 | |
|                     m_TransitionTimer = 0.0f;
 | |
|                     m_Animating = false;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void IMaterialHighlight.OnHighlight()
 | |
|         {
 | |
|             if (m_InstanceOutlineMaterial == null)
 | |
|                 return;
 | |
| 
 | |
|             PlayPulseAnimation();
 | |
|         }
 | |
| 
 | |
|         float IMaterialHighlight.OnUnhighlight()
 | |
|         {
 | |
|             if (m_InstanceOutlineMaterial == null)
 | |
|                 return 0.0f;
 | |
| 
 | |
|             PlayPulseAnimation(false);
 | |
| 
 | |
|             if (!m_TransitionWidth && !m_TransitionColor || Mathf.Approximately(m_TransitionDuration, 0f))
 | |
|                 return 0.0f;
 | |
|             else
 | |
|                 return m_TransitionDuration;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Pulses the highlight - even if it is already active
 | |
|         /// </summary>
 | |
|         /// <param name="pulseUp">Whether the highlight is fading in or out</param>
 | |
|         public void PlayPulseAnimation(bool pulseUp = true)
 | |
|         {
 | |
|             if (!m_TransitionWidth && !m_TransitionColor || Mathf.Approximately(m_TransitionDuration, 0f))
 | |
|             {
 | |
|                 m_InstanceOutlineMaterial.SetFloat(k_GFlOutlineWidth, relativeOutlineScale);
 | |
|                 m_InstanceOutlineMaterial.SetColor(k_Color, m_OutlineColor);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 // If the same animation is already occurring, we just let it play.  If it is playing backwards, we seamlessly transition
 | |
|                 if (m_Animating)
 | |
|                 {
 | |
|                     if (m_AnimatingIn != pulseUp)
 | |
|                     {
 | |
|                         m_TransitionTimer = 1.0f - m_TransitionTimer;
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     m_Animating = true;
 | |
|                     m_AnimatingIn = pulseUp;
 | |
|                     m_TransitionTimer = 0.0f;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void InstantiateHighlightMaterial()
 | |
|         {
 | |
|             if (m_Shader == null && m_HighlightMaterial == null)
 | |
|             {
 | |
|                 Debug.LogError($"{gameObject.name} has no highlight material or shader set!", this);
 | |
|                 enabled = false;
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const string outlineMaterialName = "Outline Material Instance";
 | |
| 
 | |
|             switch (m_OutlineSource)
 | |
|             {
 | |
|                 case OutlineSource.Material:
 | |
|                     if (m_HighlightMaterial == null)
 | |
|                     {
 | |
|                         Debug.LogError($"{gameObject.name} Outline highlight has no material assigned. Please assign outline material.", this);
 | |
|                         enabled = false;
 | |
|                         break;
 | |
|                     }
 | |
| 
 | |
|                     m_InstanceOutlineMaterial = new Material(m_HighlightMaterial) { name = outlineMaterialName };
 | |
|                     break;
 | |
|                 case OutlineSource.Shader:
 | |
|                     if (m_Shader == null)
 | |
|                     {
 | |
|                         Debug.LogError($"{gameObject.name} Outline highlight has no shader assigned. Please assign outline shader. ", this);
 | |
|                         enabled = false;
 | |
|                         break;
 | |
|                     }
 | |
| 
 | |
|                     m_InstanceOutlineMaterial = new Material(m_Shader) { name = outlineMaterialName };
 | |
|                     break;
 | |
|                 default:
 | |
|                     Debug.LogError($"{gameObject.name} Outline highlight has an invalid highlight mode {m_OutlineSource}.", this);
 | |
|                     enabled = false;
 | |
|                     break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         protected void OnValidate()
 | |
|         {
 | |
|             if (m_TransitionDuration < 0f)
 | |
|             {
 | |
|                 m_TransitionDuration = 0f;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |