// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
// See the LICENSE.md file in the project root for more information.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityFx.Outline
{
///
/// Renders outlines at specific camera. Should be attached to camera to function.
///
///
///
///
///
[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public sealed partial class OutlineEffect : MonoBehaviour
{
#region data
[SerializeField, Tooltip(OutlineResources.OutlineResourcesTooltip)]
private OutlineResources _outlineResources;
[SerializeField, Tooltip(OutlineResources.OutlineLayerCollectionTooltip)]
private OutlineLayerCollection _outlineLayers;
[SerializeField, HideInInspector]
private CameraEvent _cameraEvent = OutlineRenderer.RenderEvent;
private Camera _camera;
private CommandBuffer _commandBuffer;
private List _renderObjects = new List(16);
#endregion
#region interface
///
/// Gets or sets resources used by the effect implementation.
///
/// Thrown if setter argument is .
public OutlineResources OutlineResources
{
get
{
return _outlineResources;
}
set
{
if (value is null)
{
throw new ArgumentNullException(nameof(OutlineResources));
}
_outlineResources = value;
}
}
///
/// Gets collection of outline layers.
///
public OutlineLayerCollection OutlineLayers
{
get
{
return _outlineLayers;
}
set
{
_outlineLayers = value;
}
}
///
/// Gets outline layers (for internal use only).
///
internal OutlineLayerCollection OutlineLayersInternal => _outlineLayers;
///
/// Gets or sets used to render the outlines.
///
public CameraEvent RenderEvent
{
get
{
return _cameraEvent;
}
set
{
if (_cameraEvent != value)
{
if (_commandBuffer != null)
{
var camera = GetComponent();
if (camera)
{
camera.RemoveCommandBuffer(_cameraEvent, _commandBuffer);
camera.AddCommandBuffer(value, _commandBuffer);
}
}
_cameraEvent = value;
}
}
}
///
/// Adds the passed to the first outline layer. Creates the layer if needed.
///
/// The to add and render outline for.
///
public void AddGameObject(GameObject go)
{
AddGameObject(go, 0);
}
///
/// Adds the passed to the specified outline layer. Creates the layer if needed.
///
/// The to add and render outline for.
///
public void AddGameObject(GameObject go, int layerIndex)
{
if (layerIndex < 0)
{
throw new ArgumentOutOfRangeException("layerIndex");
}
CreateLayersIfNeeded();
while (_outlineLayers.Count <= layerIndex)
{
_outlineLayers.Add(new OutlineLayer());
}
_outlineLayers[layerIndex].Add(go);
}
///
/// Removes the specified from .
///
/// A to remove.
public void RemoveGameObject(GameObject go)
{
if (_outlineLayers)
{
_outlineLayers.Remove(go);
}
}
#endregion
#region MonoBehaviour
private void Awake()
{
OutlineResources.LogSrpNotSupported(this);
OutlineResources.LogPpNotSupported(this);
}
private void OnEnable()
{
InitCameraAndCommandBuffer();
}
private void OnDisable()
{
ReleaseCameraAndCommandBuffer();
}
private void OnPreRender()
{
FillCommandBuffer();
}
private void OnDestroy()
{
// TODO: Find a way to do this once per OutlineLayerCollection instance.
if (_outlineLayers)
{
_outlineLayers.Reset();
}
}
#if UNITY_EDITOR
//private void OnValidate()
//{
// InitCameraAndCommandBuffer();
// FillCommandBuffer();
//}
private void Reset()
{
_outlineLayers = null;
}
#endif
#endregion
#region implementation
private void InitCameraAndCommandBuffer()
{
_camera = GetComponent();
if (_camera && _commandBuffer is null)
{
_commandBuffer = new CommandBuffer
{
name = string.Format("{0} - {1}", GetType().Name, name)
};
_camera.depthTextureMode |= DepthTextureMode.Depth;
_camera.AddCommandBuffer(_cameraEvent, _commandBuffer);
}
}
private void ReleaseCameraAndCommandBuffer()
{
if (_commandBuffer != null)
{
if (_camera)
{
_camera.RemoveCommandBuffer(_cameraEvent, _commandBuffer);
}
_commandBuffer.Dispose();
_commandBuffer = null;
}
_camera = null;
}
private void FillCommandBuffer()
{
if (_camera && _outlineLayers && _commandBuffer != null)
{
_commandBuffer.Clear();
if (_outlineResources && _outlineResources.IsValid)
{
using (var renderer = new OutlineRenderer(_commandBuffer, _outlineResources, _camera.actualRenderingPath))
{
_renderObjects.Clear();
_outlineLayers.GetRenderObjects(_renderObjects);
renderer.Render(_renderObjects);
}
}
}
}
private void CreateLayersIfNeeded()
{
if (_outlineLayers is null)
{
_outlineLayers = ScriptableObject.CreateInstance();
_outlineLayers.name = "OutlineLayers";
}
}
#endregion
}
}