// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.Linq; using FlaxEditor.Content; using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Modes; using FlaxEditor.Viewport.Widgets; using FlaxEditor.Windows; using FlaxEngine; using FlaxEngine.GUI; using Object = FlaxEngine.Object; namespace FlaxEditor.Viewport { /// /// Main editor gizmo viewport used by the . /// /// public class MainEditorGizmoViewport : EditorGizmoViewport, IEditorPrimitivesOwner { private readonly Editor _editor; private readonly ContextMenuButton _showGridButton; private readonly ContextMenuButton _showNavigationButton; private readonly ViewportWidgetButton _gizmoModeTranslate; private readonly ViewportWidgetButton _gizmoModeRotate; private readonly ViewportWidgetButton _gizmoModeScale; private readonly ViewportWidgetButton _translateSnapping; private readonly ViewportWidgetButton _rotateSnapping; private readonly ViewportWidgetButton _scaleSnapping; private SelectionOutline _customSelectionOutline; /// /// The editor sprites rendering effect. /// /// [HideInEditor] public class EditorSpritesRenderer : PostProcessEffect { /// /// The rendering task. /// public SceneRenderTask Task; /// public EditorSpritesRenderer() { Order = -10000000; UseSingleTarget = true; } /// public override bool CanRender() { return (Task.View.Flags & ViewFlags.EditorSprites) == ViewFlags.EditorSprites && Level.ScenesCount != 0 && base.CanRender(); } /// public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output) { Profiler.BeginEventGPU("Editor Primitives"); // Prepare var renderList = RenderList.GetFromPool(); var prevList = renderContext.List; renderContext.List = renderList; renderContext.View.Pass = DrawPass.Forward; // Bind output float width = input.Width; float height = input.Height; context.SetViewport(width, height); var depthBuffer = renderContext.Buffers.DepthBuffer; var depthBufferHandle = depthBuffer.View(); if ((depthBuffer.Flags & GPUTextureFlags.ReadOnlyDepthView) == GPUTextureFlags.ReadOnlyDepthView) depthBufferHandle = depthBuffer.ViewReadOnlyDepth(); context.SetRenderTarget(depthBufferHandle, input.View()); // Collect draw calls Draw(ref renderContext); // Sort draw calls renderList.SortDrawCalls(ref renderContext, true, DrawCallsListType.Forward); // Perform the rendering renderList.ExecuteDrawCalls(ref renderContext, DrawCallsListType.Forward); // Cleanup RenderList.ReturnToPool(renderList); renderContext.List = prevList; Profiler.EndEventGPU(); } /// /// Draws the icons. /// protected virtual void Draw(ref RenderContext renderContext) { for (int i = 0; i < Level.ScenesCount; i++) { var scene = Level.GetScene(i); ViewportIconsRenderer.DrawIcons(ref renderContext, scene); } } } private bool _lockedFocus; private double _lockedFocusOffset; private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32); private EditorSpritesRenderer _editorSpritesRenderer; /// /// Drag and drop handlers /// public readonly ViewportDragHandlers DragHandlers; /// /// The transform gizmo. /// public readonly TransformGizmo TransformGizmo; /// /// The grid gizmo. /// public readonly GridGizmo Grid; /// /// The selection outline postFx. /// public SelectionOutline SelectionOutline; /// /// The editor primitives postFx. /// public EditorPrimitives EditorPrimitives; /// /// Gets or sets a value indicating whether draw shapes. /// public bool DrawDebugDraw = true; /// /// Gets the debug draw data for the viewport. /// public ViewportDebugDrawData DebugDrawData => _debugDrawData; /// /// Gets or sets a value indicating whether show navigation mesh. /// public bool ShowNavigation { get => _showNavigationButton.Checked; set => _showNavigationButton.Checked = value; } /// /// The sculpt terrain gizmo. /// public Tools.Terrain.SculptTerrainGizmoMode SculptTerrainGizmo; /// /// The paint terrain gizmo. /// public Tools.Terrain.PaintTerrainGizmoMode PaintTerrainGizmo; /// /// The edit terrain gizmo. /// public Tools.Terrain.EditTerrainGizmoMode EditTerrainGizmo; /// /// The paint foliage gizmo. /// public Tools.Foliage.PaintFoliageGizmoMode PaintFoliageGizmo; /// /// The edit foliage gizmo. /// public Tools.Foliage.EditFoliageGizmoMode EditFoliageGizmo; /// /// Initializes a new instance of the class. /// /// Editor instance. public MainEditorGizmoViewport(Editor editor) : base(Object.New(), editor.Undo, editor.Scene.Root) { _editor = editor; DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem); var inputOptions = editor.Options.Options.Input; // Prepare rendering task Task.ActorsSource = ActorsSources.Scenes; Task.ViewFlags = ViewFlags.DefaultEditor; Task.Begin += OnBegin; Task.CollectDrawCalls += OnCollectDrawCalls; Task.PostRender += OnPostRender; // Render task after the main game task so streaming and render state data will use main game task instead of editor preview Task.Order = 1; // Create post effects SelectionOutline = Object.New(); SelectionOutline.SelectionGetter = () => TransformGizmo.SelectedParents; Task.AddCustomPostFx(SelectionOutline); EditorPrimitives = Object.New(); EditorPrimitives.Viewport = this; Task.AddCustomPostFx(EditorPrimitives); _editorSpritesRenderer = Object.New(); _editorSpritesRenderer.Task = Task; Task.AddCustomPostFx(_editorSpritesRenderer); // Add transformation gizmo TransformGizmo = new TransformGizmo(this); TransformGizmo.ApplyTransformation += ApplyTransform; TransformGizmo.ModeChanged += OnGizmoModeChanged; TransformGizmo.Duplicate += Editor.Instance.SceneEditing.Duplicate; Gizmos.Active = TransformGizmo; // Add grid Grid = new GridGizmo(this); Grid.EnabledChanged += gizmo => _showGridButton.Icon = gizmo.Enabled ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; editor.SceneEditing.SelectionChanged += OnSelectionChanged; // Initialize snapping enabled from cached values if (_editor.ProjectCache.TryGetCustomData("TranslateSnapState", out var cachedState)) TransformGizmo.TranslationSnapEnable = bool.Parse(cachedState); if (_editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedState)) TransformGizmo.RotationSnapEnabled = bool.Parse(cachedState); if (_editor.ProjectCache.TryGetCustomData("ScaleSnapState", out cachedState)) TransformGizmo.ScaleSnapEnabled = bool.Parse(cachedState); if (_editor.ProjectCache.TryGetCustomData("TranslateSnapValue", out cachedState)) TransformGizmo.TranslationSnapValue = float.Parse(cachedState); if (_editor.ProjectCache.TryGetCustomData("RotationSnapValue", out cachedState)) TransformGizmo.RotationSnapValue = float.Parse(cachedState); if (_editor.ProjectCache.TryGetCustomData("ScaleSnapValue", out cachedState)) TransformGizmo.ScaleSnapValue = float.Parse(cachedState); if (_editor.ProjectCache.TryGetCustomData("TransformSpaceState", out cachedState) && Enum.TryParse(cachedState, out TransformGizmoBase.TransformSpace space)) TransformGizmo.ActiveTransformSpace = space; // Transform space widget var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true) { Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", Parent = transformSpaceWidget }; transformSpaceToggle.Toggled += OnTransformSpaceToggle; transformSpaceWidget.Parent = this; // Scale snapping widget var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true) { Checked = TransformGizmo.ScaleSnapEnabled, TooltipText = "Enable scale snapping", Parent = scaleSnappingWidget }; enableScaleSnapping.Toggled += OnScaleSnappingToggle; var scaleSnappingCM = new ContextMenu(); _scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM) { TooltipText = "Scale snapping values" }; for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++) { var v = EditorViewportScaleSnapValues[i]; var button = scaleSnappingCM.AddButton(v.ToString()); button.Tag = v; } scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick; scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide; _scaleSnapping.Parent = scaleSnappingWidget; scaleSnappingWidget.Parent = this; // Rotation snapping widget var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true) { Checked = TransformGizmo.RotationSnapEnabled, TooltipText = "Enable rotation snapping", Parent = rotateSnappingWidget }; enableRotateSnapping.Toggled += OnRotateSnappingToggle; var rotateSnappingCM = new ContextMenu(); _rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM) { TooltipText = "Rotation snapping values" }; for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++) { var v = EditorViewportRotateSnapValues[i]; var button = rotateSnappingCM.AddButton(v.ToString()); button.Tag = v; } rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick; rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide; _rotateSnapping.Parent = rotateSnappingWidget; rotateSnappingWidget.Parent = this; // Translation snapping widget var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true) { Checked = TransformGizmo.TranslationSnapEnable, TooltipText = "Enable position snapping", Parent = translateSnappingWidget }; enableTranslateSnapping.Toggled += OnTranslateSnappingToggle; var translateSnappingCM = new ContextMenu(); _translateSnapping = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM) { TooltipText = "Position snapping values" }; if (TransformGizmo.TranslationSnapValue < 0.0f) _translateSnapping.Text = "Bounding Box"; for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++) { var v = EditorViewportTranslateSnapValues[i]; var button = translateSnappingCM.AddButton(v.ToString()); button.Tag = v; } var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume"); buttonBB.Tag = -1.0f; translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick; translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide; _translateSnapping.Parent = translateSnappingWidget; translateSnappingWidget.Parent = this; // Gizmo mode widget var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true) { Tag = TransformGizmoBase.Mode.Translate, TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})", Checked = true, Parent = gizmoMode }; _gizmoModeTranslate.Toggled += OnGizmoModeToggle; _gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true) { Tag = TransformGizmoBase.Mode.Rotate, TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})", Parent = gizmoMode }; _gizmoModeRotate.Toggled += OnGizmoModeToggle; _gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true) { Tag = TransformGizmoBase.Mode.Scale, TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})", Parent = gizmoMode }; _gizmoModeScale.Toggled += OnGizmoModeToggle; gizmoMode.Parent = this; // Show grid widget _showGridButton = ViewWidgetShowMenu.AddButton("Grid", () => Grid.Enabled = !Grid.Enabled); _showGridButton.Icon = Style.Current.CheckBoxTick; _showGridButton.CloseMenuOnClick = false; // Show navigation widget _showNavigationButton = ViewWidgetShowMenu.AddButton("Navigation", () => ShowNavigation = !ShowNavigation); _showNavigationButton.CloseMenuOnClick = false; // Create camera widget ViewWidgetButtonMenu.AddSeparator(); ViewWidgetButtonMenu.AddButton("Create camera here", CreateCameraAtView); // Init gizmo modes { // Add default modes used by the editor Gizmos.AddMode(new TransformGizmoMode()); Gizmos.AddMode(new NoGizmoMode()); Gizmos.AddMode(SculptTerrainGizmo = new Tools.Terrain.SculptTerrainGizmoMode()); Gizmos.AddMode(PaintTerrainGizmo = new Tools.Terrain.PaintTerrainGizmoMode()); Gizmos.AddMode(EditTerrainGizmo = new Tools.Terrain.EditTerrainGizmoMode()); Gizmos.AddMode(PaintFoliageGizmo = new Tools.Foliage.PaintFoliageGizmoMode()); Gizmos.AddMode(EditFoliageGizmo = new Tools.Foliage.EditFoliageGizmoMode()); // Activate transform mode first Gizmos.SetActiveMode(); } // Setup input actions InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); InputActions.Add(options => options.ToggleTransformSpace, () => { OnTransformSpaceToggle(transformSpaceToggle); transformSpaceToggle.Checked = !transformSpaceToggle.Checked; }); InputActions.Add(options => options.LockFocusSelection, LockFocusSelection); InputActions.Add(options => options.FocusSelection, FocusSelection); InputActions.Add(options => options.RotateSelection, RotateSelection); InputActions.Add(options => options.Delete, _editor.SceneEditing.Delete); } /// public override void Update(float deltaTime) { base.Update(deltaTime); var selection = TransformGizmo.SelectedParents; var requestUnlockFocus = FlaxEngine.Input.Mouse.GetButtonDown(MouseButton.Right) || FlaxEngine.Input.Mouse.GetButtonDown(MouseButton.Left); if (TransformGizmo.SelectedParents.Count == 0 || (requestUnlockFocus && ContainsFocus)) { UnlockFocusSelection(); } else if (_lockedFocus) { var selectionBounds = BoundingSphere.Empty; for (int i = 0; i < selection.Count; i++) { selection[i].GetEditorSphere(out var sphere); BoundingSphere.Merge(ref selectionBounds, ref sphere, out selectionBounds); } if (ContainsFocus) { var viewportFocusDistance = Vector3.Distance(ViewPosition, selectionBounds.Center) / 10f; _lockedFocusOffset -= FlaxEngine.Input.Mouse.ScrollDelta * viewportFocusDistance; } var focusDistance = Mathf.Max(selectionBounds.Radius * 2d, 100d); ViewPosition = selectionBounds.Center + (-ViewDirection * (focusDistance + _lockedFocusOffset)); } } /// /// Overrides the selection outline effect or restored the default one. /// /// The custom selection outline or null if use default one. public void OverrideSelectionOutline(SelectionOutline customSelectionOutline) { if (_customSelectionOutline != null) { Task.RemoveCustomPostFx(_customSelectionOutline); Object.Destroy(ref _customSelectionOutline); Task.AddCustomPostFx(customSelectionOutline ? customSelectionOutline : SelectionOutline); } else if (customSelectionOutline != null) { Task.RemoveCustomPostFx(SelectionOutline); Task.AddCustomPostFx(customSelectionOutline); } _customSelectionOutline = customSelectionOutline; } private void CreateCameraAtView() { if (!Level.IsAnySceneLoaded) return; // Create actor var parent = Level.GetScene(0); var actor = new Camera { StaticFlags = StaticFlags.None, Name = Utilities.Utils.IncrementNameNumber("Camera", x => parent.GetChild(x) == null), Transform = ViewTransform, NearPlane = NearPlane, FarPlane = FarPlane, OrthographicScale = OrthographicScale, UsePerspective = !UseOrthographicProjection, FieldOfView = FieldOfView }; // Spawn Editor.Instance.SceneEditing.Spawn(actor, parent); } private void OnBegin(RenderTask task, GPUContext context) { _debugDrawData.Clear(); // Collect selected objects debug shapes and visuals var selectedParents = TransformGizmo.SelectedParents; if (selectedParents.Count > 0) { for (int i = 0; i < selectedParents.Count; i++) { if (selectedParents[i].IsActiveInHierarchy) selectedParents[i].OnDebugDraw(_debugDrawData); } } } private void OnCollectDrawCalls(ref RenderContext renderContext) { DragHandlers.CollectDrawCalls(_debugDrawData, ref renderContext); if (ShowNavigation) Editor.Internal_DrawNavMesh(); _debugDrawData.OnDraw(ref renderContext); } /// public void DrawEditorPrimitives(GPUContext context, ref RenderContext renderContext, GPUTexture target, GPUTexture targetDepth) { // Draw gizmos for (int i = 0; i < Gizmos.Count; i++) { Gizmos[i].Draw(ref renderContext); } // Draw selected objects debug shapes and visuals if (DrawDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw) { unsafe { fixed (IntPtr* actors = _debugDrawData.ActorsPtrs) { DebugDraw.DrawActors(new IntPtr(actors), _debugDrawData.ActorsCount, true); } } DebugDraw.Draw(ref renderContext, target.View(), targetDepth.View(), true); } } private void OnPostRender(GPUContext context, ref RenderContext renderContext) { bool renderPostFx = true; switch (renderContext.View.Mode) { case ViewMode.Default: case ViewMode.PhysicsColliders: renderPostFx = false; break; } if (renderPostFx) { var task = renderContext.Task; // Render editor primitives, gizmo and debug shapes in debug view modes // Note: can use Output buffer as both input and output because EditorPrimitives is using a intermediate buffers if (EditorPrimitives && EditorPrimitives.CanRender()) { EditorPrimitives.Render(context, ref renderContext, task.Output, task.Output); } // Render editor sprites if (_editorSpritesRenderer && _editorSpritesRenderer.CanRender()) { _editorSpritesRenderer.Render(context, ref renderContext, task.Output, task.Output); } // Render selection outline var selectionOutline = _customSelectionOutline ?? SelectionOutline; if (selectionOutline && selectionOutline.CanRender()) { // Use temporary intermediate buffer var desc = task.Output.Description; var temp = RenderTargetPool.Get(ref desc); selectionOutline.Render(context, ref renderContext, task.Output, temp); // Copy the results back to the output context.CopyTexture(task.Output, 0, 0, 0, 0, temp, 0); RenderTargetPool.Release(temp); } } } private void OnGizmoModeToggle(ViewportWidgetButton button) { TransformGizmo.ActiveMode = (TransformGizmoBase.Mode)(int)button.Tag; } private void OnTranslateSnappingToggle(ViewportWidgetButton button) { TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable; _editor.ProjectCache.SetCustomData("TranslateSnapState", TransformGizmo.TranslationSnapEnable.ToString()); } private void OnRotateSnappingToggle(ViewportWidgetButton button) { TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled; _editor.ProjectCache.SetCustomData("RotationSnapState", TransformGizmo.RotationSnapEnabled.ToString()); } private void OnScaleSnappingToggle(ViewportWidgetButton button) { TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled; _editor.ProjectCache.SetCustomData("ScaleSnapState", TransformGizmo.ScaleSnapEnabled.ToString()); } private void OnTransformSpaceToggle(ViewportWidgetButton button) { TransformGizmo.ToggleTransformSpace(); _editor.ProjectCache.SetCustomData("TransformSpaceState", TransformGizmo.ActiveTransformSpace.ToString()); } private void OnGizmoModeChanged() { // Update all viewport widgets status var mode = TransformGizmo.ActiveMode; _gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate; _gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate; _gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale; } private static readonly float[] EditorViewportScaleSnapValues = { 0.05f, 0.1f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 6.0f, 8.0f, }; private void OnWidgetScaleSnapClick(ContextMenuButton button) { var v = (float)button.Tag; TransformGizmo.ScaleSnapValue = v; _scaleSnapping.Text = v.ToString(); _editor.ProjectCache.SetCustomData("ScaleSnapValue", TransformGizmo.ScaleSnapValue.ToString("N")); } private void OnWidgetScaleSnapShowHide(Control control) { if (control.Visible == false) return; var ccm = (ContextMenu)control; foreach (var e in ccm.Items) { if (e is ContextMenuButton b) { var v = (float)b.Tag; b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; } } } private static readonly float[] EditorViewportRotateSnapValues = { 1.0f, 5.0f, 10.0f, 15.0f, 30.0f, 45.0f, 60.0f, 90.0f, }; private void OnWidgetRotateSnapClick(ContextMenuButton button) { var v = (float)button.Tag; TransformGizmo.RotationSnapValue = v; _rotateSnapping.Text = v.ToString(); _editor.ProjectCache.SetCustomData("RotationSnapValue", TransformGizmo.RotationSnapValue.ToString("N")); } private void OnWidgetRotateSnapShowHide(Control control) { if (control.Visible == false) return; var ccm = (ContextMenu)control; foreach (var e in ccm.Items) { if (e is ContextMenuButton b) { var v = (float)b.Tag; b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; } } } private static readonly float[] EditorViewportTranslateSnapValues = { 0.1f, 0.5f, 1.0f, 5.0f, 10.0f, 100.0f, 1000.0f, }; private void OnWidgetTranslateSnapClick(ContextMenuButton button) { var v = (float)button.Tag; TransformGizmo.TranslationSnapValue = v; if (v < 0.0f) _translateSnapping.Text = "Bounding Box"; else _translateSnapping.Text = v.ToString(); _editor.ProjectCache.SetCustomData("TranslateSnapValue", TransformGizmo.TranslationSnapValue.ToString("N")); } private void OnWidgetTranslateSnapShowHide(Control control) { if (control.Visible == false) return; var ccm = (ContextMenu)control; foreach (var e in ccm.Items) { if (e is ContextMenuButton b) { var v = (float)b.Tag; b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; } } } private void OnSelectionChanged() { var selection = _editor.SceneEditing.Selection; Gizmos.ForEach(x => x.OnSelectionChanged(selection)); } /// /// Press "R" to rotate the selected gizmo objects 45 degrees. /// public void RotateSelection() { var win = (WindowRootControl)Root; var selection = _editor.SceneEditing.Selection; var isShiftDown = win.GetKey(KeyboardKeys.Shift); Quaternion rotationDelta; if (isShiftDown) rotationDelta = Quaternion.Euler(0.0f, -45.0f, 0.0f); else rotationDelta = Quaternion.Euler(0.0f, 45.0f, 0.0f); bool useObjCenter = TransformGizmo.ActivePivot == TransformGizmoBase.PivotType.ObjectCenter; Vector3 gizmoPosition = TransformGizmo.Position; // Rotate selected objects bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; TransformGizmo.StartTransforming(); for (int i = 0; i < selection.Count; i++) { var obj = selection[i]; if (isPlayMode && obj.CanTransform == false) continue; var trans = obj.Transform; var pivotOffset = trans.Translation - gizmoPosition; if (useObjCenter || pivotOffset.IsZero) { trans.Orientation *= Quaternion.Invert(trans.Orientation) * rotationDelta * trans.Orientation; } else { Matrix.RotationQuaternion(ref trans.Orientation, out var transWorld); Matrix.RotationQuaternion(ref rotationDelta, out var deltaWorld); Matrix world = transWorld * Matrix.Translation(pivotOffset) * deltaWorld * Matrix.Translation(-pivotOffset); trans.SetRotation(ref world); trans.Translation += world.TranslationVector; } obj.Transform = trans; } TransformGizmo.EndTransforming(); } /// /// Focuses the viewport on the current selection of the gizmo. /// public void FocusSelection() { var orientation = ViewOrientation; FocusSelection(ref orientation); } /// /// Lock focus on the current selection gizmo. /// public void LockFocusSelection() { _lockedFocus = true; } /// /// Unlock focus on the current selection. /// public void UnlockFocusSelection() { _lockedFocus = false; _lockedFocusOffset = 0f; } /// /// Focuses the viewport on the current selection of the gizmo. /// /// The target view orientation. public void FocusSelection(ref Quaternion orientation) { if (TransformGizmo.SelectedParents.Count == 0) return; var gizmoBounds = Gizmos.Active.FocusBounds; if (gizmoBounds != BoundingSphere.Empty) ((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation); else ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation); } /// /// Applies the transform to the collection of scene graph nodes. /// /// The selection. /// The translation delta. /// The rotation delta. /// The scale delta. public void ApplyTransform(List selection, ref Vector3 translationDelta, ref Quaternion rotationDelta, ref Vector3 scaleDelta) { bool applyRotation = !rotationDelta.IsIdentity; bool useObjCenter = TransformGizmo.ActivePivot == TransformGizmoBase.PivotType.ObjectCenter; Vector3 gizmoPosition = TransformGizmo.Position; // Transform selected objects bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; for (int i = 0; i < selection.Count; i++) { var obj = selection[i]; // Block transforming static objects in play mode if (isPlayMode && obj.CanTransform == false) continue; var trans = obj.Transform; // Apply rotation if (applyRotation) { Vector3 pivotOffset = trans.Translation - gizmoPosition; if (useObjCenter || pivotOffset.IsZero) { //trans.Orientation *= rotationDelta; trans.Orientation *= Quaternion.Invert(trans.Orientation) * rotationDelta * trans.Orientation; } else { Matrix.RotationQuaternion(ref trans.Orientation, out var transWorld); Matrix.RotationQuaternion(ref rotationDelta, out var deltaWorld); Matrix world = transWorld * Matrix.Translation(pivotOffset) * deltaWorld * Matrix.Translation(-pivotOffset); trans.SetRotation(ref world); trans.Translation += world.TranslationVector; } } // Apply scale const float scaleLimit = 99_999_999.0f; trans.Scale = Float3.Clamp(trans.Scale + scaleDelta, new Float3(-scaleLimit), new Float3(scaleLimit)); // Apply translation trans.Translation += translationDelta; obj.Transform = trans; } } /// protected override void OrientViewport(ref Quaternion orientation) { if (TransformGizmo.SelectedParents.Count != 0) FocusSelection(ref orientation); else base.OrientViewport(ref orientation); } /// protected override void OnLeftMouseButtonUp() { // Skip if was controlling mouse or mouse is not over the area if (_prevInput.IsControllingMouse || !Bounds.Contains(ref _viewMousePos)) return; // Try to pick something with the current gizmo Gizmos.Active?.Pick(); // Keep focus Focus(); base.OnLeftMouseButtonUp(); } /// public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) { DragHandlers.ClearDragEffects(); var result = base.OnDragEnter(ref location, data); if (result != DragDropEffect.None) return result; return DragHandlers.DragEnter(ref location, data); } private bool ValidateDragItem(ContentItem contentItem) { if (!Level.IsAnySceneLoaded) return false; if (contentItem is AssetItem assetItem) { if (assetItem.OnEditorDrag(this)) return true; if (assetItem.IsOfType()) return true; if (assetItem.IsOfType()) return true; } return false; } private static bool ValidateDragActorType(ScriptType actorType) { return Level.IsAnySceneLoaded; } private static bool ValidateDragScriptItem(ScriptItem script) { return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null; } /// public override DragDropEffect OnDragMove(ref Float2 location, DragData data) { DragHandlers.ClearDragEffects(); var result = base.OnDragMove(ref location, data); if (result != DragDropEffect.None) return result; return DragHandlers.DragEnter(ref location, data); } /// public override void OnDragLeave() { DragHandlers.ClearDragEffects(); DragHandlers.OnDragLeave(); base.OnDragLeave(); } /// public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) { DragHandlers.ClearDragEffects(); var result = base.OnDragDrop(ref location, data); if (result != DragDropEffect.None) return result; return DragHandlers.DragDrop(ref location, data); } /// public override void Select(List nodes) { _editor.SceneEditing.Select(nodes); } /// public override void Spawn(Actor actor) { var parent = actor.Parent ?? Level.GetScene(0); actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null); Editor.Instance.SceneEditing.Spawn(actor); } /// public override void OnDestroy() { if (IsDisposing) return; _debugDrawData.Dispose(); if (_task != null) { // Release if task is not used to save screenshot for project icon Object.Destroy(ref _task); ReleaseResources(); } base.OnDestroy(); } private RenderTask _savedTask; private GPUTexture _savedBackBuffer; internal void SaveProjectIcon() { TakeScreenshot(StringUtils.CombinePaths(Globals.ProjectCacheFolder, "icon.png")); _savedTask = _task; _savedBackBuffer = _backBuffer; _task = null; _backBuffer = null; } internal void SaveProjectIconEnd() { if (_savedTask) { _savedTask.Enabled = false; Object.Destroy(_savedTask); ReleaseResources(); _savedTask = null; } Object.Destroy(ref _savedBackBuffer); } private void ReleaseResources() { if (Task) { Task.RemoveCustomPostFx(SelectionOutline); Task.RemoveCustomPostFx(EditorPrimitives); Task.RemoveCustomPostFx(_editorSpritesRenderer); Task.RemoveCustomPostFx(_customSelectionOutline); } Object.Destroy(ref SelectionOutline); Object.Destroy(ref EditorPrimitives); Object.Destroy(ref _editorSpritesRenderer); Object.Destroy(ref _customSelectionOutline); } } }