From af63858d800ae836f64ea716000bba86933671b2 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 17 Feb 2024 19:42:38 -0600 Subject: [PATCH 01/41] Fix Scale gizmo for multiple directions. --- Source/Editor/Gizmo/TransformGizmoBase.cs | 66 +++++++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 6123d348a..5a46b6fbf 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -253,7 +253,21 @@ namespace FlaxEditor.Gizmo _intersectPosition = ray.Position + ray.Direction * intersection; if (_lastIntersectionPosition != Vector3.Zero) _tDelta = _intersectPosition - _lastIntersectionPosition; - delta = new Vector3(0, _tDelta.Y, _tDelta.Z); + if (isScaling) + { + var tDeltaAbs = Vector3.Abs(_tDelta); + var maxDelta = Mathf.Max(tDeltaAbs.Y, tDeltaAbs.Z); + float sign = 0; + if (tDeltaAbs.Y > tDeltaAbs.Z) + sign = Mathf.Sign(_tDelta.Y); + else if (tDeltaAbs.Z > tDeltaAbs.Y) + sign = Mathf.Sign(_tDelta.Z); + delta = new Vector3(0, maxDelta * sign, maxDelta * sign); + } + else + { + delta = new Vector3(0, _tDelta.Y, _tDelta.Z); + } } break; } @@ -264,7 +278,21 @@ namespace FlaxEditor.Gizmo _intersectPosition = ray.Position + ray.Direction * intersection; if (_lastIntersectionPosition != Vector3.Zero) _tDelta = _intersectPosition - _lastIntersectionPosition; - delta = new Vector3(_tDelta.X, _tDelta.Y, 0); + if (isScaling) + { + var tDeltaAbs = Vector3.Abs(_tDelta); + var maxDelta = Mathf.Max(tDeltaAbs.X, tDeltaAbs.Y); + float sign = 0; + if (tDeltaAbs.X > tDeltaAbs.Y) + sign = Mathf.Sign(_tDelta.X); + else if (tDeltaAbs.Y > tDeltaAbs.X) + sign = Mathf.Sign(_tDelta.Y); + delta = new Vector3(maxDelta * sign, maxDelta * sign, 0); + } + else + { + delta = new Vector3(_tDelta.X, _tDelta.Y, 0); + } } break; } @@ -275,7 +303,21 @@ namespace FlaxEditor.Gizmo _intersectPosition = ray.Position + ray.Direction * intersection; if (_lastIntersectionPosition != Vector3.Zero) _tDelta = _intersectPosition - _lastIntersectionPosition; - delta = new Vector3(_tDelta.X, 0, _tDelta.Z); + if (isScaling) + { + var tDeltaAbs = Vector3.Abs(_tDelta); + var maxDelta = Mathf.Max(tDeltaAbs.X, tDeltaAbs.Z); + float sign = 0; + if (tDeltaAbs.X > tDeltaAbs.Z) + sign = Mathf.Sign(_tDelta.X); + else if (tDeltaAbs.Z > tDeltaAbs.X) + sign = Mathf.Sign(_tDelta.Z); + delta = new Vector3(maxDelta * sign, 0, maxDelta * sign); + } + else + { + delta = new Vector3(_tDelta.X, 0, _tDelta.Z); + } } break; } @@ -289,7 +331,23 @@ namespace FlaxEditor.Gizmo if (_lastIntersectionPosition != Vector3.Zero) _tDelta = _intersectPosition - _lastIntersectionPosition; } - delta = _tDelta; + if (isScaling) + { + var tDeltaAbs = Vector3.Abs(_tDelta); + var maxDelta = Mathf.Max(new[] { tDeltaAbs.X, tDeltaAbs.Y, tDeltaAbs.Z }); + float sign = 0; + if (Mathf.NearEqual(maxDelta, tDeltaAbs.X)) + sign = Mathf.Sign(_tDelta.X); + else if (Mathf.NearEqual(maxDelta, tDeltaAbs.Y)) + sign = Mathf.Sign(_tDelta.Y); + else if (Mathf.NearEqual(maxDelta, tDeltaAbs.Z)) + sign = Mathf.Sign(_tDelta.Z); + delta = new Vector3(maxDelta * sign); + } + else + { + delta = _tDelta; + } break; } } From eed44c14df98178cb18cce5e2b15d62f90b62572 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 17 Feb 2024 19:47:18 -0600 Subject: [PATCH 02/41] Simplify code --- Source/Editor/Gizmo/TransformGizmoBase.cs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 5a46b6fbf..2ce1c8e5e 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -257,11 +257,7 @@ namespace FlaxEditor.Gizmo { var tDeltaAbs = Vector3.Abs(_tDelta); var maxDelta = Mathf.Max(tDeltaAbs.Y, tDeltaAbs.Z); - float sign = 0; - if (tDeltaAbs.Y > tDeltaAbs.Z) - sign = Mathf.Sign(_tDelta.Y); - else if (tDeltaAbs.Z > tDeltaAbs.Y) - sign = Mathf.Sign(_tDelta.Z); + float sign = Mathf.Sign(tDeltaAbs.Y > tDeltaAbs.Z ? _tDelta.Y : _tDelta.Z); delta = new Vector3(0, maxDelta * sign, maxDelta * sign); } else @@ -282,11 +278,7 @@ namespace FlaxEditor.Gizmo { var tDeltaAbs = Vector3.Abs(_tDelta); var maxDelta = Mathf.Max(tDeltaAbs.X, tDeltaAbs.Y); - float sign = 0; - if (tDeltaAbs.X > tDeltaAbs.Y) - sign = Mathf.Sign(_tDelta.X); - else if (tDeltaAbs.Y > tDeltaAbs.X) - sign = Mathf.Sign(_tDelta.Y); + float sign = Mathf.Sign(tDeltaAbs.X > tDeltaAbs.Y ? _tDelta.X : _tDelta.Y); delta = new Vector3(maxDelta * sign, maxDelta * sign, 0); } else @@ -307,11 +299,7 @@ namespace FlaxEditor.Gizmo { var tDeltaAbs = Vector3.Abs(_tDelta); var maxDelta = Mathf.Max(tDeltaAbs.X, tDeltaAbs.Z); - float sign = 0; - if (tDeltaAbs.X > tDeltaAbs.Z) - sign = Mathf.Sign(_tDelta.X); - else if (tDeltaAbs.Z > tDeltaAbs.X) - sign = Mathf.Sign(_tDelta.Z); + float sign = Mathf.Sign(tDeltaAbs.X > tDeltaAbs.Z ? _tDelta.X : _tDelta.Z); delta = new Vector3(maxDelta * sign, 0, maxDelta * sign); } else From 6d77d455298178206a76590ca2c755b6d102794c Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 6 Mar 2024 06:42:20 -0600 Subject: [PATCH 03/41] use var and real for sign --- Source/Editor/Gizmo/TransformGizmoBase.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 2ce1c8e5e..3e1527e1d 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -257,7 +257,7 @@ namespace FlaxEditor.Gizmo { var tDeltaAbs = Vector3.Abs(_tDelta); var maxDelta = Mathf.Max(tDeltaAbs.Y, tDeltaAbs.Z); - float sign = Mathf.Sign(tDeltaAbs.Y > tDeltaAbs.Z ? _tDelta.Y : _tDelta.Z); + var sign = Mathf.Sign(tDeltaAbs.Y > tDeltaAbs.Z ? _tDelta.Y : _tDelta.Z); delta = new Vector3(0, maxDelta * sign, maxDelta * sign); } else @@ -278,7 +278,7 @@ namespace FlaxEditor.Gizmo { var tDeltaAbs = Vector3.Abs(_tDelta); var maxDelta = Mathf.Max(tDeltaAbs.X, tDeltaAbs.Y); - float sign = Mathf.Sign(tDeltaAbs.X > tDeltaAbs.Y ? _tDelta.X : _tDelta.Y); + var sign = Mathf.Sign(tDeltaAbs.X > tDeltaAbs.Y ? _tDelta.X : _tDelta.Y); delta = new Vector3(maxDelta * sign, maxDelta * sign, 0); } else @@ -299,7 +299,7 @@ namespace FlaxEditor.Gizmo { var tDeltaAbs = Vector3.Abs(_tDelta); var maxDelta = Mathf.Max(tDeltaAbs.X, tDeltaAbs.Z); - float sign = Mathf.Sign(tDeltaAbs.X > tDeltaAbs.Z ? _tDelta.X : _tDelta.Z); + var sign = Mathf.Sign(tDeltaAbs.X > tDeltaAbs.Z ? _tDelta.X : _tDelta.Z); delta = new Vector3(maxDelta * sign, 0, maxDelta * sign); } else @@ -323,7 +323,7 @@ namespace FlaxEditor.Gizmo { var tDeltaAbs = Vector3.Abs(_tDelta); var maxDelta = Mathf.Max(new[] { tDeltaAbs.X, tDeltaAbs.Y, tDeltaAbs.Z }); - float sign = 0; + Real sign = 0; if (Mathf.NearEqual(maxDelta, tDeltaAbs.X)) sign = Mathf.Sign(_tDelta.X); else if (Mathf.NearEqual(maxDelta, tDeltaAbs.Y)) From c858b67f2e8f7bd67f48c2ca91442af51bca83c4 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 13 May 2024 16:48:45 -0500 Subject: [PATCH 04/41] Try and fix large world issue. --- Source/Editor/Gizmo/TransformGizmoBase.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 3e1527e1d..0d209889f 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -256,8 +256,8 @@ namespace FlaxEditor.Gizmo if (isScaling) { var tDeltaAbs = Vector3.Abs(_tDelta); - var maxDelta = Mathf.Max(tDeltaAbs.Y, tDeltaAbs.Z); - var sign = Mathf.Sign(tDeltaAbs.Y > tDeltaAbs.Z ? _tDelta.Y : _tDelta.Z); + var maxDelta = Math.Max(tDeltaAbs.Y, tDeltaAbs.Z); + var sign = Math.Sign(tDeltaAbs.Y > tDeltaAbs.Z ? _tDelta.Y : _tDelta.Z); delta = new Vector3(0, maxDelta * sign, maxDelta * sign); } else @@ -277,8 +277,8 @@ namespace FlaxEditor.Gizmo if (isScaling) { var tDeltaAbs = Vector3.Abs(_tDelta); - var maxDelta = Mathf.Max(tDeltaAbs.X, tDeltaAbs.Y); - var sign = Mathf.Sign(tDeltaAbs.X > tDeltaAbs.Y ? _tDelta.X : _tDelta.Y); + var maxDelta = Math.Max(tDeltaAbs.X, tDeltaAbs.Y); + var sign = Math.Sign(tDeltaAbs.X > tDeltaAbs.Y ? _tDelta.X : _tDelta.Y); delta = new Vector3(maxDelta * sign, maxDelta * sign, 0); } else @@ -298,8 +298,8 @@ namespace FlaxEditor.Gizmo if (isScaling) { var tDeltaAbs = Vector3.Abs(_tDelta); - var maxDelta = Mathf.Max(tDeltaAbs.X, tDeltaAbs.Z); - var sign = Mathf.Sign(tDeltaAbs.X > tDeltaAbs.Z ? _tDelta.X : _tDelta.Z); + var maxDelta = Math.Max(tDeltaAbs.X, tDeltaAbs.Z); + var sign = Math.Sign(tDeltaAbs.X > tDeltaAbs.Z ? _tDelta.X : _tDelta.Z); delta = new Vector3(maxDelta * sign, 0, maxDelta * sign); } else @@ -322,14 +322,15 @@ namespace FlaxEditor.Gizmo if (isScaling) { var tDeltaAbs = Vector3.Abs(_tDelta); - var maxDelta = Mathf.Max(new[] { tDeltaAbs.X, tDeltaAbs.Y, tDeltaAbs.Z }); + var maxDelta = Math.Max(tDeltaAbs.X, tDeltaAbs.Y); + maxDelta = Math.Max(maxDelta, tDeltaAbs.Z); Real sign = 0; if (Mathf.NearEqual(maxDelta, tDeltaAbs.X)) - sign = Mathf.Sign(_tDelta.X); + sign = Math.Sign(_tDelta.X); else if (Mathf.NearEqual(maxDelta, tDeltaAbs.Y)) - sign = Mathf.Sign(_tDelta.Y); + sign = Math.Sign(_tDelta.Y); else if (Mathf.NearEqual(maxDelta, tDeltaAbs.Z)) - sign = Mathf.Sign(_tDelta.Z); + sign = Math.Sign(_tDelta.Z); delta = new Vector3(maxDelta * sign); } else From b57e847916d3f3ef03de1b51b9073dec152f7be7 Mon Sep 17 00:00:00 2001 From: Zode Date: Mon, 17 Jun 2024 21:19:31 +0300 Subject: [PATCH 05/41] Add absolute snapping --- .../Gizmo/TransformGizmoBase.Settings.cs | 5 +++ Source/Editor/Gizmo/TransformGizmoBase.cs | 42 +++++++++++++++++++ Source/Editor/Viewport/EditorGizmoViewport.cs | 20 ++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs b/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs index bbd13f688..c7af85c14 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs @@ -75,6 +75,11 @@ namespace FlaxEditor.Gizmo /// public bool ScaleSnapEnabled = false; + /// + /// True if enable absolute grid snapping + /// + public bool AbsoluteSnapEnabled = false; + /// /// Translation snap value /// diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 81f76d392..2c7580c79 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -40,6 +40,7 @@ namespace FlaxEditor.Gizmo private Vector3 _intersectPosition; private bool _isActive; private bool _isDuplicating; + private bool _hasAbsoluteSnapped; private bool _isTransforming; private Vector3 _lastIntersectionPosition; @@ -316,6 +317,7 @@ namespace FlaxEditor.Gizmo if ((isScaling ? ScaleSnapEnabled : TranslationSnapEnable) || Owner.UseSnapping) { var snapValue = new Vector3(isScaling ? ScaleSnapValue : TranslationSnapValue); + _translationScaleSnapDelta += delta; if (!isScaling && snapValue.X < 0.0f) { @@ -334,11 +336,29 @@ namespace FlaxEditor.Gizmo else snapValue.Z = (Real)b.Minimum.Z - b.Maximum.Z; } + + Vector3 absoluteDelta = Vector3.Zero; + if (!_hasAbsoluteSnapped && AbsoluteSnapEnabled) + { + _hasAbsoluteSnapped = true; + + Vector3 currentTranslationScale = isScaling ? GetSelectedTransform(0).Scale : GetSelectedTransform(0).Translation; + absoluteDelta = currentTranslationScale - new Vector3( + Mathf.Round(currentTranslationScale.X / snapValue.X) * snapValue.X, + Mathf.Round(currentTranslationScale.Y / snapValue.Y) * snapValue.Y, + Mathf.Round(currentTranslationScale.Z / snapValue.Z) * snapValue.Z); + } + delta = new Vector3( (int)(_translationScaleSnapDelta.X / snapValue.X) * snapValue.X, (int)(_translationScaleSnapDelta.Y / snapValue.Y) * snapValue.Y, (int)(_translationScaleSnapDelta.Z / snapValue.Z) * snapValue.Z); _translationScaleSnapDelta -= delta; + delta -= absoluteDelta; + } + else + { + _hasAbsoluteSnapped = false; } if (_activeMode == Mode.Translate) @@ -368,12 +388,34 @@ namespace FlaxEditor.Gizmo if (RotationSnapEnabled || Owner.UseSnapping) { float snapValue = RotationSnapValue * Mathf.DegreesToRadians; + + float absoluteDelta = 0.0f; + if (!_hasAbsoluteSnapped && AbsoluteSnapEnabled) + { + _hasAbsoluteSnapped = true; + + float currentAngle = 0.0f; + switch (_activeAxis) + { + case Axis.X: currentAngle = GetSelectedTransform(0).Orientation.EulerAngles.X; break; + case Axis.Y: currentAngle = GetSelectedTransform(0).Orientation.EulerAngles.Y; break; + case Axis.Z: currentAngle = GetSelectedTransform(0).Orientation.EulerAngles.Z; break; + } + + absoluteDelta = currentAngle - (Mathf.Round(currentAngle / RotationSnapValue) * RotationSnapValue); + } + _rotationSnapDelta += delta; float snapped = Mathf.Round(_rotationSnapDelta / snapValue) * snapValue; _rotationSnapDelta -= snapped; delta = snapped; + delta -= absoluteDelta * Mathf.DegreesToRadians; + } + else + { + _hasAbsoluteSnapped = false; } switch (_activeAxis) diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index f0681e48d..e79d3f19e 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -137,7 +137,9 @@ namespace FlaxEditor.Viewport if (useProjectCache) { // Initialize snapping enabled from cached values - if (editor.ProjectCache.TryGetCustomData("TranslateSnapState", out bool cachedBool)) + if (editor.ProjectCache.TryGetCustomData("AbsoluteSnapState", out bool cachedBool)) + transformGizmo.AbsoluteSnapEnabled = cachedBool; + if (editor.ProjectCache.TryGetCustomData("TranslateSnapState", out cachedBool)) transformGizmo.TranslationSnapEnable = cachedBool; if (editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedBool)) transformGizmo.RotationSnapEnabled = cachedBool; @@ -169,6 +171,22 @@ namespace FlaxEditor.Viewport }; transformSpaceWidget.Parent = viewport; + // Absolute snapping widget + var absoluteSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + var enableAbsoluteSnapping = new ViewportWidgetButton("A", SpriteHandle.Default, null, true) + { + Checked = transformGizmo.AbsoluteSnapEnabled, + TooltipText = "Enable absolute snapping", + Parent = absoluteSnappingWidget + }; + enableAbsoluteSnapping.Toggled += _ => + { + transformGizmo.AbsoluteSnapEnabled = !transformGizmo.AbsoluteSnapEnabled; + if (useProjectCache) + editor.ProjectCache.SetCustomData("AbsoluteSnapState", transformGizmo.AbsoluteSnapEnabled); + }; + absoluteSnappingWidget.Parent = viewport; + // Scale snapping widget var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true) From 840abf8cdb4750bfaab47f02e3a03fd75ed69b46 Mon Sep 17 00:00:00 2001 From: Zode Date: Mon, 17 Jun 2024 21:27:13 +0300 Subject: [PATCH 06/41] absolute snap only in world space --- Source/Editor/Gizmo/TransformGizmoBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 2c7580c79..5e2a20d17 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -338,7 +338,7 @@ namespace FlaxEditor.Gizmo } Vector3 absoluteDelta = Vector3.Zero; - if (!_hasAbsoluteSnapped && AbsoluteSnapEnabled) + if (!_hasAbsoluteSnapped && AbsoluteSnapEnabled && ActiveTransformSpace == TransformSpace.World) { _hasAbsoluteSnapped = true; @@ -390,7 +390,7 @@ namespace FlaxEditor.Gizmo float snapValue = RotationSnapValue * Mathf.DegreesToRadians; float absoluteDelta = 0.0f; - if (!_hasAbsoluteSnapped && AbsoluteSnapEnabled) + if (!_hasAbsoluteSnapped && AbsoluteSnapEnabled && ActiveTransformSpace == TransformSpace.World) { _hasAbsoluteSnapped = true; From 5780fb7392608e2403ee306ee9428a349bc4cf58 Mon Sep 17 00:00:00 2001 From: Zode Date: Mon, 17 Jun 2024 22:16:10 +0300 Subject: [PATCH 07/41] absolute snapping: large worlds fix --- Source/Editor/Gizmo/TransformGizmoBase.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 5e2a20d17..1715a5b02 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -343,10 +343,20 @@ namespace FlaxEditor.Gizmo _hasAbsoluteSnapped = true; Vector3 currentTranslationScale = isScaling ? GetSelectedTransform(0).Scale : GetSelectedTransform(0).Translation; - absoluteDelta = currentTranslationScale - new Vector3( - Mathf.Round(currentTranslationScale.X / snapValue.X) * snapValue.X, - Mathf.Round(currentTranslationScale.Y / snapValue.Y) * snapValue.Y, - Mathf.Round(currentTranslationScale.Z / snapValue.Z) * snapValue.Z); + if(LargeWorlds.Enable) + { + absoluteDelta = currentTranslationScale - new Vector3( + Double.Round(currentTranslationScale.X / snapValue.X) * snapValue.X, + Double.Round(currentTranslationScale.Y / snapValue.Y) * snapValue.Y, + Double.Round(currentTranslationScale.Z / snapValue.Z) * snapValue.Z); + } + else + { + absoluteDelta = currentTranslationScale - new Vector3( + Mathf.Round(currentTranslationScale.X / snapValue.X) * snapValue.X, + Mathf.Round(currentTranslationScale.Y / snapValue.Y) * snapValue.Y, + Mathf.Round(currentTranslationScale.Z / snapValue.Z) * snapValue.Z); + } } delta = new Vector3( From 7ede2504032933ce8ff089e89632cacdb1f65d84 Mon Sep 17 00:00:00 2001 From: Zode Date: Mon, 17 Jun 2024 22:59:19 +0300 Subject: [PATCH 08/41] absolute snapping: actually fix large world this time --- Source/Editor/Gizmo/TransformGizmoBase.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 1715a5b02..2ce601d1e 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -343,20 +343,10 @@ namespace FlaxEditor.Gizmo _hasAbsoluteSnapped = true; Vector3 currentTranslationScale = isScaling ? GetSelectedTransform(0).Scale : GetSelectedTransform(0).Translation; - if(LargeWorlds.Enable) - { - absoluteDelta = currentTranslationScale - new Vector3( - Double.Round(currentTranslationScale.X / snapValue.X) * snapValue.X, - Double.Round(currentTranslationScale.Y / snapValue.Y) * snapValue.Y, - Double.Round(currentTranslationScale.Z / snapValue.Z) * snapValue.Z); - } - else - { - absoluteDelta = currentTranslationScale - new Vector3( - Mathf.Round(currentTranslationScale.X / snapValue.X) * snapValue.X, - Mathf.Round(currentTranslationScale.Y / snapValue.Y) * snapValue.Y, - Mathf.Round(currentTranslationScale.Z / snapValue.Z) * snapValue.Z); - } + absoluteDelta = currentTranslationScale - new Vector3( + (Real)Math.Round(currentTranslationScale.X / snapValue.X) * snapValue.X, + (Real)Math.Round(currentTranslationScale.Y / snapValue.Y) * snapValue.Y, + (Real)Math.Round(currentTranslationScale.Z / snapValue.Z) * snapValue.Z); } delta = new Vector3( From 757b8ff855a6e9ada52a9e376f207abb619c8959 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 31 Dec 2024 00:50:42 -0600 Subject: [PATCH 09/41] Fix local origin option with multi-mesh import. --- Source/Engine/Graphics/Models/ModelData.h | 2 +- .../Tools/ModelTool/ModelTool.Assimp.cpp | 22 ++------ .../Tools/ModelTool/ModelTool.OpenFBX.cpp | 3 +- Source/Engine/Tools/ModelTool/ModelTool.cpp | 54 +++++++++++++------ 4 files changed, 46 insertions(+), 35 deletions(-) diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 7278611e6..08903a0eb 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -91,7 +91,7 @@ public: Array BlendShapes; /// - /// Global translation for this mesh to be at it's local origin. + /// Local translation for this mesh to be at it's local origin. /// Vector3 OriginTranslation = Vector3::Zero; diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index c4f27abf5..3cf47103a 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -645,24 +645,10 @@ bool ImportMesh(int32 index, ModelData& result, AssimpImporterData& data, String // Link mesh meshData->NodeIndex = nodeIndex; AssimpNode* curNode = &data.Nodes[meshData->NodeIndex]; - Vector3 translation = Vector3::Zero; - Vector3 scale = Vector3::One; - Quaternion rotation = Quaternion::Identity; - - while (true) - { - translation += curNode->LocalTransform.Translation; - scale *= curNode->LocalTransform.Scale; - rotation *= curNode->LocalTransform.Orientation; - - if (curNode->ParentIndex == -1) - break; - curNode = &data.Nodes[curNode->ParentIndex]; - } - - meshData->OriginTranslation = translation; - meshData->OriginOrientation = rotation; - meshData->Scaling = scale; + + meshData->OriginTranslation = curNode->LocalTransform.Translation; + meshData->OriginOrientation = curNode->LocalTransform.Orientation; + meshData->Scaling = curNode->LocalTransform.Scale; if (result.LODs.Count() <= lodIndex) result.LODs.Resize(lodIndex + 1); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 98747086a..7cb38ce43 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -1026,7 +1026,8 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* }*/ // Get local transform for origin shifting translation - auto translation = ToMatrix(aMesh->getGlobalTransform()).GetTranslation(); + + auto translation = Float3((float)aMesh->getLocalTranslation().x, (float)aMesh->getLocalTranslation().y, (float)aMesh->getLocalTranslation().z); auto scale = data.GlobalSettings.UnitScaleFactor; if (data.GlobalSettings.CoordAxis == ofbx::CoordSystem_RightHanded) mesh.OriginTranslation = scale * Vector3(translation.X, translation.Y, -translation.Z); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index fec4ba8f5..45353e5b8 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1549,23 +1549,25 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option // Prepare import transformation Transform importTransform(options.Translation, options.Rotation, Float3(options.Scale)); - if (options.UseLocalOrigin && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems()) - { - importTransform.Translation -= importTransform.Orientation * data.LODs[0].Meshes[0]->OriginTranslation * importTransform.Scale; - } - if (options.CenterGeometry && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems()) - { - // Calculate the bounding box (use LOD0 as a reference) - BoundingBox box = data.LODs[0].GetBox(); - auto center = data.LODs[0].Meshes[0]->OriginOrientation * importTransform.Orientation * box.GetCenter() * importTransform.Scale * data.LODs[0].Meshes[0]->Scaling; - importTransform.Translation -= center; - } // Apply the import transformation - if (!importTransform.IsIdentity() && data.Nodes.HasItems()) + if ((!importTransform.IsIdentity() || options.UseLocalOrigin || options.CenterGeometry) && data.Nodes.HasItems()) { if (options.Type == ModelType::SkinnedModel) { + // Setup other transform options + if (options.UseLocalOrigin && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems()) + { + importTransform.Translation -= importTransform.Orientation * data.LODs[0].Meshes[0]->OriginTranslation * importTransform.Scale; + } + if (options.CenterGeometry && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems()) + { + // Calculate the bounding box (use LOD0 as a reference) + BoundingBox box = data.LODs[0].GetBox(); + auto center = data.LODs[0].Meshes[0]->OriginOrientation * importTransform.Orientation * box.GetCenter() * importTransform.Scale * data.LODs[0].Meshes[0]->Scaling; + importTransform.Translation -= center; + } + // Transform the root node using the import transformation auto& root = data.Skeleton.RootNode(); Transform meshTransform = root.LocalTransform.WorldToLocal(importTransform).LocalToWorld(root.LocalTransform); @@ -1596,9 +1598,31 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } else { - // Transform the root node using the import transformation - auto& root = data.Nodes[0]; - root.LocalTransform = importTransform.LocalToWorld(root.LocalTransform); + // Transform the nodes using the import transformation + if (data.LODs.HasItems() && data.LODs[0].Meshes.HasItems()) + { + for (int i = 0; i < data.LODs[0].Meshes.Count(); ++i) + { + auto* meshData = data.LODs[0].Meshes[i]; + Transform transform = importTransform; + if (options.UseLocalOrigin) + { + transform.Translation -= transform.Orientation * meshData->OriginTranslation * transform.Scale; + } + if (options.CenterGeometry) + { + // Calculate the bounding box (use LOD0 as a reference) + BoundingBox box = data.LODs[0].GetBox(); + auto center = meshData->OriginOrientation * transform.Orientation * box.GetCenter() * transform.Scale * meshData->Scaling; + transform.Translation -= center; + } + + int32 nodeIndex = meshData->NodeIndex; + + auto& node = data.Nodes[nodeIndex]; + node.LocalTransform = transform.LocalToWorld(node.LocalTransform); + } + } } } From d0d5ad4657c04b10738475451a4f79dcc4462ceb Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 12 Jan 2025 21:33:01 -0600 Subject: [PATCH 10/41] 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 11/41] 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 12/41] 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 13/41] 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 14/41] 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 15/41] 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 16/41] 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 17/41] 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 18/41] 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 19/41] 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 20/41] 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 21/41] 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 22/41] 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 1ece21e25c5d9d7018803222093c625971fe8178 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 8 Mar 2025 10:46:11 -0600 Subject: [PATCH 23/41] Deselect locked object on scene change. --- Source/Editor/Windows/PropertiesWindow.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Editor/Windows/PropertiesWindow.cs b/Source/Editor/Windows/PropertiesWindow.cs index 423d3af18..d10b1916f 100644 --- a/Source/Editor/Windows/PropertiesWindow.cs +++ b/Source/Editor/Windows/PropertiesWindow.cs @@ -74,6 +74,11 @@ namespace FlaxEditor.Windows if (Level.ScenesCount > 1) return; _actorScrollValues.Clear(); + if (LockObjects) + { + LockObjects = false; + Presenter.Deselect(); + } } private void OnScrollValueChanged() From 837a4927c13adf760d2e9619a239a8b5a124dda6 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 8 Mar 2025 10:50:46 -0600 Subject: [PATCH 24/41] 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 e9f2d94a2ed453e11ea2e95cf36cc7ac19fee3f1 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 8 Mar 2025 11:07:07 -0600 Subject: [PATCH 25/41] Remove object editor drop down if disabled. --- Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs index 9a8b80d40..e74c9bd06 100644 --- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs @@ -204,7 +204,7 @@ namespace FlaxEditor.CustomEditors.Editors var frameRect = new Rectangle(0, 0, Width, 16); if (isSelected) frameRect.Width -= 16; - if (_supportsPickDropDown) + if (_supportsPickDropDown && isEnabled) frameRect.Width -= 16; var nameRect = new Rectangle(2, 1, frameRect.Width - 4, 14); var button1Rect = new Rectangle(nameRect.Right + 2, 1, 14, 14); @@ -240,7 +240,7 @@ namespace FlaxEditor.CustomEditors.Editors } // Draw picker button - if (_supportsPickDropDown) + if (_supportsPickDropDown && isEnabled) { var pickerRect = isSelected ? button2Rect : button1Rect; Render2D.DrawSprite(style.ArrowDown, pickerRect, isEnabled && pickerRect.Contains(_mousePos) ? style.Foreground : style.ForegroundGrey); From 5340eac14ac446c8905c2309c609c178a0baccc3 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 9 Mar 2025 13:14:26 -0500 Subject: [PATCH 26/41] 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); } } - } } From 0abd953ded7c02f75e0f99d91cb808cd168b70e1 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 9 Mar 2025 15:29:01 -0500 Subject: [PATCH 27/41] Better selection bounds for multi axis transform gizmo --- Source/Editor/Gizmo/TransformGizmoBase.Settings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs b/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs index 5b54a7c79..9f1fb770a 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs @@ -35,12 +35,12 @@ namespace FlaxEditor.Gizmo /// /// The inner minimum of the multiscale /// - private const float InnerExtend = AxisOffset + 0.5f; + private const float InnerExtend = AxisOffset; /// /// The outer maximum of the multiscale /// - private const float OuterExtend = AxisOffset * 3.5f; + private const float OuterExtend = AxisOffset + 1.25f; // Cube with the size AxisThickness, then moves it along the axis (AxisThickness) and finally makes it really long (AxisLength) private BoundingBox XAxisBox = new BoundingBox(new Vector3(-AxisThickness), new Vector3(AxisThickness)).MakeOffsetted(AxisOffset * Vector3.UnitX).Merge(AxisLength * Vector3.UnitX); From 7a83bce996c213802ddb80d61caa9770bf804238 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 10 Mar 2025 12:46:30 -0500 Subject: [PATCH 28/41] Add using c: in search filtering to find control types. --- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index a1b709efd..5dd5f6cbe 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -238,7 +238,7 @@ namespace FlaxEditor.SceneGraph.GUI } else { - if (Actor !=null) + if (Actor != null) { var actorTypeText = trimmedFilter.Replace("a:", "", StringComparison.OrdinalIgnoreCase).Trim(); var name = TypeUtils.GetTypeDisplayName(Actor.GetType()); @@ -248,6 +248,26 @@ namespace FlaxEditor.SceneGraph.GUI } } } + // Check for control type + else if (trimmedFilter.Contains("c:", StringComparison.OrdinalIgnoreCase)) + { + if (trimmedFilter.Equals("c:", StringComparison.OrdinalIgnoreCase)) + { + if (Actor != null) + hasFilter = true; + } + else + { + if (Actor != null && Actor is UIControl uic && uic.Control != null) + { + var controlTypeText = trimmedFilter.Replace("c:", "", StringComparison.OrdinalIgnoreCase).Trim(); + var name = TypeUtils.GetTypeDisplayName(uic.Control.GetType()); + var nameNoSpaces = name.Replace(" ", ""); + if (name.Contains(controlTypeText, StringComparison.OrdinalIgnoreCase) || nameNoSpaces.Contains(controlTypeText, StringComparison.OrdinalIgnoreCase)) + hasFilter = true; + } + } + } // Match text else { From cd72f4f19b8438915f5f99502edd265d44fb36ee Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Mar 2025 00:04:57 +0100 Subject: [PATCH 29/41] Add `Edit GUI` option to Game Window #3269 --- Source/Editor/Windows/GameWindow.cs | 34 ++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index ebc64cbd8..540beb2bf 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -21,7 +21,7 @@ namespace FlaxEditor.Windows { private readonly RenderOutputControl _viewport; private readonly GameRoot _guiRoot; - private bool _showGUI = true; + private bool _showGUI = true, _editGUI = true; private bool _showDebugDraw = false; private bool _audioMuted = false; private float _audioVolume = 1; @@ -84,6 +84,22 @@ namespace FlaxEditor.Windows } } + /// + /// Gets or sets a value indicating whether allow editing game GUI in the view or keep it visible-only. + /// + public bool EditGUI + { + get => _editGUI; + set + { + if (value != _editGUI) + { + _editGUI = value; + _guiRoot.Editable = value; + } + } + } + /// /// Gets or sets a value indicating whether show Debug Draw shapes in the view or keep it hidden. /// @@ -275,8 +291,9 @@ namespace FlaxEditor.Windows /// private class GameRoot : UIEditorRoot { - public override bool EnableInputs => !Time.GamePaused && Editor.IsPlayMode; - public override bool EnableSelecting => !Editor.IsPlayMode || Time.GamePaused; + internal bool Editable = true; + public override bool EnableInputs => !Time.GamePaused && Editor.IsPlayMode && Editable; + public override bool EnableSelecting => (!Editor.IsPlayMode || Time.GamePaused) && Editable; public override TransformGizmo TransformGizmo => Editor.Instance.MainTransformGizmo; } @@ -668,6 +685,13 @@ namespace FlaxEditor.Windows checkbox.StateChanged += x => ShowGUI = x.Checked; } + // Edit GUI + { + var button = menu.AddButton("Edit GUI"); + var checkbox = new CheckBox(140, 2, EditGUI) { Parent = button }; + checkbox.StateChanged += x => EditGUI = x.Checked; + } + // Show Debug Draw { var button = menu.AddButton("Show Debug Draw"); @@ -1191,6 +1215,7 @@ namespace FlaxEditor.Windows public override void OnLayoutSerialize(XmlWriter writer) { writer.WriteAttributeString("ShowGUI", ShowGUI.ToString()); + writer.WriteAttributeString("EditGUI", EditGUI.ToString()); writer.WriteAttributeString("ShowDebugDraw", ShowDebugDraw.ToString()); writer.WriteAttributeString("DefaultViewportScaling", JsonSerializer.Serialize(_defaultViewportScaling)); writer.WriteAttributeString("CustomViewportScaling", JsonSerializer.Serialize(_customViewportScaling)); @@ -1201,6 +1226,8 @@ namespace FlaxEditor.Windows { if (bool.TryParse(node.GetAttribute("ShowGUI"), out bool value1)) ShowGUI = value1; + if (bool.TryParse(node.GetAttribute("EditGUI"), out value1)) + EditGUI = value1; if (bool.TryParse(node.GetAttribute("ShowDebugDraw"), out value1)) ShowDebugDraw = value1; if (node.HasAttribute("CustomViewportScaling")) @@ -1226,6 +1253,7 @@ namespace FlaxEditor.Windows public override void OnLayoutDeserialize() { ShowGUI = true; + EditGUI = true; ShowDebugDraw = false; } } From a54299a560a204b7374ed0512a28611b29266f63 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Mar 2025 13:05:15 +0100 Subject: [PATCH 30/41] Optimize ViewportRubberBandSelector #3151 --- .../Gizmo/ViewportRubberBandSelector.cs | 217 +++++++++++------- Source/Editor/SceneGraph/ActorNode.cs | 29 +-- .../SceneGraph/Actors/StaticModelNode.cs | 58 +++-- .../Viewport/MainEditorGizmoViewport.cs | 7 +- Source/Engine/Graphics/Mesh.cs | 4 +- Source/Engine/Graphics/Models/Mesh.cpp | 11 +- Source/Engine/Graphics/Models/Mesh.h | 4 +- 7 files changed, 208 insertions(+), 122 deletions(-) diff --git a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs index ddb889dd8..5ad861820 100644 --- a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs +++ b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs @@ -1,8 +1,6 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. -using System; using System.Collections.Generic; -using FlaxEditor; using FlaxEditor.Gizmo; using FlaxEditor.SceneGraph; using FlaxEditor.Viewport; @@ -20,7 +18,8 @@ public class ViewportRubberBandSelector private Float2 _cachedStartingMousePosition; private Rectangle _rubberBandRect; private Rectangle _lastRubberBandRect; - + private List _nodesCache; + private List _hitsCache; private IGizmoOwner _owner; /// @@ -53,7 +52,7 @@ public class ViewportRubberBandSelector { _tryStartRubberBand = false; } - + if (_isRubberBandSpanning) { _isRubberBandSpanning = false; @@ -61,7 +60,7 @@ public class ViewportRubberBandSelector } return false; } - + /// /// Tries to create a rubber band selection. /// @@ -75,7 +74,7 @@ public class ViewportRubberBandSelector _isRubberBandSpanning = false; return; } - + if (_tryStartRubberBand && (Mathf.Abs(_owner.MouseDelta.X) > 0.1f || Mathf.Abs(_owner.MouseDelta.Y) > 0.1f) && canStart) { _isRubberBandSpanning = true; @@ -87,90 +86,138 @@ public class ViewportRubberBandSelector { _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 && _owner is not PrefabWindowViewport) - hits.Add(_owner.SceneGraphRoot.Find(a.GetPrefabRoot())); - else - hits.Add(node); - } - } - - var editor = Editor.Instance; - if (_owner.IsControlDown) - { - var newSelection = new List(); - var currentSelection = _owner.SceneGraphRoot.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 = _owner.SceneGraphRoot.Selection; - newSelection.AddRange(hits); - newSelection.AddRange(currentSelection); - _owner.Select(newSelection); - } - else - { - _owner.Select(hits); - } + UpdateRubberBand(ref viewFrustum); } } } + private struct ViewportProjection + { + private Viewport _viewport; + private Matrix _viewProjection; + + public void Init(EditorViewport editorViewport) + { + // Inline EditorViewport.ProjectPoint to save on calculation for large set of points + _viewport = new Viewport(0, 0, editorViewport.Width, editorViewport.Height); + var frustum = editorViewport.ViewFrustum; + _viewProjection = frustum.Matrix; + } + + public void ProjectPoint(ref Vector3 worldSpaceLocation, out Float2 viewportSpaceLocation) + { + _viewport.Project(ref worldSpaceLocation, ref _viewProjection, out var projected); + viewportSpaceLocation = new Float2((float)projected.X, (float)projected.Y); + } + } + + private void UpdateRubberBand(ref BoundingFrustum viewFrustum) + { + Profiler.BeginEvent("UpdateRubberBand"); + + // 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 + if (_nodesCache == null) + _nodesCache = new List(); + else + _nodesCache.Clear(); + var nodes = _nodesCache; + _owner.SceneGraphRoot.GetAllChildActorNodes(nodes); + if (_hitsCache == null) + _hitsCache = new List(); + else + _hitsCache.Clear(); + var hits = _hitsCache; + + // Process all nodes + var projection = new ViewportProjection(); + projection.Init(_owner.Viewport); + 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(); + if (LoopOverPoints(points, ref adjustedRect, ref projection)) + { + if (a.HasPrefabLink && _owner is not PrefabWindowViewport) + hits.Add(_owner.SceneGraphRoot.Find(a.GetPrefabRoot())); + else + hits.Add(node); + } + } + + // Process selection + if (_owner.IsControlDown) + { + var newSelection = new List(); + var currentSelection = new List(_owner.SceneGraphRoot.SceneContext.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 = new List(_owner.SceneGraphRoot.SceneContext.Selection); + newSelection.AddRange(hits); + newSelection.AddRange(currentSelection); + _owner.Select(newSelection); + } + else + { + _owner.Select(hits); + } + + Profiler.EndEvent(); + } + + private bool LoopOverPoints(Vector3[] points, ref Rectangle adjustedRect, ref ViewportProjection projection) + { + Profiler.BeginEvent("LoopOverPoints"); + bool containsAllPoints = points.Length != 0; + for (int i = 0; i < points.Length; i++) + { + projection.ProjectPoint(ref points[i], out var loc); + if (!adjustedRect.Contains(loc)) + { + containsAllPoints = false; + break; + } + } + Profiler.EndEvent(); + return containsAllPoints; + } + /// /// Used to draw the rubber band. Begins render 2D. /// diff --git a/Source/Editor/SceneGraph/ActorNode.cs b/Source/Editor/SceneGraph/ActorNode.cs index 5191f83c3..0740fb84f 100644 --- a/Source/Editor/SceneGraph/ActorNode.cs +++ b/Source/Editor/SceneGraph/ActorNode.cs @@ -188,25 +188,28 @@ namespace FlaxEditor.SceneGraph /// 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++) + GetAllChildActorNodes(nodes); + return nodes.ToArray(); + } + + /// + /// Get all nested actor nodes under this actor node. + /// + /// The output list to fill with results. + public void GetAllChildActorNodes(List nodes) + { + var children = ChildNodes; + if (children == null) + return; + for (int i = 0; i < children.Count; i++) { - if (ChildNodes[i] is ActorNode node) + if (children[i] is ActorNode node) { nodes.Add(node); - var childNodes = node.GetAllChildActorNodes(); - if (childNodes.Length > 0) - { - nodes.AddRange(childNodes); - } + node.GetAllChildActorNodes(nodes); } } - return nodes.ToArray(); } /// diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs index 02161d277..b024c0287 100644 --- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs +++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs @@ -24,6 +24,9 @@ namespace FlaxEditor.SceneGraph.Actors public sealed class StaticModelNode : ActorNode { private Dictionary _vertices; + private Vector3[] _selectionPoints; + private Transform _selectionPointsTransform; + private Model _selectionPointsModel; /// public StaticModelNode(Actor actor) @@ -31,6 +34,16 @@ namespace FlaxEditor.SceneGraph.Actors { } + /// + public override void OnDispose() + { + _vertices = null; + _selectionPoints = null; + _selectionPointsModel = null; + + base.OnDispose(); + } + /// public override bool OnVertexSnap(ref Ray ray, Real hitDistance, out Vector3 result) { @@ -91,25 +104,40 @@ namespace FlaxEditor.SceneGraph.Actors /// 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) + if (Actor is StaticModel sm && sm.Model) { - var points = mesh.GetCollisionProxyPoints(); - foreach (var point in points) + // Try to use cache + var model = sm.Model; + var transform = Actor.Transform; + if (_selectionPoints != null && + _selectionPointsTransform == transform && + _selectionPointsModel == model) + return _selectionPoints; + Profiler.BeginEvent("GetActorSelectionPoints"); + + // Check collision proxy points for more accurate selection + var vecPoints = new List(); + var m = model.LODs[0]; + foreach (var mesh in m.Meshes) { - vecPoints.Add(Actor.Transform.LocalToWorld(point)); + var points = mesh.GetCollisionProxyPoints(); + vecPoints.EnsureCapacity(vecPoints.Count + points.Length); + for (int i = 0; i < points.Length; i++) + { + vecPoints.Add(transform.LocalToWorld(points[i])); + } + } + + Profiler.EndEvent(); + if (vecPoints.Count != 0) + { + _selectionPoints = vecPoints.ToArray(); + _selectionPointsTransform = transform; + _selectionPointsModel = model; + return _selectionPoints; } } - - // Fall back to base actor editor box if no points from collision proxy. - if (vecPoints.Count == 0) - return base.GetActorSelectionPoints(); - return vecPoints.ToArray(); + return base.GetActorSelectionPoints(); } private void OnAddMeshCollider(EditorWindow window) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index b8b7271c2..da3cf5bf6 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -111,7 +111,6 @@ namespace FlaxEditor.Viewport private double _lockedFocusOffset; private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32); private EditorSpritesRenderer _editorSpritesRenderer; - private ViewportRubberBandSelector _rubberBandSelector; /// @@ -607,9 +606,9 @@ namespace FlaxEditor.Viewport { base.OnMouseMove(location); - // Dont allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled - _rubberBandSelector.TryCreateRubberBand(!((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown), - _viewMousePos, ViewFrustum); + // Don't allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled + bool canStart = !((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown); + _rubberBandSelector.TryCreateRubberBand(canStart, _viewMousePos, ViewFrustum); } /// diff --git a/Source/Engine/Graphics/Mesh.cs b/Source/Engine/Graphics/Mesh.cs index e87a38ae7..1b8c16dbe 100644 --- a/Source/Engine/Graphics/Mesh.cs +++ b/Source/Engine/Graphics/Mesh.cs @@ -634,13 +634,15 @@ namespace FlaxEngine return result; } +#if FLAX_EDITOR /// /// Gets the collision proxy points for the mesh. /// /// The triangle points in the collision proxy. - internal Float3[] GetCollisionProxyPoints() + internal Vector3[] GetCollisionProxyPoints() { return Internal_GetCollisionProxyPoints(__unmanagedPtr, out _); } +#endif } } diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index ac194d905..721aaffd8 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -847,13 +847,16 @@ MArray* Mesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI return result; } -Array Mesh::GetCollisionProxyPoints() const +#if USE_EDITOR + +Array Mesh::GetCollisionProxyPoints() const { + PROFILE_CPU(); Array result; #if USE_PRECISE_MESH_INTERSECTS - for (int i = 0; i < _collisionProxy.Triangles.Count(); ++i) + for (int32 i = 0; i < _collisionProxy.Triangles.Count(); i++) { - auto triangle = _collisionProxy.Triangles[i]; + auto triangle = _collisionProxy.Triangles.Get()[i]; result.Add(triangle.V0); result.Add(triangle.V1); result.Add(triangle.V2); @@ -863,3 +866,5 @@ Array Mesh::GetCollisionProxyPoints() const } #endif + +#endif diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 2c8f8beda..78c60ec50 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -320,6 +320,8 @@ 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; +#if USE_EDITOR + API_FUNCTION(NoProxy) Array GetCollisionProxyPoints() const; +#endif #endif }; From f256b0670fd91bd519665343084d1a32e7a86acc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Mar 2025 13:07:53 +0100 Subject: [PATCH 31/41] Add mouse capture usage for new rubber band selection #3151 --- .../Editor/Gizmo/ViewportRubberBandSelector.cs | 10 ++++++++-- .../Editor/Viewport/MainEditorGizmoViewport.cs | 17 +++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs index 5ad861820..8b0fb1e97 100644 --- a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs +++ b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs @@ -34,12 +34,15 @@ public class ViewportRubberBandSelector /// /// Triggers the start of a rubber band selection. /// - public void TryStartingRubberBandSelection() + /// True if selection started, otherwise false. + public bool TryStartingRubberBandSelection() { if (!_isRubberBandSpanning && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown) { _tryStartRubberBand = true; + return true; } + return false; } /// @@ -248,9 +251,12 @@ public class ViewportRubberBandSelector /// /// Immediately stops the rubber band. /// - public void StopRubberBand() + /// True if rubber band was active before stopping. + public bool StopRubberBand() { + var result = _tryStartRubberBand; _isRubberBandSpanning = false; _tryStartRubberBand = false; + return result; } } diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index da3cf5bf6..a1dc61b4a 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -493,14 +493,18 @@ namespace FlaxEditor.Viewport public override void OnLostFocus() { base.OnLostFocus(); - _rubberBandSelector.StopRubberBand(); + + if (_rubberBandSelector.StopRubberBand()) + EndMouseCapture(); } /// public override void OnMouseLeave() { base.OnMouseLeave(); - _rubberBandSelector.StopRubberBand(); + + if (_rubberBandSelector.StopRubberBand()) + EndMouseCapture(); } /// @@ -615,8 +619,11 @@ namespace FlaxEditor.Viewport protected override void OnLeftMouseButtonDown() { base.OnLeftMouseButtonDown(); - - _rubberBandSelector.TryStartingRubberBandSelection(); + + if (_rubberBandSelector.TryStartingRubberBandSelection()) + { + StartMouseCapture(); + } } /// @@ -629,6 +636,8 @@ namespace FlaxEditor.Viewport // Select rubberbanded rect actor nodes or pick with gizmo if (!_rubberBandSelector.ReleaseRubberBandSelection()) { + EndMouseCapture(); + // Try to pick something with the current gizmo Gizmos.Active?.Pick(); } From 9c15094855bc0271a052b265159983eb437ddcf4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Mar 2025 14:05:18 +0100 Subject: [PATCH 32/41] Minor tweaks #2722 --- .../Gizmo/TransformGizmoBase.Settings.cs | 2 +- Source/Editor/Gizmo/TransformGizmoBase.cs | 13 +++++---- Source/Editor/Viewport/EditorGizmoViewport.cs | 28 ++++++++++--------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs b/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs index 7630d4c26..4d9326bb5 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs @@ -76,7 +76,7 @@ namespace FlaxEditor.Gizmo public bool ScaleSnapEnabled = false; /// - /// True if enable absolute grid snapping + /// True if enable absolute grid snapping (snaps objects to world-space grid, not the one relative to gizmo location) /// public bool AbsoluteSnapEnabled = false; diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 886d56c4d..da1ed575e 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -2,8 +2,10 @@ #if USE_LARGE_WORLDS using Real = System.Double; +using Mathr = FlaxEngine.Mathd; #else using Real = System.Single; +using Mathr = FlaxEngine.Mathf; #endif using System; @@ -390,13 +392,13 @@ namespace FlaxEditor.Gizmo Vector3 absoluteDelta = Vector3.Zero; if (!_hasAbsoluteSnapped && AbsoluteSnapEnabled && ActiveTransformSpace == TransformSpace.World) { + // Remove delta to offset local-space grid into the world-space grid _hasAbsoluteSnapped = true; - Vector3 currentTranslationScale = isScaling ? GetSelectedTransform(0).Scale : GetSelectedTransform(0).Translation; absoluteDelta = currentTranslationScale - new Vector3( - (Real)Math.Round(currentTranslationScale.X / snapValue.X) * snapValue.X, - (Real)Math.Round(currentTranslationScale.Y / snapValue.Y) * snapValue.Y, - (Real)Math.Round(currentTranslationScale.Z / snapValue.Z) * snapValue.Z); + Mathr.Round(currentTranslationScale.X / snapValue.X) * snapValue.X, + Mathr.Round(currentTranslationScale.Y / snapValue.Y) * snapValue.Y, + Mathr.Round(currentTranslationScale.Z / snapValue.Z) * snapValue.Z); } delta = new Vector3( @@ -442,8 +444,8 @@ namespace FlaxEditor.Gizmo float absoluteDelta = 0.0f; if (!_hasAbsoluteSnapped && AbsoluteSnapEnabled && ActiveTransformSpace == TransformSpace.World) { + // Remove delta to offset world-space grid into the local-space grid _hasAbsoluteSnapped = true; - float currentAngle = 0.0f; switch (_activeAxis) { @@ -451,7 +453,6 @@ namespace FlaxEditor.Gizmo case Axis.Y: currentAngle = GetSelectedTransform(0).Orientation.EulerAngles.Y; break; case Axis.Z: currentAngle = GetSelectedTransform(0).Orientation.EulerAngles.Z; break; } - absoluteDelta = currentAngle - (Mathf.Round(currentAngle / RotationSnapValue) * RotationSnapValue); } diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index 46e81b4c0..39c3d4bf3 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -164,30 +164,32 @@ namespace FlaxEditor.Viewport TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", Parent = transformSpaceWidget }; - transformSpaceToggle.Toggled += _ => - { - transformGizmo.ToggleTransformSpace(); - if (useProjectCache) - editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString()); - }; transformSpaceWidget.Parent = viewport; // Absolute snapping widget var absoluteSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableAbsoluteSnapping = new ViewportWidgetButton("A", SpriteHandle.Default, null, true) + var enableAbsoluteSnapping = new ViewportWidgetButton("A", SpriteHandle.Invalid, null, true) { Checked = transformGizmo.AbsoluteSnapEnabled, - TooltipText = "Enable absolute snapping", + TooltipText = "Enable absolute grid snapping (world-space absolute grid, rather than object-relative grid)", Parent = absoluteSnappingWidget }; enableAbsoluteSnapping.Toggled += _ => { transformGizmo.AbsoluteSnapEnabled = !transformGizmo.AbsoluteSnapEnabled; if (useProjectCache) - editor.ProjectCache.SetCustomData("AbsoluteSnapState", transformGizmo.AbsoluteSnapEnabled); + editor.ProjectCache.SetCustomData("AbsoluteSnapState", transformGizmo.AbsoluteSnapEnabled); }; absoluteSnappingWidget.Parent = viewport; + transformSpaceToggle.Toggled += _ => + { + transformGizmo.ToggleTransformSpace(); + if (useProjectCache) + editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString()); + absoluteSnappingWidget.Visible = transformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World; + }; + // Scale snapping widget var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true) @@ -401,17 +403,17 @@ namespace FlaxEditor.Viewport gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate; gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale; }; - + // Setup input actions - viewport.InputActions.Add(options => options.TranslateMode, () => + viewport.InputActions.Add(options => options.TranslateMode, () => { viewport.GetInput(out var input); if (input.IsMouseRightDown) return; - + transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate; }); - viewport.InputActions.Add(options => options.RotateMode, () => + viewport.InputActions.Add(options => options.RotateMode, () => { viewport.GetInput(out var input); if (input.IsMouseRightDown) From 21c07b7bf3f63542e1218d28c9fc2d7f6df7588e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Mar 2025 16:41:27 +0100 Subject: [PATCH 33/41] Fix regression from f256b0670fd91bd519665343084d1a32e7a86acc #3277 --- .../Gizmo/ViewportRubberBandSelector.cs | 17 ++++++++++- .../Viewport/MainEditorGizmoViewport.cs | 29 ++++++++++++------- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs index 8b0fb1e97..0381c6535 100644 --- a/Source/Editor/Gizmo/ViewportRubberBandSelector.cs +++ b/Source/Editor/Gizmo/ViewportRubberBandSelector.cs @@ -13,6 +13,7 @@ namespace FlaxEngine.Gizmo; /// public class ViewportRubberBandSelector { + private bool _isMosueCaptured; private bool _isRubberBandSpanning; private bool _tryStartRubberBand; private Float2 _cachedStartingMousePosition; @@ -51,11 +52,15 @@ public class ViewportRubberBandSelector /// Returns true if rubber band is currently spanning public bool ReleaseRubberBandSelection() { + if (_isMosueCaptured) + { + _isMosueCaptured = false; + _owner.Viewport.EndMouseCapture(); + } if (_tryStartRubberBand) { _tryStartRubberBand = false; } - if (_isRubberBandSpanning) { _isRubberBandSpanning = false; @@ -91,6 +96,11 @@ public class ViewportRubberBandSelector _rubberBandRect.Height = mousePosition.Y - _cachedStartingMousePosition.Y; if (_lastRubberBandRect != _rubberBandRect) { + if (!_isMosueCaptured) + { + _isMosueCaptured = true; + _owner.Viewport.StartMouseCapture(); + } UpdateRubberBand(ref viewFrustum); } } @@ -254,6 +264,11 @@ public class ViewportRubberBandSelector /// True if rubber band was active before stopping. public bool StopRubberBand() { + if (_isMosueCaptured) + { + _isMosueCaptured = false; + _owner.Viewport.EndMouseCapture(); + } var result = _tryStartRubberBand; _isRubberBandSpanning = false; _tryStartRubberBand = false; diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index a1dc61b4a..587942844 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -494,8 +494,7 @@ namespace FlaxEditor.Viewport { base.OnLostFocus(); - if (_rubberBandSelector.StopRubberBand()) - EndMouseCapture(); + _rubberBandSelector.StopRubberBand(); } /// @@ -503,8 +502,7 @@ namespace FlaxEditor.Viewport { base.OnMouseLeave(); - if (_rubberBandSelector.StopRubberBand()) - EndMouseCapture(); + _rubberBandSelector.StopRubberBand(); } /// @@ -620,10 +618,7 @@ namespace FlaxEditor.Viewport { base.OnLeftMouseButtonDown(); - if (_rubberBandSelector.TryStartingRubberBandSelection()) - { - StartMouseCapture(); - } + _rubberBandSelector.TryStartingRubberBandSelection(); } /// @@ -636,8 +631,6 @@ namespace FlaxEditor.Viewport // Select rubberbanded rect actor nodes or pick with gizmo if (!_rubberBandSelector.ReleaseRubberBandSelection()) { - EndMouseCapture(); - // Try to pick something with the current gizmo Gizmos.Active?.Pick(); } @@ -648,6 +641,22 @@ namespace FlaxEditor.Viewport base.OnLeftMouseButtonUp(); } + /// + public override bool OnMouseUp(Float2 location, MouseButton button) + { + if (base.OnMouseUp(location, button)) + return true; + + // Handle mouse going up when using rubber band with mouse capture that click up outside the view + if (button == MouseButton.Left && !new Rectangle(Float2.Zero, Size).Contains(ref location)) + { + _rubberBandSelector.ReleaseRubberBandSelection(); + return true; + } + + return false; + } + /// public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) { From cab0f92da82bc631bc79230a21a8d2dffe4ced85 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Mar 2025 17:01:33 +0100 Subject: [PATCH 34/41] Fix crash when importing large audio file --- Source/Engine/ContentImporters/ImportAudio.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Engine/ContentImporters/ImportAudio.cpp b/Source/Engine/ContentImporters/ImportAudio.cpp index 00b914259..1cb6143be 100644 --- a/Source/Engine/ContentImporters/ImportAudio.cpp +++ b/Source/Engine/ContentImporters/ImportAudio.cpp @@ -159,19 +159,19 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder& else { // Split audio data into a several chunks (uniform data spread) - const int32 minChunkSize = 1 * 1024 * 1024; // 1 MB - const int32 dataAlignment = info.NumChannels * bytesPerSample; // Ensure to never split samples in-between (eg. 24-bit that uses 3 bytes) - const int32 chunkSize = Math::Max(minChunkSize, (int32)Math::AlignUp(bufferSize / ASSET_FILE_DATA_CHUNKS, dataAlignment)); - const int32 chunksCount = Math::CeilToInt((float)bufferSize / chunkSize); + const uint32 minChunkSize = 1 * 1024 * 1024; // 1 MB + const uint32 dataAlignment = info.NumChannels * bytesPerSample * ASSET_FILE_DATA_CHUNKS; // Ensure to never split samples in-between (eg. 24-bit that uses 3 bytes) + const uint32 chunkSize = Math::AlignUp(Math::Max(minChunkSize, bufferSize / ASSET_FILE_DATA_CHUNKS), dataAlignment); + const int32 chunksCount = Math::CeilToInt((float)bufferSize / (float)chunkSize); ASSERT(chunksCount > 0 && chunksCount <= ASSET_FILE_DATA_CHUNKS); byte* ptr = sampleBuffer.Get(); - int32 size = bufferSize; + uint32 size = bufferSize; for (int32 chunkIndex = 0; chunkIndex < chunksCount; chunkIndex++) { if (context.AllocateChunk(chunkIndex)) return CreateAssetResult::CannotAllocateChunk; - const int32 t = Math::Min(size, chunkSize); + const uint32 t = Math::Min(size, chunkSize); WRITE_DATA(chunkIndex, ptr, t); From a3cc3c79e2bf8417d3cab2b80a91a1b6cbab88cf Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Mar 2025 18:10:19 +0100 Subject: [PATCH 35/41] Another fix for spatial and non-spatial, mono and multi-channel audio sourcs playback on OpenAL #3206 --- .../Engine/Audio/OpenAL/AudioBackendOAL.cpp | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp index c7e9dc41b..93c244555 100644 --- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp +++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp @@ -65,6 +65,7 @@ namespace ALC struct SourceData { AudioDataInfo Format; + float Pan; bool Spatial; }; @@ -106,6 +107,26 @@ namespace ALC namespace Source { + void SetupSpatial(uint32 sourceID, float pan, bool spatial) + { + alSourcei(sourceID, AL_SOURCE_RELATIVE, !spatial); // Non-spatial sounds use AL_POSITION for panning +#ifdef AL_SOFT_source_spatialize + alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, spatial || Math::Abs(pan) > ZeroTolerance ? AL_TRUE : AL_FALSE); // Fix multi-channel sources played as spatial or non-spatial sources played with panning +#endif + if (spatial) + { +#ifdef AL_EXT_STEREO_ANGLES + const float panAngle = pan * PI_HALF; + const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians + alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles); +#endif + } + else + { + alSource3f(sourceID, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); + } + } + void Rebuild(uint32& sourceID, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler) { ASSERT_LOW_LAYER(sourceID == 0); @@ -116,11 +137,8 @@ namespace ALC alSourcef(sourceID, AL_PITCH, pitch); alSourcef(sourceID, AL_SEC_OFFSET, 0.0f); alSourcei(sourceID, AL_LOOPING, loop); - alSourcei(sourceID, AL_SOURCE_RELATIVE, !spatial); // Non-spatial sounds use AL_POSITION for panning alSourcei(sourceID, AL_BUFFER, 0); -#ifdef AL_SOFT_source_spatialize - alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE); // Always spatialize, fixes multi-channel played as spatial -#endif + SetupSpatial(sourceID, pan, spatial); if (spatial) { alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation); @@ -128,18 +146,12 @@ namespace ALC alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(minDistance)); alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position)); alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(Vector3::Zero)); -#ifdef AL_EXT_STEREO_ANGLES - const float panAngle = pan * PI_HALF; - const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians - alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles); -#endif } else { alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f); alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f); alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f); - alSource3f(sourceID, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); alSource3f(sourceID, AL_VELOCITY, 0.0f, 0.0f, 0.0f); } } @@ -160,12 +172,11 @@ namespace ALC if (Device == nullptr) return; - ALCint attrsHrtf[] = { ALC_HRTF_SOFT, ALC_TRUE }; - const ALCint* attrList = nullptr; + ALCint attrList[] = { ALC_HRTF_SOFT, ALC_FALSE }; if (Audio::GetEnableHRTF()) { LOG(Info, "Enabling OpenAL HRTF"); - attrList = attrsHrtf; + attrList[1] = ALC_TRUE; } Context = alcCreateContext(Device, attrList); @@ -318,6 +329,7 @@ uint32 AudioBackendOAL::Source_Add(const AudioDataInfo& format, const Vector3& p auto& data = ALC::SourcesData[sourceID]; data.Format = format; data.Spatial = spatial; + data.Pan = pan; ALC::Locker.Unlock(); return sourceID; @@ -370,20 +382,11 @@ void AudioBackendOAL::Source_PitchChanged(uint32 sourceID, float pitch) void AudioBackendOAL::Source_PanChanged(uint32 sourceID, float pan) { ALC::Locker.Lock(); - const bool spatial = ALC::SourcesData[sourceID].Spatial; + auto& e = ALC::SourcesData[sourceID]; + e.Pan = pan; + const bool spatial = e.Spatial; ALC::Locker.Unlock(); - if (spatial) - { -#ifdef AL_EXT_STEREO_ANGLES - const float panAngle = pan * PI_HALF; - const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians - alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles); -#endif - } - else - { - alSource3f(sourceID, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan)); - } + ALC::Source::SetupSpatial(sourceID, pan, spatial); } void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop) @@ -393,6 +396,9 @@ void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop) void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler) { + ALC::Locker.Lock(); + const bool pan = ALC::SourcesData[sourceID].Spatial; + ALC::Locker.Unlock(); if (spatial) { alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation); @@ -405,6 +411,7 @@ void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f); alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f); } + ALC::Source::SetupSpatial(sourceID, pan, spatial); } void AudioBackendOAL::Source_Play(uint32 sourceID) @@ -602,7 +609,7 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferID, byte* samples, const AudioDa if (!format) { - LOG(Error, "Not suppported audio data format for OpenAL device: BitDepth={}, NumChannels={}", info.BitDepth, info.NumChannels); + LOG(Error, "Not supported audio data format for OpenAL device: BitDepth={}, NumChannels={}", info.BitDepth, info.NumChannels); } } From 3c303514a4e9f6cab8c5ab8e0ffc691ed77879b9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Mar 2025 18:10:48 +0100 Subject: [PATCH 36/41] Fix crash when using audio playback and playing editor meanwhile --- Source/Editor/Managed/ManagedEditor.cpp | 5 +++++ Source/Editor/Utilities/EditorScene.cpp | 3 +++ Source/Editor/Windows/Assets/AudioClipWindow.cs | 1 - Source/Engine/Scripting/Scripting.cpp | 5 +++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index 652878d4d..afa47a159 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -606,6 +606,11 @@ void ManagedEditor::WipeOutLeftoverSceneObjects() { if (sceneObject->HasParent()) continue; // Skip sub-objects + auto* actor = Cast(sceneObject); + if (!actor) + actor = sceneObject->GetParent(); + if (actor && actor->HasTag(TEXT("__EditorInternal"))) + continue; // Skip internal objects used by Editor (eg. EditorScene) LOG(Error, "Object '{}' (ID={}, Type={}) is still in memory after play end but should be destroyed (memory leak).", sceneObject->GetNamePath(), sceneObject->GetID(), sceneObject->GetType().ToString()); sceneObject->DeleteObject(); diff --git a/Source/Editor/Utilities/EditorScene.cpp b/Source/Editor/Utilities/EditorScene.cpp index e42580111..88d0cd5e3 100644 --- a/Source/Editor/Utilities/EditorScene.cpp +++ b/Source/Editor/Utilities/EditorScene.cpp @@ -10,6 +10,9 @@ EditorScene::EditorScene(const SpawnParams& params) SceneBeginData beginData; EditorScene::BeginPlay(&beginData); beginData.OnDone(); + + // Mark as internal to prevent collection in ManagedEditor::WipeOutLeftoverSceneObjects + Tags.Add(Tags::Get(TEXT("__EditorInternal"))); } void EditorScene::Update() diff --git a/Source/Editor/Windows/Assets/AudioClipWindow.cs b/Source/Editor/Windows/Assets/AudioClipWindow.cs index 35072620b..0bb52c7ab 100644 --- a/Source/Editor/Windows/Assets/AudioClipWindow.cs +++ b/Source/Editor/Windows/Assets/AudioClipWindow.cs @@ -1,6 +1,5 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. -using System.IO; using System.Xml; using FlaxEditor.Content; using FlaxEditor.Content.Import; diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 4731a088d..a0b55376f 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -732,7 +732,12 @@ Array Scripting::GetObjects() { Array objects; _objectsLocker.Lock(); +#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING + for (const auto& e : _objectsDictionary) + objects.Add(e.Value.Ptr); +#else _objectsDictionary.GetValues(objects); +#endif _objectsLocker.Unlock(); return objects; } From 2af6bdc283a15f762229bde1f83d80a16605f7c8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Mar 2025 18:46:25 +0100 Subject: [PATCH 37/41] Fix audio artifacts when importing 32-bit IEE `wav` files --- Source/Engine/Tools/AudioTool/AudioTool.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Tools/AudioTool/AudioTool.cpp b/Source/Engine/Tools/AudioTool/AudioTool.cpp index f1709af7e..18e483b96 100644 --- a/Source/Engine/Tools/AudioTool/AudioTool.cpp +++ b/Source/Engine/Tools/AudioTool/AudioTool.cpp @@ -4,7 +4,7 @@ #include "AudioTool.h" #include "Engine/Core/Core.h" -#include "Engine/Core/Memory/Allocation.h" +#include "Engine/Core/Math/Math.h" #if USE_EDITOR #include "Engine/Serialization/Serialization.h" #include "Engine/Scripting/Enums.h" @@ -307,8 +307,9 @@ void AudioTool::ConvertFromFloat(const float* input, int32* output, uint32 numSa { for (uint32 i = 0; i < numSamples; i++) { - const float sample = *(float*)input; - output[i] = static_cast(sample * 2147483647.0f); + float sample = *(float*)input; + sample = Math::Clamp(sample, -1.0f + ZeroTolerance, +1.0f - ZeroTolerance); + output[i] = static_cast(sample * 2147483648.0f); input++; } } From 862c2db11a0e25c175ad5a647bd59614c451d485 Mon Sep 17 00:00:00 2001 From: xxSeys1 Date: Tue, 11 Mar 2025 21:18:23 +0100 Subject: [PATCH 38/41] fix some typos --- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 2 +- Source/Engine/Render2D/Font.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 5dd5f6cbe..d165e9f2e 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -166,7 +166,7 @@ namespace FlaxEditor.SceneGraph.GUI /// The filter text. public void UpdateFilter(string filterText) { - // SKip hidden actors + // Skip hidden actors var actor = Actor; if (actor != null && (actor.HideFlags & HideFlags.HideInHierarchy) != 0) return; diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index 187d5b24a..d642e4116 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -399,7 +399,7 @@ public: /// /// The input text to test. /// The layout properties. - /// The minimum size for that text and fot to render properly. + /// The minimum size for that text and font to render properly. API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); /// @@ -408,7 +408,7 @@ public: /// The input text to test. /// The input text range (substring range of the input text parameter). /// The layout properties. - /// The minimum size for that text and fot to render properly. + /// The minimum size for that text and font to render properly. API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) { return MeasureText(textRange.Substring(text), layout); @@ -418,7 +418,7 @@ public: /// Measures minimum size of the rectangle that will be needed to draw given text /// . /// The input text to test. - /// The minimum size for that text and fot to render properly. + /// The minimum size for that text and font to render properly. API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text) { return MeasureText(text, TextLayoutOptions()); @@ -429,7 +429,7 @@ public: /// . /// The input text to test. /// The input text range (substring range of the input text parameter). - /// The minimum size for that text and fot to render properly. + /// The minimum size for that text and font to render properly. API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange) { return MeasureText(textRange.Substring(text), TextLayoutOptions()); From f11349f16c806dbd7df41de191e644a976d36ec2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Mar 2025 22:09:19 +0100 Subject: [PATCH 39/41] Fix compilation --- Source/Engine/Tools/AudioTool/AudioTool.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Tools/AudioTool/AudioTool.cpp b/Source/Engine/Tools/AudioTool/AudioTool.cpp index 18e483b96..aa9570989 100644 --- a/Source/Engine/Tools/AudioTool/AudioTool.cpp +++ b/Source/Engine/Tools/AudioTool/AudioTool.cpp @@ -5,6 +5,7 @@ #include "AudioTool.h" #include "Engine/Core/Core.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Core/Memory/Allocation.h" #if USE_EDITOR #include "Engine/Serialization/Serialization.h" #include "Engine/Scripting/Enums.h" From 9d466266413c257582ff292348ad6a61efdfec37 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Mar 2025 22:58:01 +0100 Subject: [PATCH 40/41] Fix new rubberband selection when using arc cam via `alt` key --- 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 587942844..943bb6e3c 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -609,7 +609,7 @@ namespace FlaxEditor.Viewport base.OnMouseMove(location); // Don't allow rubber band selection when gizmo is controlling mouse, vertex painting mode, or cloth painting is enabled - bool canStart = !((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown); + bool canStart = !((Gizmos.Active.IsControllingMouse || Gizmos.Active is VertexPaintingGizmo || Gizmos.Active is ClothPaintingGizmo) || IsControllingMouse || IsRightMouseButtonDown || IsAltKeyDown); _rubberBandSelector.TryCreateRubberBand(canStart, _viewMousePos, ViewFrustum); } From 5d32bb962f6740d9dfe8d0f44840352595501017 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Mar 2025 23:09:47 +0100 Subject: [PATCH 41/41] Add warning when cloth paint is incorrect size --- Source/Engine/Level/Actors/ModelInstanceActor.cpp | 5 +++++ Source/Engine/Level/Actors/ModelInstanceActor.h | 2 ++ Source/Engine/Physics/Actors/Cloth.cpp | 4 ++++ Source/Engine/Serialization/JsonTools.cpp | 2 +- 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.cpp b/Source/Engine/Level/Actors/ModelInstanceActor.cpp index 6021daadc..6c770ad64 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.cpp +++ b/Source/Engine/Level/Actors/ModelInstanceActor.cpp @@ -9,6 +9,11 @@ ModelInstanceActor::ModelInstanceActor(const SpawnParams& params) { } +String ModelInstanceActor::MeshReference::ToString() const +{ + return String::Format(TEXT("Actor={},LOD={},Mesh={}"), Actor ? Actor->GetNamePath() : String::Empty, LODIndex, MeshIndex); +} + void ModelInstanceActor::SetEntries(const Array& value) { WaitForModelLoad(); diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h index f3c6cd5e1..247a51df2 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.h +++ b/Source/Engine/Level/Actors/ModelInstanceActor.h @@ -27,6 +27,8 @@ API_CLASS(Abstract) class FLAXENGINE_API ModelInstanceActor : public Actor API_FIELD() int32 LODIndex = 0; // Index of the mesh (within the LOD). API_FIELD() int32 MeshIndex = 0; + + String ToString() const; }; protected: diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 3b7bf6718..f9ae6b259 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -644,6 +644,7 @@ void Cloth::CalculateInvMasses(Array& invMasses) if (_paint.Count() != verticesCount) { // Fix incorrect paint data + LOG(Warning, "Incorrect cloth '{}' paint size {} for mesh '{}' that has {} vertices", GetNamePath(), _paint.Count(), mesh.ToString(), verticesCount); int32 countBefore = _paint.Count(); _paint.Resize(verticesCount); for (int32 i = countBefore; i < verticesCount; i++) @@ -781,7 +782,10 @@ bool Cloth::OnPreUpdate() if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount)) return false; if (verticesCount != _paint.Count()) + { + LOG(Warning, "Incorrect cloth '{}' paint size {} for mesh '{}' that has {} vertices", GetNamePath(), _paint.Count(), mesh.ToString(), verticesCount); return false; + } PROFILE_CPU_NAMED("Skinned Pose"); auto vbStride = (uint32)verticesData.Length() / verticesCount; ASSERT(vbStride == sizeof(VB0SkinnedElementType)); diff --git a/Source/Engine/Serialization/JsonTools.cpp b/Source/Engine/Serialization/JsonTools.cpp index be711f77a..b8d24c2ca 100644 --- a/Source/Engine/Serialization/JsonTools.cpp +++ b/Source/Engine/Serialization/JsonTools.cpp @@ -185,7 +185,7 @@ Ray JsonTools::GetRay(const Value& value) { return Ray( GetVector3(value, "Position", Vector3::Zero), - GetVector3(value, "Direction", Vector3::One) + GetVector3(value, "Direction", Vector3::Forward) ); }