using System;
using System.Collections.Generic;
using UnityEngine;
namespace EPOOutline
{
///
/// Component which holds the outline settings for a specific object.
///
[ExecuteAlways]
public partial class Outlinable : MonoBehaviour
{
private static HashSet outlinables = new HashSet();
[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;
#pragma warning restore CS0649
///
/// to render the outlinable.
///
public RenderStyle RenderStyle
{
get => renderStyle;
set => renderStyle = value;
}
///
/// to render the outlinable.
///
public ComplexMaskingMode ComplexMaskingMode
{
get => complexMaskingMode;
set => complexMaskingMode = value;
}
///
/// to render the outlinable.
///
public OutlinableDrawingMode DrawingMode
{
get => drawingMode;
set => drawingMode = value;
}
///
/// The layer of the outlinable.
///
public int OutlineLayer
{
get => outlineLayer;
set => outlineLayer = value;
}
///
/// The list of the outline targets of the outlinable.
///
Note that it's immutable. To manage the targets, please use:
///
///
///
///
public IReadOnlyList OutlineTargets => outlineTargets;
///
/// The parameters of the outline that are used when is set to .
///
public OutlineProperties OutlineParameters => outlineParameters;
///
/// The parameters of the outline that are used for the front rendering when is set to .
///
public OutlineProperties FrontParameters => frontParameters;
///
/// The parameters of the outline that are used for the back rendering when is set to .
///
public OutlineProperties BackParameters => backParameters;
internal bool NeedsFillMask
{
get
{
if ((drawingMode & OutlinableDrawingMode.Normal) == 0)
return false;
if (renderStyle != RenderStyle.FrontBack)
return false;
return (frontParameters.Enabled || backParameters.Enabled) && (frontParameters.FillPass.Material != null || backParameters.FillPass.Material != null);
}
}
///
/// Adds renderer with all its sub-meshes to the targets list.
///
/// The renderer to be added to the list.
/// The optional function that provides the in case if some configuration is needed
public void AddRenderer(Renderer rendererToAdd, OutlineTargetProvider targetProvider = null)
{
var submeshCount = RendererUtility.GetSubmeshCount(rendererToAdd);
for (var index = 0; index < submeshCount; index++)
{
var target = targetProvider == null
? new OutlineTarget(rendererToAdd, index)
: targetProvider(rendererToAdd, index);
AddTarget(target);
}
}
[Obsolete("It's obsolete and will be removed. Use AddTarget instead")]
public void TryAddTarget(OutlineTarget target)
{
AddTarget(target);
}
///
/// Adds to the list of targets.
///
/// The target to add to the list.
public void AddTarget(OutlineTarget target)
{
outlineTargets.Add(target);
ValidateTargets();
}
///
/// Removes from the list of targets.
///
/// The target to remove.
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);
}
}
///
/// Gets or sets the at the specific index.
///
/// The index to get or set the target to/from.
public OutlineTarget this[int index]
{
get => outlineTargets[index];
set
{
outlineTargets[index] = value;
ValidateTargets();
}
}
///
/// Provides the outline targets count.
///
public int OutlineTargetsCount => outlineTargets.Count;
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);
for (var index = 0; index < OutlineTargets.Count; index++)
{
var target = OutlineTargets[index];
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(List outlinablesList)
{
outlinablesList.Clear();
foreach (var outlinable in outlinables)
outlinablesList.Add(outlinable);
}
///
/// Adds all child renderers to the rendering list of the outlinable.
///
/// that will be used during the adding process.
public void AddAllChildRenderersToRenderingList(RenderersAddingMode renderersAddingMode = RenderersAddingMode.All)
{
outlineTargets.Clear();
var renderers = GetComponentsInChildren(true);
foreach (var rendererToAdd in renderers)
{
if (!MatchingMode(rendererToAdd, renderersAddingMode))
continue;
var submeshesCount = RendererUtility.GetSubmeshCount(rendererToAdd);
for (var index = 0; index < submeshesCount; index++)
AddTarget(new OutlineTarget(rendererToAdd, index));
}
}
private void Update()
{
if (!shouldValidateTargets)
return;
shouldValidateTargets = false;
ValidateTargets();
}
private bool MatchingMode(Renderer rendererToMatch, RenderersAddingMode mode)
{
return
(!(rendererToMatch is MeshRenderer) && !(rendererToMatch is SkinnedMeshRenderer) && !(rendererToMatch is SpriteRenderer) && (mode & RenderersAddingMode.Others) != RenderersAddingMode.None) ||
(rendererToMatch is MeshRenderer && (mode & RenderersAddingMode.MeshRenderer) != RenderersAddingMode.None) ||
(rendererToMatch is SpriteRenderer && (mode & RenderersAddingMode.SpriteRenderer) != RenderersAddingMode.None) ||
(rendererToMatch is SkinnedMeshRenderer && (mode & RenderersAddingMode.SkinnedMeshRenderer) != RenderersAddingMode.None);
}
#if UNITY_EDITOR
public void OnDrawGizmosSelected()
{
foreach (var target in outlineTargets)
{
if (target.Renderer == null)
continue;
if (target.BoundsMode != BoundsMode.Manual)
continue;
var bounds = target.BoundsMode != BoundsMode.Manual ? target.Renderer.bounds : target.Bounds;
Gizmos.matrix = target.Renderer.transform.localToWorldMatrix;
Gizmos.color = new Color(1.0f, 0.5f, 0.0f, 0.2f);
var size = bounds.size;
if (target.BoundsMode == BoundsMode.Manual)
{
var scale = target.Renderer.transform.localScale;
size.x /= scale.x;
size.y /= scale.y;
size.z /= scale.z;
}
Gizmos.DrawCube(bounds.center, size);
Gizmos.DrawWireCube(bounds.center, size);
}
}
#endif
}
}