using System.Collections.Generic; using UnityEngine; using System; using System.Security.Cryptography; namespace EPOOutline { public enum DilateRenderMode { PostProcessing, EdgeShift } public enum RenderStyle { Single = 1, FrontBack = 2 } [Flags] public enum OutlinableDrawingMode { Normal = 1, ZOnly = 2, GenericMask = 4, Obstacle = 8, Mask = 16 } [Flags] public enum RenderersAddingMode { All = -1, None = 0, MeshRenderer = 1, SkinnedMeshRenderer = 2, SpriteRenderer = 4, Others = 4096 } public enum BoundsMode { Default, ForceRecalculate, Manual } public enum ComplexMaskingMode { None, ObstaclesMode, MaskingMode } [ExecuteAlways] public class Outlinable : MonoBehaviour { private static List tempListeners = new List(); private static HashSet outlinables = new HashSet(); [System.Serializable] public class OutlineProperties { #pragma warning disable CS0649 [SerializeField] private bool enabled = true; public bool Enabled { get { return enabled; } set { enabled = value; } } [SerializeField] private Color color = Color.yellow; public Color Color { get { return color; } set { color = value; } } [SerializeField] [Range(0.0f, 1.0f)] private float dilateShift = 1.0f; public float DilateShift { get { return dilateShift; } set { dilateShift = value; } } [SerializeField] [Range(0.0f, 1.0f)] private float blurShift = 1.0f; public float BlurShift { get { return blurShift; } set { blurShift = value; } } [SerializeField, SerializedPassInfo("Fill style", "Hidden/EPO/Fill/")] private SerializedPass fillPass = new SerializedPass(); public SerializedPass FillPass { get { return fillPass; } } public void SetFillPass(SerializedPass pass) { fillPass = pass; } #pragma warning restore CS0649 } [SerializeField] private ComplexMaskingMode complexMaskingMode; [SerializeField] private OutlinableDrawingMode drawingMode = OutlinableDrawingMode.Normal; [SerializeField] private int outlineLayer = 0; [SerializeField] private List outlineTargets = new List(); [SerializeField] private RenderStyle renderStyle = RenderStyle.Single; #pragma warning disable CS0649 [SerializeField] private OutlineProperties outlineParameters = new OutlineProperties(); [SerializeField] private OutlineProperties backParameters = new OutlineProperties(); [SerializeField] private OutlineProperties frontParameters = new OutlineProperties(); private bool shouldValidateTargets = false; [SerializeField] private bool showOutlineTargets=false; #pragma warning restore CS0649 public bool ShowOutLineTargets { get { return showOutlineTargets; } set { showOutlineTargets = value; } } public RenderStyle RenderStyle { get { return renderStyle; } set { renderStyle = value; } } public ComplexMaskingMode ComplexMaskingMode { get { return complexMaskingMode; } set { complexMaskingMode = value; } } public bool ComplexMaskingEnabled { get { return complexMaskingMode != ComplexMaskingMode.None; } } public OutlinableDrawingMode DrawingMode { get { return drawingMode; } set { drawingMode = value; } } public int OutlineLayer { get { return outlineLayer; } set { outlineLayer = value; } } public IReadOnlyList OutlineTargets { get { return outlineTargets; } } public OutlineProperties OutlineParameters { get { return outlineParameters; } } public OutlineProperties BackParameters { get { return backParameters; } } public bool NeedFillMask { get { if ((drawingMode & OutlinableDrawingMode.Normal) == 0) return false; if (renderStyle == RenderStyle.FrontBack) return (frontParameters.Enabled || backParameters.Enabled) && (frontParameters.FillPass.Material != null || backParameters.FillPass.Material != null); else return false; } } public OutlineProperties FrontParameters { get { return frontParameters; } } public bool IsObstacle { get { return (drawingMode & OutlinableDrawingMode.Obstacle) != 0; } } public bool TryAddTarget(OutlineTarget target) { outlineTargets.Add(target); ValidateTargets(); return true; } public void RemoveTarget(OutlineTarget target) { outlineTargets.Remove(target); if (target.renderer != null) { var listener = target.renderer.GetComponent(); if (listener == null) return; listener.RemoveCallback(this, UpdateVisibility); } } public OutlineTarget this[int index] { get { return outlineTargets[index]; } set { outlineTargets[index] = value; ValidateTargets(); } } private void Reset() { AddAllChildRenderersToRenderingList(RenderersAddingMode.SkinnedMeshRenderer | RenderersAddingMode.MeshRenderer | RenderersAddingMode.SpriteRenderer); } private void OnValidate() { outlineLayer = Mathf.Clamp(outlineLayer, 0, 63); shouldValidateTargets = true; } private void SubscribeToVisibilityChange(GameObject go) { var listener = go.GetComponent(); if (listener == null) { listener = go.AddComponent(); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(listener); UnityEditor.EditorUtility.SetDirty(go); #endif } listener.RemoveCallback(this, UpdateVisibility); listener.AddCallback(this, UpdateVisibility); listener.ForceUpdate(); } private void UpdateVisibility() { if (!enabled) { outlinables.Remove(this); return; } outlineTargets.RemoveAll(x => x.renderer == null); foreach (var target in OutlineTargets) target.IsVisible = target.renderer.isVisible; outlineTargets.RemoveAll(x => x.renderer == null); foreach (var target in outlineTargets) { if (target.IsVisible) { outlinables.Add(this); return; } } outlinables.Remove(this); } private void OnEnable() { UpdateVisibility(); } private void OnDisable() { outlinables.Remove(this); } private void Awake() { ValidateTargets(); } private void ValidateTargets() { outlineTargets.RemoveAll(x => x.renderer == null); foreach (var target in outlineTargets) SubscribeToVisibilityChange(target.renderer.gameObject); } private void OnDestroy() { outlinables.Remove(this); } public static void GetAllActiveOutlinables(Camera camera, List outlinablesList) { outlinablesList.Clear(); foreach (var outlinable in outlinables) outlinablesList.Add(outlinable); } private int GetSubmeshCount(Renderer renderer) { if (renderer is MeshRenderer) return renderer.GetComponent().sharedMesh.subMeshCount; else if (renderer is SkinnedMeshRenderer) return (renderer as SkinnedMeshRenderer).sharedMesh.subMeshCount; else return 1; } [ContextMenu("ResetRenderer")] public void AddAllChildRenderersToRenderingList(RenderersAddingMode renderersAddingMode = RenderersAddingMode.All) { outlineTargets.Clear(); var renderers = GetComponentsInChildren(true); foreach (var renderer in renderers) { if (!MatchingMode(renderer, renderersAddingMode)) continue; var submeshesCount = GetSubmeshCount(renderer); for (var index = 0; index < submeshesCount; index++) TryAddTarget(new OutlineTarget(renderer, index)); } } private void Update() { if (!shouldValidateTargets) return; shouldValidateTargets = false; ValidateTargets(); } private bool MatchingMode(Renderer renderer, RenderersAddingMode mode) { return (!(renderer is MeshRenderer) && !(renderer is SkinnedMeshRenderer) && !(renderer is SpriteRenderer) && (mode & RenderersAddingMode.Others) != RenderersAddingMode.None) || (renderer is MeshRenderer && (mode & RenderersAddingMode.MeshRenderer) != RenderersAddingMode.None) || (renderer is SpriteRenderer && (mode & RenderersAddingMode.SpriteRenderer) != RenderersAddingMode.None) || (renderer is SkinnedMeshRenderer && (mode & RenderersAddingMode.SkinnedMeshRenderer) != RenderersAddingMode.None); } #if UNITY_EDITOR public void OnDrawGizmosSelected() { foreach (var target in outlineTargets) { if (target.Renderer == null || target.BoundsMode != BoundsMode.Manual) continue; Gizmos.matrix = target.Renderer.transform.localToWorldMatrix; Gizmos.color = new Color(1.0f, 0.5f, 0.0f, 0.2f); var size = target.Bounds.size; var scale = target.Renderer.transform.localScale; size.x /= scale.x; size.y /= scale.y; size.z /= scale.z; Gizmos.DrawCube(target.Bounds.center, size); Gizmos.DrawWireCube(target.Bounds.center, size); } } #endif } }