From d0d5ad4657c04b10738475451a4f79dcc4462ceb Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 12 Jan 2025 21:33:01 -0600 Subject: [PATCH 01/15] Add rubber band select to scene editor viewport for selecting multiple actors. --- .../Viewport/MainEditorGizmoViewport.cs | 94 ++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index cbb35e155..885251a7d 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -7,10 +7,12 @@ using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; +using FlaxEditor.Tools; using FlaxEditor.Viewport.Modes; using FlaxEditor.Windows; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEngine.Tools; using Object = FlaxEngine.Object; namespace FlaxEditor.Viewport @@ -108,6 +110,10 @@ namespace FlaxEditor.Viewport private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32); private EditorSpritesRenderer _editorSpritesRenderer; + private bool _isRubberBandSpanning; + private Float2 _cachedStartingMousePosition; + private Rectangle _rubberBandRect; + /// /// Drag and drop handlers /// @@ -287,6 +293,26 @@ namespace FlaxEditor.Viewport var focusDistance = Mathf.Max(selectionBounds.Radius * 2d, 100d); ViewPosition = selectionBounds.Center + (-ViewDirection * (focusDistance + _lockedFocusOffset)); } + + // Dont allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled + if (_isRubberBandSpanning && (Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown) + { + _isRubberBandSpanning = false; + } + + // Start rubber band selection + if (IsLeftMouseButtonDown && !MouseDelta.IsZero && !_isRubberBandSpanning && !Gizmos.Active.IsControllingMouse && !IsControllingMouse && !IsRightMouseButtonDown) + { + _isRubberBandSpanning = true; + _cachedStartingMousePosition = _viewMousePos; + _rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero); + } + else if (_isRubberBandSpanning) + { + var size = _viewMousePos - _cachedStartingMousePosition; + _rubberBandRect.Width = _viewMousePos.X - _cachedStartingMousePosition.X; + _rubberBandRect.Height = _viewMousePos.Y - _cachedStartingMousePosition.Y; + } } /// @@ -367,7 +393,16 @@ namespace FlaxEditor.Viewport { Gizmos[i].Draw(ref renderContext); } - + + // Draw RubberBand for rect selection + if (_isRubberBandSpanning) + { + Render2D.Begin(context, target, targetDepth); + Render2D.FillRectangle(_rubberBandRect, Style.Current.Selection); + Render2D.DrawRectangle(_rubberBandRect, Style.Current.SelectionBorder); + Render2D.End(); + } + // Draw selected objects debug shapes and visuals if (DrawDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw) { @@ -582,9 +617,62 @@ namespace FlaxEditor.Viewport // Skip if was controlling mouse or mouse is not over the area if (_prevInput.IsControllingMouse || !Bounds.Contains(ref _viewMousePos)) return; + + // Select rubberbanded rect actor nodes + if (_isRubberBandSpanning) + { + _isRubberBandSpanning = false; + if (_rubberBandRect.Width < 0 || _rubberBandRect.Height < 0) + { + // make sure we have a well-formed rectangle i.e. size is positive and X/Y is upper left corner + var size = _rubberBandRect.Size; + _rubberBandRect.X = Mathf.Min(_rubberBandRect.X, _rubberBandRect.X + _rubberBandRect.Width); + _rubberBandRect.Y = Mathf.Min(_rubberBandRect.Y, _rubberBandRect.Y + _rubberBandRect.Height); + size.X = Mathf.Abs(size.X); + size.Y = Mathf.Abs(size.Y); + _rubberBandRect.Size = size; + } - // Try to pick something with the current gizmo - Gizmos.Active?.Pick(); + var view = new Ray(ViewPosition, ViewDirection); + List hits = new List(); + // Todo: expose resolution to editor settings + var resolution = 100; + for (int i = 0; i < resolution; i++) + { + for (int j = 0; j < resolution; j++) + { + var point = new Float2(_rubberBandRect.Left + ((_rubberBandRect.Right - _rubberBandRect.Left) / resolution) * i, _rubberBandRect.Top + ((_rubberBandRect.Bottom - _rubberBandRect.Top) / resolution) * j); + var ray = ConvertMouseToRay(ref point); + var hit = SceneGraphRoot.RayCast(ref ray, ref view, out _); + if (hit != null && hit is ActorNode actorNode) + hits.Add(hit); + } + } + + if (IsControlDown) + { + var newSelection = new List(); + var currentSelection = _editor.SceneEditing.Selection; + newSelection.AddRange(currentSelection); + foreach (var hit in hits) + { + if (currentSelection.Contains(hit)) + newSelection.Remove(hit); + else + newSelection.Add(hit); + } + Select(newSelection); + } + else + { + Select(hits); + } + } + else + { + // Try to pick something with the current gizmo + Gizmos.Active?.Pick(); + } // Keep focus Focus(); From 3b7cb00af2024c2cbf3228127aa29eefa7cadf28 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 12 Jan 2025 21:41:24 -0600 Subject: [PATCH 02/15] Add shift to continue selection --- Source/Editor/Viewport/MainEditorGizmoViewport.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 885251a7d..a709ffe6e 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -663,6 +663,14 @@ namespace FlaxEditor.Viewport } Select(newSelection); } + else if (((WindowRootControl)Root).GetKey(KeyboardKeys.Shift)) + { + var newSelection = new List(); + var currentSelection = _editor.SceneEditing.Selection; + newSelection.AddRange(hits); + newSelection.AddRange(currentSelection); + Select(newSelection); + } else { Select(hits); From eb43d4813d3b569ecb23e9e5ccce3264d673a3c5 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 25 Jan 2025 15:30:22 -0600 Subject: [PATCH 03/15] Faster selection, select while dragging, only select if actor box is in rect. --- .../Viewport/MainEditorGizmoViewport.cs | 169 ++++++++++++------ 1 file changed, 113 insertions(+), 56 deletions(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index a709ffe6e..6c5844e6a 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -113,6 +113,7 @@ namespace FlaxEditor.Viewport private bool _isRubberBandSpanning; private Float2 _cachedStartingMousePosition; private Rectangle _rubberBandRect; + private Rectangle _lastRubberBandRect; /// /// Drag and drop handlers @@ -295,13 +296,13 @@ namespace FlaxEditor.Viewport } // Dont allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled - if (_isRubberBandSpanning && (Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown) + if (_isRubberBandSpanning && ((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown)) { _isRubberBandSpanning = false; } // Start rubber band selection - if (IsLeftMouseButtonDown && !MouseDelta.IsZero && !_isRubberBandSpanning && !Gizmos.Active.IsControllingMouse && !IsControllingMouse && !IsRightMouseButtonDown) + if (IsLeftMouseButtonDown && (Mathf.Abs(MouseDelta.X) > 0.1f || Mathf.Abs(MouseDelta.Y) > 0.1f) && !_isRubberBandSpanning && !Gizmos.Active.IsControllingMouse && !IsControllingMouse && !IsRightMouseButtonDown) { _isRubberBandSpanning = true; _cachedStartingMousePosition = _viewMousePos; @@ -309,9 +310,104 @@ namespace FlaxEditor.Viewport } else if (_isRubberBandSpanning) { - var size = _viewMousePos - _cachedStartingMousePosition; _rubberBandRect.Width = _viewMousePos.X - _cachedStartingMousePosition.X; _rubberBandRect.Height = _viewMousePos.Y - _cachedStartingMousePosition.Y; + + if (_lastRubberBandRect != _rubberBandRect) + { + // Select rubberbanded rect actor nodes + var adjustedRect = _rubberBandRect; + _lastRubberBandRect = _rubberBandRect; + if (adjustedRect.Width < 0 || adjustedRect.Height < 0) + { + // make sure we have a well-formed rectangle i.e. size is positive and X/Y is upper left corner + var size = adjustedRect.Size; + adjustedRect.X = Mathf.Min(adjustedRect.X, adjustedRect.X + adjustedRect.Width); + adjustedRect.Y = Mathf.Min(adjustedRect.Y, adjustedRect.Y + adjustedRect.Height); + size.X = Mathf.Abs(size.X); + size.Y = Mathf.Abs(size.Y); + adjustedRect.Size = size; + } + + List hits = new List(); + var allActors = Level.GetActors(true); + foreach (var a in allActors) + { + var actorBox = a.EditorBox; + if (ViewFrustum.Contains(actorBox) == ContainmentType.Disjoint) + continue; + + // Check is control and skip if canvas is 2D + if (a is UIControl control) + { + UICanvas canvas = null; + var controlParent = control.Parent; + while (controlParent != null || controlParent is Scene) + { + if (controlParent is UICanvas uiCanvas) + { + canvas = uiCanvas; + break; + } + controlParent = controlParent.Parent; + } + + if (canvas != null) + { + if (canvas.Is2D) + { + continue; + } + } + } + + // Check if all corners are in box to select it. + var corners = actorBox.GetCorners(); + var containsAllCorners = true; + foreach (var c in corners) + { + Viewport.ProjectPoint(c, out var loc); + if (!adjustedRect.Contains(loc)) + { + containsAllCorners = false; + break; + } + } + + if (containsAllCorners) + { + hits.Add(SceneGraphRoot.Find(a)); + } + } + + if (IsControlDown) + { + var newSelection = new List(); + var currentSelection = _editor.SceneEditing.Selection; + newSelection.AddRange(currentSelection); + foreach (var hit in hits) + { + if (currentSelection.Contains(hit)) + newSelection.Remove(hit); + else + newSelection.Add(hit); + } + Select(newSelection); + } + else if (((WindowRootControl)Root).GetKey(KeyboardKeys.Shift)) + { + var newSelection = new List(); + var currentSelection = _editor.SceneEditing.Selection; + newSelection.AddRange(hits); + newSelection.AddRange(currentSelection); + Select(newSelection); + } + else + { + Select(hits); + } + } + } } @@ -513,6 +609,20 @@ namespace FlaxEditor.Viewport TransformGizmo.EndTransforming(); } + /// + public override void OnLostFocus() + { + base.OnLostFocus(); + _isRubberBandSpanning = false; + } + + /// + public override void OnMouseLeave() + { + base.OnMouseLeave(); + _isRubberBandSpanning = false; + } + /// /// Focuses the viewport on the current selection of the gizmo. /// @@ -622,59 +732,6 @@ namespace FlaxEditor.Viewport if (_isRubberBandSpanning) { _isRubberBandSpanning = false; - if (_rubberBandRect.Width < 0 || _rubberBandRect.Height < 0) - { - // make sure we have a well-formed rectangle i.e. size is positive and X/Y is upper left corner - var size = _rubberBandRect.Size; - _rubberBandRect.X = Mathf.Min(_rubberBandRect.X, _rubberBandRect.X + _rubberBandRect.Width); - _rubberBandRect.Y = Mathf.Min(_rubberBandRect.Y, _rubberBandRect.Y + _rubberBandRect.Height); - size.X = Mathf.Abs(size.X); - size.Y = Mathf.Abs(size.Y); - _rubberBandRect.Size = size; - } - - var view = new Ray(ViewPosition, ViewDirection); - List hits = new List(); - // Todo: expose resolution to editor settings - var resolution = 100; - for (int i = 0; i < resolution; i++) - { - for (int j = 0; j < resolution; j++) - { - var point = new Float2(_rubberBandRect.Left + ((_rubberBandRect.Right - _rubberBandRect.Left) / resolution) * i, _rubberBandRect.Top + ((_rubberBandRect.Bottom - _rubberBandRect.Top) / resolution) * j); - var ray = ConvertMouseToRay(ref point); - var hit = SceneGraphRoot.RayCast(ref ray, ref view, out _); - if (hit != null && hit is ActorNode actorNode) - hits.Add(hit); - } - } - - if (IsControlDown) - { - var newSelection = new List(); - var currentSelection = _editor.SceneEditing.Selection; - newSelection.AddRange(currentSelection); - foreach (var hit in hits) - { - if (currentSelection.Contains(hit)) - newSelection.Remove(hit); - else - newSelection.Add(hit); - } - Select(newSelection); - } - else if (((WindowRootControl)Root).GetKey(KeyboardKeys.Shift)) - { - var newSelection = new List(); - var currentSelection = _editor.SceneEditing.Selection; - newSelection.AddRange(hits); - newSelection.AddRange(currentSelection); - Select(newSelection); - } - else - { - Select(hits); - } } else { From 4598cdb2d09de7199e8684d993cd2f0df0fd1edf Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 25 Jan 2025 15:32:18 -0600 Subject: [PATCH 04/15] Fix check for control parent. --- Source/Editor/Viewport/MainEditorGizmoViewport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 6c5844e6a..88f59b0c9 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -342,7 +342,7 @@ namespace FlaxEditor.Viewport { UICanvas canvas = null; var controlParent = control.Parent; - while (controlParent != null || controlParent is Scene) + while (controlParent != null || controlParent is not Scene) { if (controlParent is UICanvas uiCanvas) { From 4d4c046556f9d8989ae8edcf583c721259d15395 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 25 Jan 2025 15:35:07 -0600 Subject: [PATCH 05/15] small cleanup with uicanvas searching. --- Source/Editor/Viewport/MainEditorGizmoViewport.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 88f59b0c9..e106aeec2 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -342,7 +342,7 @@ namespace FlaxEditor.Viewport { UICanvas canvas = null; var controlParent = control.Parent; - while (controlParent != null || controlParent is not Scene) + while (controlParent != null && controlParent is not Scene) { if (controlParent is UICanvas uiCanvas) { @@ -355,11 +355,14 @@ namespace FlaxEditor.Viewport if (canvas != null) { if (canvas.Is2D) - { continue; - } } } + else if (a is UICanvas uiCanvas) + { + if (uiCanvas.Is2D) + continue; + } // Check if all corners are in box to select it. var corners = actorBox.GetCorners(); From fa06a8cc169c8f63692b2b29e814e8989102c186 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 26 Jan 2025 13:45:31 -0600 Subject: [PATCH 06/15] Fix rubberband taking over when tyring to move objects. Move rubberband out of update method. --- Source/Editor/Gizmo/TransformGizmoBase.cs | 6 +- .../Viewport/MainEditorGizmoViewport.cs | 263 ++++++++++-------- 2 files changed, 150 insertions(+), 119 deletions(-) diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 8fe91c252..f19cdf195 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -42,6 +42,7 @@ namespace FlaxEditor.Gizmo private bool _isDuplicating; private bool _isTransforming; + private bool _isSelected; private Vector3 _lastIntersectionPosition; private Quaternion _rotationDelta = Quaternion.Identity; @@ -408,7 +409,7 @@ namespace FlaxEditor.Gizmo } /// - public override bool IsControllingMouse => _isTransforming; + public override bool IsControllingMouse => _isTransforming || _isSelected; /// public override void Update(float dt) @@ -433,6 +434,7 @@ namespace FlaxEditor.Gizmo // Check if user is holding left mouse button and any axis is selected if (isLeftBtnDown && _activeAxis != Axis.None) { + _isSelected = true; // setting later is too late, need to set here for rubber band selection in GizmoViewport switch (_activeMode) { case Mode.Translate: @@ -450,6 +452,7 @@ namespace FlaxEditor.Gizmo } else { + _isSelected = false; // If nothing selected, try to select any axis if (!isLeftBtnDown && !Owner.IsRightMouseButtonDown) { @@ -517,6 +520,7 @@ namespace FlaxEditor.Gizmo // Clear cache _accMoveDelta = Vector3.Zero; _lastIntersectionPosition = _intersectPosition = Vector3.Zero; + _isSelected = false; EndTransforming(); } } diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index e106aeec2..44952bf92 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -111,6 +111,7 @@ namespace FlaxEditor.Viewport private EditorSpritesRenderer _editorSpritesRenderer; private bool _isRubberBandSpanning; + private bool _tryStartRubberBand; private Float2 _cachedStartingMousePosition; private Rectangle _rubberBandRect; private Rectangle _lastRubberBandRect; @@ -294,124 +295,6 @@ namespace FlaxEditor.Viewport var focusDistance = Mathf.Max(selectionBounds.Radius * 2d, 100d); ViewPosition = selectionBounds.Center + (-ViewDirection * (focusDistance + _lockedFocusOffset)); } - - // Dont allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled - if (_isRubberBandSpanning && ((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown)) - { - _isRubberBandSpanning = false; - } - - // Start rubber band selection - if (IsLeftMouseButtonDown && (Mathf.Abs(MouseDelta.X) > 0.1f || Mathf.Abs(MouseDelta.Y) > 0.1f) && !_isRubberBandSpanning && !Gizmos.Active.IsControllingMouse && !IsControllingMouse && !IsRightMouseButtonDown) - { - _isRubberBandSpanning = true; - _cachedStartingMousePosition = _viewMousePos; - _rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero); - } - else if (_isRubberBandSpanning) - { - _rubberBandRect.Width = _viewMousePos.X - _cachedStartingMousePosition.X; - _rubberBandRect.Height = _viewMousePos.Y - _cachedStartingMousePosition.Y; - - if (_lastRubberBandRect != _rubberBandRect) - { - // Select rubberbanded rect actor nodes - var adjustedRect = _rubberBandRect; - _lastRubberBandRect = _rubberBandRect; - if (adjustedRect.Width < 0 || adjustedRect.Height < 0) - { - // make sure we have a well-formed rectangle i.e. size is positive and X/Y is upper left corner - var size = adjustedRect.Size; - adjustedRect.X = Mathf.Min(adjustedRect.X, adjustedRect.X + adjustedRect.Width); - adjustedRect.Y = Mathf.Min(adjustedRect.Y, adjustedRect.Y + adjustedRect.Height); - size.X = Mathf.Abs(size.X); - size.Y = Mathf.Abs(size.Y); - adjustedRect.Size = size; - } - - List hits = new List(); - var allActors = Level.GetActors(true); - foreach (var a in allActors) - { - var actorBox = a.EditorBox; - if (ViewFrustum.Contains(actorBox) == ContainmentType.Disjoint) - continue; - - // Check is control and skip if canvas is 2D - if (a is UIControl control) - { - UICanvas canvas = null; - var controlParent = control.Parent; - while (controlParent != null && controlParent is not Scene) - { - if (controlParent is UICanvas uiCanvas) - { - canvas = uiCanvas; - break; - } - controlParent = controlParent.Parent; - } - - if (canvas != null) - { - if (canvas.Is2D) - continue; - } - } - else if (a is UICanvas uiCanvas) - { - if (uiCanvas.Is2D) - continue; - } - - // Check if all corners are in box to select it. - var corners = actorBox.GetCorners(); - var containsAllCorners = true; - foreach (var c in corners) - { - Viewport.ProjectPoint(c, out var loc); - if (!adjustedRect.Contains(loc)) - { - containsAllCorners = false; - break; - } - } - - if (containsAllCorners) - { - hits.Add(SceneGraphRoot.Find(a)); - } - } - - if (IsControlDown) - { - var newSelection = new List(); - var currentSelection = _editor.SceneEditing.Selection; - newSelection.AddRange(currentSelection); - foreach (var hit in hits) - { - if (currentSelection.Contains(hit)) - newSelection.Remove(hit); - else - newSelection.Add(hit); - } - Select(newSelection); - } - else if (((WindowRootControl)Root).GetKey(KeyboardKeys.Shift)) - { - var newSelection = new List(); - var currentSelection = _editor.SceneEditing.Selection; - newSelection.AddRange(hits); - newSelection.AddRange(currentSelection); - Select(newSelection); - } - else - { - Select(hits); - } - } - - } } /// @@ -617,6 +500,7 @@ namespace FlaxEditor.Viewport { base.OnLostFocus(); _isRubberBandSpanning = false; + _tryStartRubberBand = false; } /// @@ -624,6 +508,7 @@ namespace FlaxEditor.Viewport { base.OnMouseLeave(); _isRubberBandSpanning = false; + _tryStartRubberBand = false; } /// @@ -724,12 +609,154 @@ namespace FlaxEditor.Viewport base.OrientViewport(ref orientation); } + /// + public override void OnMouseMove(Float2 location) + { + base.OnMouseMove(location); + + // Dont allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled + if (_isRubberBandSpanning && ((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown)) + { + _isRubberBandSpanning = false; + } + + if (_tryStartRubberBand && (Mathf.Abs(MouseDelta.X) > 0.1f || Mathf.Abs(MouseDelta.Y) > 0.1f) && !_isRubberBandSpanning && !Gizmos.Active.IsControllingMouse && !IsControllingMouse && !IsRightMouseButtonDown) + { + _isRubberBandSpanning = true; + _cachedStartingMousePosition = _viewMousePos; + _rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero); + } + else if (_isRubberBandSpanning && !Gizmos.Active.IsControllingMouse && !IsControllingMouse && !IsRightMouseButtonDown) + { + _rubberBandRect.Width = _viewMousePos.X - _cachedStartingMousePosition.X; + _rubberBandRect.Height = _viewMousePos.Y - _cachedStartingMousePosition.Y; + + if (_lastRubberBandRect != _rubberBandRect) + { + // Select rubberbanded rect actor nodes + var adjustedRect = _rubberBandRect; + _lastRubberBandRect = _rubberBandRect; + if (adjustedRect.Width < 0 || adjustedRect.Height < 0) + { + // make sure we have a well-formed rectangle i.e. size is positive and X/Y is upper left corner + var size = adjustedRect.Size; + adjustedRect.X = Mathf.Min(adjustedRect.X, adjustedRect.X + adjustedRect.Width); + adjustedRect.Y = Mathf.Min(adjustedRect.Y, adjustedRect.Y + adjustedRect.Height); + size.X = Mathf.Abs(size.X); + size.Y = Mathf.Abs(size.Y); + adjustedRect.Size = size; + } + + List hits = new List(); + var allActors = Level.GetActors(true); + foreach (var a in allActors) + { + if (a.HideFlags is HideFlags.DontSelect or HideFlags.FullyHidden || a is EmptyActor) + continue; + + var actorBox = a.EditorBox; + if (ViewFrustum.Contains(actorBox) == ContainmentType.Disjoint) + continue; + + // Check is control and skip if canvas is 2D + if (a is UIControl control) + { + UICanvas canvas = null; + var controlParent = control.Parent; + while (controlParent != null && controlParent is not Scene) + { + if (controlParent is UICanvas uiCanvas) + { + canvas = uiCanvas; + break; + } + controlParent = controlParent.Parent; + } + + if (canvas != null) + { + if (canvas.Is2D) + continue; + } + } + else if (a is UICanvas uiCanvas) + { + if (uiCanvas.Is2D) + continue; + } + + // Check if all corners are in box to select it. + var corners = actorBox.GetCorners(); + var containsAllCorners = true; + foreach (var c in corners) + { + Viewport.ProjectPoint(c, out var loc); + if (!adjustedRect.Contains(loc)) + { + containsAllCorners = false; + break; + } + } + + if (containsAllCorners) + { + hits.Add(SceneGraphRoot.Find(a)); + } + } + + if (IsControlDown) + { + var newSelection = new List(); + var currentSelection = _editor.SceneEditing.Selection; + newSelection.AddRange(currentSelection); + foreach (var hit in hits) + { + if (currentSelection.Contains(hit)) + newSelection.Remove(hit); + else + newSelection.Add(hit); + } + Select(newSelection); + } + else if (((WindowRootControl)Root).GetKey(KeyboardKeys.Shift)) + { + var newSelection = new List(); + var currentSelection = _editor.SceneEditing.Selection; + newSelection.AddRange(hits); + newSelection.AddRange(currentSelection); + Select(newSelection); + } + else + { + Select(hits); + } + } + + } + } + + /// + protected override void OnLeftMouseButtonDown() + { + base.OnLeftMouseButtonDown(); + + if (!_isRubberBandSpanning && !Gizmos.Active.IsControllingMouse && !IsControllingMouse && !IsRightMouseButtonDown) + { + _tryStartRubberBand = true; + } + } + /// protected override void OnLeftMouseButtonUp() { // Skip if was controlling mouse or mouse is not over the area if (_prevInput.IsControllingMouse || !Bounds.Contains(ref _viewMousePos)) return; + + if (_tryStartRubberBand) + { + _tryStartRubberBand = false; + } // Select rubberbanded rect actor nodes if (_isRubberBandSpanning) From fc4c2c53fc5e9da86f3e2f9d2176a8f623d32474 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 26 Jan 2025 13:50:07 -0600 Subject: [PATCH 07/15] select parent of prefab if it has a prefab link. --- Source/Editor/Viewport/MainEditorGizmoViewport.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 44952bf92..a6d5f1c36 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -658,7 +658,7 @@ namespace FlaxEditor.Viewport if (ViewFrustum.Contains(actorBox) == ContainmentType.Disjoint) continue; - // Check is control and skip if canvas is 2D + // Check if control and skip if canvas is 2D if (a is UIControl control) { UICanvas canvas = null; @@ -700,7 +700,10 @@ namespace FlaxEditor.Viewport if (containsAllCorners) { - hits.Add(SceneGraphRoot.Find(a)); + if (a.HasPrefabLink) + hits.Add(SceneGraphRoot.Find(a.GetPrefabRoot())); + else + hits.Add(SceneGraphRoot.Find(a)); } } From abe2482a012cf23a6936465bc81ddf8ad28b2e77 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 27 Jan 2025 18:28:06 -0600 Subject: [PATCH 08/15] Use mesh collision proxy if available. --- .../Viewport/MainEditorGizmoViewport.cs | 53 +++++++++++++++---- Source/Engine/Graphics/Mesh.cs | 9 ++++ Source/Engine/Graphics/Models/Mesh.cpp | 13 +++++ Source/Engine/Graphics/Models/Mesh.h | 1 + 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index a6d5f1c36..4c0891124 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -13,6 +13,7 @@ using FlaxEditor.Windows; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Tools; + using Object = FlaxEngine.Object; namespace FlaxEditor.Viewport @@ -684,21 +685,53 @@ namespace FlaxEditor.Viewport if (uiCanvas.Is2D) continue; } - - // Check if all corners are in box to select it. - var corners = actorBox.GetCorners(); - var containsAllCorners = true; - foreach (var c in corners) + + var containsAllPoints = true; + var fallBackToBox = false; + if (a is StaticModel sm) { - Viewport.ProjectPoint(c, out var loc); - if (!adjustedRect.Contains(loc)) + List extraPoints = new List(); + var m = sm.Model.LODs[0]; + foreach (var mesh in m.Meshes) { - containsAllCorners = false; - break; + var points = mesh.GetCollisionProxyPoints(); + if (points.Length == 0) + { + fallBackToBox = true; + break; + } + foreach (var point in points) + { + Viewport.ProjectPoint(a.Transform.LocalToWorld(point), out var loc); + if (!adjustedRect.Contains(loc)) + { + containsAllPoints = false; + break; + } + } } } + else + { + fallBackToBox = true; + } - if (containsAllCorners) + if (fallBackToBox) + { + // Check if all corners are in box to select it. + var corners = actorBox.GetCorners(); + foreach (var c in corners) + { + Viewport.ProjectPoint(c, out var loc); + if (!adjustedRect.Contains(loc)) + { + containsAllPoints = false; + break; + } + } + } + + if (containsAllPoints) { if (a.HasPrefabLink) hits.Add(SceneGraphRoot.Find(a.GetPrefabRoot())); diff --git a/Source/Engine/Graphics/Mesh.cs b/Source/Engine/Graphics/Mesh.cs index 42604c872..e44dbec7b 100644 --- a/Source/Engine/Graphics/Mesh.cs +++ b/Source/Engine/Graphics/Mesh.cs @@ -633,5 +633,14 @@ namespace FlaxEngine throw new Exception("Failed to download mesh data."); return result; } + + /// + /// Gets the collision proxy points for the mesh. + /// + /// The triangle points in the collision proxy. + public Float3[] GetCollisionProxyPoints() + { + return Internal_GetCollisionProxyPoints(__unmanagedPtr, out _); + } } } diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 77c2089e1..365280a44 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -847,4 +847,17 @@ MArray* Mesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI return result; } +Array Mesh::GetCollisionProxyPoints() const +{ + Array result; + for (int i = 0; i < _collisionProxy.Triangles.Count(); ++i) + { + auto triangle = _collisionProxy.Triangles[i]; + result.Add(triangle.V0); + result.Add(triangle.V1); + result.Add(triangle.V2); + } + return result; +} + #endif diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 813836b85..2c8f8beda 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -320,5 +320,6 @@ private: API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj); API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj); API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI); + API_FUNCTION(NoProxy) Array GetCollisionProxyPoints() const; #endif }; From 6461ffbd0dce6063c92e54cddcab6e8be0a6a13b Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 28 Jan 2025 15:58:18 -0600 Subject: [PATCH 09/15] Skip selecting scene actor. --- Source/Editor/Viewport/MainEditorGizmoViewport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 4c0891124..03619bb17 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -652,7 +652,7 @@ namespace FlaxEditor.Viewport var allActors = Level.GetActors(true); foreach (var a in allActors) { - if (a.HideFlags is HideFlags.DontSelect or HideFlags.FullyHidden || a is EmptyActor) + if (a.HideFlags is HideFlags.DontSelect or HideFlags.FullyHidden || a is EmptyActor || a is Scene) continue; var actorBox = a.EditorBox; From 16ed7272a7f5b8b0d5795c35a020e642fce57c7c Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 28 Jan 2025 16:05:19 -0600 Subject: [PATCH 10/15] Skip not active actors. --- Source/Editor/Viewport/MainEditorGizmoViewport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 03619bb17..bef9c37cf 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -652,7 +652,7 @@ namespace FlaxEditor.Viewport var allActors = Level.GetActors(true); foreach (var a in allActors) { - if (a.HideFlags is HideFlags.DontSelect or HideFlags.FullyHidden || a is EmptyActor || a is Scene) + if (a.HideFlags is HideFlags.DontSelect or HideFlags.FullyHidden || a is EmptyActor || a is Scene || !a.IsActive) continue; var actorBox = a.EditorBox; From 45a24a05c58aeb747e8e9c99d51e954a091d5254 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 28 Jan 2025 16:22:02 -0600 Subject: [PATCH 11/15] Add check for if static model has a model. --- .../Viewport/MainEditorGizmoViewport.cs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index bef9c37cf..813b7c9ac 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -690,24 +690,26 @@ namespace FlaxEditor.Viewport var fallBackToBox = false; if (a is StaticModel sm) { - List extraPoints = new List(); - var m = sm.Model.LODs[0]; - foreach (var mesh in m.Meshes) + if (sm.Model) { - var points = mesh.GetCollisionProxyPoints(); - if (points.Length == 0) + var m = sm.Model.LODs[0]; + foreach (var mesh in m.Meshes) { - fallBackToBox = true; - break; - } - foreach (var point in points) - { - Viewport.ProjectPoint(a.Transform.LocalToWorld(point), out var loc); - if (!adjustedRect.Contains(loc)) + var points = mesh.GetCollisionProxyPoints(); + if (points.Length == 0) { - containsAllPoints = false; + fallBackToBox = true; break; } + foreach (var point in points) + { + Viewport.ProjectPoint(a.Transform.LocalToWorld(point), out var loc); + if (!adjustedRect.Contains(loc)) + { + containsAllPoints = false; + break; + } + } } } } From 5ea6e7b49dc0c68860cebbbe754e6606d04da4f0 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 8 Mar 2025 09:38:01 -0600 Subject: [PATCH 12/15] Refactor to ViewportRubberBandSelector class and use actor node virtual methods. --- .../Gizmo/ViewportRubberBandSelector.cs | 209 ++++++++++++++++++ Source/Editor/SceneGraph/ActorNode.cs | 45 ++++ Source/Editor/SceneGraph/Actors/CameraNode.cs | 6 + Source/Editor/SceneGraph/Actors/SceneNode.cs | 6 + .../SceneGraph/Actors/StaticModelNode.cs | 24 ++ .../Editor/SceneGraph/Actors/UICanvasNode.cs | 6 + .../Editor/SceneGraph/Actors/UIControlNode.cs | 26 +++ .../Viewport/MainEditorGizmoViewport.cs | 201 ++--------------- Source/Engine/Graphics/Mesh.cs | 2 +- 9 files changed, 337 insertions(+), 188 deletions(-) create mode 100644 Source/Editor/Gizmo/ViewportRubberBandSelector.cs diff --git a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs new file mode 100644 index 000000000..1f61aeb0a --- /dev/null +++ b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs @@ -0,0 +1,209 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using FlaxEditor; +using FlaxEditor.Gizmo; +using FlaxEditor.SceneGraph; +using FlaxEngine.GUI; + +namespace FlaxEngine.Gizmo; + +/// +/// Class for adding viewport rubber band selection. +/// +public class ViewportRubberBandSelector +{ + private bool _isRubberBandSpanning; + private bool _tryStartRubberBand; + private Float2 _cachedStartingMousePosition; + private Rectangle _rubberBandRect; + private Rectangle _lastRubberBandRect; + + private IGizmoOwner _owner; + + /// + /// Constructs a rubber band selector with a designated gizmo owner. + /// + /// The gizmo owner. + public ViewportRubberBandSelector(IGizmoOwner owner) + { + _owner = owner; + } + + /// + /// Triggers the start of a rubber band selection. + /// + public void TryStartingRubberBandSelection() + { + if (!_isRubberBandSpanning && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown) + { + _tryStartRubberBand = true; + } + } + + /// + /// Release the rubber band selection. + /// + /// Returns true if rubber band is currently spanning + public bool ReleaseRubberBandSelection() + { + if (_tryStartRubberBand) + { + _tryStartRubberBand = false; + } + + if (_isRubberBandSpanning) + { + _isRubberBandSpanning = false; + return true; + } + return false; + } + + /// + /// Tries to create a rubber band selection. + /// + /// Whether the creation can start. + /// The current mouse position. + /// The view frustum. + public void TryCreateRubberBand(bool canStart, Float2 mousePosition, BoundingFrustum viewFrustum) + { + if (_isRubberBandSpanning && !canStart) + { + _isRubberBandSpanning = false; + return; + } + + if (_tryStartRubberBand && (Mathf.Abs(_owner.MouseDelta.X) > 0.1f || Mathf.Abs(_owner.MouseDelta.Y) > 0.1f) && canStart) + { + _isRubberBandSpanning = true; + _cachedStartingMousePosition = mousePosition; + _rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero); + _tryStartRubberBand = false; + } + else if (_isRubberBandSpanning && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown) + { + _rubberBandRect.Width = mousePosition.X - _cachedStartingMousePosition.X; + _rubberBandRect.Height = mousePosition.Y - _cachedStartingMousePosition.Y; + + if (_lastRubberBandRect != _rubberBandRect) + { + // Select rubberbanded rect actor nodes + var adjustedRect = _rubberBandRect; + _lastRubberBandRect = _rubberBandRect; + if (adjustedRect.Width < 0 || adjustedRect.Height < 0) + { + // make sure we have a well-formed rectangle i.e. size is positive and X/Y is upper left corner + var size = adjustedRect.Size; + adjustedRect.X = Mathf.Min(adjustedRect.X, adjustedRect.X + adjustedRect.Width); + adjustedRect.Y = Mathf.Min(adjustedRect.Y, adjustedRect.Y + adjustedRect.Height); + size.X = Mathf.Abs(size.X); + size.Y = Mathf.Abs(size.Y); + adjustedRect.Size = size; + } + + // Get hits from graph nodes. + List hits = new List(); + var nodes = _owner.SceneGraphRoot.GetAllChildActorNodes(); + foreach (var node in nodes) + { + // Check for custom can select code + if (!node.CanSelectActorNodeWithSelector()) + continue; + + var a = node.Actor; + // Skip actor if outside of view frustum + var actorBox = a.EditorBox; + if (viewFrustum.Contains(actorBox) == ContainmentType.Disjoint) + continue; + + // Get valid selection points + var points = node.GetActorSelectionPoints(); + bool containsAllPoints = points.Length != 0; + foreach (var point in points) + { + _owner.Viewport.ProjectPoint(point, out var loc); + if (!adjustedRect.Contains(loc)) + { + containsAllPoints = false; + break; + } + } + if (containsAllPoints) + { + if (a.HasPrefabLink) + hits.Add(_owner.SceneGraphRoot.Find(a.GetPrefabRoot())); + else + hits.Add(node); + } + } + + var editor = Editor.Instance; + if (_owner.IsControlDown) + { + var newSelection = new List(); + var currentSelection = editor.SceneEditing.Selection; + newSelection.AddRange(currentSelection); + foreach (var hit in hits) + { + if (currentSelection.Contains(hit)) + newSelection.Remove(hit); + else + newSelection.Add(hit); + } + _owner.Select(newSelection); + } + else if (Input.GetKey(KeyboardKeys.Shift)) + { + var newSelection = new List(); + var currentSelection = editor.SceneEditing.Selection; + newSelection.AddRange(hits); + newSelection.AddRange(currentSelection); + _owner.Select(newSelection); + } + else + { + _owner.Select(hits); + } + } + + } + } + + /// + /// Used to draw the rubber band. Begins render 2D. + /// + /// The GPU Context. + /// The GPU texture target. + /// The GPU texture target depth. + public void Draw(GPUContext context, GPUTexture target, GPUTexture targetDepth) + { + // Draw RubberBand for rect selection + if (!_isRubberBandSpanning) + return; + Render2D.Begin(context, target, targetDepth); + Draw2D(); + Render2D.End(); + } + + /// + /// Used to draw the rubber band. Use if already rendering 2D context. + /// + public void Draw2D() + { + if (!_isRubberBandSpanning) + return; + Render2D.FillRectangle(_rubberBandRect, Style.Current.Selection); + Render2D.DrawRectangle(_rubberBandRect, Style.Current.SelectionBorder); + } + + /// + /// Immediately stops the rubber band. + /// + public void StopRubberBand() + { + _isRubberBandSpanning = false; + _tryStartRubberBand = false; + } +} diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs index 91bf26103..68a149de0 100644 --- a/Source/Editor/SceneGraph/ActorNode.cs +++ b/Source/Editor/SceneGraph/ActorNode.cs @@ -182,6 +182,51 @@ namespace FlaxEditor.SceneGraph return null; } + /// + /// Get all nested actor nodes under this actor node. + /// + /// An array of ActorNodes + public ActorNode[] GetAllChildActorNodes() + { + // Check itself + if (ChildNodes == null || ChildNodes.Count == 0) + return []; + + // Check deeper + var nodes = new List(); + for (int i = 0; i < ChildNodes.Count; i++) + { + if (ChildNodes[i] is ActorNode node) + { + nodes.Add(node); + var childNodes = node.GetAllChildActorNodes(); + if (childNodes.Length > 0) + { + nodes.AddRange(childNodes); + } + } + } + return nodes.ToArray(); + } + + /// + /// Whether an actor node can be selected with a selector. + /// + /// True if the actor node can be selected + public virtual bool CanSelectActorNodeWithSelector() + { + return Actor && Actor.HideFlags is not (HideFlags.DontSelect or HideFlags.FullyHidden) && Actor is not EmptyActor && IsActive; + } + + /// + /// The selection points used to check if an actor node can be selected. + /// + /// The points to use if the actor can be selected. + public virtual Vector3[] GetActorSelectionPoints() + { + return Actor.EditorBox.GetCorners(); + } + /// /// Gets a value indicating whether this actor can be used to create prefab from it (as a root). /// diff --git a/Source/Editor/SceneGraph/Actors/CameraNode.cs b/Source/Editor/SceneGraph/Actors/CameraNode.cs index 6c485314d..cada3364c 100644 --- a/Source/Editor/SceneGraph/Actors/CameraNode.cs +++ b/Source/Editor/SceneGraph/Actors/CameraNode.cs @@ -58,5 +58,11 @@ namespace FlaxEditor.SceneGraph.Actors return Camera.Internal_IntersectsItselfEditor(FlaxEngine.Object.GetUnmanagedPtr(_actor), ref ray.Ray, out distance); } + + /// + public override Vector3[] GetActorSelectionPoints() + { + return [Actor.Position]; + } } } diff --git a/Source/Editor/SceneGraph/Actors/SceneNode.cs b/Source/Editor/SceneGraph/Actors/SceneNode.cs index bb5998ef4..9e380ff91 100644 --- a/Source/Editor/SceneGraph/Actors/SceneNode.cs +++ b/Source/Editor/SceneGraph/Actors/SceneNode.cs @@ -33,6 +33,12 @@ namespace FlaxEditor.SceneGraph.Actors } } + /// + public override bool CanSelectActorNodeWithSelector() + { + return false; + } + /// /// Gets the scene. /// diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs index 885cbd5b5..02161d277 100644 --- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs +++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs @@ -88,6 +88,30 @@ namespace FlaxEditor.SceneGraph.Actors contextMenu.AddButton("Add collider", () => OnAddMeshCollider(window)).Enabled = ((StaticModel)Actor).Model != null; } + /// + public override Vector3[] GetActorSelectionPoints() + { + if (Actor is not StaticModel sm || !sm.Model) + return base.GetActorSelectionPoints(); + + // Check collision proxy points for more accurate selection. + var vecPoints = new List(); + var m = sm.Model.LODs[0]; + foreach (var mesh in m.Meshes) + { + var points = mesh.GetCollisionProxyPoints(); + foreach (var point in points) + { + vecPoints.Add(Actor.Transform.LocalToWorld(point)); + } + } + + // Fall back to base actor editor box if no points from collision proxy. + if (vecPoints.Count == 0) + return base.GetActorSelectionPoints(); + return vecPoints.ToArray(); + } + private void OnAddMeshCollider(EditorWindow window) { // Allow collider to be added to evey static model selection diff --git a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs index 91bd6c530..d38816f01 100644 --- a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs +++ b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs @@ -78,5 +78,11 @@ namespace FlaxEditor.SceneGraph.Actors if (Actor is UICanvas uiCanvas && uiCanvas.Is3D) DebugDraw.DrawWireBox(uiCanvas.Bounds, Color.BlueViolet); } + + /// + public override bool CanSelectActorNodeWithSelector() + { + return Actor is UICanvas uiCanvas && uiCanvas.Is3D; + } } } diff --git a/Source/Editor/SceneGraph/Actors/UIControlNode.cs b/Source/Editor/SceneGraph/Actors/UIControlNode.cs index 1d912db48..dc5263de2 100644 --- a/Source/Editor/SceneGraph/Actors/UIControlNode.cs +++ b/Source/Editor/SceneGraph/Actors/UIControlNode.cs @@ -40,5 +40,31 @@ namespace FlaxEditor.SceneGraph.Actors control.PerformLayout(); } } + + /// + public override bool CanSelectActorNodeWithSelector() + { + // Check if control and skip if canvas is 2D + if (Actor is not UIControl uiControl) + return false; + UICanvas canvas = null; + var controlParent = uiControl.Parent; + while (controlParent != null && controlParent is not Scene) + { + if (controlParent is UICanvas uiCanvas) + { + canvas = uiCanvas; + break; + } + controlParent = controlParent.Parent; + } + + if (canvas != null) + { + if (canvas.Is2D) + return false; + } + return true; + } } } diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 813b7c9ac..b8b7271c2 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -11,6 +11,7 @@ using FlaxEditor.Tools; using FlaxEditor.Viewport.Modes; using FlaxEditor.Windows; using FlaxEngine; +using FlaxEngine.Gizmo; using FlaxEngine.GUI; using FlaxEngine.Tools; @@ -111,11 +112,7 @@ namespace FlaxEditor.Viewport private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32); private EditorSpritesRenderer _editorSpritesRenderer; - private bool _isRubberBandSpanning; - private bool _tryStartRubberBand; - private Float2 _cachedStartingMousePosition; - private Rectangle _rubberBandRect; - private Rectangle _lastRubberBandRect; + private ViewportRubberBandSelector _rubberBandSelector; /// /// Drag and drop handlers @@ -222,6 +219,9 @@ namespace FlaxEditor.Viewport TransformGizmo.ApplyTransformation += ApplyTransform; TransformGizmo.Duplicate += _editor.SceneEditing.Duplicate; Gizmos.Active = TransformGizmo; + + // Add rubber band selector + _rubberBandSelector = new ViewportRubberBandSelector(this); // Add grid Grid = new GridGizmo(this); @@ -378,13 +378,7 @@ namespace FlaxEditor.Viewport } // Draw RubberBand for rect selection - if (_isRubberBandSpanning) - { - Render2D.Begin(context, target, targetDepth); - Render2D.FillRectangle(_rubberBandRect, Style.Current.Selection); - Render2D.DrawRectangle(_rubberBandRect, Style.Current.SelectionBorder); - Render2D.End(); - } + _rubberBandSelector.Draw(context, target, targetDepth); // Draw selected objects debug shapes and visuals if (DrawDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw) @@ -500,16 +494,14 @@ namespace FlaxEditor.Viewport public override void OnLostFocus() { base.OnLostFocus(); - _isRubberBandSpanning = false; - _tryStartRubberBand = false; + _rubberBandSelector.StopRubberBand(); } /// public override void OnMouseLeave() { base.OnMouseLeave(); - _isRubberBandSpanning = false; - _tryStartRubberBand = false; + _rubberBandSelector.StopRubberBand(); } /// @@ -616,172 +608,16 @@ namespace FlaxEditor.Viewport base.OnMouseMove(location); // Dont allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled - if (_isRubberBandSpanning && ((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown)) - { - _isRubberBandSpanning = false; - } - - if (_tryStartRubberBand && (Mathf.Abs(MouseDelta.X) > 0.1f || Mathf.Abs(MouseDelta.Y) > 0.1f) && !_isRubberBandSpanning && !Gizmos.Active.IsControllingMouse && !IsControllingMouse && !IsRightMouseButtonDown) - { - _isRubberBandSpanning = true; - _cachedStartingMousePosition = _viewMousePos; - _rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero); - } - else if (_isRubberBandSpanning && !Gizmos.Active.IsControllingMouse && !IsControllingMouse && !IsRightMouseButtonDown) - { - _rubberBandRect.Width = _viewMousePos.X - _cachedStartingMousePosition.X; - _rubberBandRect.Height = _viewMousePos.Y - _cachedStartingMousePosition.Y; - - if (_lastRubberBandRect != _rubberBandRect) - { - // Select rubberbanded rect actor nodes - var adjustedRect = _rubberBandRect; - _lastRubberBandRect = _rubberBandRect; - if (adjustedRect.Width < 0 || adjustedRect.Height < 0) - { - // make sure we have a well-formed rectangle i.e. size is positive and X/Y is upper left corner - var size = adjustedRect.Size; - adjustedRect.X = Mathf.Min(adjustedRect.X, adjustedRect.X + adjustedRect.Width); - adjustedRect.Y = Mathf.Min(adjustedRect.Y, adjustedRect.Y + adjustedRect.Height); - size.X = Mathf.Abs(size.X); - size.Y = Mathf.Abs(size.Y); - adjustedRect.Size = size; - } - - List hits = new List(); - var allActors = Level.GetActors(true); - foreach (var a in allActors) - { - if (a.HideFlags is HideFlags.DontSelect or HideFlags.FullyHidden || a is EmptyActor || a is Scene || !a.IsActive) - continue; - - var actorBox = a.EditorBox; - if (ViewFrustum.Contains(actorBox) == ContainmentType.Disjoint) - continue; - - // Check if control and skip if canvas is 2D - if (a is UIControl control) - { - UICanvas canvas = null; - var controlParent = control.Parent; - while (controlParent != null && controlParent is not Scene) - { - if (controlParent is UICanvas uiCanvas) - { - canvas = uiCanvas; - break; - } - controlParent = controlParent.Parent; - } - - if (canvas != null) - { - if (canvas.Is2D) - continue; - } - } - else if (a is UICanvas uiCanvas) - { - if (uiCanvas.Is2D) - continue; - } - - var containsAllPoints = true; - var fallBackToBox = false; - if (a is StaticModel sm) - { - if (sm.Model) - { - var m = sm.Model.LODs[0]; - foreach (var mesh in m.Meshes) - { - var points = mesh.GetCollisionProxyPoints(); - if (points.Length == 0) - { - fallBackToBox = true; - break; - } - foreach (var point in points) - { - Viewport.ProjectPoint(a.Transform.LocalToWorld(point), out var loc); - if (!adjustedRect.Contains(loc)) - { - containsAllPoints = false; - break; - } - } - } - } - } - else - { - fallBackToBox = true; - } - - if (fallBackToBox) - { - // Check if all corners are in box to select it. - var corners = actorBox.GetCorners(); - foreach (var c in corners) - { - Viewport.ProjectPoint(c, out var loc); - if (!adjustedRect.Contains(loc)) - { - containsAllPoints = false; - break; - } - } - } - - if (containsAllPoints) - { - if (a.HasPrefabLink) - hits.Add(SceneGraphRoot.Find(a.GetPrefabRoot())); - else - hits.Add(SceneGraphRoot.Find(a)); - } - } - - if (IsControlDown) - { - var newSelection = new List(); - var currentSelection = _editor.SceneEditing.Selection; - newSelection.AddRange(currentSelection); - foreach (var hit in hits) - { - if (currentSelection.Contains(hit)) - newSelection.Remove(hit); - else - newSelection.Add(hit); - } - Select(newSelection); - } - else if (((WindowRootControl)Root).GetKey(KeyboardKeys.Shift)) - { - var newSelection = new List(); - var currentSelection = _editor.SceneEditing.Selection; - newSelection.AddRange(hits); - newSelection.AddRange(currentSelection); - Select(newSelection); - } - else - { - Select(hits); - } - } - - } + _rubberBandSelector.TryCreateRubberBand(!((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown), + _viewMousePos, ViewFrustum); } /// protected override void OnLeftMouseButtonDown() { base.OnLeftMouseButtonDown(); - - if (!_isRubberBandSpanning && !Gizmos.Active.IsControllingMouse && !IsControllingMouse && !IsRightMouseButtonDown) - { - _tryStartRubberBand = true; - } + + _rubberBandSelector.TryStartingRubberBandSelection(); } /// @@ -791,17 +627,8 @@ namespace FlaxEditor.Viewport if (_prevInput.IsControllingMouse || !Bounds.Contains(ref _viewMousePos)) return; - if (_tryStartRubberBand) - { - _tryStartRubberBand = false; - } - - // Select rubberbanded rect actor nodes - if (_isRubberBandSpanning) - { - _isRubberBandSpanning = false; - } - else + // Select rubberbanded rect actor nodes or pick with gizmo + if (!_rubberBandSelector.ReleaseRubberBandSelection()) { // Try to pick something with the current gizmo Gizmos.Active?.Pick(); diff --git a/Source/Engine/Graphics/Mesh.cs b/Source/Engine/Graphics/Mesh.cs index e44dbec7b..e87a38ae7 100644 --- a/Source/Engine/Graphics/Mesh.cs +++ b/Source/Engine/Graphics/Mesh.cs @@ -638,7 +638,7 @@ namespace FlaxEngine /// Gets the collision proxy points for the mesh. /// /// The triangle points in the collision proxy. - public Float3[] GetCollisionProxyPoints() + internal Float3[] GetCollisionProxyPoints() { return Internal_GetCollisionProxyPoints(__unmanagedPtr, out _); } From 54dc66d22e7e0193d33e3bbd2e88316229f89e72 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 8 Mar 2025 09:41:00 -0600 Subject: [PATCH 13/15] Add base check to control and canvas nodes. --- Source/Editor/SceneGraph/Actors/UICanvasNode.cs | 2 +- Source/Editor/SceneGraph/Actors/UIControlNode.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs index d38816f01..ef7663e5d 100644 --- a/Source/Editor/SceneGraph/Actors/UICanvasNode.cs +++ b/Source/Editor/SceneGraph/Actors/UICanvasNode.cs @@ -82,7 +82,7 @@ namespace FlaxEditor.SceneGraph.Actors /// public override bool CanSelectActorNodeWithSelector() { - return Actor is UICanvas uiCanvas && uiCanvas.Is3D; + return Actor is UICanvas uiCanvas && uiCanvas.Is3D && base.CanSelectActorNodeWithSelector(); } } } diff --git a/Source/Editor/SceneGraph/Actors/UIControlNode.cs b/Source/Editor/SceneGraph/Actors/UIControlNode.cs index dc5263de2..57a956665 100644 --- a/Source/Editor/SceneGraph/Actors/UIControlNode.cs +++ b/Source/Editor/SceneGraph/Actors/UIControlNode.cs @@ -64,7 +64,7 @@ namespace FlaxEditor.SceneGraph.Actors if (canvas.Is2D) return false; } - return true; + return base.CanSelectActorNodeWithSelector(); } } } From 837a4927c13adf760d2e9619a239a8b5a124dda6 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 8 Mar 2025 10:50:46 -0600 Subject: [PATCH 14/15] Try fix build issues. --- Source/Engine/Graphics/Models/Mesh.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 365280a44..ac194d905 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -850,6 +850,7 @@ MArray* Mesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI Array Mesh::GetCollisionProxyPoints() const { Array result; +#if USE_PRECISE_MESH_INTERSECTS for (int i = 0; i < _collisionProxy.Triangles.Count(); ++i) { auto triangle = _collisionProxy.Triangles[i]; @@ -857,6 +858,7 @@ Array Mesh::GetCollisionProxyPoints() const result.Add(triangle.V1); result.Add(triangle.V2); } +#endif return result; } From 5340eac14ac446c8905c2309c609c178a0baccc3 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 9 Mar 2025 13:14:26 -0500 Subject: [PATCH 15/15] Small fixes. --- Source/Editor/Gizmo/ViewportRubberBandSelector.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs index 1f61aeb0a..ddb889dd8 100644 --- a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs +++ b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using FlaxEditor; using FlaxEditor.Gizmo; using FlaxEditor.SceneGraph; +using FlaxEditor.Viewport; using FlaxEngine.GUI; namespace FlaxEngine.Gizmo; @@ -132,7 +133,7 @@ public class ViewportRubberBandSelector } if (containsAllPoints) { - if (a.HasPrefabLink) + if (a.HasPrefabLink && _owner is not PrefabWindowViewport) hits.Add(_owner.SceneGraphRoot.Find(a.GetPrefabRoot())); else hits.Add(node); @@ -143,7 +144,7 @@ public class ViewportRubberBandSelector if (_owner.IsControlDown) { var newSelection = new List(); - var currentSelection = editor.SceneEditing.Selection; + var currentSelection = _owner.SceneGraphRoot.Selection; newSelection.AddRange(currentSelection); foreach (var hit in hits) { @@ -157,7 +158,7 @@ public class ViewportRubberBandSelector else if (Input.GetKey(KeyboardKeys.Shift)) { var newSelection = new List(); - var currentSelection = editor.SceneEditing.Selection; + var currentSelection = _owner.SceneGraphRoot.Selection; newSelection.AddRange(hits); newSelection.AddRange(currentSelection); _owner.Select(newSelection); @@ -167,7 +168,6 @@ public class ViewportRubberBandSelector _owner.Select(hits); } } - } }