// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using FlaxEditor.Options; using FlaxEditor.SceneGraph; using FlaxEngine; namespace FlaxEditor.Gizmo { /// /// In-build postFx used to render outline for selected objects in editor. /// [HideInEditor] public class SelectionOutline : PostProcessEffect { private Material _outlineMaterial; private MaterialInstance _material; private Color _color0, _color1; private bool _enabled; private bool _useEditorOptions; /// /// The cached actors list used for drawing (reusable to reduce memory allocations). Always cleared before and after objects rendering. /// protected List _actors; /// /// The selection getter. /// public Func> SelectionGetter; /// /// Gets or sets a value indicating whether show selection outline effect. /// public bool ShowSelectionOutline { get => _enabled; set => _enabled = value; } /// /// Gets or sets the selection outline first color (top of the screen-space gradient). /// public Color SelectionOutlineColor0 { get => _color0; set => _color0 = value; } /// /// Gets or sets the selection outline second color (bottom of the screen-space gradient). /// public Color SelectionOutlineColor1 { get => _color1; set => _color1 = value; } /// /// 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. /// 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; } } } /// /// Initializes a new instance of the class. /// public SelectionOutline() { _outlineMaterial = FlaxEngine.Content.LoadAsyncInternal("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; } /// /// Gets a value indicating whether this instance has data ready. /// protected bool HasDataReady => _enabled && _material && _outlineMaterial.IsLoaded; /// public override bool CanRender() { return _enabled && _material && _outlineMaterial.IsLoaded && SelectionGetter().Count > 0; } /// 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(ref desc); context.ClearDepth(customDepth.View()); // Draw objects to depth buffer if (_actors == null) _actors = new List(); 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, ref 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)); } /// /// Draws the selected object to depth buffer. /// /// The context. /// The task. /// The custom depth (output). 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); } } }