Files
FlaxEngine/Source/Editor/Gizmo/SelectionOutline.cs

191 lines
6.7 KiB
C#

// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEditor.Options;
using FlaxEditor.SceneGraph;
using FlaxEngine;
namespace FlaxEditor.Gizmo
{
/// <summary>
/// In-build postFx used to render outline for selected objects in editor.
/// </summary>
[HideInEditor]
public class SelectionOutline : PostProcessEffect
{
private Material _outlineMaterial;
private MaterialInstance _material;
private Color _color0, _color1;
private bool _enabled;
private bool _useEditorOptions;
/// <summary>
/// The cached actors list used for drawing (reusable to reduce memory allocations). Always cleared before and after objects rendering.
/// </summary>
protected List<Actor> _actors;
/// <summary>
/// The selection getter.
/// </summary>
public Func<List<SceneGraphNode>> SelectionGetter;
/// <summary>
/// Gets or sets a value indicating whether show selection outline effect.
/// </summary>
public bool ShowSelectionOutline
{
get => _enabled;
set => _enabled = value;
}
/// <summary>
/// Gets or sets the selection outline first color (top of the screen-space gradient).
/// </summary>
public Color SelectionOutlineColor0
{
get => _color0;
set => _color0 = value;
}
/// <summary>
/// Gets or sets the selection outline second color (bottom of the screen-space gradient).
/// </summary>
public Color SelectionOutlineColor1
{
get => _color1;
set => _color1 = value;
}
/// <summary>
/// Gets or sets a value indicating whether use editor options for selection outline color and visibility. Otherwise, if disabled it can be controlled from code.
/// </summary>
public bool UseEditorOptions
{
get => _useEditorOptions;
set
{
if (_useEditorOptions != value)
{
var options = Editor.Instance.Options;
if (_useEditorOptions)
options.OptionsChanged -= OnOptionsChanged;
_useEditorOptions = value;
if (_useEditorOptions)
options.OptionsChanged += OnOptionsChanged;
}
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SelectionOutline"/> class.
/// </summary>
public SelectionOutline()
{
Order = -90000; // Draw before any other editor shapes (except grid gizmo)
_outlineMaterial = FlaxEngine.Content.LoadAsyncInternal<Material>("Editor/Gizmo/SelectionOutlineMaterial");
if (_outlineMaterial)
{
_material = _outlineMaterial.CreateVirtualInstance();
}
else
{
Editor.LogWarning("Failed to load gizmo selection outline material");
}
_useEditorOptions = true;
var options = Editor.Instance.Options;
options.OptionsChanged += OnOptionsChanged;
OnOptionsChanged(options.Options);
}
private void OnOptionsChanged(EditorOptions options)
{
_enabled = options.Visual.ShowSelectionOutline;
_color0 = options.Visual.SelectionOutlineColor0;
_color1 = options.Visual.SelectionOutlineColor1;
}
/// <summary>
/// Gets a value indicating whether this instance has data ready.
/// </summary>
protected bool HasDataReady => _enabled && _material && _outlineMaterial.IsLoaded;
/// <inheritdoc />
public override bool CanRender()
{
return _enabled && _material && _outlineMaterial.IsLoaded && SelectionGetter().Count > 0;
}
/// <inheritdoc />
public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output)
{
Profiler.BeginEventGPU("Selection Outline");
// Pick a temporary depth buffer
var desc = GPUTextureDescription.New2D(input.Width, input.Height, PixelFormat.D32_Float, GPUTextureFlags.DepthStencil | GPUTextureFlags.ShaderResource);
var customDepth = RenderTargetPool.Get(desc);
context.ClearDepth(customDepth.View());
// Draw objects to depth buffer
if (_actors == null)
_actors = new List<Actor>();
else
_actors.Clear();
DrawSelectionDepth(context, renderContext.Task, customDepth);
_actors.Clear();
var near = renderContext.View.Near;
var far = renderContext.View.Far;
var projection = renderContext.View.Projection;
// Render outline
_material.SetParameterValue("OutlineColor0", _color0);
_material.SetParameterValue("OutlineColor1", _color1);
_material.SetParameterValue("CustomDepth", customDepth);
_material.SetParameterValue("ViewInfo", new Float4(1.0f / projection.M11, 1.0f / projection.M22, far / (far - near), (-far * near) / (far - near) / far));
Renderer.DrawPostFxMaterial(context, renderContext, _material, output, input.View());
// Cleanup
RenderTargetPool.Release(customDepth);
Profiler.EndEventGPU();
}
private void CollectActors(Actor actor)
{
_actors.Add(actor);
for (int i = 0; i < actor.ChildrenCount; i++)
CollectActors(actor.GetChild(i));
}
/// <summary>
/// Draws the selected object to depth buffer.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="task">The task.</param>
/// <param name="customDepth">The custom depth (output).</param>
protected virtual void DrawSelectionDepth(GPUContext context, SceneRenderTask task, GPUTexture customDepth)
{
// Get selected actors
var selection = SelectionGetter();
_actors.Capacity = Mathf.NextPowerOfTwo(Mathf.Max(_actors.Capacity, selection.Count));
for (int i = 0; i < selection.Count; i++)
{
if (selection[i] is ActorNode actorNode && actorNode.Actor != null)
CollectActors(actorNode.Actor);
}
if (_actors.Count == 0)
return;
// Render selected objects depth
Renderer.DrawSceneDepth(context, task, customDepth, _actors);
}
}
}