using System; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace HighlightingSystem { public enum LoopMode { Once, Loop, PingPong, ClampForever, } public enum Easing { Linear, QuadIn, QuadOut, QuadInOut, CubicIn, CubicOut, CubicInOut, SineIn, SineOut, SineInOut, } public enum RendererFilterMode { None, Include, Exclude, } [AddComponentMenu("Highlighting System/Highlighter", 0)] public class Highlighter : HighlighterCore { #region Constants protected const float HALFPI = Mathf.PI * 0.5f; #endregion #region Inspector Fields /* General */ /// /// When set to true - highlighting will be rendered on top of all other geometry. /// public bool overlay { get { return _overlay; } set { _overlay = value; } } /// /// Controls occluder mode. /// public bool occluder { get { return _occluder; } set { _occluder = value; } } /* Tween */ public bool tween { get { return _tween; } set { TweenSet(value); } } public Gradient tweenGradient { get { return _tweenGradient; } set { _tweenGradient = value; } } public float tweenDuration { get { return _tweenDuration; } set { _tweenDuration = value; ValidateRanges(); } } public float tweenDelay { get { return _tweenDelay; } set { _tweenDelay = value; } } public bool tweenUseUnscaledTime { get { return _tweenUseUnscaledTime; } set { if (_tweenUseUnscaledTime != value) { float delta = GetTweenTime() - _tweenStart; _tweenUseUnscaledTime = value; _tweenStart = GetTweenTime() - delta; } } } public LoopMode tweenLoop { get { return _tweenLoop; } set { _tweenLoop = value; } } public Easing tweenEasing { get { return _tweenEasing; } set { _tweenEasing = value; } } public bool tweenReverse { get { return _tweenReverse; } set { _tweenReverse = value; } } public int tweenRepeatCount { get { return _tweenRepeatCount; } set { _tweenRepeatCount = value; } } /* Constant */ public bool constant { get { return _constant; } set { ConstantSet(value); } } public Color constantColor { get { return _constantColor; } set { _constantColor = value; } } public float constantFadeTime { set { _constantFadeInTime = value; _constantFadeOutTime = value; ValidateRanges(); ConstantSet(); } } public float constantFadeInTime { get { return _constantFadeInTime; } set { _constantFadeInTime = value; ValidateRanges(); if (_constant) { ConstantSet(); } } } public float constantFadeOutTime { get { return _constantFadeOutTime; } set { _constantFadeOutTime = value; ValidateRanges(); if (!_constant) { ConstantSet(); } } } public bool constantUseUnscaledTime { get { return _constantUseUnscaledTime; } set { if (_constantUseUnscaledTime != value) { float delta = GetConstantTime() - _constantStart; _constantUseUnscaledTime = value; _constantStart = GetConstantTime() - delta; } } } public Easing constantEasing { get { return _constantEasing; } set { _constantEasing = value; } } public RendererFilterMode filterMode { get { return _filterMode; } set { if (_filterMode != value) { _filterMode = value; // Update highlighted Renderers SetDirty(); } } } /// /// Make sure to trigger SetDirty() after modifying this list /// public List filterList { get { return _filterList; } } protected override RendererFilter rendererFilterToUse { get { if (rendererFilter != null) { return rendererFilter; } else if (_filterMode == RendererFilterMode.None) { return globalRendererFilter != null ? globalRendererFilter : DefaultRendererFilter; } else if (_filterMode == RendererFilterMode.Include) { return TransformFilterInclude; } else if (_filterMode == RendererFilterMode.Exclude) { return TransformFilterExclude; } // Should never happen return DefaultRendererFilter; } } #endregion #region Protected Fields // General [SerializeField] protected bool _overlay; [SerializeField] protected bool _occluder; // Hover (do not serialize this!) protected Color _hoverColor = Color.red; protected int _hoverFrame = -1; // Tween [SerializeField] protected bool _tween = false; [SerializeField] protected Gradient _tweenGradient = new Gradient() { colorKeys = new GradientColorKey[] { new GradientColorKey(new Color(0f, 1f, 1f, 1f), 0f), new GradientColorKey(new Color(0f, 1f, 1f, 1f), 1f), }, alphaKeys = new GradientAlphaKey[] { new GradientAlphaKey(0f, 0f), new GradientAlphaKey(1f, 1f), } }; [SerializeField] protected float _tweenDuration = 1f; [SerializeField] protected bool _tweenReverse = false; [SerializeField] protected LoopMode _tweenLoop = LoopMode.PingPong; [SerializeField] protected Easing _tweenEasing = Easing.Linear; [SerializeField] protected float _tweenDelay = 0f; [SerializeField] protected int _tweenRepeatCount = -1; [SerializeField] protected bool _tweenUseUnscaledTime = false; // Constant highlighting mode (with optional fade in/out transition) [SerializeField] protected bool _constant = false; [SerializeField] protected Color _constantColor = Color.yellow; [SerializeField] protected float _constantFadeInTime = 0.1f; [SerializeField] protected float _constantFadeOutTime = 0.25f; [SerializeField] protected Easing _constantEasing = Easing.Linear; [SerializeField] protected bool _constantUseUnscaledTime = false; [SerializeField] protected RendererFilterMode _filterMode = RendererFilterMode.None; [SerializeField] protected List _filterList = new List(); // Runtime only (do not serialize this!) protected bool _tweenEnabled; protected float _tweenStart; // Time when the tween has started protected bool _constantEnabled; protected float _constantStart; protected float _constantDuration; #endregion #region Protected Accessors protected bool hover { get { return _hoverFrame == Time.frameCount; } set { _hoverFrame = value ? Time.frameCount : -1; } } // constantValue is always increasing from 0 to 1 (for both fade in and out transitions) protected float constantValue { get { return _constantDuration > 0f ? Mathf.Clamp01((GetConstantTime() - _constantStart) / _constantDuration) : 1f; } } #endregion #region MonoBehaviour // protected virtual void OnDidApplyAnimationProperties() { ValidateAll(); } #if UNITY_EDITOR // void OnValidate() { ValidateAll(); } // void Reset() { ValidateAll(); } #endif #endregion #region MonoBehaviour Overrides // protected override void OnEnableSafe() { ValidateAll(); } // protected override void OnDisableSafe() { // Make sure transition won't continue if component will be re-enabled _tweenEnabled = false; _constantEnabled = false; _constantStart = GetConstantTime() - _constantDuration; } #endregion #region Validation // protected void ValidateAll() { ValidateRanges(); TweenSet(); ConstantSet(); SetDirty(); } // protected void ValidateRanges() { if (_tweenDuration < 0f) { _tweenDuration = 0f; } if (_constantFadeInTime < 0f) { _constantFadeInTime = 0f; } if (_constantFadeOutTime < 0f) { _constantFadeInTime = 0f; } } #endregion #region Public Methods /// /// Turn on one-frame highlighting with specified color. /// Can be called multiple times per update, color only from the latest call will be used. /// /// /// Highlighting color. /// public void Hover(Color color) { _hoverColor = color; hover = true; } /// /// Fade in constant highlighting using specified transition duration. /// /// /// Transition time. /// public void ConstantOn(float fadeTime = 0.25f) { ConstantSet(fadeTime, true); } /// /// Fade in constant highlighting using specified color and transition duration. /// /// /// Constant highlighting color. /// /// /// Transition duration. /// public void ConstantOn(Color color, float fadeTime = 0.25f) { _constantColor = color; ConstantSet(fadeTime, true); } /// /// Fade out constant highlighting using specified transition duration. /// /// /// Transition time. /// public void ConstantOff(float fadeTime = 0.25f) { ConstantSet(fadeTime, false); } /// /// Switch constant highlighting using specified transition duration. /// /// /// Transition time. /// public void ConstantSwitch(float fadeTime = 0.25f) { ConstantSet(fadeTime, !_constant); } /// /// Turn on constant highlighting immediately (without fading in). /// public void ConstantOnImmediate() { ConstantSet(0f, true); } /// /// Turn on constant highlighting using specified color immediately (without fading in). /// /// /// Constant highlighting color. /// public void ConstantOnImmediate(Color color) { _constantColor = color; ConstantSet(0f, true); } /// /// Turn off constant highlighting immediately (without fading out). /// public void ConstantOffImmediate() { ConstantSet(0f, false); } /// /// Switch constant highlighting immediately (without fading in/out). /// public void ConstantSwitchImmediate() { ConstantSet(0f, !_constant); } /// /// Turn off all types of highlighting (occlusion mode remains intact). /// public void Off() { hover = false; TweenSet(false); ConstantSet(0f, false); } // public void TweenStart() { TweenSet(true); } // public void TweenStop() { TweenSet(false); } // Updates tween state. (!) Sets _tween value. public void TweenSet(bool value) { _tween = value; if (_tweenEnabled != _tween) { _tweenEnabled = _tween; _tweenStart = GetTweenTime(); } } // Updates constant state. (!) Sets _constant value. public void ConstantSet(float fadeTime, bool value) { // Order matters. Should always set _constantDuration prior to _constantEnabled // Transition duration if (fadeTime < 0f) { fadeTime = 0f; } if (_constantDuration != fadeTime) { float timeNow = GetConstantTime(); // Recalculate start time if duration changed _constantStart = _constantDuration > 0f ? timeNow - (fadeTime / _constantDuration) * (timeNow - _constantStart) : timeNow - fadeTime; _constantDuration = fadeTime; } // Transition target _constant = value; if (_constantEnabled != _constant) { _constantEnabled = _constant; // Recalculate start time if value changed _constantStart = GetConstantTime() - _constantDuration * (1f - constantValue); } } #endregion #region Protected Methods // Helper protected void TweenSet() { TweenSet(_tween); } // Helper protected void ConstantSet() { ConstantSet(_constant); } // Helper protected void ConstantSet(bool value) { ConstantSet(value ? constantFadeInTime : _constantFadeOutTime, value); } // Updates highlighting color protected override void UpdateHighlighting() { // Hover if (hover) { color = _hoverColor; mode = _overlay ? HighlighterMode.Overlay : HighlighterMode.Default; return; } // Tween if (_tweenEnabled) { float delta = GetTweenTime() - (_tweenStart + _tweenDelay); if (delta >= 0f) { float t = _tweenDuration > 0f ? delta / _tweenDuration : 0f; t = Loop(t, _tweenLoop, _tweenReverse, _tweenRepeatCount); if (t >= 0f) { t = Ease(t, _tweenEasing); color = _tweenGradient.Evaluate(t); mode = _overlay ? HighlighterMode.Overlay : HighlighterMode.Default; return; } } } // Constant float c = _constantEnabled ? constantValue : 1f - constantValue; if (c > 0f) { c = Ease(c, _constantEasing); color = _constantColor; color.a *= c; mode = _overlay ? HighlighterMode.Overlay : HighlighterMode.Default; return; } // Occluder if (_occluder) { mode = HighlighterMode.Occluder; return; } // Disabled mode = HighlighterMode.Disabled; } #endregion #region Protected Methods // protected bool TransformFilterInclude(Renderer renderer, List submeshIndices) { Transform child = renderer.transform; for (int i = 0; i < _filterList.Count; i++) { Transform parent = _filterList[i]; if (parent == null) { continue; } // Highlight if child of inclusion list transform if (child.IsChildOf(parent)) { // Highlight all submeshes submeshIndices.Add(-1); return true; } } return false; } // protected bool TransformFilterExclude(Renderer renderer, List submeshIndices) { Transform child = renderer.transform; for (int i = 0; i < _filterList.Count; i++) { Transform parent = _filterList[i]; if (parent == null) { continue; } // Do not highlight if child of exclusion list transform if (child.IsChildOf(parent)) { return false; } } // Highlight all submeshes submeshIndices.Add(-1); return true; } // protected float Loop(float x, LoopMode loop, bool reverse = false, int repeatCount = -1) { float y = -1f; switch (loop) { default: case LoopMode.Once: if (x >= 0f && x <= 1f) { y = x; } break; case LoopMode.Loop: int n1 = Mathf.FloorToInt(x); if (repeatCount < 0 || n1 < repeatCount) { y = x - n1; } break; case LoopMode.PingPong: // Performs 0-1-0 transition in tweenDuration time //int n2 = Mathf.FloorToInt(x); //if (repeatCount < 0 || n2 < repeatCount) //{ // y = 1f - Mathf.Abs((x - n2) * 2f - 1f); //} // Performs 0-1-0 transition in 2 * tweenDuration time, // so the 0-1 transition is performed with the same speed as in Loop mode. int n2 = Mathf.FloorToInt(x * 0.5f); if (repeatCount < 0 || n2 < repeatCount) { y = 1f - Mathf.Abs(x - n2 * 2f - 1f); } break; case LoopMode.ClampForever: y = Mathf.Clamp01(x); break; } // Reverse. Check y for positive value since there is no loop if it's negative. if (y >= 0f && reverse) { y = 1f - y; } return y; } // protected float Ease(float x, Easing easing) { x = Mathf.Clamp01(x); float y; switch (easing) { // Linear default: case Easing.Linear: y = x; break; // Quad case Easing.QuadIn: y = x * x; break; case Easing.QuadOut: y = -x * (x - 2f); break; case Easing.QuadInOut: y = x < 0.5f ? 2f * x * x : 2f * x * (2f - x) - 1f; break; // Cubic case Easing.CubicIn: y = x * x * x; break; case Easing.CubicOut: x = x - 1f; y = x * x * x + 1f; break; case Easing.CubicInOut: if (x < 0.5f) { y = 4f * x * x * x; } else { x = 2f * x - 2f; y = 0.5f * (x * x * x + 2f); } break; // Sine case Easing.SineIn: y = 1f - Mathf.Cos(x * HALFPI); break; case Easing.SineOut: y = Mathf.Sin(x * HALFPI); break; case Easing.SineInOut: y = -0.5f * (Mathf.Cos(x * Mathf.PI) - 1f); break; //y = x * x * (3f - 2f * x); } return y; } // protected float GetTweenTime() { return _tweenUseUnscaledTime ? Time.unscaledTime : Time.time; } // protected float GetConstantTime() { return _constantUseUnscaledTime ? Time.unscaledTime : Time.time; } #endregion #region Static Methods // Color helper static public Color HSVToRGB(float hue, float saturation, float value) { float x = 6f * Mathf.Clamp01(hue); saturation = Mathf.Clamp01(saturation); value = Mathf.Clamp01(value); return new Color ( value * (1f + (Mathf.Clamp01(Mathf.Max(2f - x, x - 4f)) - 1f) * saturation), value * (1f + (Mathf.Clamp01(Mathf.Min(x, 4f - x)) - 1f) * saturation), value * (1f + (Mathf.Clamp01(Mathf.Min(x - 2f, 6f - x)) - 1f) * saturation) ); } #endregion #region DEPRECATED private GradientColorKey[] flashingColorKeys = new GradientColorKey[] { new GradientColorKey(new Color(0f, 1f, 1f, 0f), 0f), new GradientColorKey(new Color(0f, 1f, 1f, 1f), 1f) }; private GradientAlphaKey[] flashingAlphaKeys = new GradientAlphaKey[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f), }; /// /// DEPRECATED. Use hover = true; instead. /// [System.Obsolete] public void On() { hover = true; } /// /// DEPRECATED. Use Hover(Color color) instead. /// [System.Obsolete] public void On(Color color) { Hover(color); } /// /// DEPRECATED. Use Hover(new Color(1f, 0f, 0f, 1f)); instead. /// [System.Obsolete] public void OnParams(Color color) { _hoverColor = color; } /// /// DEPRECATED. Use constantColor = new Color(0f, 1f, 1f, 1f); instead. /// [System.Obsolete] public void ConstantParams(Color color) { _constantColor = color; } /// /// DEPRECATED. Use tweenGradient and tweenDuration instead. /// [System.Obsolete] public void FlashingParams(Color color1, Color color2, float freq) { flashingColorKeys[0].color = color1; flashingColorKeys[1].color = color2; _tweenGradient.SetKeys(flashingColorKeys, flashingAlphaKeys); _tweenDuration = 1f / freq; } /// /// DEPRECATED. Use TweenStart() instead. /// [System.Obsolete] public void FlashingOn() { TweenSet(true); } /// /// DEPRECATED. Use tweenGradient instead. /// [System.Obsolete] public void FlashingOn(Color color1, Color color2) { flashingColorKeys[0].color = color1; flashingColorKeys[1].color = color2; _tweenGradient.SetKeys(flashingColorKeys, flashingAlphaKeys); TweenSet(true); } /// /// DEPRECATED. Use tweenGradient and tweenDuration instead. /// [System.Obsolete] public void FlashingOn(Color color1, Color color2, float freq) { flashingColorKeys[0].color = color1; flashingColorKeys[1].color = color2; _tweenGradient.SetKeys(flashingColorKeys, flashingAlphaKeys); _tweenDuration = 1f / freq; TweenSet(true); } /// /// DEPRECATED. Use tweenDuration instead. /// [System.Obsolete] public void FlashingOn(float freq) { _tweenDuration = 1f / freq; TweenSet(true); } /// /// DEPRECATED. Use TweenStop() instead. /// [System.Obsolete] public void FlashingOff() { TweenSet(false); } /// /// DEPRECATED. Use TweenStart() and TweenStop() instead. /// [System.Obsolete] public void FlashingSwitch() { tween = !tween; } #endregion } }