diff --git a/Content/Shaders/DebugDraw.flax b/Content/Shaders/DebugDraw.flax index 284c55f64..455cf8c20 100644 --- a/Content/Shaders/DebugDraw.flax +++ b/Content/Shaders/DebugDraw.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10fefac1db81c41041c95970dc13694a17dfbca09819d6f7344b5d45f5e8c0df -size 2060 +oid sha256:7f8833a76cdda54b6c1de3b98f7993b835d17a5ac60b0bf11a9ee9faa42cc177 +size 2108 diff --git a/Flax.flaxproj b/Flax.flaxproj index 04c59c3a3..209887b20 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -4,7 +4,7 @@ "Major": 1, "Minor": 8, "Revision": 0, - "Build": 65045 + "Build": 65046 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.", diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index fb33d470c..88ce53b78 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -46,10 +46,13 @@ namespace FlaxEditor.CustomEditors.Editors var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor); XElement.ValueBox.BorderSelectedColor = AxisColorX; + XElement.ValueBox.Category = Utils.ValueCategory.Distance; YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor); YElement.ValueBox.BorderSelectedColor = AxisColorY; + YElement.ValueBox.Category = Utils.ValueCategory.Distance; ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor); ZElement.ValueBox.BorderSelectedColor = AxisColorZ; + ZElement.ValueBox.Category = Utils.ValueCategory.Distance; } } @@ -68,10 +71,13 @@ namespace FlaxEditor.CustomEditors.Editors var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor); XElement.ValueBox.BorderSelectedColor = AxisColorX; + XElement.ValueBox.Category = Utils.ValueCategory.Angle; YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor); YElement.ValueBox.BorderSelectedColor = AxisColorY; + YElement.ValueBox.Category = Utils.ValueCategory.Angle; ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor); ZElement.ValueBox.BorderSelectedColor = AxisColorZ; + ZElement.ValueBox.Category = Utils.ValueCategory.Angle; } } diff --git a/Source/Editor/CustomEditors/Editors/DoubleEditor.cs b/Source/Editor/CustomEditors/Editors/DoubleEditor.cs index 6e3048eb9..2049860c6 100644 --- a/Source/Editor/CustomEditors/Editors/DoubleEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DoubleEditor.cs @@ -21,32 +21,28 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - _element = null; - - // Try get limit attribute for value min/max range setting and slider speed + var doubleValue = layout.DoubleValue(); + doubleValue.ValueBox.ValueChanged += OnValueChanged; + doubleValue.ValueBox.SlidingEnd += ClearToken; + _element = doubleValue; var attributes = Values.GetAttributes(); if (attributes != null) { - var limit = attributes.FirstOrDefault(x => x is LimitAttribute); - if (limit != null) + var limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute); + doubleValue.SetLimits(limit); + var valueCategory = ((ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute))?.Category ?? Utils.ValueCategory.None; + if (valueCategory != Utils.ValueCategory.None) { - // Use double value editor with limit - var doubleValue = layout.DoubleValue(); - doubleValue.SetLimits((LimitAttribute)limit); - doubleValue.ValueBox.ValueChanged += OnValueChanged; - doubleValue.ValueBox.SlidingEnd += ClearToken; - _element = doubleValue; - return; + doubleValue.SetCategory(valueCategory); + LinkedLabel.SetupContextMenu += (label, menu, editor) => + { + menu.AddSeparator(); + var mb = menu.AddButton("Show formatted", bt => { doubleValue.SetCategory(bt.Checked ? valueCategory : Utils.ValueCategory.None); }); + mb.AutoCheck = true; + mb.Checked = doubleValue.ValueBox.Category != Utils.ValueCategory.None; + }; } } - if (_element == null) - { - // Use double value editor - var doubleValue = layout.DoubleValue(); - doubleValue.ValueBox.ValueChanged += OnValueChanged; - doubleValue.ValueBox.SlidingEnd += ClearToken; - _element = doubleValue; - } } private void OnValueChanged() diff --git a/Source/Editor/CustomEditors/Editors/FloatEditor.cs b/Source/Editor/CustomEditors/Editors/FloatEditor.cs index 83d2c3f21..9d3ff1490 100644 --- a/Source/Editor/CustomEditors/Editors/FloatEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FloatEditor.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using FlaxEditor.CustomEditors.Elements; using FlaxEngine; +using Utils = FlaxEngine.Utils; namespace FlaxEditor.CustomEditors.Editors { @@ -27,41 +28,39 @@ namespace FlaxEditor.CustomEditors.Editors public override void Initialize(LayoutElementsContainer layout) { _element = null; - - // Try get limit attribute for value min/max range setting and slider speed var attributes = Values.GetAttributes(); + var range = (RangeAttribute)attributes?.FirstOrDefault(x => x is RangeAttribute); + if (range != null) + { + // Use slider + var slider = layout.Slider(); + slider.Slider.SetLimits(range); + slider.Slider.ValueChanged += OnValueChanged; + slider.Slider.SlidingEnd += ClearToken; + _element = slider; + return; + } + + var floatValue = layout.FloatValue(); + floatValue.ValueBox.ValueChanged += OnValueChanged; + floatValue.ValueBox.SlidingEnd += ClearToken; + _element = floatValue; if (attributes != null) { - var range = attributes.FirstOrDefault(x => x is RangeAttribute); - if (range != null) + var limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute); + floatValue.SetLimits(limit); + var valueCategory = ((ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute))?.Category ?? Utils.ValueCategory.None; + if (valueCategory != Utils.ValueCategory.None) { - // Use slider - var slider = layout.Slider(); - slider.SetLimits((RangeAttribute)range); - slider.Slider.ValueChanged += OnValueChanged; - slider.Slider.SlidingEnd += ClearToken; - _element = slider; - return; + floatValue.SetCategory(valueCategory); + LinkedLabel.SetupContextMenu += (label, menu, editor) => + { + menu.AddSeparator(); + var mb = menu.AddButton("Show formatted", bt => { floatValue.SetCategory(bt.Checked ? valueCategory : Utils.ValueCategory.None); }); + mb.AutoCheck = true; + mb.Checked = floatValue.ValueBox.Category != Utils.ValueCategory.None; + }; } - var limit = attributes.FirstOrDefault(x => x is LimitAttribute); - if (limit != null) - { - // Use float value editor with limit - var floatValue = layout.FloatValue(); - floatValue.SetLimits((LimitAttribute)limit); - floatValue.ValueBox.ValueChanged += OnValueChanged; - floatValue.ValueBox.SlidingEnd += ClearToken; - _element = floatValue; - return; - } - } - if (_element == null) - { - // Use float value editor - var floatValue = layout.FloatValue(); - floatValue.ValueBox.ValueChanged += OnValueChanged; - floatValue.ValueBox.SlidingEnd += ClearToken; - _element = floatValue; } } diff --git a/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs b/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs index 453090d42..7712f1a08 100644 --- a/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs @@ -45,14 +45,17 @@ namespace FlaxEditor.CustomEditors.Editors gridControl.SlotsVertically = 1; XElement = grid.FloatValue(); + XElement.ValueBox.Category = Utils.ValueCategory.Angle; XElement.ValueBox.ValueChanged += OnValueChanged; XElement.ValueBox.SlidingEnd += ClearToken; YElement = grid.FloatValue(); + YElement.ValueBox.Category = Utils.ValueCategory.Angle; YElement.ValueBox.ValueChanged += OnValueChanged; YElement.ValueBox.SlidingEnd += ClearToken; ZElement = grid.FloatValue(); + ZElement.ValueBox.Category = Utils.ValueCategory.Angle; ZElement.ValueBox.ValueChanged += OnValueChanged; ZElement.ValueBox.SlidingEnd += ClearToken; diff --git a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs index da0969002..e7170a22d 100644 --- a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs +++ b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs @@ -70,25 +70,44 @@ namespace FlaxEditor.CustomEditors.Editors LimitAttribute limit = null; var attributes = Values.GetAttributes(); + var category = Utils.ValueCategory.None; if (attributes != null) { limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute); + var categoryAttribute = (ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute); + if (categoryAttribute != null) + category = categoryAttribute.Category; } XElement = grid.FloatValue(); XElement.SetLimits(limit); + XElement.SetCategory(category); XElement.ValueBox.ValueChanged += OnXValueChanged; XElement.ValueBox.SlidingEnd += ClearToken; YElement = grid.FloatValue(); YElement.SetLimits(limit); + YElement.SetCategory(category); YElement.ValueBox.ValueChanged += OnYValueChanged; YElement.ValueBox.SlidingEnd += ClearToken; ZElement = grid.FloatValue(); ZElement.SetLimits(limit); + ZElement.SetCategory(category); ZElement.ValueBox.ValueChanged += OnZValueChanged; ZElement.ValueBox.SlidingEnd += ClearToken; + LinkedLabel.SetupContextMenu += (label, menu, editor) => + { + menu.AddSeparator(); + var mb = menu.AddButton("Show formatted", bt => + { + XElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None); + YElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None); + ZElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None); + }); + mb.AutoCheck = true; + mb.Checked = XElement.ValueBox.Category != Utils.ValueCategory.None; + }; } private void OnXValueChanged() @@ -248,26 +267,45 @@ namespace FlaxEditor.CustomEditors.Editors gridControl.SlotsVertically = 1; LimitAttribute limit = null; + Utils.ValueCategory category = Utils.ValueCategory.None; var attributes = Values.GetAttributes(); if (attributes != null) { limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute); + var categoryAttribute = (ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute); + if (categoryAttribute != null) + category = categoryAttribute.Category; } XElement = grid.DoubleValue(); XElement.SetLimits(limit); + XElement.SetCategory(category); XElement.ValueBox.ValueChanged += OnValueChanged; XElement.ValueBox.SlidingEnd += ClearToken; YElement = grid.DoubleValue(); YElement.SetLimits(limit); + YElement.SetCategory(category); YElement.ValueBox.ValueChanged += OnValueChanged; YElement.ValueBox.SlidingEnd += ClearToken; ZElement = grid.DoubleValue(); ZElement.SetLimits(limit); + ZElement.SetCategory(category); ZElement.ValueBox.ValueChanged += OnValueChanged; ZElement.ValueBox.SlidingEnd += ClearToken; + LinkedLabel.SetupContextMenu += (label, menu, editor) => + { + menu.AddSeparator(); + var mb = menu.AddButton("Show formatted", bt => + { + XElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None); + YElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None); + ZElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None); + }); + mb.AutoCheck = true; + mb.Checked = XElement.ValueBox.Category != Utils.ValueCategory.None; + }; } private void OnValueChanged() diff --git a/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs b/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs index f426df605..cf16a1194 100644 --- a/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs +++ b/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs @@ -51,6 +51,15 @@ namespace FlaxEditor.CustomEditors.Elements } } + /// + /// Sets the editor value category. + /// + /// The category. + public void SetCategory(Utils.ValueCategory category) + { + ValueBox.Category = category; + } + /// /// Sets the editor limits from member . /// diff --git a/Source/Editor/CustomEditors/Elements/FloatValueElement.cs b/Source/Editor/CustomEditors/Elements/FloatValueElement.cs index 43490da7c..aabdb79e4 100644 --- a/Source/Editor/CustomEditors/Elements/FloatValueElement.cs +++ b/Source/Editor/CustomEditors/Elements/FloatValueElement.cs @@ -51,6 +51,15 @@ namespace FlaxEditor.CustomEditors.Elements } } + /// + /// Sets the editor value category. + /// + /// The category. + public void SetCategory(Utils.ValueCategory category) + { + ValueBox.Category = category; + } + /// /// Sets the editor limits from member . /// diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 4cee6d971..d0076068e 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -290,7 +290,6 @@ namespace FlaxEditor StateMachine = new EditorStateMachine(this); Undo = new EditorUndo(this); - UIControl.FallbackParentGetDelegate += OnUIControlFallbackParentGet; if (newProject) InitProject(); @@ -355,27 +354,6 @@ namespace FlaxEditor StateMachine.LoadingState.StartInitEnding(skipCompile); } - private ContainerControl OnUIControlFallbackParentGet(UIControl control) - { - // Check if prefab root control is this UIControl - var loadingPreview = Viewport.Previews.PrefabPreview.LoadingPreview; - var activePreviews = Viewport.Previews.PrefabPreview.ActivePreviews; - if (activePreviews != null) - { - foreach (var preview in activePreviews) - { - if (preview == loadingPreview || - (preview.Instance != null && (preview.Instance == control || preview.Instance.HasActorInHierarchy(control)))) - { - // Link it to the prefab preview to see it in the editor - preview.customControlLinked = control; - return preview; - } - } - } - return null; - } - internal void RegisterModule(EditorModule module) { Log("Register Editor module " + module); diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index 141b3aa60..bc96cf3bc 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -431,7 +431,6 @@ namespace FlaxEditor.GUI /// protected CurveEditor() { - _tickStrengths = new float[TickSteps.Length]; Accessor.GetDefaultValue(out DefaultValue); var style = Style.Current; @@ -780,75 +779,31 @@ namespace FlaxEditor.GUI return _mainPanel.PointToParent(point); } - private void DrawAxis(Float2 axis, ref Rectangle viewRect, float min, float max, float pixelRange) + private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange) { - int minDistanceBetweenTicks = 20; - int maxDistanceBetweenTicks = 60; - var range = max - min; - - // Find the strength for each modulo number tick marker - int smallestTick = 0; - int biggestTick = TickSteps.Length - 1; - for (int i = TickSteps.Length - 1; i >= 0; i--) + Utilities.Utils.DrawCurveTicks((float tick, float strength) => { - // Calculate how far apart these modulo tick steps are spaced - float tickSpacing = TickSteps[i] * pixelRange / range; + var p = PointFromKeyframes(axis * tick, ref viewRect); - // Calculate the strength of the tick markers based on the spacing - _tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks)); + // Draw line + var lineRect = new Rectangle + ( + viewRect.Location + (p - 0.5f) * axis, + Float2.Lerp(viewRect.Size, Float2.One, axis) + ); + Render2D.FillRectangle(lineRect, _linesColor.AlphaMultiplied(strength)); - // Beyond threshold the ticks don't get any bigger or fatter - if (_tickStrengths[i] >= 1) - biggestTick = i; - - // Do not show small tick markers - if (tickSpacing <= minDistanceBetweenTicks) - { - smallestTick = i; - break; - } - } - - // Draw all tick levels - int tickLevels = biggestTick - smallestTick + 1; - for (int level = 0; level < tickLevels; level++) - { - float strength = _tickStrengths[smallestTick + level]; - if (strength <= Mathf.Epsilon) - continue; - - // Draw all ticks - int l = Mathf.Clamp(smallestTick + level, 0, TickSteps.Length - 1); - int startTick = Mathf.FloorToInt(min / TickSteps[l]); - int endTick = Mathf.CeilToInt(max / TickSteps[l]); - for (int i = startTick; i <= endTick; i++) - { - if (l < biggestTick && (i % Mathf.RoundToInt(TickSteps[l + 1] / TickSteps[l]) == 0)) - continue; - - var tick = i * TickSteps[l]; - var p = PointFromKeyframes(axis * tick, ref viewRect); - - // Draw line - var lineRect = new Rectangle - ( - viewRect.Location + (p - 0.5f) * axis, - Float2.Lerp(viewRect.Size, Float2.One, axis) - ); - Render2D.FillRectangle(lineRect, _linesColor.AlphaMultiplied(strength)); - - // Draw label - string label = tick.ToString(CultureInfo.InvariantCulture); - var labelRect = new Rectangle - ( - viewRect.X + 4.0f + (p.X * axis.X), - viewRect.Y - LabelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X), - 50, - LabelsSize - ); - Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f); - } - } + // Draw label + string label = tick.ToString(CultureInfo.InvariantCulture); + var labelRect = new Rectangle + ( + viewRect.X + 4.0f + (p.X * axis.X), + viewRect.Y - LabelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X), + 50, + LabelsSize + ); + Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f); + }, TickSteps, ref _tickStrengths, min, max, pixelRange); } /// @@ -890,9 +845,9 @@ namespace FlaxEditor.GUI Render2D.PushClip(ref viewRect); if ((ShowAxes & UseMode.Vertical) == UseMode.Vertical) - DrawAxis(Float2.UnitX, ref viewRect, min.X, max.X, pixelRange.X); + DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X); if ((ShowAxes & UseMode.Horizontal) == UseMode.Horizontal) - DrawAxis(Float2.UnitY, ref viewRect, min.Y, max.Y, pixelRange.Y); + DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y); Render2D.PopClip(); } diff --git a/Source/Editor/GUI/Input/DoubleValueBox.cs b/Source/Editor/GUI/Input/DoubleValueBox.cs index e4a8c6d3f..86acc1203 100644 --- a/Source/Editor/GUI/Input/DoubleValueBox.cs +++ b/Source/Editor/GUI/Input/DoubleValueBox.cs @@ -3,6 +3,7 @@ using System; using FlaxEditor.Utilities; using FlaxEngine; +using Utils = FlaxEngine.Utils; namespace FlaxEditor.GUI.Input { @@ -13,6 +14,8 @@ namespace FlaxEditor.GUI.Input [HideInEditor] public class DoubleValueBox : ValueBox { + private Utils.ValueCategory _category = Utils.ValueCategory.None; + /// public override double Value { @@ -129,10 +132,25 @@ namespace FlaxEditor.GUI.Input Value = Value; } + /// + /// Gets or sets the category of the value. This can be none for just a number or a more specific one like a distance. + /// + public Utils.ValueCategory Category + { + get => _category; + set + { + if (_category == value) + return; + _category = value; + UpdateText(); + } + } + /// protected sealed override void UpdateText() { - SetText(Utilities.Utils.FormatFloat(_value)); + SetText(Utilities.Utils.FormatFloat(_value, Category)); } /// diff --git a/Source/Editor/GUI/Input/FloatValueBox.cs b/Source/Editor/GUI/Input/FloatValueBox.cs index 5fc90f260..f22ecd89c 100644 --- a/Source/Editor/GUI/Input/FloatValueBox.cs +++ b/Source/Editor/GUI/Input/FloatValueBox.cs @@ -1,9 +1,9 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; -using System.Globalization; using FlaxEditor.Utilities; using FlaxEngine; +using Utils = FlaxEngine.Utils; namespace FlaxEditor.GUI.Input { @@ -14,6 +14,8 @@ namespace FlaxEditor.GUI.Input [HideInEditor] public class FloatValueBox : ValueBox { + private Utils.ValueCategory _category = Utils.ValueCategory.None; + /// public override float Value { @@ -137,10 +139,25 @@ namespace FlaxEditor.GUI.Input Value = Value; } + /// + /// Gets or sets the category of the value. This can be none for just a number or a more specific one like a distance. + /// + public Utils.ValueCategory Category + { + get => _category; + set + { + if (_category == value) + return; + _category = value; + UpdateText(); + } + } + /// protected sealed override void UpdateText() { - SetText(Utilities.Utils.FormatFloat(_value)); + SetText(Utilities.Utils.FormatFloat(_value, Category)); } /// diff --git a/Source/Editor/GUI/Timeline/GUI/Background.cs b/Source/Editor/GUI/Timeline/GUI/Background.cs index 4ec634a0b..7bda8f4c0 100644 --- a/Source/Editor/GUI/Timeline/GUI/Background.cs +++ b/Source/Editor/GUI/Timeline/GUI/Background.cs @@ -28,7 +28,6 @@ namespace FlaxEditor.GUI.Timeline.GUI { _timeline = timeline; _tickSteps = Utilities.Utils.CurveTickSteps; - _tickStrengths = new float[_tickSteps.Length]; } private void UpdateSelectionRectangle() @@ -173,55 +172,20 @@ namespace FlaxEditor.GUI.Timeline.GUI var rightFrame = Mathf.Ceil((right - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond; var min = leftFrame; var max = rightFrame; - int smallestTick = 0; - int biggestTick = _tickSteps.Length - 1; - for (int i = _tickSteps.Length - 1; i >= 0; i--) - { - // Calculate how far apart these modulo tick steps are spaced - float tickSpacing = _tickSteps[i] * _timeline.Zoom; - - // Calculate the strength of the tick markers based on the spacing - _tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks)); - - // Beyond threshold the ticks don't get any bigger or fatter - if (_tickStrengths[i] >= 1) - biggestTick = i; - - // Do not show small tick markers - if (tickSpacing <= minDistanceBetweenTicks) - { - smallestTick = i; - break; - } - } - int tickLevels = biggestTick - smallestTick + 1; // Draw vertical lines for time axis - for (int level = 0; level < tickLevels; level++) + var pixelsInRange = _timeline.Zoom; + var pixelRange = pixelsInRange * (max - min); + var tickRange = Utilities.Utils.DrawCurveTicks((float tick, float strength) => { - float strength = _tickStrengths[smallestTick + level]; - if (strength <= Mathf.Epsilon) - continue; - - // Draw all ticks - int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1); - var lStep = _tickSteps[l]; - var lNextStep = _tickSteps[l + 1]; - int startTick = Mathf.FloorToInt(min / lStep); - int endTick = Mathf.CeilToInt(max / lStep); - Color lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength); - for (int i = startTick; i <= endTick; i++) - { - if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0)) - continue; - var tick = i * lStep; - var time = tick / _timeline.FramesPerSecond; - var x = time * zoom + Timeline.StartOffset; - - // Draw line - Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor); - } - } + var time = tick / _timeline.FramesPerSecond; + var x = time * zoom + Timeline.StartOffset; + var lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength); + Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor); + }, _tickSteps, ref _tickStrengths, min, max, pixelRange, minDistanceBetweenTicks, maxDistanceBetweenTicks); + var smallestTick = tickRange.X; + var biggestTick = tickRange.Y; + var tickLevels = biggestTick - smallestTick + 1; // Draw selection rectangle if (_isSelecting) diff --git a/Source/Editor/Gizmo/GizmosCollection.cs b/Source/Editor/Gizmo/GizmosCollection.cs index b77d22e84..0f69c0756 100644 --- a/Source/Editor/Gizmo/GizmosCollection.cs +++ b/Source/Editor/Gizmo/GizmosCollection.cs @@ -161,5 +161,20 @@ namespace FlaxEditor.Gizmo } throw new ArgumentException("Not added mode to activate."); } + + /// + /// Gets the gizmo of a given type or returns null if not added. + /// + /// Type of the gizmo. + /// Found gizmo or null. + public T Get() where T : GizmoBase + { + foreach (var e in this) + { + if (e is T asT) + return asT; + } + return null; + } } } diff --git a/Source/Editor/Gizmo/GridGizmo.cs b/Source/Editor/Gizmo/GridGizmo.cs index 8638e1b54..a96651472 100644 --- a/Source/Editor/Gizmo/GridGizmo.cs +++ b/Source/Editor/Gizmo/GridGizmo.cs @@ -21,7 +21,7 @@ namespace FlaxEditor.Gizmo { Order = -100; UseSingleTarget = true; - Location = PostProcessEffectLocation.BeforeForwardPass; + Location = PostProcessEffectLocation.AfterAntiAliasingPass; } ~Renderer() @@ -46,7 +46,8 @@ namespace FlaxEditor.Gizmo var plane = new Plane(Vector3.Zero, Vector3.UnitY); var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos); - float space = Editor.Instance.Options.Options.Viewport.ViewportGridScale, size; + var options = Editor.Instance.Options.Options; + float space = options.Viewport.ViewportGridScale, size; if (dst <= 500.0f) { size = 8000; @@ -62,8 +63,12 @@ namespace FlaxEditor.Gizmo size = 100000; } - Color color = Color.Gray * 0.7f; + float bigLineIntensity = 0.8f; + Color bigColor = Color.Gray * bigLineIntensity; + Color color = bigColor * 0.8f; int count = (int)(size / space); + int midLine = count / 2; + int bigLinesMod = count / 8; Vector3 start = new Vector3(0, 0, size * -0.5f); Vector3 end = new Vector3(0, 0, size * 0.5f); @@ -71,7 +76,12 @@ namespace FlaxEditor.Gizmo for (int i = 0; i <= count; i++) { start.X = end.X = i * space + start.Z; - DebugDraw.DrawLine(start, end, color); + Color lineColor = color; + if (i == midLine) + lineColor = Color.Blue * bigLineIntensity; + else if (i % bigLinesMod == 0) + lineColor = bigColor; + DebugDraw.DrawLine(start, end, lineColor); } start = new Vector3(size * -0.5f, 0, 0); @@ -80,7 +90,12 @@ namespace FlaxEditor.Gizmo for (int i = 0; i <= count; i++) { start.Z = end.Z = i * space + start.X; - DebugDraw.DrawLine(start, end, color); + Color lineColor = color; + if (i == midLine) + lineColor = Color.Red * bigLineIntensity; + else if (i % bigLinesMod == 0) + lineColor = bigColor; + DebugDraw.DrawLine(start, end, lineColor); } DebugDraw.Draw(ref renderContext, input.View(), null, true); diff --git a/Source/Editor/Gizmo/IGizmoOwner.cs b/Source/Editor/Gizmo/IGizmoOwner.cs index 7237d724b..0a5c520a7 100644 --- a/Source/Editor/Gizmo/IGizmoOwner.cs +++ b/Source/Editor/Gizmo/IGizmoOwner.cs @@ -117,5 +117,10 @@ namespace FlaxEditor.Gizmo /// /// The new actor to spawn. void Spawn(Actor actor); + + /// + /// Opens the context menu at the current mouse location (using current selection). + /// + void OpenContextMenu(); } } diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs index 64f969990..cd5e16e92 100644 --- a/Source/Editor/Gizmo/TransformGizmo.cs +++ b/Source/Editor/Gizmo/TransformGizmo.cs @@ -42,6 +42,11 @@ namespace FlaxEditor.Gizmo /// public Action Duplicate; + /// + /// Gets the array of selected objects. + /// + public List Selection => _selection; + /// /// Gets the array of selected parent objects (as actors). /// diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs new file mode 100644 index 000000000..43461b0a6 --- /dev/null +++ b/Source/Editor/Gizmo/UIEditorGizmo.cs @@ -0,0 +1,891 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using FlaxEditor.Gizmo; +using FlaxEditor.SceneGraph; +using FlaxEditor.SceneGraph.Actors; +using FlaxEditor.Viewport.Cameras; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor +{ + /// + /// UI editor camera. + /// + [HideInEditor] + internal sealed class UIEditorCamera : ViewportCamera + { + public UIEditorRoot UIEditor; + + public void ShowActors(IEnumerable actors) + { + var root = UIEditor.UIRoot; + if (root == null) + return; + + // Calculate bounds of all selected objects + var areaRect = Rectangle.Empty; + foreach (var actor in actors) + { + Rectangle bounds; + if (actor is UIControl uiControl && uiControl.HasControl && uiControl.IsActive) + { + var control = uiControl.Control; + bounds = control.EditorBounds; + + var ul = control.PointToParent(root, bounds.UpperLeft); + var ur = control.PointToParent(root, bounds.UpperRight); + var bl = control.PointToParent(root, bounds.BottomLeft); + var br = control.PointToParent(root, bounds.BottomRight); + + var min = Float2.Min(Float2.Min(ul, ur), Float2.Min(bl, br)); + var max = Float2.Max(Float2.Max(ul, ur), Float2.Max(bl, br)); + bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero)); + } + else if (actor is UICanvas uiCanvas && uiCanvas.IsActive && uiCanvas.GUI.Parent == root) + { + bounds = uiCanvas.GUI.Bounds; + } + else + continue; + + if (areaRect == Rectangle.Empty) + areaRect = bounds; + else + areaRect = Rectangle.Union(areaRect, bounds); + } + if (areaRect == Rectangle.Empty) + return; + + // Add margin + areaRect = areaRect.MakeExpanded(100.0f); + + // Show bounds + UIEditor.ViewScale = (UIEditor.Size / areaRect.Size).MinValue * 0.95f; + UIEditor.ViewCenterPosition = areaRect.Center; + } + + public override void FocusSelection(GizmosCollection gizmos, ref Quaternion orientation) + { + ShowActors(gizmos.Get().Selection, ref orientation); + } + + public override void ShowActor(Actor actor) + { + ShowActors(new[] { actor }); + } + + public override void ShowActors(List selection, ref Quaternion orientation) + { + ShowActors(selection.ConvertAll(x => (Actor)x.EditableObject)); + } + + public override void UpdateView(float dt, ref Vector3 moveDelta, ref Float2 mouseDelta, out bool centerMouse) + { + centerMouse = false; + } + } + + /// + /// Root control for UI Controls presentation in the game/prefab viewport. + /// + [HideInEditor] + internal class UIEditorRoot : InputsPassThrough + { + /// + /// View for the UI structure to be linked in for camera zoom and panning operations. + /// + private sealed class View : ContainerControl + { + public View(UIEditorRoot parent) + { + AutoFocus = false; + ClipChildren = false; + CullChildren = false; + Pivot = Float2.Zero; + Size = new Float2(1920, 1080); + Parent = parent; + } + + public override bool RayCast(ref Float2 location, out Control hit) + { + // Ignore self + return RayCastChildren(ref location, out hit); + } + + public override bool IntersectsContent(ref Float2 locationParent, out Float2 location) + { + location = PointFromParent(ref locationParent); + return true; + } + + public override void DrawSelf() + { + var uiRoot = (UIEditorRoot)Parent; + if (!uiRoot.EnableBackground) + return; + + // Draw canvas area + var bounds = new Rectangle(Float2.Zero, Size); + Render2D.FillRectangle(bounds, new Color(0, 0, 0, 0.2f)); + } + } + + /// + /// Cached placement of the widget used to size/edit control + /// + private struct Widget + { + public UIControl UIControl; + public Rectangle Bounds; + public Float2 ResizeAxis; + public CursorType Cursor; + } + + private bool _mouseMovesControl, _mouseMovesView, _mouseMovesWidget; + private Float2 _mouseMovesPos, _moveSnapDelta; + private float _mouseMoveSum; + private UndoMultiBlock _undoBlock; + private View _view; + private float[] _gridTickSteps = Utilities.Utils.CurveTickSteps, _gridTickStrengths; + private List _widgets; + private Widget _activeWidget; + + /// + /// True if enable displaying UI editing background and grid elements. + /// + public virtual bool EnableBackground => false; + + /// + /// True if enable selecting controls with mouse button. + /// + public virtual bool EnableSelecting => false; + + /// + /// True if enable panning and zooming the view. + /// + public bool EnableCamera => _view != null && EnableBackground; + + /// + /// Transform gizmo to use sync with (selection, snapping, transformation settings). + /// + public virtual TransformGizmo TransformGizmo => null; + + /// + /// The root control for controls to be linked in. + /// + public readonly ContainerControl UIRoot; + + internal Float2 ViewPosition + { + get => _view.Location / -ViewScale; + set => _view.Location = value * -ViewScale; + } + + internal Float2 ViewCenterPosition + { + get => (_view.Location - Size * 0.5f) / -ViewScale; + set => _view.Location = Size * 0.5f + value * -ViewScale; + } + + internal float ViewScale + { + get => _view?.Scale.X ?? 1; + set + { + if (_view == null) + return; + value = Mathf.Clamp(value, 0.1f, 4.0f); + _view.Scale = new Float2(value); + } + } + + public UIEditorRoot(bool enableCamera = false) + { + AnchorPreset = AnchorPresets.StretchAll; + Offsets = Margin.Zero; + AutoFocus = false; + UIRoot = this; + CullChildren = false; + ClipChildren = true; + if (enableCamera) + { + _view = new View(this); + UIRoot = _view; + } + } + + public override bool OnMouseDown(Float2 location, MouseButton button) + { + if (base.OnMouseDown(location, button)) + return true; + + var transformGizmo = TransformGizmo; + var owner = transformGizmo?.Owner; + if (_widgets != null && _widgets.Count != 0 && button == MouseButton.Left) + { + foreach (var widget in _widgets) + { + if (widget.Bounds.Contains(ref location)) + { + // Initialize widget movement + _activeWidget = widget; + _mouseMovesWidget = true; + _mouseMovesPos = location; + Cursor = widget.Cursor; + StartUndo(); + Focus(); + StartMouseCapture(); + return true; + } + } + } + if (EnableSelecting && owner != null && !_mouseMovesControl && button == MouseButton.Left) + { + // Raycast the control under the mouse + var mousePos = PointFromWindow(RootWindow.MousePosition); + if (RayCastControl(ref mousePos, out var hitControl)) + { + var uiControlNode = FindUIControlNode(hitControl); + if (uiControlNode != null) + { + // Select node (with additive mode) + var selection = new List(); + if (Root.GetKey(KeyboardKeys.Control)) + { + // Add/remove from selection + selection.AddRange(transformGizmo.Selection); + if (transformGizmo.Selection.Contains(uiControlNode)) + selection.Remove(uiControlNode); + else + selection.Add(uiControlNode); + } + else + { + // Select + selection.Add(uiControlNode); + } + owner.Select(selection); + + // Initialize control movement + _mouseMovesControl = true; + _mouseMovesPos = location; + _mouseMoveSum = 0.0f; + _moveSnapDelta = Float2.Zero; + Focus(); + StartMouseCapture(); + return true; + } + } + // Allow deselecting if user clicks on nothing + else + { + owner.Select(null); + } + } + if (EnableCamera && (button == MouseButton.Right || button == MouseButton.Middle)) + { + // Initialize surface movement + _mouseMovesView = true; + _mouseMovesPos = location; + _mouseMoveSum = 0.0f; + Focus(); + StartMouseCapture(); + return true; + } + + return Focus(this); + } + + public override void OnMouseMove(Float2 location) + { + base.OnMouseMove(location); + + // Change cursor if mouse is over active control widget + bool cursorChanged = false; + if (_widgets != null && _widgets.Count != 0 && !_mouseMovesControl && !_mouseMovesWidget && !_mouseMovesView) + { + foreach (var widget in _widgets) + { + if (widget.Bounds.Contains(ref location)) + { + Cursor = widget.Cursor; + cursorChanged = true; + } + else if (Cursor != CursorType.Default && !cursorChanged) + { + Cursor = CursorType.Default; + } + } + } + + var transformGizmo = TransformGizmo; + if (_mouseMovesControl && transformGizmo != null) + { + // Calculate transform delta + var delta = location - _mouseMovesPos; + if (transformGizmo.TranslationSnapEnable || transformGizmo.Owner.UseSnapping) + { + _moveSnapDelta += delta; + delta = Float2.SnapToGrid(_moveSnapDelta, new Float2(transformGizmo.TranslationSnapValue * ViewScale)); + _moveSnapDelta -= delta; + } + + // Move selected controls + if (delta.LengthSquared > 0.0f) + { + StartUndo(); + var moved = false; + var moveLocation = _mouseMovesPos + delta; + var selection = transformGizmo.Selection; + for (var i = 0; i < selection.Count; i++) + { + if (IsValidControl(selection[i], out var uiControl)) + { + // Move control (handle any control transformations by moving in editor's local-space) + var control = uiControl.Control; + var localLocation = control.LocalLocation; + var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation); + control.LocalLocation = localLocation + uiControlDelta; + + // Don't move if layout doesn't allow it + if (control.Parent != null) + control.Parent.PerformLayout(); + else + control.PerformLayout(); + + // Check if control was moved (parent container could block it) + if (localLocation != control.LocalLocation) + moved = true; + } + } + _mouseMovesPos = location; + _mouseMoveSum += delta.Length; + if (moved) + Cursor = CursorType.SizeAll; + } + } + if (_mouseMovesWidget && _activeWidget.UIControl) + { + // Calculate transform delta + var resizeAxisAbs = _activeWidget.ResizeAxis.Absolute; + var resizeAxisPos = Float2.Clamp(_activeWidget.ResizeAxis, Float2.Zero, Float2.One); + var resizeAxisNeg = Float2.Clamp(-_activeWidget.ResizeAxis, Float2.Zero, Float2.One); + var delta = location - _mouseMovesPos; + // TODO: scale/size snapping? + delta *= resizeAxisAbs; + + // Resize control via widget + var moveLocation = _mouseMovesPos + delta; + var control = _activeWidget.UIControl.Control; + var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation); + control.LocalLocation += uiControlDelta * resizeAxisNeg; + control.Size += uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg; + + // Don't move if layout doesn't allow it + if (control.Parent != null) + control.Parent.PerformLayout(); + else + control.PerformLayout(); + + _mouseMovesPos = location; + } + if (_mouseMovesView) + { + // Move view + var delta = location - _mouseMovesPos; + if (delta.LengthSquared > 4.0f) + { + _mouseMovesPos = location; + _mouseMoveSum += delta.Length; + _view.Location += delta; + Cursor = CursorType.SizeAll; + } + } + } + + public override bool OnMouseUp(Float2 location, MouseButton button) + { + EndMovingControls(); + EndMovingWidget(); + if (_mouseMovesView) + { + EndMovingView(); + if (button == MouseButton.Right && _mouseMoveSum < 2.0f) + TransformGizmo.Owner.OpenContextMenu(); + } + + return base.OnMouseUp(location, button); + } + + public override void OnMouseLeave() + { + EndMovingControls(); + EndMovingView(); + EndMovingWidget(); + + base.OnMouseLeave(); + } + + public override void OnLostFocus() + { + EndMovingControls(); + EndMovingView(); + EndMovingWidget(); + + base.OnLostFocus(); + } + + public override bool OnMouseWheel(Float2 location, float delta) + { + if (base.OnMouseWheel(location, delta)) + return true; + + if (EnableCamera && !_mouseMovesControl) + { + // Zoom view + var nextViewScale = ViewScale + delta * 0.1f; + if (delta > 0 && !_mouseMovesControl) + { + // Scale towards mouse when zooming in + var nextCenterPosition = ViewPosition + location / ViewScale; + ViewScale = nextViewScale; + ViewPosition = nextCenterPosition - (location / ViewScale); + } + else + { + // Scale while keeping center position when zooming out or when dragging view + var viewCenter = ViewCenterPosition; + ViewScale = nextViewScale; + ViewCenterPosition = viewCenter; + } + + return true; + } + + return false; + } + + public override void Draw() + { + if (EnableBackground && _view != null) + { + // Draw background + Surface.VisjectSurface.DrawBackgroundDefault(Editor.Instance.UI.VisjectSurfaceBackground, Width, Height); + + // Draw grid + var viewRect = GetClientArea(); + var upperLeft = _view.PointFromParent(viewRect.Location); + var bottomRight = _view.PointFromParent(viewRect.Size); + var min = Float2.Min(upperLeft, bottomRight); + var max = Float2.Max(upperLeft, bottomRight); + var pixelRange = (max - min) * ViewScale; + Render2D.PushClip(ref viewRect); + DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X); + DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y); + Render2D.PopClip(); + } + + base.Draw(); + + if (!_mouseMovesWidget) + { + // Clear widgets to collect them during drawing + _widgets?.Clear(); + } + + bool drawAnySelectedControl = false; + var transformGizmo = TransformGizmo; + var mousePos = PointFromWindow(RootWindow.MousePosition); + if (transformGizmo != null) + { + // Selected UI controls outline + var selection = transformGizmo.Selection; + for (var i = 0; i < selection.Count; i++) + { + if (IsValidControl(selection[i], out var controlActor)) + { + DrawControl(controlActor, controlActor.Control, true, ref mousePos, ref drawAnySelectedControl, EnableSelecting); + } + } + } + if (EnableSelecting && !_mouseMovesControl && !_mouseMovesWidget && IsMouseOver) + { + // Highlight control under mouse for easier selecting (except if already selected) + if (RayCastControl(ref mousePos, out var hitControl) && + (transformGizmo == null || !transformGizmo.Selection.Any(x => x.EditableObject is UIControl controlActor && controlActor.Control == hitControl))) + { + DrawControl(null, hitControl, false, ref mousePos, ref drawAnySelectedControl); + } + } + if (drawAnySelectedControl) + Render2D.PopTransform(); + + if (EnableBackground) + { + // Draw border + if (ContainsFocus) + { + Render2D.DrawRectangle(new Rectangle(1, 1, Width - 2, Height - 2), Editor.IsPlayMode ? Color.OrangeRed : Style.Current.BackgroundSelected); + } + } + } + + public override void OnDestroy() + { + if (IsDisposing) + return; + EndMovingControls(); + EndMovingView(); + EndMovingWidget(); + + base.OnDestroy(); + } + + private Float2 GetControlDelta(Control control, ref Float2 start, ref Float2 end) + { + var pointOrigin = control.Parent ?? control; + var startPos = pointOrigin.PointFromParent(this, start); + var endPos = pointOrigin.PointFromParent(this, end); + return endPos - startPos; + } + + private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange) + { + var style = Style.Current; + var linesColor = style.ForegroundDisabled.RGBMultiplied(0.5f); + var labelsColor = style.ForegroundDisabled; + var labelsSize = 10.0f; + Utilities.Utils.DrawCurveTicks((float tick, float strength) => + { + var p = _view.PointToParent(axis * tick); + + // Draw line + var lineRect = new Rectangle + ( + viewRect.Location + (p - 0.5f) * axis, + Float2.Lerp(viewRect.Size, Float2.One, axis) + ); + Render2D.FillRectangle(lineRect, linesColor.AlphaMultiplied(strength)); + + // Draw label + string label = tick.ToString(System.Globalization.CultureInfo.InvariantCulture); + var labelRect = new Rectangle + ( + viewRect.X + 4.0f + (p.X * axis.X), + viewRect.Y - labelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X), + 50, + labelsSize + ); + Render2D.DrawText(style.FontSmall, label, labelRect, labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f); + }, _gridTickSteps, ref _gridTickStrengths, min, max, pixelRange); + } + + private void DrawControl(UIControl uiControl, Control control, bool selection, ref Float2 mousePos, ref bool drawAnySelectedControl, bool withWidgets = false) + { + if (!drawAnySelectedControl) + { + drawAnySelectedControl = true; + Render2D.PushTransform(ref _cachedTransform); + } + var options = Editor.Instance.Options.Options.Visual; + + // Draw bounds + var bounds = control.EditorBounds; + var ul = control.PointToParent(this, bounds.UpperLeft); + var ur = control.PointToParent(this, bounds.UpperRight); + var bl = control.PointToParent(this, bounds.BottomLeft); + var br = control.PointToParent(this, bounds.BottomRight); + var color = selection ? options.SelectionOutlineColor0 : Style.Current.SelectionBorder; +#if false + // AABB + var min = Float2.Min(Float2.Min(ul, ur), Float2.Min(bl, br)); + var max = Float2.Max(Float2.Max(ul, ur), Float2.Max(bl, br)); + bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero)); + Render2D.DrawRectangle(bounds, color, options.UISelectionOutlineSize); +#else + // OBB + Render2D.DrawLine(ul, ur, color, options.UISelectionOutlineSize); + Render2D.DrawLine(ur, br, color, options.UISelectionOutlineSize); + Render2D.DrawLine(br, bl, color, options.UISelectionOutlineSize); + Render2D.DrawLine(bl, ul, color, options.UISelectionOutlineSize); +#endif + if (withWidgets) + { + // Draw sizing widgets + if (_widgets == null) + _widgets = new List(); + var widgetSize = 8.0f; + var viewScale = ViewScale; + if (viewScale < 0.7f) + widgetSize *= viewScale; + var controlSize = control.Size.Absolute.MinValue / 50.0f; + if (controlSize < 1.0f) + widgetSize *= Mathf.Clamp(controlSize + 0.1f, 0.1f, 1.0f); + var cornerSize = new Float2(widgetSize); + DrawControlWidget(uiControl, ref ul, ref mousePos, ref cornerSize, new Float2(-1, -1), CursorType.SizeNWSE); + DrawControlWidget(uiControl, ref ur, ref mousePos, ref cornerSize, new Float2(1, -1), CursorType.SizeNESW); + DrawControlWidget(uiControl, ref bl, ref mousePos, ref cornerSize, new Float2(-1, 1), CursorType.SizeNESW); + DrawControlWidget(uiControl, ref br, ref mousePos, ref cornerSize, new Float2(1, 1), CursorType.SizeNWSE); + var edgeSizeV = new Float2(widgetSize * 2, widgetSize); + var edgeSizeH = new Float2(edgeSizeV.Y, edgeSizeV.X); + Float2.Lerp(ref ul, ref bl, 0.5f, out var el); + Float2.Lerp(ref ur, ref br, 0.5f, out var er); + Float2.Lerp(ref ul, ref ur, 0.5f, out var eu); + Float2.Lerp(ref bl, ref br, 0.5f, out var eb); + DrawControlWidget(uiControl, ref el, ref mousePos, ref edgeSizeH, new Float2(-1, 0), CursorType.SizeWE); + DrawControlWidget(uiControl, ref er, ref mousePos, ref edgeSizeH, new Float2(1, 0), CursorType.SizeWE); + DrawControlWidget(uiControl, ref eu, ref mousePos, ref edgeSizeV, new Float2(0, -1), CursorType.SizeNS); + DrawControlWidget(uiControl, ref eb, ref mousePos, ref edgeSizeV, new Float2(0, 1), CursorType.SizeNS); + + // TODO: draw anchors + } + } + + private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size, Float2 resizeAxis, CursorType cursor) + { + var style = Style.Current; + var rect = new Rectangle(pos - size * 0.5f, size); + if (rect.Contains(ref mousePos)) + { + Render2D.FillRectangle(rect, style.Foreground); + } + else + { + Render2D.FillRectangle(rect, style.ForegroundGrey); + Render2D.DrawRectangle(rect, style.Foreground); + } + if (!_mouseMovesWidget && uiControl != null) + { + // Collect widget + _widgets.Add(new Widget + { + UIControl = uiControl, + Bounds = rect, + ResizeAxis = resizeAxis, + Cursor = cursor, + }); + } + } + + private bool IsValidControl(SceneGraphNode node, out UIControl uiControl) + { + uiControl = null; + if (node.EditableObject is UIControl controlActor) + uiControl = controlActor; + return uiControl != null && + uiControl.Control != null && + uiControl.Control.VisibleInHierarchy && + uiControl.Control.RootWindow != null; + } + + private bool RayCastControl(ref Float2 location, out Control hit) + { +#if false + // Raycast only controls with content (eg. skips transparent panels) + return RayCastChildren(ref location, out hit); +#else + // Find any control under mouse (hierarchical) + hit = GetChildAtRecursive(location); + if (hit is View || hit is CanvasContainer) + hit = null; + return hit != null; +#endif + } + + private UIControlNode FindUIControlNode(Control control) + { + return FindUIControlNode(TransformGizmo.Owner.SceneGraphRoot, control); + } + + private UIControlNode FindUIControlNode(SceneGraphNode node, Control control) + { + var result = node as UIControlNode; + if (result != null && ((UIControl)result.Actor).Control == control) + return result; + foreach (var e in node.ChildNodes) + { + result = FindUIControlNode(e, control); + if (result != null) + return result; + } + return null; + } + + private void StartUndo() + { + var undo = TransformGizmo?.Owner?.Undo; + if (undo == null || _undoBlock != null) + return; + _undoBlock = new UndoMultiBlock(undo, TransformGizmo.Selection.ConvertAll(x => x.EditableObject), "Edit control"); + } + + private void EndUndo() + { + if (_undoBlock == null) + return; + _undoBlock.Dispose(); + _undoBlock = null; + } + + private void EndMovingControls() + { + if (!_mouseMovesControl) + return; + _mouseMovesControl = false; + EndMouseCapture(); + Cursor = CursorType.Default; + EndUndo(); + } + + private void EndMovingView() + { + if (!_mouseMovesView) + return; + _mouseMovesView = false; + EndMouseCapture(); + Cursor = CursorType.Default; + } + + private void EndMovingWidget() + { + if (!_mouseMovesWidget) + return; + _mouseMovesWidget = false; + _activeWidget = new Widget(); + EndMouseCapture(); + Cursor = CursorType.Default; + EndUndo(); + } + } + + /// + /// Control that can optionally disable inputs to the children. + /// + [HideInEditor] + internal class InputsPassThrough : ContainerControl + { + private bool _isMouseOver; + + /// + /// True if enable input events passing to the UI. + /// + public virtual bool EnableInputs => true; + + public override bool RayCast(ref Float2 location, out Control hit) + { + return RayCastChildren(ref location, out hit); + } + + public override bool ContainsPoint(ref Float2 location, bool precise = false) + { + if (precise) + return false; + return base.ContainsPoint(ref location, precise); + } + + public override bool OnCharInput(char c) + { + if (!EnableInputs) + return false; + return base.OnCharInput(c); + } + + public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) + { + if (!EnableInputs) + return DragDropEffect.None; + return base.OnDragDrop(ref location, data); + } + + public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) + { + if (!EnableInputs) + return DragDropEffect.None; + return base.OnDragEnter(ref location, data); + } + + public override void OnDragLeave() + { + if (!EnableInputs) + return; + base.OnDragLeave(); + } + + public override DragDropEffect OnDragMove(ref Float2 location, DragData data) + { + if (!EnableInputs) + return DragDropEffect.None; + return base.OnDragMove(ref location, data); + } + + public override bool OnKeyDown(KeyboardKeys key) + { + if (!EnableInputs) + return false; + return base.OnKeyDown(key); + } + + public override void OnKeyUp(KeyboardKeys key) + { + if (!EnableInputs) + return; + base.OnKeyUp(key); + } + + public override bool OnMouseDoubleClick(Float2 location, MouseButton button) + { + if (!EnableInputs) + return false; + return base.OnMouseDoubleClick(location, button); + } + + public override bool OnMouseDown(Float2 location, MouseButton button) + { + if (!EnableInputs) + return false; + return base.OnMouseDown(location, button); + } + + public override bool IsMouseOver => _isMouseOver; + + public override void OnMouseEnter(Float2 location) + { + _isMouseOver = true; + if (!EnableInputs) + return; + base.OnMouseEnter(location); + } + + public override void OnMouseLeave() + { + _isMouseOver = false; + if (!EnableInputs) + return; + base.OnMouseLeave(); + } + + public override void OnMouseMove(Float2 location) + { + if (!EnableInputs) + return; + base.OnMouseMove(location); + } + + public override bool OnMouseUp(Float2 location, MouseButton button) + { + if (!EnableInputs) + return false; + return base.OnMouseUp(location, button); + } + + public override bool OnMouseWheel(Float2 location, float delta) + { + if (!EnableInputs) + return false; + return base.OnMouseWheel(location, delta); + } + } +} diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs index 47147eaa8..59954dec5 100644 --- a/Source/Editor/Options/InputBinding.cs +++ b/Source/Editor/Options/InputBinding.cs @@ -201,8 +201,8 @@ namespace FlaxEditor.Options /// True if input has been processed, otherwise false. public bool Process(Control control) { - var root = control.Root; - return root.GetKey(Key) && ProcessModifiers(control); + var root = control?.Root; + return root != null && root.GetKey(Key) && ProcessModifiers(control); } /// diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 05557f3c0..4cf1fe050 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using FlaxEditor.GUI.Docking; +using FlaxEditor.Utilities; using FlaxEngine; namespace FlaxEditor.Options @@ -116,6 +117,27 @@ namespace FlaxEditor.Options BorderlessWindow, } + /// + /// Options for formatting numerical values. + /// + public enum ValueFormattingType + { + /// + /// No formatting. + /// + None, + + /// + /// Format using the base SI unit. + /// + BaseUnit, + + /// + /// Format using a unit that matches the value best. + /// + AutoUnit, + } + /// /// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required. /// @@ -174,6 +196,20 @@ namespace FlaxEditor.Options [EditorDisplay("Interface"), EditorOrder(280), Tooltip("Editor content window orientation.")] public FlaxEngine.GUI.Orientation ContentWindowOrientation { get; set; } = FlaxEngine.GUI.Orientation.Horizontal; + /// + /// Gets or sets the formatting option for numeric values in the editor. + /// + [DefaultValue(ValueFormattingType.None)] + [EditorDisplay("Interface"), EditorOrder(300)] + public ValueFormattingType ValueFormatting { get; set; } + + /// + /// Gets or sets the option to put a space between numbers and units for unit formatting. + /// + [DefaultValue(false)] + [EditorDisplay("Interface"), EditorOrder(310)] + public bool SeparateValueAndUnit { get; set; } + /// /// Gets or sets the timestamps prefix mode for output log messages. /// diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 2dceb3400..debc0a663 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -200,6 +200,27 @@ namespace FlaxEditor.Options EditorAssets.Cache.OnEditorOptionsChanged(Options); + // Units formatting options + bool useUnitsFormatting = Options.Interface.ValueFormatting != InterfaceOptions.ValueFormattingType.None; + bool automaticUnitsFormatting = Options.Interface.ValueFormatting == InterfaceOptions.ValueFormattingType.AutoUnit; + bool separateValueAndUnit = Options.Interface.SeparateValueAndUnit; + if (useUnitsFormatting != Utilities.Units.UseUnitsFormatting || + automaticUnitsFormatting != Utilities.Units.AutomaticUnitsFormatting || + separateValueAndUnit != Utilities.Units.SeparateValueAndUnit) + { + Utilities.Units.UseUnitsFormatting = useUnitsFormatting; + Utilities.Units.AutomaticUnitsFormatting = automaticUnitsFormatting; + Utilities.Units.SeparateValueAndUnit = separateValueAndUnit; + + // Refresh UI in property panels + Editor.Windows.PropertiesWin?.Presenter.BuildLayoutOnUpdate(); + foreach (var window in Editor.Windows.Windows) + { + if (window is Windows.Assets.PrefabWindow prefabWindow) + prefabWindow.Presenter.BuildLayoutOnUpdate(); + } + } + // Send event OptionsChanged?.Invoke(Options); } diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs index 2747e1023..1178f54de 100644 --- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs +++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs @@ -617,8 +617,9 @@ namespace FlaxEditor.Surface.Archetypes public override void SetLocation(int index, Float2 location) { var dataA = (Float4)_node.Values[4 + index * 2]; + var ranges = (Float4)_node.Values[0]; - dataA.X = location.X; + dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y); _node.Values[4 + index * 2] = dataA; _node.Surface.MarkAsEdited(); @@ -750,9 +751,10 @@ namespace FlaxEditor.Surface.Archetypes public override void SetLocation(int index, Float2 location) { var dataA = (Float4)_node.Values[4 + index * 2]; + var ranges = (Float4)_node.Values[0]; - dataA.X = location.X; - dataA.Y = location.Y; + dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y); + dataA.Y = Mathf.Clamp(location.Y, ranges.Z, ranges.W); _node.Values[4 + index * 2] = dataA; _node.Surface.MarkAsEdited(); diff --git a/Source/Editor/Surface/VisjectSurface.Draw.cs b/Source/Editor/Surface/VisjectSurface.Draw.cs index 13da1fb97..59a56e3f3 100644 --- a/Source/Editor/Surface/VisjectSurface.Draw.cs +++ b/Source/Editor/Surface/VisjectSurface.Draw.cs @@ -64,7 +64,11 @@ namespace FlaxEditor.Surface /// protected virtual void DrawBackground() { - var background = Style.Background; + DrawBackgroundDefault(Style.Background, Width, Height); + } + + internal static void DrawBackgroundDefault(Texture background, float width, float height) + { if (background && background.ResidentMipLevels > 0) { var bSize = background.Size; @@ -77,8 +81,8 @@ namespace FlaxEditor.Surface if (pos.Y > 0) pos.Y -= bh; - int maxI = Mathf.CeilToInt(Width / bw + 1.0f); - int maxJ = Mathf.CeilToInt(Height / bh + 1.0f); + int maxI = Mathf.CeilToInt(width / bw + 1.0f); + int maxJ = Mathf.CeilToInt(height / bh + 1.0f); for (int i = 0; i < maxI; i++) { diff --git a/Source/Editor/Utilities/ShuntingYardParser.cs b/Source/Editor/Utilities/ShuntingYardParser.cs index aa435416f..5ba8e969c 100644 --- a/Source/Editor/Utilities/ShuntingYardParser.cs +++ b/Source/Editor/Utilities/ShuntingYardParser.cs @@ -121,6 +121,37 @@ namespace FlaxEditor.Utilities ["e"] = Math.E, ["infinity"] = double.MaxValue, ["-infinity"] = -double.MaxValue, + ["m"] = Units.Meters2Units, + ["cm"] = Units.Meters2Units / 100, + ["km"] = Units.Meters2Units * 1000, + ["s"] = 1, + ["ms"] = 0.001, + ["min"] = 60, + ["h"] = 3600, + ["cm²"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100), + ["cm³"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100) * (Units.Meters2Units / 100), + ["dm²"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10), + ["dm³"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10), + ["l"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10), + ["m²"] = Units.Meters2Units * Units.Meters2Units, + ["m³"] = Units.Meters2Units * Units.Meters2Units * Units.Meters2Units, + ["kg"] = 1, + ["g"] = 0.001, + ["n"] = Units.Meters2Units + }; + + /// + /// List known units which cannot be handled as a variable easily because they contain operator symbols (mostly a forward slash). The value is the factor to calculate game units. + /// + private static readonly IDictionary UnitSymbols = new Dictionary + { + ["cm/s"] = Units.Meters2Units / 100, + ["cm/s²"] = Units.Meters2Units / 100, + ["m/s"] = Units.Meters2Units, + ["m/s²"] = Units.Meters2Units, + ["km/h"] = 1 / 3.6 * Units.Meters2Units, + // Nm is here because these values are compared case-sensitive, and we don't want to confuse nanometers and Newtonmeters + ["Nm"] = Units.Meters2Units * Units.Meters2Units, }; /// @@ -156,7 +187,7 @@ namespace FlaxEditor.Utilities if (Operators.ContainsKey(str)) return TokenType.Operator; - if (char.IsLetter(c)) + if (char.IsLetter(c) || c == '²' || c == '³') return TokenType.Variable; throw new ParsingException("wrong character"); @@ -170,7 +201,24 @@ namespace FlaxEditor.Utilities public static IEnumerable Tokenize(string text) { // Prepare text - text = text.Replace(',', '.'); + text = text.Replace(',', '.').Replace("°", ""); + foreach (var kv in UnitSymbols) + { + int idx; + do + { + idx = text.IndexOf(kv.Key, StringComparison.InvariantCulture); + if (idx > 0) + { + if (DetermineType(text[idx - 1]) != TokenType.Number) + throw new ParsingException($"unit found without a number: {kv.Key} at {idx} in {text}"); + if (Mathf.Abs(kv.Value - 1) < Mathf.Epsilon) + text = text.Remove(idx, kv.Key.Length); + else + text = text.Replace(kv.Key, "*" + kv.Value); + } + } while (idx > 0); + } // Necessary to correctly parse negative numbers var previous = TokenType.WhiteSpace; @@ -240,6 +288,11 @@ namespace FlaxEditor.Utilities } else if (type == TokenType.Variable) { + if (previous == TokenType.Number) + { + previous = TokenType.Operator; + yield return new Token(TokenType.Operator, "*"); + } // Continue till the end of the variable while (i + 1 < text.Length && DetermineType(text[i + 1]) == TokenType.Variable) { @@ -335,7 +388,7 @@ namespace FlaxEditor.Utilities } else { - throw new ParsingException("unknown variable"); + throw new ParsingException($"unknown variable : {token.Value}"); } } else @@ -372,6 +425,15 @@ namespace FlaxEditor.Utilities } } + // if stack has more than one item we're not finished with evaluating + // we assume the remaining values are all factors to be multiplied + if (stack.Count > 1) + { + var v1 = stack.Pop(); + while (stack.Count > 0) + v1 *= stack.Pop(); + return v1; + } return stack.Pop(); } diff --git a/Source/Editor/Utilities/Units.cs b/Source/Editor/Utilities/Units.cs new file mode 100644 index 000000000..64977c18b --- /dev/null +++ b/Source/Editor/Utilities/Units.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +namespace FlaxEditor.Utilities; + +/// +/// Units display utilities for Editor. +/// +public class Units +{ + /// + /// Factor of units per meter. + /// + public static readonly float Meters2Units = 100f; + + /// + /// False to always show game units without any postfix. + /// + public static bool UseUnitsFormatting = true; + + /// + /// Add a space between numbers and units for readability. + /// + public static bool SeparateValueAndUnit = true; + + /// + /// If set to true, the distance unit is chosen on the magnitude, otherwise it's meters. + /// + public static bool AutomaticUnitsFormatting = true; + + /// + /// Return the unit according to user settings. + /// + /// The unit name. + /// The formatted text. + public static string Unit(string unit) + { + if (SeparateValueAndUnit) + return $" {unit}"; + return unit; + } +} diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index b403c1489..1b76de47e 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -243,6 +243,63 @@ namespace FlaxEditor.Utilities 500000, 1000000, 5000000, 10000000, 100000000 }; + internal delegate void DrawCurveTick(float tick, float strength); + + internal static Int2 DrawCurveTicks(DrawCurveTick drawTick, float[] tickSteps, ref float[] tickStrengths, float min, float max, float pixelRange, float minDistanceBetweenTicks = 20, float maxDistanceBetweenTicks = 60) + { + if (tickStrengths == null || tickStrengths.Length != tickSteps.Length) + tickStrengths = new float[tickSteps.Length]; + + // Find the strength for each modulo number tick marker + var pixelsInRange = pixelRange / (max - min); + var smallestTick = 0; + var biggestTick = tickSteps.Length - 1; + for (int i = tickSteps.Length - 1; i >= 0; i--) + { + // Calculate how far apart these modulo tick steps are spaced + float tickSpacing = tickSteps[i] * pixelsInRange; + + // Calculate the strength of the tick markers based on the spacing + tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks)); + + // Beyond threshold the ticks don't get any bigger or fatter + if (tickStrengths[i] >= 1) + biggestTick = i; + + // Do not show small tick markers + if (tickSpacing <= minDistanceBetweenTicks) + { + smallestTick = i; + break; + } + } + var tickLevels = biggestTick - smallestTick + 1; + + // Draw all tick levels + for (int level = 0; level < tickLevels; level++) + { + float strength = tickStrengths[smallestTick + level]; + if (strength <= Mathf.Epsilon) + continue; + + // Draw all ticks + int l = Mathf.Clamp(smallestTick + level, 0, tickSteps.Length - 1); + var lStep = tickSteps[l]; + var lNextStep = tickSteps[l + 1]; + int startTick = Mathf.FloorToInt(min / lStep); + int endTick = Mathf.CeilToInt(max / lStep); + for (int i = startTick; i <= endTick; i++) + { + if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0)) + continue; + var tick = i * lStep; + drawTick(tick, strength); + } + } + + return new Int2(smallestTick, biggestTick); + } + /// /// Determines whether the specified path string contains any invalid character. /// @@ -1187,6 +1244,71 @@ namespace FlaxEditor.Utilities return StringUtils.GetPathWithoutExtension(path); } + private static string InternalFormat(double value, string format, FlaxEngine.Utils.ValueCategory category) + { + switch (category) + { + case FlaxEngine.Utils.ValueCategory.Distance: + if (!Units.AutomaticUnitsFormatting) + return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m"); + var absValue = Mathf.Abs(value); + // in case a unit != cm this would be (value / Meters2Units * 100) + if (absValue < Units.Meters2Units) + return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("cm"); + if (absValue < Units.Meters2Units * 1000) + return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m"); + return (value / 1000 / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("km"); + case FlaxEngine.Utils.ValueCategory.Angle: return value.ToString(format, CultureInfo.InvariantCulture) + "°"; + case FlaxEngine.Utils.ValueCategory.Time: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("s"); + // some fonts have a symbol for that: "\u33A7" + case FlaxEngine.Utils.ValueCategory.Speed: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s"); + case FlaxEngine.Utils.ValueCategory.Acceleration: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s²"); + case FlaxEngine.Utils.ValueCategory.Area: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m²"); + case FlaxEngine.Utils.ValueCategory.Volume: return (value / Units.Meters2Units / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m³"); + case FlaxEngine.Utils.ValueCategory.Mass: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("kg"); + case FlaxEngine.Utils.ValueCategory.Force: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("N"); + case FlaxEngine.Utils.ValueCategory.Torque: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("Nm"); + case FlaxEngine.Utils.ValueCategory.None: + default: return FormatFloat(value); + } + } + + /// + /// Format a float value either as-is, with a distance unit or with a degree sign. + /// + /// The value to format. + /// The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign. + /// The formatted string. + public static string FormatFloat(float value, FlaxEngine.Utils.ValueCategory category) + { + if (float.IsPositiveInfinity(value) || value == float.MaxValue) + return "Infinity"; + if (float.IsNegativeInfinity(value) || value == float.MinValue) + return "-Infinity"; + if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None) + return FormatFloat(value); + const string format = "G7"; + return InternalFormat(value, format, category); + } + + /// + /// Format a double value either as-is, with a distance unit or with a degree sign + /// + /// The value to format. + /// The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign. + /// The formatted string. + public static string FormatFloat(double value, FlaxEngine.Utils.ValueCategory category) + { + if (double.IsPositiveInfinity(value) || value == double.MaxValue) + return "Infinity"; + if (double.IsNegativeInfinity(value) || value == double.MinValue) + return "-Infinity"; + if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None) + return FormatFloat(value); + const string format = "G15"; + return InternalFormat(value, format, category); + } + /// /// Formats the floating point value (double precision) into the readable text representation. /// @@ -1198,7 +1320,7 @@ namespace FlaxEditor.Utilities return "Infinity"; if (float.IsNegativeInfinity(value) || value == float.MinValue) return "-Infinity"; - string str = value.ToString("r", CultureInfo.InvariantCulture); + string str = value.ToString("R", CultureInfo.InvariantCulture); return FormatFloat(str, value < 0); } @@ -1213,7 +1335,7 @@ namespace FlaxEditor.Utilities return "Infinity"; if (double.IsNegativeInfinity(value) || value == double.MinValue) return "-Infinity"; - string str = value.ToString("r", CultureInfo.InvariantCulture); + string str = value.ToString("R", CultureInfo.InvariantCulture); return FormatFloat(str, value < 0); } diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp index d3cd2c940..797b4edea 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp +++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp @@ -187,6 +187,8 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor* actor, Mesh::DrawInfo& draw) { + if (!actor || !actor->IsActiveInHierarchy()) + return; auto& view = renderContext.View; const BoundingFrustum frustum = view.Frustum; Matrix m1, m2, world; @@ -208,8 +210,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor draw.DrawState = &drawState; draw.Deformation = nullptr; - // Support custom icons through types, but not onces that were added through actors, - // since they cant register while in prefab view anyway + // Support custom icons through types, but not ones that were added through actors, since they cant register while in prefab view anyway if (ActorTypeToTexture.TryGet(actor->GetTypeHandle(), texture)) { // Use custom texture diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs index 83fc49cd6..45770b007 100644 --- a/Source/Editor/Viewport/Cameras/FPSCamera.cs +++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs @@ -6,9 +6,7 @@ using Real = System.Double; using Real = System.Single; #endif -using System.Collections.Generic; using FlaxEditor.Gizmo; -using FlaxEditor.SceneGraph; using FlaxEngine; namespace FlaxEditor.Viewport.Cameras @@ -85,86 +83,8 @@ namespace FlaxEditor.Viewport.Cameras _moveStartTime = Time.UnscaledGameTime; } - /// - /// Moves the viewport to visualize the actor. - /// - /// The actor to preview. - public void ShowActor(Actor actor) - { - Editor.GetActorEditorSphere(actor, out BoundingSphere sphere); - ShowSphere(ref sphere); - } - - /// - /// Moves the viewport to visualize selected actors. - /// - /// The actors to show. - /// The used orientation. - public void ShowActor(Actor actor, ref Quaternion orientation) - { - Editor.GetActorEditorSphere(actor, out BoundingSphere sphere); - ShowSphere(ref sphere, ref orientation); - } - - /// - /// Moves the viewport to visualize selected actors. - /// - /// The actors to show. - public void ShowActors(List selection) - { - if (selection.Count == 0) - return; - - BoundingSphere mergesSphere = BoundingSphere.Empty; - for (int i = 0; i < selection.Count; i++) - { - selection[i].GetEditorSphere(out var sphere); - BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); - } - - if (mergesSphere == BoundingSphere.Empty) - return; - ShowSphere(ref mergesSphere); - } - - /// - /// Moves the viewport to visualize selected actors. - /// - /// The actors to show. - /// The used orientation. - public void ShowActors(List selection, ref Quaternion orientation) - { - if (selection.Count == 0) - return; - - BoundingSphere mergesSphere = BoundingSphere.Empty; - for (int i = 0; i < selection.Count; i++) - { - selection[i].GetEditorSphere(out var sphere); - BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); - } - - if (mergesSphere == BoundingSphere.Empty) - return; - ShowSphere(ref mergesSphere, ref orientation); - } - - /// - /// Moves the camera to visualize given world area defined by the sphere. - /// - /// The sphere. - public void ShowSphere(ref BoundingSphere sphere) - { - var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f); - ShowSphere(ref sphere, ref q); - } - - /// - /// Moves the camera to visualize given world area defined by the sphere. - /// - /// The sphere. - /// The camera orientation. - public void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation) + /// + public override void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation) { Vector3 position; if (Viewport.UseOrthographicProjection) diff --git a/Source/Editor/Viewport/Cameras/ViewportCamera.cs b/Source/Editor/Viewport/Cameras/ViewportCamera.cs index 5e25a29bb..f8019a481 100644 --- a/Source/Editor/Viewport/Cameras/ViewportCamera.cs +++ b/Source/Editor/Viewport/Cameras/ViewportCamera.cs @@ -6,6 +6,9 @@ using Real = System.Double; using Real = System.Single; #endif +using System.Collections.Generic; +using FlaxEditor.Gizmo; +using FlaxEditor.SceneGraph; using FlaxEngine; namespace FlaxEditor.Viewport.Cameras @@ -33,6 +36,90 @@ namespace FlaxEditor.Viewport.Cameras /// public virtual bool UseMovementSpeed => true; + /// + /// Focuses the viewport on the current selection of the gizmo. + /// + /// The gizmo collection (from viewport). + /// The target view orientation. + public virtual void FocusSelection(GizmosCollection gizmos, ref Quaternion orientation) + { + var transformGizmo = gizmos.Get(); + if (transformGizmo == null || transformGizmo.SelectedParents.Count == 0) + return; + if (gizmos.Active != null) + { + var gizmoBounds = gizmos.Active.FocusBounds; + if (gizmoBounds != BoundingSphere.Empty) + { + ShowSphere(ref gizmoBounds, ref orientation); + return; + } + } + ShowActors(transformGizmo.SelectedParents, ref orientation); + } + + /// + /// Moves the viewport to visualize the actor. + /// + /// The actor to preview. + public virtual void ShowActor(Actor actor) + { + Editor.GetActorEditorSphere(actor, out BoundingSphere sphere); + ShowSphere(ref sphere); + } + + /// + /// Moves the viewport to visualize selected actors. + /// + /// The actors to show. + public void ShowActors(List selection) + { + var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f); + ShowActors(selection, ref q); + } + + /// + /// Moves the viewport to visualize selected actors. + /// + /// The actors to show. + /// The used orientation. + public virtual void ShowActors(List selection, ref Quaternion orientation) + { + if (selection.Count == 0) + return; + + BoundingSphere mergesSphere = BoundingSphere.Empty; + for (int i = 0; i < selection.Count; i++) + { + selection[i].GetEditorSphere(out var sphere); + BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); + } + + if (mergesSphere == BoundingSphere.Empty) + return; + ShowSphere(ref mergesSphere, ref orientation); + } + + /// + /// Moves the camera to visualize given world area defined by the sphere. + /// + /// The sphere. + public void ShowSphere(ref BoundingSphere sphere) + { + var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f); + ShowSphere(ref sphere, ref q); + } + + /// + /// Moves the camera to visualize given world area defined by the sphere. + /// + /// The sphere. + /// The camera orientation. + public virtual void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation) + { + SetArcBallView(orientation, sphere.Center, sphere.Radius); + } + /// /// Sets view orientation and position to match the arc ball camera style view for the given target object bounds. /// diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index 8f038ce17..856ae20da 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -1,9 +1,12 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +using System; using System.Collections.Generic; using FlaxEditor.Gizmo; +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEditor.Viewport.Cameras; +using FlaxEditor.Viewport.Widgets; using FlaxEngine; using FlaxEngine.GUI; @@ -41,6 +44,7 @@ namespace FlaxEditor.Viewport Gizmos[i].Update(deltaTime); } } + /// public EditorViewport Viewport => this; @@ -75,10 +79,10 @@ namespace FlaxEditor.Viewport public Float2 MouseDelta => _mouseDelta * 1000; /// - public bool UseSnapping => Root.GetKey(KeyboardKeys.Control); + public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false; /// - public bool UseDuplicate => Root.GetKey(KeyboardKeys.Shift); + public bool UseDuplicate => Root?.GetKey(KeyboardKeys.Shift) ?? false; /// public Undo Undo { get; } @@ -92,6 +96,9 @@ namespace FlaxEditor.Viewport /// public abstract void Spawn(Actor actor); + /// + public abstract void OpenContextMenu(); + /// protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; @@ -121,5 +128,284 @@ namespace FlaxEditor.Viewport base.OnDestroy(); } + + internal static void AddGizmoViewportWidgets(EditorViewport viewport, TransformGizmo transformGizmo, bool useProjectCache = false) + { + var editor = Editor.Instance; + var inputOptions = editor.Options.Options.Input; + + if (useProjectCache) + { + // Initialize snapping enabled from cached values + if (editor.ProjectCache.TryGetCustomData("TranslateSnapState", out var cachedState)) + transformGizmo.TranslationSnapEnable = bool.Parse(cachedState); + if (editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedState)) + transformGizmo.RotationSnapEnabled = bool.Parse(cachedState); + if (editor.ProjectCache.TryGetCustomData("ScaleSnapState", out cachedState)) + transformGizmo.ScaleSnapEnabled = bool.Parse(cachedState); + if (editor.ProjectCache.TryGetCustomData("TranslateSnapValue", out cachedState)) + transformGizmo.TranslationSnapValue = float.Parse(cachedState); + if (editor.ProjectCache.TryGetCustomData("RotationSnapValue", out cachedState)) + transformGizmo.RotationSnapValue = float.Parse(cachedState); + if (editor.ProjectCache.TryGetCustomData("ScaleSnapValue", out cachedState)) + transformGizmo.ScaleSnapValue = float.Parse(cachedState); + if (editor.ProjectCache.TryGetCustomData("TransformSpaceState", out cachedState) && Enum.TryParse(cachedState, out TransformGizmoBase.TransformSpace space)) + transformGizmo.ActiveTransformSpace = space; + } + + // Transform space widget + var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true) + { + Checked = transformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, + TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", + Parent = transformSpaceWidget + }; + transformSpaceToggle.Toggled += _ => + { + transformGizmo.ToggleTransformSpace(); + if (useProjectCache) + editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString()); + }; + transformSpaceWidget.Parent = viewport; + + // Scale snapping widget + var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true) + { + Checked = transformGizmo.ScaleSnapEnabled, + TooltipText = "Enable scale snapping", + Parent = scaleSnappingWidget + }; + enableScaleSnapping.Toggled += _ => + { + transformGizmo.ScaleSnapEnabled = !transformGizmo.ScaleSnapEnabled; + if (useProjectCache) + editor.ProjectCache.SetCustomData("ScaleSnapState", transformGizmo.ScaleSnapEnabled.ToString()); + }; + var scaleSnappingCM = new ContextMenu(); + var scaleSnapping = new ViewportWidgetButton(transformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM) + { + TooltipText = "Scale snapping values" + }; + for (int i = 0; i < ScaleSnapValues.Length; i++) + { + var v = ScaleSnapValues[i]; + var button = scaleSnappingCM.AddButton(v.ToString()); + button.Tag = v; + } + scaleSnappingCM.ButtonClicked += button => + { + var v = (float)button.Tag; + transformGizmo.ScaleSnapValue = v; + scaleSnapping.Text = v.ToString(); + if (useProjectCache) + editor.ProjectCache.SetCustomData("ScaleSnapValue", transformGizmo.ScaleSnapValue.ToString("N")); + }; + scaleSnappingCM.VisibleChanged += control => + { + if (control.Visible == false) + return; + var ccm = (ContextMenu)control; + foreach (var e in ccm.Items) + { + if (e is ContextMenuButton b) + { + var v = (float)b.Tag; + b.Icon = Mathf.Abs(transformGizmo.ScaleSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; + } + } + }; + scaleSnapping.Parent = scaleSnappingWidget; + scaleSnappingWidget.Parent = viewport; + + // Rotation snapping widget + var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true) + { + Checked = transformGizmo.RotationSnapEnabled, + TooltipText = "Enable rotation snapping", + Parent = rotateSnappingWidget + }; + enableRotateSnapping.Toggled += _ => + { + transformGizmo.RotationSnapEnabled = !transformGizmo.RotationSnapEnabled; + if (useProjectCache) + editor.ProjectCache.SetCustomData("RotationSnapState", transformGizmo.RotationSnapEnabled.ToString()); + }; + var rotateSnappingCM = new ContextMenu(); + var rotateSnapping = new ViewportWidgetButton(transformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM) + { + TooltipText = "Rotation snapping values" + }; + for (int i = 0; i < RotateSnapValues.Length; i++) + { + var v = RotateSnapValues[i]; + var button = rotateSnappingCM.AddButton(v.ToString()); + button.Tag = v; + } + rotateSnappingCM.ButtonClicked += button => + { + var v = (float)button.Tag; + transformGizmo.RotationSnapValue = v; + rotateSnapping.Text = v.ToString(); + if (useProjectCache) + editor.ProjectCache.SetCustomData("RotationSnapValue", transformGizmo.RotationSnapValue.ToString("N")); + }; + rotateSnappingCM.VisibleChanged += control => + { + if (control.Visible == false) + return; + var ccm = (ContextMenu)control; + foreach (var e in ccm.Items) + { + if (e is ContextMenuButton b) + { + var v = (float)b.Tag; + b.Icon = Mathf.Abs(transformGizmo.RotationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; + } + } + }; + rotateSnapping.Parent = rotateSnappingWidget; + rotateSnappingWidget.Parent = viewport; + + // Translation snapping widget + var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true) + { + Checked = transformGizmo.TranslationSnapEnable, + TooltipText = "Enable position snapping", + Parent = translateSnappingWidget + }; + enableTranslateSnapping.Toggled += _ => + { + transformGizmo.TranslationSnapEnable = !transformGizmo.TranslationSnapEnable; + if (useProjectCache) + editor.ProjectCache.SetCustomData("TranslateSnapState", transformGizmo.TranslationSnapEnable.ToString()); + }; + var translateSnappingCM = new ContextMenu(); + var translateSnapping = new ViewportWidgetButton(transformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM) + { + TooltipText = "Position snapping values" + }; + if (transformGizmo.TranslationSnapValue < 0.0f) + translateSnapping.Text = "Bounding Box"; + for (int i = 0; i < TranslateSnapValues.Length; i++) + { + var v = TranslateSnapValues[i]; + var button = translateSnappingCM.AddButton(v.ToString()); + button.Tag = v; + } + var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume"); + buttonBB.Tag = -1.0f; + translateSnappingCM.ButtonClicked += button => + { + var v = (float)button.Tag; + transformGizmo.TranslationSnapValue = v; + if (v < 0.0f) + translateSnapping.Text = "Bounding Box"; + else + translateSnapping.Text = v.ToString(); + if (useProjectCache) + editor.ProjectCache.SetCustomData("TranslateSnapValue", transformGizmo.TranslationSnapValue.ToString("N")); + }; + translateSnappingCM.VisibleChanged += control => + { + if (control.Visible == false) + return; + var ccm = (ContextMenu)control; + foreach (var e in ccm.Items) + { + if (e is ContextMenuButton b) + { + var v = (float)b.Tag; + b.Icon = Mathf.Abs(transformGizmo.TranslationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; + } + } + }; + translateSnapping.Parent = translateSnappingWidget; + translateSnappingWidget.Parent = viewport; + + // Gizmo mode widget + var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + var gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true) + { + Tag = TransformGizmoBase.Mode.Translate, + TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})", + Checked = true, + Parent = gizmoMode + }; + gizmoModeTranslate.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate; + var gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true) + { + Tag = TransformGizmoBase.Mode.Rotate, + TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})", + Parent = gizmoMode + }; + gizmoModeRotate.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate; + var gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true) + { + Tag = TransformGizmoBase.Mode.Scale, + TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})", + Parent = gizmoMode + }; + gizmoModeScale.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale; + gizmoMode.Parent = viewport; + transformGizmo.ModeChanged += () => + { + var mode = transformGizmo.ActiveMode; + gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate; + gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate; + gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale; + }; + + // Setup input actions + viewport.InputActions.Add(options => options.TranslateMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); + viewport.InputActions.Add(options => options.RotateMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); + viewport.InputActions.Add(options => options.ScaleMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); + viewport.InputActions.Add(options => options.ToggleTransformSpace, () => + { + transformGizmo.ToggleTransformSpace(); + if (useProjectCache) + editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString()); + transformSpaceToggle.Checked = !transformSpaceToggle.Checked; + }); + } + + internal static readonly float[] TranslateSnapValues = + { + 0.1f, + 0.5f, + 1.0f, + 5.0f, + 10.0f, + 100.0f, + 1000.0f, + }; + + internal static readonly float[] RotateSnapValues = + { + 1.0f, + 5.0f, + 10.0f, + 15.0f, + 30.0f, + 45.0f, + 60.0f, + 90.0f, + }; + + internal static readonly float[] ScaleSnapValues = + { + 0.05f, + 0.1f, + 0.25f, + 0.5f, + 1.0f, + 2.0f, + 4.0f, + 6.0f, + 8.0f, + }; } } diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 785f0e651..2eeb1e8c0 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -10,7 +10,6 @@ using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Widgets; using FlaxEngine; using FlaxEngine.GUI; -using Newtonsoft.Json; using JsonSerializer = FlaxEngine.Json.JsonSerializer; namespace FlaxEditor.Viewport @@ -154,6 +153,7 @@ namespace FlaxEditor.Viewport // Input + internal bool _disableInputUpdate; private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown; private int _deltaFilteringStep; private Float2 _startPos; @@ -704,9 +704,9 @@ namespace FlaxEditor.Viewport // Camera Viewpoints { var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu; - for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++) + for (int i = 0; i < CameraViewpointValues.Length; i++) { - var co = EditorViewportCameraViewpointValues[i]; + var co = CameraViewpointValues[i]; var button = cameraView.AddButton(co.Name); button.Tag = co.Orientation; } @@ -899,9 +899,9 @@ namespace FlaxEditor.Viewport viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32; viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None).Icon = Editor.Instance.Icons.Rotate32; viewFlags.AddSeparator(); - for (int i = 0; i < EditorViewportViewFlagsValues.Length; i++) + for (int i = 0; i < ViewFlagsValues.Length; i++) { - var v = EditorViewportViewFlagsValues[i]; + var v = ViewFlagsValues[i]; var button = viewFlags.AddButton(v.Name); button.CloseMenuOnClick = false; button.Tag = v.Mode; @@ -933,9 +933,9 @@ namespace FlaxEditor.Viewport } }); debugView.AddSeparator(); - for (int i = 0; i < EditorViewportViewModeValues.Length; i++) + for (int i = 0; i < ViewModeValues.Length; i++) { - ref var v = ref EditorViewportViewModeValues[i]; + ref var v = ref ViewModeValues[i]; if (v.Options != null) { var childMenu = debugView.AddChildMenu(v.Name).ContextMenu; @@ -989,12 +989,12 @@ namespace FlaxEditor.Viewport #endregion View mode widget } - InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Top").Orientation))); - InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation))); - InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Front").Orientation))); - InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Back").Orientation))); - InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); - InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); + InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Top").Orientation))); + InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation))); + InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Front").Orientation))); + InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Back").Orientation))); + InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); + InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown); InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1)); InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1)); @@ -1496,6 +1496,9 @@ namespace FlaxEditor.Viewport { base.Update(deltaTime); + if (_disableInputUpdate) + return; + // Update camera bool useMovementSpeed = false; if (_camera != null) @@ -1534,7 +1537,7 @@ namespace FlaxEditor.Viewport } bool useMouse = IsControllingMouse || (Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height)); _prevInput = _input; - var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl)); + var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot)); if (canUseInput && ContainsFocus && hit == null) _input.Gather(win.Window, useMouse); else @@ -1867,7 +1870,7 @@ namespace FlaxEditor.Viewport } } - private readonly CameraViewpoint[] EditorViewportCameraViewpointValues = + private readonly CameraViewpoint[] CameraViewpointValues = { new CameraViewpoint("Front", new Float3(0, 180, 0)), new CameraViewpoint("Back", new Float3(0, 0, 0)), @@ -1898,7 +1901,7 @@ namespace FlaxEditor.Viewport } } - private static readonly ViewModeOptions[] EditorViewportViewModeValues = + private static readonly ViewModeOptions[] ViewModeValues = { new ViewModeOptions(ViewMode.Default, "Default"), new ViewModeOptions(ViewMode.Unlit, "Unlit"), @@ -1970,7 +1973,7 @@ namespace FlaxEditor.Viewport } } - private static readonly ViewFlagOptions[] EditorViewportViewFlagsValues = + private static readonly ViewFlagOptions[] ViewFlagsValues = { new ViewFlagOptions(ViewFlags.AntiAliasing, "Anti Aliasing"), new ViewFlagOptions(ViewFlags.Shadows, "Shadows"), @@ -2005,16 +2008,13 @@ namespace FlaxEditor.Viewport { if (cm.Visible == false) return; - var ccm = (ContextMenu)cm; foreach (var e in ccm.Items) { if (e is ContextMenuButton b && b.Tag != null) { var v = (ViewFlags)b.Tag; - b.Icon = (Task.View.Flags & v) != 0 - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; + b.Icon = (Task.View.Flags & v) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; } } } @@ -2023,7 +2023,6 @@ namespace FlaxEditor.Viewport { if (cm.Visible == false) return; - var ccm = (ContextMenu)cm; var layersMask = Task.ViewLayersMask; foreach (var e in ccm.Items) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 541bbcfba..7d08213fa 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -2,15 +2,12 @@ using System; using System.Collections.Generic; -using System.Linq; using FlaxEditor.Content; using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; -using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Modes; -using FlaxEditor.Viewport.Widgets; using FlaxEditor.Windows; using FlaxEngine; using FlaxEngine.GUI; @@ -28,13 +25,6 @@ namespace FlaxEditor.Viewport private readonly ContextMenuButton _showGridButton; private readonly ContextMenuButton _showNavigationButton; - private readonly ViewportWidgetButton _gizmoModeTranslate; - private readonly ViewportWidgetButton _gizmoModeRotate; - private readonly ViewportWidgetButton _gizmoModeScale; - - private readonly ViewportWidgetButton _translateSnapping; - private readonly ViewportWidgetButton _rotateSnapping; - private readonly ViewportWidgetButton _scaleSnapping; private SelectionOutline _customSelectionOutline; @@ -196,7 +186,6 @@ namespace FlaxEditor.Viewport { _editor = editor; DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem); - var inputOptions = editor.Options.Options.Input; // Prepare rendering task Task.ActorsSource = ActorsSources.Scenes; @@ -222,8 +211,7 @@ namespace FlaxEditor.Viewport // Add transformation gizmo TransformGizmo = new TransformGizmo(this); TransformGizmo.ApplyTransformation += ApplyTransform; - TransformGizmo.ModeChanged += OnGizmoModeChanged; - TransformGizmo.Duplicate += Editor.Instance.SceneEditing.Duplicate; + TransformGizmo.Duplicate += _editor.SceneEditing.Duplicate; Gizmos.Active = TransformGizmo; // Add grid @@ -232,144 +220,8 @@ namespace FlaxEditor.Viewport editor.SceneEditing.SelectionChanged += OnSelectionChanged; - // Initialize snapping enabled from cached values - if (_editor.ProjectCache.TryGetCustomData("TranslateSnapState", out var cachedState)) - TransformGizmo.TranslationSnapEnable = bool.Parse(cachedState); - if (_editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedState)) - TransformGizmo.RotationSnapEnabled = bool.Parse(cachedState); - if (_editor.ProjectCache.TryGetCustomData("ScaleSnapState", out cachedState)) - TransformGizmo.ScaleSnapEnabled = bool.Parse(cachedState); - if (_editor.ProjectCache.TryGetCustomData("TranslateSnapValue", out cachedState)) - TransformGizmo.TranslationSnapValue = float.Parse(cachedState); - if (_editor.ProjectCache.TryGetCustomData("RotationSnapValue", out cachedState)) - TransformGizmo.RotationSnapValue = float.Parse(cachedState); - if (_editor.ProjectCache.TryGetCustomData("ScaleSnapValue", out cachedState)) - TransformGizmo.ScaleSnapValue = float.Parse(cachedState); - if (_editor.ProjectCache.TryGetCustomData("TransformSpaceState", out cachedState) && Enum.TryParse(cachedState, out TransformGizmoBase.TransformSpace space)) - TransformGizmo.ActiveTransformSpace = space; - - // Transform space widget - var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true) - { - Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, - TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", - Parent = transformSpaceWidget - }; - transformSpaceToggle.Toggled += OnTransformSpaceToggle; - transformSpaceWidget.Parent = this; - - // Scale snapping widget - var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true) - { - Checked = TransformGizmo.ScaleSnapEnabled, - TooltipText = "Enable scale snapping", - Parent = scaleSnappingWidget - }; - enableScaleSnapping.Toggled += OnScaleSnappingToggle; - - var scaleSnappingCM = new ContextMenu(); - _scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM) - { - TooltipText = "Scale snapping values" - }; - - for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++) - { - var v = EditorViewportScaleSnapValues[i]; - var button = scaleSnappingCM.AddButton(v.ToString()); - button.Tag = v; - } - scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick; - scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide; - _scaleSnapping.Parent = scaleSnappingWidget; - scaleSnappingWidget.Parent = this; - - // Rotation snapping widget - var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true) - { - Checked = TransformGizmo.RotationSnapEnabled, - TooltipText = "Enable rotation snapping", - Parent = rotateSnappingWidget - }; - enableRotateSnapping.Toggled += OnRotateSnappingToggle; - - var rotateSnappingCM = new ContextMenu(); - _rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM) - { - TooltipText = "Rotation snapping values" - }; - - for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++) - { - var v = EditorViewportRotateSnapValues[i]; - var button = rotateSnappingCM.AddButton(v.ToString()); - button.Tag = v; - } - rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick; - rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide; - _rotateSnapping.Parent = rotateSnappingWidget; - rotateSnappingWidget.Parent = this; - - // Translation snapping widget - var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true) - { - Checked = TransformGizmo.TranslationSnapEnable, - TooltipText = "Enable position snapping", - Parent = translateSnappingWidget - }; - enableTranslateSnapping.Toggled += OnTranslateSnappingToggle; - - var translateSnappingCM = new ContextMenu(); - _translateSnapping = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM) - { - TooltipText = "Position snapping values" - }; - if (TransformGizmo.TranslationSnapValue < 0.0f) - _translateSnapping.Text = "Bounding Box"; - - for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++) - { - var v = EditorViewportTranslateSnapValues[i]; - var button = translateSnappingCM.AddButton(v.ToString()); - button.Tag = v; - } - var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume"); - buttonBB.Tag = -1.0f; - - translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick; - translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide; - _translateSnapping.Parent = translateSnappingWidget; - translateSnappingWidget.Parent = this; - - // Gizmo mode widget - var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true) - { - Tag = TransformGizmoBase.Mode.Translate, - TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})", - Checked = true, - Parent = gizmoMode - }; - _gizmoModeTranslate.Toggled += OnGizmoModeToggle; - _gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true) - { - Tag = TransformGizmoBase.Mode.Rotate, - TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})", - Parent = gizmoMode - }; - _gizmoModeRotate.Toggled += OnGizmoModeToggle; - _gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true) - { - Tag = TransformGizmoBase.Mode.Scale, - TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})", - Parent = gizmoMode - }; - _gizmoModeScale.Toggled += OnGizmoModeToggle; - gizmoMode.Parent = this; + // Gizmo widgets + AddGizmoViewportWidgets(this, TransformGizmo); // Show grid widget _showGridButton = ViewWidgetShowMenu.AddButton("Grid", () => Grid.Enabled = !Grid.Enabled); @@ -400,14 +252,6 @@ namespace FlaxEditor.Viewport } // Setup input actions - InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); - InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); - InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); - InputActions.Add(options => options.ToggleTransformSpace, () => - { - OnTransformSpaceToggle(transformSpaceToggle); - transformSpaceToggle.Checked = !transformSpaceToggle.Checked; - }); InputActions.Add(options => options.LockFocusSelection, LockFocusSelection); InputActions.Add(options => options.FocusSelection, FocusSelection); InputActions.Add(options => options.RotateSelection, RotateSelection); @@ -486,7 +330,7 @@ namespace FlaxEditor.Viewport }; // Spawn - Editor.Instance.SceneEditing.Spawn(actor, parent); + _editor.SceneEditing.Spawn(actor, parent); } private void OnBegin(RenderTask task, GPUContext context) @@ -552,7 +396,7 @@ namespace FlaxEditor.Viewport var task = renderContext.Task; // Render editor primitives, gizmo and debug shapes in debug view modes - // Note: can use Output buffer as both input and output because EditorPrimitives is using a intermediate buffers + // Note: can use Output buffer as both input and output because EditorPrimitives is using an intermediate buffer if (EditorPrimitives && EditorPrimitives.CanRender()) { EditorPrimitives.Render(context, ref renderContext, task.Output, task.Output); @@ -581,161 +425,6 @@ namespace FlaxEditor.Viewport } } - private void OnGizmoModeToggle(ViewportWidgetButton button) - { - TransformGizmo.ActiveMode = (TransformGizmoBase.Mode)(int)button.Tag; - } - - private void OnTranslateSnappingToggle(ViewportWidgetButton button) - { - TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable; - _editor.ProjectCache.SetCustomData("TranslateSnapState", TransformGizmo.TranslationSnapEnable.ToString()); - } - - private void OnRotateSnappingToggle(ViewportWidgetButton button) - { - TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled; - _editor.ProjectCache.SetCustomData("RotationSnapState", TransformGizmo.RotationSnapEnabled.ToString()); - } - - private void OnScaleSnappingToggle(ViewportWidgetButton button) - { - TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled; - _editor.ProjectCache.SetCustomData("ScaleSnapState", TransformGizmo.ScaleSnapEnabled.ToString()); - } - - private void OnTransformSpaceToggle(ViewportWidgetButton button) - { - TransformGizmo.ToggleTransformSpace(); - _editor.ProjectCache.SetCustomData("TransformSpaceState", TransformGizmo.ActiveTransformSpace.ToString()); - } - - private void OnGizmoModeChanged() - { - // Update all viewport widgets status - var mode = TransformGizmo.ActiveMode; - _gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate; - _gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate; - _gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale; - } - - private static readonly float[] EditorViewportScaleSnapValues = - { - 0.05f, - 0.1f, - 0.25f, - 0.5f, - 1.0f, - 2.0f, - 4.0f, - 6.0f, - 8.0f, - }; - - private void OnWidgetScaleSnapClick(ContextMenuButton button) - { - var v = (float)button.Tag; - TransformGizmo.ScaleSnapValue = v; - _scaleSnapping.Text = v.ToString(); - _editor.ProjectCache.SetCustomData("ScaleSnapValue", TransformGizmo.ScaleSnapValue.ToString("N")); - } - - private void OnWidgetScaleSnapShowHide(Control control) - { - if (control.Visible == false) - return; - - var ccm = (ContextMenu)control; - foreach (var e in ccm.Items) - { - if (e is ContextMenuButton b) - { - var v = (float)b.Tag; - b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; - } - } - } - - private static readonly float[] EditorViewportRotateSnapValues = - { - 1.0f, - 5.0f, - 10.0f, - 15.0f, - 30.0f, - 45.0f, - 60.0f, - 90.0f, - }; - - private void OnWidgetRotateSnapClick(ContextMenuButton button) - { - var v = (float)button.Tag; - TransformGizmo.RotationSnapValue = v; - _rotateSnapping.Text = v.ToString(); - _editor.ProjectCache.SetCustomData("RotationSnapValue", TransformGizmo.RotationSnapValue.ToString("N")); - } - - private void OnWidgetRotateSnapShowHide(Control control) - { - if (control.Visible == false) - return; - - var ccm = (ContextMenu)control; - foreach (var e in ccm.Items) - { - if (e is ContextMenuButton b) - { - var v = (float)b.Tag; - b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; - } - } - } - - private static readonly float[] EditorViewportTranslateSnapValues = - { - 0.1f, - 0.5f, - 1.0f, - 5.0f, - 10.0f, - 100.0f, - 1000.0f, - }; - - private void OnWidgetTranslateSnapClick(ContextMenuButton button) - { - var v = (float)button.Tag; - TransformGizmo.TranslationSnapValue = v; - if (v < 0.0f) - _translateSnapping.Text = "Bounding Box"; - else - _translateSnapping.Text = v.ToString(); - _editor.ProjectCache.SetCustomData("TranslateSnapValue", TransformGizmo.TranslationSnapValue.ToString("N")); - } - - private void OnWidgetTranslateSnapShowHide(Control control) - { - if (control.Visible == false) - return; - - var ccm = (ContextMenu)control; - foreach (var e in ccm.Items) - { - if (e is ContextMenuButton b) - { - var v = (float)b.Tag; - b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; - } - } - } - private void OnSelectionChanged() { var selection = _editor.SceneEditing.Selection; @@ -761,7 +450,7 @@ namespace FlaxEditor.Viewport Vector3 gizmoPosition = TransformGizmo.Position; // Rotate selected objects - bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; + bool isPlayMode = _editor.StateMachine.IsPlayMode; TransformGizmo.StartTransforming(); for (int i = 0; i < selection.Count; i++) { @@ -819,14 +508,7 @@ namespace FlaxEditor.Viewport /// The target view orientation. public void FocusSelection(ref Quaternion orientation) { - if (TransformGizmo.SelectedParents.Count == 0) - return; - - var gizmoBounds = Gizmos.Active.FocusBounds; - if (gizmoBounds != BoundingSphere.Empty) - ((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation); - else - ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation); + ViewportCamera.FocusSelection(Gizmos, ref orientation); } /// @@ -843,7 +525,7 @@ namespace FlaxEditor.Viewport Vector3 gizmoPosition = TransformGizmo.Position; // Transform selected objects - bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; + bool isPlayMode = _editor.StateMachine.IsPlayMode; for (int i = 0; i < selection.Count; i++) { var obj = selection[i]; @@ -985,7 +667,14 @@ namespace FlaxEditor.Viewport { var parent = actor.Parent ?? Level.GetScene(0); actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null); - Editor.Instance.SceneEditing.Spawn(actor); + _editor.SceneEditing.Spawn(actor); + } + + /// + public override void OpenContextMenu() + { + var mouse = PointFromWindow(Root.MousePosition); + _editor.Windows.SceneWin.ShowContextMenu(this, mouse); } /// diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index b9ee1c730..081c1d4f1 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -2,14 +2,13 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.Content; using FlaxEditor.Gizmo; -using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Previews; -using FlaxEditor.Viewport.Widgets; using FlaxEditor.Windows.Assets; using FlaxEngine; using FlaxEngine.GUI; @@ -29,7 +28,6 @@ namespace FlaxEditor.Viewport { public PrefabWindowViewport Viewport; - /// public override bool CanRender() { return (Task.View.Flags & ViewFlags.EditorSprites) == ViewFlags.EditorSprites && Enabled; @@ -41,19 +39,34 @@ namespace FlaxEditor.Viewport } } + [HideInEditor] + private sealed class PrefabUIEditorRoot : UIEditorRoot + { + private readonly PrefabWindowViewport _viewport; + private bool UI => _viewport._hasUILinkedCached; + + public PrefabUIEditorRoot(PrefabWindowViewport viewport) + : base(true) + { + _viewport = viewport; + Parent = viewport; + } + + public override bool EnableInputs => !UI; + public override bool EnableSelecting => UI; + public override bool EnableBackground => UI; + public override TransformGizmo TransformGizmo => _viewport.TransformGizmo; + } + private readonly PrefabWindow _window; private UpdateDelegate _update; - private readonly ViewportWidgetButton _gizmoModeTranslate; - private readonly ViewportWidgetButton _gizmoModeRotate; - private readonly ViewportWidgetButton _gizmoModeScale; - - private ViewportWidgetButton _translateSnappng; - private ViewportWidgetButton _rotateSnapping; - private ViewportWidgetButton _scaleSnapping; - private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32); private PrefabSpritesRenderer _spritesRenderer; + private IntPtr _tempDebugDrawContext; + + private bool _hasUILinkedCached; + private PrefabUIEditorRoot _uiRoot; /// /// Drag and drop handlers @@ -106,144 +119,74 @@ namespace FlaxEditor.Viewport // Add transformation gizmo TransformGizmo = new TransformGizmo(this); TransformGizmo.ApplyTransformation += ApplyTransform; - TransformGizmo.ModeChanged += OnGizmoModeChanged; TransformGizmo.Duplicate += _window.Duplicate; Gizmos.Active = TransformGizmo; - // Transform space widget - var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Globe32, null, true) - { - Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, - TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", - Parent = transformSpaceWidget - }; - transformSpaceToggle.Toggled += OnTransformSpaceToggle; - transformSpaceWidget.Parent = this; + // Use custom root for UI controls + _uiRoot = new PrefabUIEditorRoot(this); + _uiRoot.IndexInParent = 0; // Move viewport down below other widgets in the viewport + _uiParentLink = _uiRoot.UIRoot; - // Scale snapping widget - var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableScaleSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.ScaleSnap32, null, true) - { - Checked = TransformGizmo.ScaleSnapEnabled, - TooltipText = "Enable scale snapping", - Parent = scaleSnappingWidget - }; - enableScaleSnapping.Toggled += OnScaleSnappingToggle; - - var scaleSnappingCM = new ContextMenu(); - _scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM) - { - TooltipText = "Scale snapping values" - }; - - for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++) - { - var v = EditorViewportScaleSnapValues[i]; - var button = scaleSnappingCM.AddButton(v.ToString()); - button.Tag = v; - } - scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick; - scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide; - _scaleSnapping.Parent = scaleSnappingWidget; - scaleSnappingWidget.Parent = this; - - // Rotation snapping widget - var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableRotateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.RotateSnap32, null, true) - { - Checked = TransformGizmo.RotationSnapEnabled, - TooltipText = "Enable rotation snapping", - Parent = rotateSnappingWidget - }; - enableRotateSnapping.Toggled += OnRotateSnappingToggle; - - var rotateSnappingCM = new ContextMenu(); - _rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM) - { - TooltipText = "Rotation snapping values" - }; - - for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++) - { - var v = EditorViewportRotateSnapValues[i]; - var button = rotateSnappingCM.AddButton(v.ToString()); - button.Tag = v; - } - rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick; - rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide; - _rotateSnapping.Parent = rotateSnappingWidget; - rotateSnappingWidget.Parent = this; - - // Translation snapping widget - var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Grid32, null, true) - { - Checked = TransformGizmo.TranslationSnapEnable, - TooltipText = "Enable position snapping", - Parent = translateSnappingWidget - }; - enableTranslateSnapping.Toggled += OnTranslateSnappingToggle; - - var translateSnappingCM = new ContextMenu(); - _translateSnappng = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM) - { - TooltipText = "Position snapping values" - }; - - for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++) - { - var v = EditorViewportTranslateSnapValues[i]; - var button = translateSnappingCM.AddButton(v.ToString()); - button.Tag = v; - } - translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick; - translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide; - _translateSnappng.Parent = translateSnappingWidget; - translateSnappingWidget.Parent = this; - - // Gizmo mode widget - var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate32, null, true) - { - Tag = TransformGizmoBase.Mode.Translate, - TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})", - Checked = true, - Parent = gizmoMode - }; - _gizmoModeTranslate.Toggled += OnGizmoModeToggle; - _gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate32, null, true) - { - Tag = TransformGizmoBase.Mode.Rotate, - TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})", - Parent = gizmoMode - }; - _gizmoModeRotate.Toggled += OnGizmoModeToggle; - _gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale32, null, true) - { - Tag = TransformGizmoBase.Mode.Scale, - TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})", - Parent = gizmoMode - }; - _gizmoModeScale.Toggled += OnGizmoModeToggle; - gizmoMode.Parent = this; + EditorGizmoViewport.AddGizmoViewportWidgets(this, TransformGizmo); // Setup input actions - InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); - InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); - InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); - InputActions.Add(options => options.ToggleTransformSpace, () => - { - OnTransformSpaceToggle(transformSpaceToggle); - transformSpaceToggle.Checked = !transformSpaceToggle.Checked; - }); InputActions.Add(options => options.FocusSelection, ShowSelectedActors); SetUpdate(ref _update, OnUpdate); } + /// + /// Updates the viewport's gizmos, especially to toggle between 3D and UI editing modes. + /// + internal void UpdateGizmoMode() + { + // Skip if gizmo mode was unmodified + if (_hasUILinked == _hasUILinkedCached) + return; + _hasUILinkedCached = _hasUILinked; + + if (_hasUILinked) + { + // UI widget + Gizmos.Active = null; + ViewportCamera = new UIEditorCamera { UIEditor = _uiRoot }; + + // Hide 3D visuals + ShowEditorPrimitives = false; + ShowDefaultSceneActors = false; + ShowDebugDraw = false; + + // Show whole UI on startup + var canvas = (CanvasRootControl)_uiParentLink.Children.FirstOrDefault(x => x is CanvasRootControl); + if (canvas != null) + ViewportCamera.ShowActor(canvas.Canvas); + else if (Instance is UIControl) + ViewportCamera.ShowActor(Instance); + } + else + { + // Generic prefab + Gizmos.Active = TransformGizmo; + ViewportCamera = new FPSCamera(); + } + + // Update default components usage + bool defaultFeatures = !_hasUILinked; + _disableInputUpdate = _hasUILinked; + _spritesRenderer.Enabled = defaultFeatures; + SelectionOutline.Enabled = defaultFeatures; + _showDefaultSceneButton.Visible = defaultFeatures; + _cameraWidget.Visible = defaultFeatures; + _cameraButton.Visible = defaultFeatures; + _orthographicModeButton.Visible = defaultFeatures; + Task.Enabled = defaultFeatures; + UseAutomaticTaskManagement = defaultFeatures; + TintColor = defaultFeatures ? Color.White : Color.Transparent; + } + private void OnUpdate(float deltaTime) { + UpdateGizmoMode(); for (int i = 0; i < Gizmos.Count; i++) { Gizmos[i].Update(deltaTime); @@ -258,11 +201,19 @@ namespace FlaxEditor.Viewport var selectedParents = TransformGizmo.SelectedParents; if (selectedParents.Count > 0) { + // Use temporary Debug Draw context to pull any debug shapes drawing in Scene Graph Nodes - those are used in OnDebugDraw down below + if (_tempDebugDrawContext == IntPtr.Zero) + _tempDebugDrawContext = DebugDraw.AllocateContext(); + DebugDraw.SetContext(_tempDebugDrawContext); + DebugDraw.UpdateContext(_tempDebugDrawContext, 1.0f); + for (int i = 0; i < selectedParents.Count; i++) { if (selectedParents[i].IsActiveInHierarchy) selectedParents[i].OnDebugDraw(_debugDrawData); } + + DebugDraw.SetContext(IntPtr.Zero); } } @@ -306,7 +257,7 @@ namespace FlaxEditor.Viewport public void ShowSelectedActors() { var orient = ViewOrientation; - ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient); + ViewportCamera.ShowActors(TransformGizmo.SelectedParents, ref orient); } /// @@ -366,6 +317,13 @@ namespace FlaxEditor.Viewport _window.Spawn(actor); } + /// + public void OpenContextMenu() + { + var mouse = PointFromWindow(Root.MousePosition); + _window.ShowContextMenu(this, ref mouse); + } + /// protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; @@ -385,151 +343,6 @@ namespace FlaxEditor.Viewport root.UpdateCallbacksToRemove.Add(_update); } - private void OnGizmoModeToggle(ViewportWidgetButton button) - { - TransformGizmo.ActiveMode = (TransformGizmoBase.Mode)(int)button.Tag; - } - - private void OnTranslateSnappingToggle(ViewportWidgetButton button) - { - TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable; - } - - private void OnRotateSnappingToggle(ViewportWidgetButton button) - { - TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled; - } - - private void OnScaleSnappingToggle(ViewportWidgetButton button) - { - TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled; - } - - private void OnTransformSpaceToggle(ViewportWidgetButton button) - { - TransformGizmo.ToggleTransformSpace(); - } - - private void OnGizmoModeChanged() - { - // Update all viewport widgets status - var mode = TransformGizmo.ActiveMode; - _gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate; - _gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate; - _gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale; - } - - private static readonly float[] EditorViewportScaleSnapValues = - { - 0.05f, - 0.1f, - 0.25f, - 0.5f, - 1.0f, - 2.0f, - 4.0f, - 6.0f, - 8.0f, - }; - - private void OnWidgetScaleSnapClick(ContextMenuButton button) - { - var v = (float)button.Tag; - TransformGizmo.ScaleSnapValue = v; - _scaleSnapping.Text = v.ToString(); - } - - private void OnWidgetScaleSnapShowHide(Control control) - { - if (control.Visible == false) - return; - - var ccm = (ContextMenu)control; - foreach (var e in ccm.Items) - { - if (e is ContextMenuButton b) - { - var v = (float)b.Tag; - b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; - } - } - } - - private static readonly float[] EditorViewportRotateSnapValues = - { - 1.0f, - 5.0f, - 10.0f, - 15.0f, - 30.0f, - 45.0f, - 60.0f, - 90.0f, - }; - - private void OnWidgetRotateSnapClick(ContextMenuButton button) - { - var v = (float)button.Tag; - TransformGizmo.RotationSnapValue = v; - _rotateSnapping.Text = v.ToString(); - } - - private void OnWidgetRotateSnapShowHide(Control control) - { - if (control.Visible == false) - return; - - var ccm = (ContextMenu)control; - foreach (var e in ccm.Items) - { - if (e is ContextMenuButton b) - { - var v = (float)b.Tag; - b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; - } - } - } - - private static readonly float[] EditorViewportTranslateSnapValues = - { - 0.1f, - 0.5f, - 1.0f, - 5.0f, - 10.0f, - 100.0f, - 1000.0f, - }; - - private void OnWidgetTranslateSnapClick(ContextMenuButton button) - { - var v = (float)button.Tag; - TransformGizmo.TranslationSnapValue = v; - _translateSnappng.Text = v.ToString(); - } - - private void OnWidgetTranslateSnapShowHide(Control control) - { - if (control.Visible == false) - return; - - var ccm = (ContextMenu)control; - foreach (var e in ccm.Items) - { - if (e is ContextMenuButton b) - { - var v = (float)b.Tag; - b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; - } - } - } - private void OnSelectionChanged() { Gizmos.ForEach(x => x.OnSelectionChanged(_window.Selection)); @@ -584,40 +397,6 @@ namespace FlaxEditor.Viewport } } - /// - public override void Draw() - { - base.Draw(); - - // Selected UI controls outline - bool drawAnySelectedControl = false; - // TODO: optimize this (eg. cache list of selected UIControl's when selection gets changed) - for (var i = 0; i < _window.Selection.Count; i++) - { - if (_window.Selection[i]?.EditableObject is UIControl controlActor && controlActor && controlActor.Control != null && controlActor.Control.VisibleInHierarchy && controlActor.Control.RootWindow != null) - { - if (!drawAnySelectedControl) - { - drawAnySelectedControl = true; - Render2D.PushTransform(ref _cachedTransform); - } - var control = controlActor.Control; - var bounds = control.EditorBounds; - var p1 = control.PointToParent(this, bounds.UpperLeft); - var p2 = control.PointToParent(this, bounds.UpperRight); - var p3 = control.PointToParent(this, bounds.BottomLeft); - var p4 = control.PointToParent(this, bounds.BottomRight); - var min = Float2.Min(Float2.Min(p1, p2), Float2.Min(p3, p4)); - var max = Float2.Max(Float2.Max(p1, p2), Float2.Max(p3, p4)); - bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero)); - var options = Editor.Instance.Options.Options.Visual; - Render2D.DrawRectangle(bounds, options.SelectionOutlineColor0, options.UISelectionOutlineSize); - } - } - if (drawAnySelectedControl) - Render2D.PopTransform(); - } - /// protected override void OnLeftMouseButtonUp() { @@ -760,14 +539,7 @@ namespace FlaxEditor.Viewport /// The target view orientation. public void FocusSelection(ref Quaternion orientation) { - if (TransformGizmo.SelectedParents.Count == 0) - return; - - var gizmoBounds = Gizmos.Active.FocusBounds; - if (gizmoBounds != BoundingSphere.Empty) - ((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation); - else - ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation); + ViewportCamera.FocusSelection(Gizmos, ref orientation); } /// @@ -792,6 +564,13 @@ namespace FlaxEditor.Viewport /// public override void OnDestroy() { + if (IsDisposing) + return; + if (_tempDebugDrawContext != IntPtr.Zero) + { + DebugDraw.FreeContext(_tempDebugDrawContext); + _tempDebugDrawContext = IntPtr.Zero; + } FlaxEngine.Object.Destroy(ref SelectionOutline); FlaxEngine.Object.Destroy(ref _spritesRenderer); diff --git a/Source/Editor/Viewport/Previews/AssetPreview.cs b/Source/Editor/Viewport/Previews/AssetPreview.cs index ee9ee3195..c460a24de 100644 --- a/Source/Editor/Viewport/Previews/AssetPreview.cs +++ b/Source/Editor/Viewport/Previews/AssetPreview.cs @@ -21,7 +21,7 @@ namespace FlaxEditor.Viewport.Previews /// public abstract class AssetPreview : EditorViewport, IEditorPrimitivesOwner { - private ContextMenuButton _showDefaultSceneButton; + internal ContextMenuButton _showDefaultSceneButton; private IntPtr _debugDrawContext; private bool _debugDrawEnable; private bool _editorPrimitivesEnable; diff --git a/Source/Editor/Viewport/Previews/PrefabPreview.cs b/Source/Editor/Viewport/Previews/PrefabPreview.cs index ff235bd04..16ec8f132 100644 --- a/Source/Editor/Viewport/Previews/PrefabPreview.cs +++ b/Source/Editor/Viewport/Previews/PrefabPreview.cs @@ -1,8 +1,8 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; -using System.Collections.Generic; using FlaxEngine; +using FlaxEngine.GUI; using Object = FlaxEngine.Object; namespace FlaxEditor.Viewport.Previews @@ -13,19 +13,11 @@ namespace FlaxEditor.Viewport.Previews /// public class PrefabPreview : AssetPreview { - /// - /// The currently spawned prefab instance owner. Used to link some actors such as UIControl to preview scene and view. - /// - internal static PrefabPreview LoadingPreview; - - /// - /// The list of active prefab previews. Used to link some actors such as UIControl to preview scene and view. - /// - internal static List ActivePreviews; - private Prefab _prefab; private Actor _instance; - internal UIControl customControlLinked; + private UIControl _uiControlLinked; + internal bool _hasUILinked; + internal ContainerControl _uiParentLink; /// /// Gets or sets the prefab asset to preview. @@ -54,13 +46,10 @@ namespace FlaxEditor.Viewport.Previews _prefab.WaitForLoaded(); // Spawn prefab - LoadingPreview = this; var instance = PrefabManager.SpawnPrefab(_prefab, null); - LoadingPreview = null; if (instance == null) { _prefab = null; - ActivePreviews.Remove(this); throw new Exception("Failed to spawn a prefab for the preview."); } @@ -84,11 +73,11 @@ namespace FlaxEditor.Viewport.Previews if (_instance) { // Unlink UI control - if (customControlLinked) + if (_uiControlLinked) { - if (customControlLinked.Control?.Parent == this) - customControlLinked.Control.Parent = null; - customControlLinked = null; + if (_uiControlLinked.Control?.Parent == _uiParentLink) + _uiControlLinked.Control.Parent = null; + _uiControlLinked = null; } // Remove for the preview @@ -96,27 +85,51 @@ namespace FlaxEditor.Viewport.Previews } _instance = value; + _hasUILinked = false; if (_instance) { // Add to the preview Task.AddCustomActor(_instance); - - // Link UI canvases to the preview - LinkCanvas(_instance); + UpdateLinkage(); } } } + private void UpdateLinkage() + { + // Clear flag + _hasUILinked = false; + + // Link UI canvases to the preview (eg. after canvas added to the prefab) + LinkCanvas(_instance); + + // Link UI control to the preview + if (_uiControlLinked == null && + _instance is UIControl uiControl && + uiControl.Control != null && + uiControl.Control.Parent == null) + { + uiControl.Control.Parent = _uiParentLink; + _uiControlLinked = uiControl; + _hasUILinked = true; + } + else if (_uiControlLinked != null) + _hasUILinked = true; + } + private void LinkCanvas(Actor actor) { if (actor is UICanvas uiCanvas) - uiCanvas.EditorOverride(Task, this); + { + uiCanvas.EditorOverride(Task, _uiParentLink); + if (uiCanvas.GUI.Parent == _uiParentLink) + _hasUILinked = true; + } + var children = actor.ChildrenCount; for (int i = 0; i < children; i++) - { LinkCanvas(actor.GetChild(i)); - } } /// @@ -126,9 +139,8 @@ namespace FlaxEditor.Viewport.Previews public PrefabPreview(bool useWidgets) : base(useWidgets) { - if (ActivePreviews == null) - ActivePreviews = new List(); - ActivePreviews.Add(this); + // Link to itself by default + _uiParentLink = this; } /// @@ -138,15 +150,13 @@ namespace FlaxEditor.Viewport.Previews if (_instance != null) { - // Link UI canvases to the preview (eg. after canvas added to the prefab) - LinkCanvas(_instance); + UpdateLinkage(); } } /// public override void OnDestroy() { - ActivePreviews.Remove(this); Prefab = null; base.OnDestroy(); diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs index 72fcce828..86a127647 100644 --- a/Source/Editor/Viewport/ViewportDraggingHelper.cs +++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs @@ -146,7 +146,7 @@ namespace FlaxEditor.Viewport var gridPlane = new Plane(Vector3.Zero, Vector3.Up); var flags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives; hit = _owner.SceneGraphRoot.RayCast(ref ray, ref view, out var closest, out var normal, flags); - var girdGizmo = (GridGizmo)_owner.Gizmos.FirstOrDefault(x => x is GridGizmo); + var girdGizmo = _owner.Gizmos.Get(); if (hit != null) { // Use hit location @@ -180,7 +180,7 @@ namespace FlaxEditor.Viewport var location = hitLocation + new Vector3(0, bottomToCenter, 0); // Apply grid snapping if enabled - var transformGizmo = (TransformGizmo)_owner.Gizmos.FirstOrDefault(x => x is TransformGizmo); + var transformGizmo = _owner.Gizmos.Get(); if (transformGizmo != null && (_owner.UseSnapping || transformGizmo.TranslationSnapEnable)) { float snapValue = transformGizmo.TranslationSnapValue; diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index ae87826b0..2411daf86 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -186,6 +186,10 @@ namespace FlaxEditor.Windows.Assets base.Initialize(layout); + // Ignore import settings GUI if the type is not animation. This removes the import UI if the animation asset was not created using an import. + if (proxy.ImportSettings.Settings.Type != FlaxEngine.Tools.ModelTool.ModelType.Animation) + return; + // Import Settings { var group = layout.Group("Import Settings"); diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index d7efb1260..21e037fbc 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -360,10 +360,9 @@ namespace FlaxEditor.Windows.Assets /// /// The parent control. /// The location (within a given control). - private void ShowContextMenu(Control parent, ref Float2 location) + internal void ShowContextMenu(Control parent, ref Float2 location) { var contextMenu = CreateContextMenu(); - contextMenu.Show(parent, location); } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs index cb4c7fd0f..be20c176c 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs @@ -3,11 +3,9 @@ using System; using System.Collections.Generic; using System.Linq; -using FlaxEditor.Gizmo; using FlaxEditor.GUI.Tree; using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph.GUI; -using FlaxEditor.Viewport.Cameras; using FlaxEngine; namespace FlaxEditor.Windows.Assets @@ -64,8 +62,11 @@ namespace FlaxEditor.Windows.Assets private void OnSelectionUndo(SceneGraphNode[] toSelect) { Selection.Clear(); - Selection.AddRange(toSelect); - + foreach (var e in toSelect) + { + if (e != null) + Selection.Add(e); + } OnSelectionChanges(); } @@ -118,11 +119,13 @@ namespace FlaxEditor.Windows.Assets /// The nodes. public void Select(List nodes) { + nodes?.RemoveAll(x => x == null); if (nodes == null || nodes.Count == 0) { Deselect(); return; } + if (Utils.ArraysEqual(Selection, nodes)) return; diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 2e3e700a2..b788821cf 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -132,7 +132,7 @@ namespace FlaxEditor.Windows.Assets IsScrollable = false, Offsets = new Margin(0, 0, 0, 18 + 6), }; - _searchBox = new SearchBox() + _searchBox = new SearchBox { AnchorPreset = AnchorPresets.HorizontalStretchMiddle, Parent = headerPanel, @@ -140,7 +140,8 @@ namespace FlaxEditor.Windows.Assets }; _searchBox.TextChanged += OnSearchBoxTextChanged; - _treePanel = new Panel() + // Prefab structure tree + _treePanel = new Panel { AnchorPreset = AnchorPresets.StretchAll, Offsets = new Margin(0.0f, 0.0f, headerPanel.Bottom, 0.0f), @@ -148,8 +149,6 @@ namespace FlaxEditor.Windows.Assets IsScrollable = true, Parent = sceneTreePanel, }; - - // Prefab structure tree Graph = new LocalSceneGraph(new CustomRootNode(this)); Graph.Root.TreeNode.Expand(true); _tree = new PrefabTree @@ -316,11 +315,7 @@ namespace FlaxEditor.Windows.Assets return; // Restore - _viewport.Prefab = _asset; - Graph.MainActor = _viewport.Instance; - Selection.Clear(); - Select(Graph.Main); - Graph.Root.TreeNode.Expand(true); + OnPrefabOpened(); _undo.Clear(); ClearEditedFlag(); } @@ -346,6 +341,16 @@ namespace FlaxEditor.Windows.Assets } } + private void OnPrefabOpened() + { + _viewport.Prefab = _asset; + _viewport.UpdateGizmoMode(); + Graph.MainActor = _viewport.Instance; + Selection.Clear(); + Select(Graph.Main); + Graph.Root.TreeNode.Expand(true); + } + /// public override void Save() { @@ -355,7 +360,7 @@ namespace FlaxEditor.Windows.Assets try { - Editor.Scene.OnSaveStart(_viewport); + Editor.Scene.OnSaveStart(_viewport._uiParentLink); // Simply update changes Editor.Prefabs.ApplyAll(_viewport.Instance); @@ -375,7 +380,7 @@ namespace FlaxEditor.Windows.Assets } finally { - Editor.Scene.OnSaveEnd(_viewport); + Editor.Scene.OnSaveEnd(_viewport._uiParentLink); } } @@ -417,13 +422,8 @@ namespace FlaxEditor.Windows.Assets return; } - _viewport.Prefab = _asset; - Graph.MainActor = _viewport.Instance; + OnPrefabOpened(); _focusCamera = true; - Selection.Clear(); - Select(Graph.Main); - Graph.Root.TreeNode.Expand(true); - _undo.Clear(); ClearEditedFlag(); @@ -468,11 +468,7 @@ namespace FlaxEditor.Windows.Assets _viewport.Prefab = null; if (_asset.IsLoaded) { - _viewport.Prefab = _asset; - Graph.MainActor = _viewport.Instance; - Selection.Clear(); - Select(Graph.Main); - Graph.Root.TreeNode.ExpandAll(true); + OnPrefabOpened(); } } finally @@ -484,7 +480,6 @@ namespace FlaxEditor.Windows.Assets if (_focusCamera && _viewport.Task.FrameCount > 1) { _focusCamera = false; - Editor.GetActorEditorSphere(_viewport.Instance, out BoundingSphere bounds); _viewport.ViewPosition = bounds.Center - _viewport.ViewDirection * (bounds.Radius * 1.2f); } diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index c96aa8e59..3e29b979f 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -281,6 +281,9 @@ namespace FlaxEditor.Windows _view.OnDelete += Delete; _view.OnDuplicate += Duplicate; _view.OnPaste += Paste; + + _view.InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus()); + InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus()); } private ContextMenu OnViewDropdownPopupCreate(ComboBox comboBox) diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index f1ea43d9b..fe0c79084 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Xml; +using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEditor.Options; @@ -194,133 +195,14 @@ namespace FlaxEditor.Windows public bool Active; } - private class GameRoot : ContainerControl + /// + /// Root control for game UI preview in Editor. Supports basic UI editing via . + /// + private class GameRoot : UIEditorRoot { - public bool EnableEvents => !Time.GamePaused; - - public override bool RayCast(ref Float2 location, out Control hit) - { - return RayCastChildren(ref location, out hit); - } - - public override bool ContainsPoint(ref Float2 location, bool precise = false) - { - if (precise) - return false; - return base.ContainsPoint(ref location, precise); - } - - public override bool OnCharInput(char c) - { - if (!EnableEvents) - return false; - - return base.OnCharInput(c); - } - - public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) - { - if (!EnableEvents) - return DragDropEffect.None; - - return base.OnDragDrop(ref location, data); - } - - public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) - { - if (!EnableEvents) - return DragDropEffect.None; - - return base.OnDragEnter(ref location, data); - } - - public override void OnDragLeave() - { - if (!EnableEvents) - return; - - base.OnDragLeave(); - } - - public override DragDropEffect OnDragMove(ref Float2 location, DragData data) - { - if (!EnableEvents) - return DragDropEffect.None; - - return base.OnDragMove(ref location, data); - } - - public override bool OnKeyDown(KeyboardKeys key) - { - if (!EnableEvents) - return false; - - return base.OnKeyDown(key); - } - - public override void OnKeyUp(KeyboardKeys key) - { - if (!EnableEvents) - return; - - base.OnKeyUp(key); - } - - public override bool OnMouseDoubleClick(Float2 location, MouseButton button) - { - if (!EnableEvents) - return false; - - return base.OnMouseDoubleClick(location, button); - } - - public override bool OnMouseDown(Float2 location, MouseButton button) - { - if (!EnableEvents) - return false; - - return base.OnMouseDown(location, button); - } - - public override void OnMouseEnter(Float2 location) - { - if (!EnableEvents) - return; - - base.OnMouseEnter(location); - } - - public override void OnMouseLeave() - { - if (!EnableEvents) - return; - - base.OnMouseLeave(); - } - - public override void OnMouseMove(Float2 location) - { - if (!EnableEvents) - return; - - base.OnMouseMove(location); - } - - public override bool OnMouseUp(Float2 location, MouseButton button) - { - if (!EnableEvents) - return false; - - return base.OnMouseUp(location, button); - } - - public override bool OnMouseWheel(Float2 location, float delta) - { - if (!EnableEvents) - return false; - - return base.OnMouseWheel(location, delta); - } + public override bool EnableInputs => !Time.GamePaused && Editor.IsPlayMode; + public override bool EnableSelecting => !Editor.IsPlayMode || Time.GamePaused; + public override TransformGizmo TransformGizmo => Editor.Instance.MainTransformGizmo; } /// @@ -348,13 +230,9 @@ namespace FlaxEditor.Windows // Override the game GUI root _guiRoot = new GameRoot { - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - //Visible = false, - AutoFocus = false, Parent = _viewport }; - RootControl.GameRoot = _guiRoot; + RootControl.GameRoot = _guiRoot.UIRoot; SizeChanged += control => { ResizeViewport(); }; @@ -382,6 +260,56 @@ namespace FlaxEditor.Windows Editor.Instance.Windows.ProfilerWin.Clear(); Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); }); + InputActions.Add(options => options.Save, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.SaveAll(); + }); + InputActions.Add(options => options.Undo, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.PerformUndo(); + Focus(); + }); + InputActions.Add(options => options.Redo, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.PerformRedo(); + Focus(); + }); + InputActions.Add(options => options.Cut, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.SceneEditing.Cut(); + }); + InputActions.Add(options => options.Copy, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.SceneEditing.Copy(); + }); + InputActions.Add(options => options.Paste, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.SceneEditing.Paste(); + }); + InputActions.Add(options => options.Duplicate, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.SceneEditing.Duplicate(); + }); + InputActions.Add(options => options.Delete, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.SceneEditing.Delete(); + }); } private void ChangeViewportRatio(ViewportScaleOptions v) @@ -916,35 +844,6 @@ namespace FlaxEditor.Windows Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } - // Selected UI controls outline - bool drawAnySelectedControl = false; - // TODO: optimize this (eg. cache list of selected UIControl's when selection gets changed) - var selection = Editor.SceneEditing.Selection; - for (var i = 0; i < selection.Count; i++) - { - if (selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null && controlActor.Control.VisibleInHierarchy && controlActor.Control.RootWindow != null) - { - if (!drawAnySelectedControl) - { - drawAnySelectedControl = true; - Render2D.PushTransform(ref _viewport._cachedTransform); - } - var options = Editor.Options.Options.Visual; - var control = controlActor.Control; - var bounds = control.EditorBounds; - var p1 = control.PointToParent(_viewport, bounds.UpperLeft); - var p2 = control.PointToParent(_viewport, bounds.UpperRight); - var p3 = control.PointToParent(_viewport, bounds.BottomLeft); - var p4 = control.PointToParent(_viewport, bounds.BottomRight); - var min = Float2.Min(Float2.Min(p1, p2), Float2.Min(p3, p4)); - var max = Float2.Max(Float2.Max(p1, p2), Float2.Max(p3, p4)); - bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero)); - Render2D.DrawRectangle(bounds, options.SelectionOutlineColor0, options.UISelectionOutlineSize); - } - } - if (drawAnySelectedControl) - Render2D.PopTransform(); - // Play mode hints and overlay if (Editor.StateMachine.IsPlayMode) { diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 87d18246a..cd76412c8 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -65,6 +65,11 @@ namespace FlaxEditor.Windows /// public OutputLogWindow Window; + /// + /// The input actions collection to processed during user input. + /// + public InputActionsContainer InputActions = new InputActionsContainer(); + /// /// The default text style. /// @@ -80,6 +85,14 @@ namespace FlaxEditor.Windows /// public TextBlockStyle ErrorStyle; + /// + public override bool OnKeyDown(KeyboardKeys key) + { + if (InputActions.Process(Editor.Instance, this, key)) + return true; + return base.OnKeyDown(key); + } + /// protected override void OnParseTextBlocks() { @@ -201,6 +214,9 @@ namespace FlaxEditor.Windows // Setup editor options Editor.Options.OptionsChanged += OnEditorOptionsChanged; OnEditorOptionsChanged(Editor.Options.Options); + + _output.InputActions.Add(options => options.Search, () => _searchBox.Focus()); + InputActions.Add(options => options.Search, () => _searchBox.Focus()); GameCooker.Event += OnGameCookerEvent; ScriptsBuilder.CompilationFailed += OnScriptsCompilationFailed; diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 4ff4752ac..f4a49195f 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; -using System.Collections.Generic; using System.Linq; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; @@ -258,10 +257,9 @@ namespace FlaxEditor.Windows /// /// The parent control. /// The location (within a given control). - private void ShowContextMenu(Control parent, Float2 location) + internal void ShowContextMenu(Control parent, Float2 location) { var contextMenu = CreateContextMenu(); - contextMenu.Show(parent, location); } diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index f47cd4243..9231ce970 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -221,7 +221,6 @@ namespace FlaxEditor.Windows { if (!Editor.StateMachine.CurrentState.CanEditScene) return; - ShowContextMenu(node, location); } diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index 688a3f7b5..7735d8b28 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -584,7 +584,43 @@ Asset::LoadResult BinaryAsset::loadAsset() ASSERT(Storage && _header.ID.IsValid() && _header.TypeName.HasChars()); auto lock = Storage->Lock(); - return load(); + auto chunksToPreload = getChunksToPreload(); + if (chunksToPreload != 0) + { + // Ensure that any chunks that were requested before are loaded in memory (in case streaming flushed them out after timeout) + for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) + { + const auto chunk = _header.Chunks[i]; + if (GET_CHUNK_FLAG(i) & chunksToPreload && chunk && chunk->IsMissing()) + Storage->LoadAssetChunk(chunk); + } + } + const LoadResult result = load(); +#if !BUILD_RELEASE + if (result == LoadResult::MissingDataChunk) + { + // Provide more insights on potentially missing asset data chunk + Char chunksBitMask[ASSET_FILE_DATA_CHUNKS + 1]; + Char chunksExistBitMask[ASSET_FILE_DATA_CHUNKS + 1]; + Char chunksLoadBitMask[ASSET_FILE_DATA_CHUNKS + 1]; + for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) + { + if (const FlaxChunk* chunk = _header.Chunks[i]) + { + chunksBitMask[i] = '1'; + chunksExistBitMask[i] = chunk->ExistsInFile() ? '1' : '0'; + chunksLoadBitMask[i] = chunk->IsLoaded() ? '1' : '0'; + } + else + { + chunksBitMask[i] = chunksExistBitMask[i] = chunksLoadBitMask[i] = '0'; + } + } + chunksBitMask[ASSET_FILE_DATA_CHUNKS] = chunksExistBitMask[ASSET_FILE_DATA_CHUNKS] = chunksLoadBitMask[ASSET_FILE_DATA_CHUNKS] = 0; + LOG(Warning, "Asset reports missing data chunk. Chunks bitmask: {}, existing chunks: {} loaded chunks: {}. '{}'", chunksBitMask, chunksExistBitMask, chunksLoadBitMask, ToString()); + } +#endif + return result; } void BinaryAsset::releaseStorage() diff --git a/Source/Engine/Content/Storage/AssetHeader.h b/Source/Engine/Content/Storage/AssetHeader.h index 0185a0ff4..ad5c8efb9 100644 --- a/Source/Engine/Content/Storage/AssetHeader.h +++ b/Source/Engine/Content/Storage/AssetHeader.h @@ -82,36 +82,17 @@ public: /// /// Gets the amount of created asset chunks. /// - /// Created asset chunks - int32 GetChunksCount() const - { - int32 result = 0; - for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) - { - if (Chunks[i] != nullptr) - result++; - } - return result; - } + int32 GetChunksCount() const; /// /// Deletes all chunks. Warning! Chunks are managed internally, use with caution! /// - void DeleteChunks() - { - for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) - { - SAFE_DELETE(Chunks[i]); - } - } + void DeleteChunks(); /// /// Unlinks all chunks. /// - void UnlinkChunks() - { - Platform::MemoryClear(Chunks, sizeof(Chunks)); - } + void UnlinkChunks(); /// /// Gets string with a human-readable info about that header diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index 5711a8b64..74b114f52 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -20,6 +20,30 @@ #endif #include +int32 AssetHeader::GetChunksCount() const +{ + int32 result = 0; + for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) + { + if (Chunks[i] != nullptr) + result++; + } + return result; +} + +void AssetHeader::DeleteChunks() +{ + for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) + { + SAFE_DELETE(Chunks[i]); + } +} + +void AssetHeader::UnlinkChunks() +{ + Platform::MemoryClear(Chunks, sizeof(Chunks)); +} + String AssetHeader::ToString() const { return String::Format(TEXT("ID: {0}, TypeName: {1}, Chunks Count: {2}"), ID, TypeName, GetChunksCount()); diff --git a/Source/Engine/Core/Math/Transform.h b/Source/Engine/Core/Math/Transform.h index 02a9e5a79..2c51bfa4a 100644 --- a/Source/Engine/Core/Math/Transform.h +++ b/Source/Engine/Core/Math/Transform.h @@ -18,13 +18,13 @@ API_STRUCT() struct FLAXENGINE_API Transform /// /// The translation vector of the transform. /// - API_FIELD(Attributes="EditorOrder(10), EditorDisplay(null, \"Position\")") + API_FIELD(Attributes="EditorOrder(10), EditorDisplay(null, \"Position\"), ValueCategory(Utils.ValueCategory.Distance)") Vector3 Translation; /// /// The rotation of the transform. /// - API_FIELD(Attributes="EditorOrder(20), EditorDisplay(null, \"Rotation\")") + API_FIELD(Attributes="EditorOrder(20), EditorDisplay(null, \"Rotation\"), ValueCategory(Utils.ValueCategory.Angle)") Quaternion Orientation; /// diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 46b15a6e2..ea8cdb196 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -127,7 +127,8 @@ PACK_STRUCT(struct Vertex { PACK_STRUCT(struct Data { Matrix ViewProjection; - Float3 Padding; + Float2 Padding; + float ClipPosZBias; bool EnableDepthTest; }); @@ -356,6 +357,19 @@ namespace Float3 CircleCache[DEBUG_DRAW_CIRCLE_VERTICES]; Array SphereTriangleCache; DebugSphereCache SphereCache[3]; + +#if COMPILE_WITH_DEV_ENV + void OnShaderReloading(Asset* obj) + { + DebugDrawPsLinesDefault.Release(); + DebugDrawPsLinesDepthTest.Release(); + DebugDrawPsWireTrianglesDefault.Release(); + DebugDrawPsWireTrianglesDepthTest.Release(); + DebugDrawPsTrianglesDefault.Release(); + DebugDrawPsTrianglesDepthTest.Release(); + } + +#endif }; extern int32 BoxTrianglesIndicesCache[]; @@ -615,17 +629,19 @@ void DebugDrawService::Update() GlobalContext.DebugDrawDefault.Update(deltaTime); GlobalContext.DebugDrawDepthTest.Update(deltaTime); - // Check if need to setup a resources + // Lazy-init resources if (DebugDrawShader == nullptr) { - // Shader DebugDrawShader = Content::LoadAsyncInternal(TEXT("Shaders/DebugDraw")); if (DebugDrawShader == nullptr) { LOG(Fatal, "Cannot load DebugDraw shader"); } +#if COMPILE_WITH_DEV_ENV + DebugDrawShader->OnReloading.Bind(&OnShaderReloading); +#endif } - if (DebugDrawVB == nullptr && DebugDrawShader && DebugDrawShader->IsLoaded()) + if (DebugDrawPsWireTrianglesDepthTest.Depth == nullptr && DebugDrawShader && DebugDrawShader->IsLoaded()) { bool failed = false; const auto shader = DebugDrawShader->GetShader(); @@ -661,10 +677,11 @@ void DebugDrawService::Update() { LOG(Fatal, "Cannot setup DebugDraw service!"); } - - // Vertex buffer - DebugDrawVB = New((uint32)(DEBUG_DRAW_INITIAL_VB_CAPACITY * sizeof(Vertex)), (uint32)sizeof(Vertex), TEXT("DebugDraw.VB")); } + + // Vertex buffer + if (DebugDrawVB == nullptr) + DebugDrawVB = New((uint32)(DEBUG_DRAW_INITIAL_VB_CAPACITY * sizeof(Vertex)), (uint32)sizeof(Vertex), TEXT("DebugDraw.VB")); } void DebugDrawService::Dispose() @@ -723,7 +740,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe // Ensure to have shader loaded and any lines to render const int32 debugDrawDepthTestCount = Context->DebugDrawDepthTest.Count(); const int32 debugDrawDefaultCount = Context->DebugDrawDefault.Count(); - if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || debugDrawDepthTestCount + debugDrawDefaultCount == 0) + if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || debugDrawDepthTestCount + debugDrawDefaultCount == 0 || DebugDrawPsWireTrianglesDepthTest.Depth == nullptr) return; if (renderContext.Buffers == nullptr || !DebugDrawVB) return; @@ -739,6 +756,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe } Context->LastViewPos = view.Position; Context->LastViewProj = view.Projection; + TaaJitterRemoveContext taaJitterRemove(view); // Fallback to task buffers if (target == nullptr && renderContext.Task) @@ -766,8 +784,9 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe const auto cb = DebugDrawShader->GetShader()->GetCB(0); Data data; Matrix vp; - Matrix::Multiply(view.View, view.NonJitteredProjection, vp); + Matrix::Multiply(view.View, view.Projection, vp); Matrix::Transpose(vp, data.ViewProjection); + data.ClipPosZBias = -0.2f; // Reduce Z-fighting artifacts (eg. editor grid) data.EnableDepthTest = enableDepthTest; context->UpdateCB(cb, &data); context->BindCB(0, cb); @@ -848,6 +867,8 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe { PROFILE_GPU_CPU_NAMED("Text"); auto features = Render2D::Features; + + // Disable vertex snapping when rendering 3D text Render2D::Features = (Render2D::RenderingFeatures)((uint32)features & ~(uint32)Render2D::RenderingFeatures::VertexSnapping); if (!DebugDrawFont) diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index ad28f1dce..d1b8c8037 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -960,7 +960,7 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable /// The speed at which the exposure changes when the scene brightness moves from a bright area to a dark area (brightness goes down). /// API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)EyeAdaptationSettingsOverride.SpeedDown)") - float SpeedDown = 1.0f; + float SpeedDown = 10.0f; /// /// The pre-exposure value applied to the scene color before performing post-processing (such as bloom, lens flares, etc.). @@ -984,7 +984,7 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable /// The maximum brightness for the auto exposure which limits the upper brightness the eye can adapt within. /// API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(6), PostProcessSetting((int)EyeAdaptationSettingsOverride.MaxBrightness), EditorDisplay(null, \"Maximum Brightness\")") - float MaxBrightness = 2.0f; + float MaxBrightness = 15.0f; /// /// The lower bound for the luminance histogram of the scene color. This value is in percent and limits the pixels below this brightness. Use values in the range of 60-80. Used only in AutomaticHistogram mode. @@ -996,7 +996,7 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable /// The upper bound for the luminance histogram of the scene color. This value is in percent and limits the pixels above this brightness. Use values in the range of 80-95. Used only in AutomaticHistogram mode. /// API_FIELD(Attributes="Limit(1, 99, 0.001f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.HistogramHighPercent)") - float HistogramHighPercent = 98.0f; + float HistogramHighPercent = 90.0f; public: /// @@ -1079,10 +1079,10 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable CameraArtifactsSettingsOverride OverrideFlags = Override::None; /// - /// Strength of the vignette effect. Value 0 hides it. The default value is 0.8. + /// Strength of the vignette effect. Value 0 hides it. The default value is 0.4. /// API_FIELD(Attributes="Limit(0, 2, 0.001f), EditorOrder(0), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteIntensity)") - float VignetteIntensity = 0.8f; + float VignetteIntensity = 0.4f; /// /// Color of the vignette. @@ -1230,25 +1230,25 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable /// Strength of the effect. A value of 0 disables it. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(0), PostProcessSetting((int)LensFlaresSettingsOverride.Intensity)") - float Intensity = 1.0f; + float Intensity = 0.5f; /// /// Amount of lens flares ghosts. /// API_FIELD(Attributes="Limit(0, 16), EditorOrder(1), PostProcessSetting((int)LensFlaresSettingsOverride.Ghosts)") - int32 Ghosts = 8; + int32 Ghosts = 4; /// /// Lens flares halo width. /// API_FIELD(Attributes="EditorOrder(2), PostProcessSetting((int)LensFlaresSettingsOverride.HaloWidth)") - float HaloWidth = 0.16f; + float HaloWidth = 0.04f; /// /// Lens flares halo intensity. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(3), PostProcessSetting((int)LensFlaresSettingsOverride.HaloIntensity)") - float HaloIntensity = 0.666f; + float HaloIntensity = 0.5f; /// /// Ghost samples dispersal parameter. @@ -1584,7 +1584,7 @@ API_STRUCT() struct FLAXENGINE_API MotionBlurSettings : ISerializable /// The blur effect strength. A value of 0 disables it, while higher values increase the effect. /// API_FIELD(Attributes="Limit(0, 5, 0.01f), EditorOrder(1), PostProcessSetting((int)MotionBlurSettingsOverride.Scale)") - float Scale = 1.0f; + float Scale = 0.5f; /// /// The amount of sample points used during motion blur rendering. It affects blur quality and performance. diff --git a/Source/Engine/Graphics/RenderView.cpp b/Source/Engine/Graphics/RenderView.cpp index 95a09f6c4..730895267 100644 --- a/Source/Engine/Graphics/RenderView.cpp +++ b/Source/Engine/Graphics/RenderView.cpp @@ -18,6 +18,7 @@ void RenderView::Prepare(RenderContext& renderContext) // Check if use TAA (need to modify the projection matrix) Float2 taaJitter; NonJitteredProjection = Projection; + IsTaaResolved = false; if (renderContext.List->Setup.UseTemporalAAJitter) { // Move to the next frame @@ -82,6 +83,18 @@ void RenderView::PrepareCache(const RenderContext& renderContext, float width, f MainScreenSize = mainView->ScreenSize; } +void RenderView::UpdateCachedData() +{ + Matrix::Invert(View, IV); + Matrix::Invert(Projection, IP); + Matrix viewProjection; + Matrix::Multiply(View, Projection, viewProjection); + Frustum.SetMatrix(viewProjection); + Matrix::Invert(viewProjection, IVP); + CullingFrustum = Frustum; + NonJitteredProjection = Projection; +} + void RenderView::SetUp(const Matrix& viewProjection) { // Copy data @@ -201,3 +214,27 @@ void RenderView::GetWorldMatrix(const Transform& transform, Matrix& world) const const Float3 translation = transform.Translation - Origin; Matrix::Transformation(transform.Scale, transform.Orientation, translation, world); } + +TaaJitterRemoveContext::TaaJitterRemoveContext(const RenderView& view) +{ + if (view.IsTaaResolved) + { + // Cancel-out sub-pixel jitter when drawing geometry after TAA has been resolved + _view = (RenderView*)&view; + _prevProjection = view.Projection; + _prevNonJitteredProjection = view.NonJitteredProjection; + _view->Projection = _prevNonJitteredProjection; + _view->UpdateCachedData(); + } +} + +TaaJitterRemoveContext::~TaaJitterRemoveContext() +{ + if (_view) + { + // Restore projection + _view->Projection = _prevProjection; + _view->UpdateCachedData(); + _view->NonJitteredProjection = _prevNonJitteredProjection; + } +} diff --git a/Source/Engine/Graphics/RenderView.h b/Source/Engine/Graphics/RenderView.h index d73b45c53..0b37e8b28 100644 --- a/Source/Engine/Graphics/RenderView.h +++ b/Source/Engine/Graphics/RenderView.h @@ -117,6 +117,11 @@ public: /// API_FIELD() bool IsCullingDisabled = false; + /// + /// True if TAA has been resolved when rendering view and frame doesn't contain jitter anymore. Rendering geometry after this point should not use jitter anymore (eg. editor gizmos or custom geometry as overlay). + /// + API_FIELD() bool IsTaaResolved = false; + /// /// The static flags mask used to hide objects that don't have a given static flags. Eg. use StaticFlags::Lightmap to render only objects that can use lightmap. /// @@ -160,7 +165,7 @@ public: API_FIELD() DEPRECATED float ShadowModelLODDistanceFactor = 1.0f; /// - /// The Temporal Anti-Aliasing jitter frame index. + /// Temporal Anti-Aliasing jitter frame index. /// API_FIELD() int32 TaaFrameIndex = 0; @@ -261,6 +266,11 @@ public: RenderView& operator=(const RenderView& other) = default; PRAGMA_ENABLE_DEPRECATION_WARNINGS + /// + /// Updates the cached data for the view (inverse matrices, etc.). + /// + void UpdateCachedData(); + // Set up view with custom params // @param viewProjection View * Projection matrix void SetUp(const Matrix& viewProjection); @@ -344,3 +354,15 @@ public: world.M43 -= Origin.Z; } }; + +// Removes TAA jitter from the RenderView when drawing geometry after TAA has been resolved to prevent unwanted jittering. +struct TaaJitterRemoveContext +{ +private: + RenderView* _view = nullptr; + Matrix _prevProjection, _prevNonJitteredProjection; + +public: + TaaJitterRemoveContext(const RenderView& view); + ~TaaJitterRemoveContext(); +}; diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp index 0e6ad8f3a..2af300f06 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp @@ -10,6 +10,7 @@ #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Materials/MaterialShader.h" #include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h" +#include "FlaxEngine.Gen.h" const Char* ShaderProfileCacheDirNames[] = { @@ -179,6 +180,7 @@ bool ShaderCacheManagerService::Init() // Validate the database cache version (need to recompile all shaders on shader cache format change) struct CacheVersion { + int32 EngineVersion = -1; int32 ShaderCacheVersion = -1; int32 MaterialGraphVersion = -1; int32 ParticleGraphVersion = -1; @@ -193,7 +195,8 @@ bool ShaderCacheManagerService::Init() LOG(Warning, "Failed to read the shaders cache database version file."); } } - if (cacheVersion.ShaderCacheVersion != GPU_SHADER_CACHE_VERSION + if (cacheVersion.EngineVersion != FLAXENGINE_VERSION_BUILD + || cacheVersion.ShaderCacheVersion != GPU_SHADER_CACHE_VERSION || cacheVersion.MaterialGraphVersion != MATERIAL_GRAPH_VERSION || cacheVersion.ParticleGraphVersion != PARTICLE_GPU_GRAPH_VERSION ) @@ -209,6 +212,7 @@ bool ShaderCacheManagerService::Init() LOG(Error, "Failed to createe the shaders cache database directory."); } + cacheVersion.EngineVersion = FLAXENGINE_VERSION_BUILD; cacheVersion.ShaderCacheVersion = GPU_SHADER_CACHE_VERSION; cacheVersion.MaterialGraphVersion = MATERIAL_GRAPH_VERSION; cacheVersion.ParticleGraphVersion = PARTICLE_GPU_GRAPH_VERSION; diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h index e7c7971b7..190126e54 100644 --- a/Source/Engine/Level/Actors/Camera.h +++ b/Source/Engine/Level/Actors/Camera.h @@ -77,7 +77,7 @@ public: /// /// Gets the camera's field of view (in degrees). /// - API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective))") + API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective)), ValueCategory(Utils.ValueCategory.Angle)") float GetFieldOfView() const; /// @@ -99,7 +99,7 @@ public: /// /// Gets camera's near plane distance. /// - API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\")") + API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\"), ValueCategory(Utils.ValueCategory.Distance)") float GetNearPlane() const; /// @@ -110,7 +110,7 @@ public: /// /// Gets camera's far plane distance. /// - API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\")") + API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\"), ValueCategory(Utils.ValueCategory.Distance)") float GetFarPlane() const; /// diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index aa6772904..07ff609bd 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -553,13 +553,11 @@ const Span StaticModel::GetMaterialSlots() const MaterialBase* StaticModel::GetMaterial(int32 entryIndex) { - if (Model) - Model->WaitForLoaded(); - else + if (!Model || Model->WaitForLoaded()) return nullptr; CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr); MaterialBase* material = Entries[entryIndex].Material.Get(); - if (!material) + if (!material && entryIndex < Model->MaterialSlots.Count()) { material = Model->MaterialSlots[entryIndex].Material.Get(); if (!material) diff --git a/Source/Engine/Physics/Actors/RigidBody.cpp b/Source/Engine/Physics/Actors/RigidBody.cpp index 870bba867..04672552c 100644 --- a/Source/Engine/Physics/Actors/RigidBody.cpp +++ b/Source/Engine/Physics/Actors/RigidBody.cpp @@ -569,7 +569,7 @@ void RigidBody::OnTransformChanged() void RigidBody::OnPhysicsSceneChanged(PhysicsScene* previous) { - PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _actor); + PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _actor, true); void* scene = GetPhysicsScene()->GetPhysicsScene(); PhysicsBackend::AddSceneActor(scene, _actor); const bool putToSleep = !_startAwake && GetEnableSimulation() && !GetIsKinematic() && IsActiveInHierarchy(); diff --git a/Source/Engine/Physics/Actors/RigidBody.h b/Source/Engine/Physics/Actors/RigidBody.h index f005f1baa..c862a8e97 100644 --- a/Source/Engine/Physics/Actors/RigidBody.h +++ b/Source/Engine/Physics/Actors/RigidBody.h @@ -181,7 +181,7 @@ public: /// /// Gets the mass value measured in kilograms (use override value only if OverrideMass is checked). /// - API_PROPERTY(Attributes="EditorOrder(110), Limit(0), EditorDisplay(\"Rigid Body\")") + API_PROPERTY(Attributes="EditorOrder(110), Limit(0), EditorDisplay(\"Rigid Body\"), ValueCategory(Utils.ValueCategory.Mass)") float GetMass() const; /// diff --git a/Source/Engine/Physics/Colliders/BoxCollider.h b/Source/Engine/Physics/Colliders/BoxCollider.h index f413fa042..58298ada8 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.h +++ b/Source/Engine/Physics/Colliders/BoxCollider.h @@ -23,7 +23,7 @@ public: /// Gets the size of the box, measured in the object's local space. /// /// The box size will be scaled by the actor's world scale. - API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(typeof(Float3), \"100,100,100\"), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(typeof(Float3), \"100,100,100\"), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE Float3 GetSize() const { return _size; diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.h b/Source/Engine/Physics/Colliders/CapsuleCollider.h index 60526750c..c225ac2a5 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.h +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.h @@ -25,7 +25,7 @@ public: /// Gets the radius of the sphere, measured in the object's local space. /// /// The sphere radius will be scaled by the actor's world scale. - API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(20.0f), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(20.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE float GetRadius() const { return _radius; @@ -41,7 +41,7 @@ public: /// Gets the height of the capsule, measured in the object's local space between the centers of the hemispherical ends. /// /// The capsule height will be scaled by the actor's world scale. - API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(100.0f), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(100.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE float GetHeight() const { return _height; diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 386c49eb5..545f4e9d9 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -73,7 +73,7 @@ public: /// /// Gets the radius of the sphere, measured in the object's local space. The sphere radius will be scaled by the actor's world scale. /// - API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") float GetRadius() const; /// @@ -84,7 +84,7 @@ public: /// /// Gets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale. /// - API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") float GetHeight() const; /// @@ -95,7 +95,7 @@ public: /// /// Gets the slope limit (in degrees). Limits the collider to only climb slopes that are less steep (in degrees) than the indicated value. /// - API_PROPERTY(Attributes="EditorOrder(210), DefaultValue(45.0f), Limit(0, 100), EditorDisplay(\"Character Controller\")") + API_PROPERTY(Attributes="EditorOrder(210), DefaultValue(45.0f), Limit(0, 100), EditorDisplay(\"Character Controller\"), ValueCategory(Utils.ValueCategory.Angle)") float GetSlopeLimit() const; /// @@ -117,7 +117,7 @@ public: /// /// Gets the step height. The character will step up a stair only if it is closer to the ground than the indicated value. This should not be greater than the Character Controller’s height or it will generate an error. /// - API_PROPERTY(Attributes="EditorOrder(220), DefaultValue(30.0f), Limit(0), EditorDisplay(\"Character Controller\")") + API_PROPERTY(Attributes="EditorOrder(220), DefaultValue(30.0f), Limit(0), EditorDisplay(\"Character Controller\"), ValueCategory(Utils.ValueCategory.Distance)") float GetStepOffset() const; /// @@ -139,7 +139,7 @@ public: /// /// Gets the minimum move distance of the character controller. The minimum traveled distance to consider. If traveled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. /// - API_PROPERTY(Attributes="EditorOrder(230), DefaultValue(0.0f), Limit(0, 1000), EditorDisplay(\"Character Controller\")") + API_PROPERTY(Attributes="EditorOrder(230), DefaultValue(0.0f), Limit(0, 1000), EditorDisplay(\"Character Controller\"), ValueCategory(Utils.ValueCategory.Distance)") float GetMinMoveDistance() const; /// diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 1ad6b4946..d8541a222 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -438,7 +438,7 @@ void Collider::OnPhysicsSceneChanged(PhysicsScene* previous) if (_staticActor != nullptr) { - PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _staticActor); + PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _staticActor, true); void* scene = GetPhysicsScene()->GetPhysicsScene(); PhysicsBackend::AddSceneActor(scene, _staticActor); } diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index a2e115dcc..05f2a6eb2 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -52,7 +52,7 @@ public: /// /// Gets the center of the collider, measured in the object's local space. /// - API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE Vector3 GetCenter() const { return _center; @@ -66,7 +66,7 @@ public: /// /// Gets the contact offset. Colliders whose distance is less than the sum of their ContactOffset values will generate contacts. The contact offset must be positive. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated. /// - API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(2.0f), Limit(0, 100), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(2.0f), Limit(0, 100), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE float GetContactOffset() const { return _contactOffset; diff --git a/Source/Engine/Physics/Colliders/SphereCollider.h b/Source/Engine/Physics/Colliders/SphereCollider.h index 084dd0700..e3b295eda 100644 --- a/Source/Engine/Physics/Colliders/SphereCollider.h +++ b/Source/Engine/Physics/Colliders/SphereCollider.h @@ -21,7 +21,7 @@ public: /// Gets the radius of the sphere, measured in the object's local space. /// /// The sphere radius will be scaled by the actor's world scale. - API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE float GetRadius() const { return _radius; diff --git a/Source/Engine/Physics/Joints/DistanceJoint.h b/Source/Engine/Physics/Joints/DistanceJoint.h index 8cb0e5fd0..0f0f59b01 100644 --- a/Source/Engine/Physics/Joints/DistanceJoint.h +++ b/Source/Engine/Physics/Joints/DistanceJoint.h @@ -67,7 +67,7 @@ public: /// Gets the allowed minimum distance for the joint. /// /// Used only when DistanceJointFlag.MinDistance flag is set. The minimum distance must be no more than the maximum distance. Default: 0, Range: [0, float.MaxValue]. - API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(0.0f), Limit(0.0f), EditorDisplay(\"Joint\")") + API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(0.0f), Limit(0.0f), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE float GetMinDistance() const { return _minDistance; @@ -83,7 +83,7 @@ public: /// Gets the allowed maximum distance for the joint. /// /// Used only when DistanceJointFlag.MaxDistance flag is set. The maximum distance must be no less than the minimum distance. Default: 0, Range: [0, float.MaxValue]. - API_PROPERTY(Attributes="EditorOrder(120), DefaultValue(10.0f), Limit(0.0f), EditorDisplay(\"Joint\")") + API_PROPERTY(Attributes="EditorOrder(120), DefaultValue(10.0f), Limit(0.0f), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE float GetMaxDistance() const { return _maxDistance; diff --git a/Source/Engine/Physics/Joints/Joint.h b/Source/Engine/Physics/Joints/Joint.h index f4733f01d..a4584a07e 100644 --- a/Source/Engine/Physics/Joints/Joint.h +++ b/Source/Engine/Physics/Joints/Joint.h @@ -38,7 +38,7 @@ public: /// /// Gets the break force. Determines the maximum force the joint can apply before breaking. Broken joints no longer participate in physics simulation. /// - API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\")") + API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Force)") FORCE_INLINE float GetBreakForce() const { return _breakForce; @@ -52,7 +52,7 @@ public: /// /// Gets the break torque. Determines the maximum torque the joint can apply before breaking. Broken joints no longer participate in physics simulation. /// - API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\")") + API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Torque)") FORCE_INLINE float GetBreakTorque() const { return _breakTorque; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 0c77738f6..5d86ed8c9 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -1998,11 +1998,14 @@ void PhysicsBackend::AddSceneActor(void* scene, void* actor) FlushLocker.Unlock(); } -void PhysicsBackend::RemoveSceneActor(void* scene, void* actor) +void PhysicsBackend::RemoveSceneActor(void* scene, void* actor, bool immediately) { auto scenePhysX = (ScenePhysX*)scene; FlushLocker.Lock(); - scenePhysX->RemoveActors.Add((PxActor*)actor); + if (immediately) + scenePhysX->Scene->removeActor(*(PxActor*)actor); + else + scenePhysX->RemoveActors.Add((PxActor*)actor); FlushLocker.Unlock(); } diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h index 518201573..8bd493267 100644 --- a/Source/Engine/Physics/PhysicsBackend.h +++ b/Source/Engine/Physics/PhysicsBackend.h @@ -113,7 +113,7 @@ public: static void SetSceneBounceThresholdVelocity(void* scene, float value); static void SetSceneOrigin(void* scene, const Vector3& oldOrigin, const Vector3& newOrigin); static void AddSceneActor(void* scene, void* actor); - static void RemoveSceneActor(void* scene, void* actor); + static void RemoveSceneActor(void* scene, void* actor, bool immediately = false); static void AddSceneActorAction(void* scene, void* actor, ActionType action); #if COMPILE_WITH_PROFILER static void GetSceneStatistics(void* scene, PhysicsStatistics& result); diff --git a/Source/Engine/Physics/PhysicsBackendEmpty.cpp b/Source/Engine/Physics/PhysicsBackendEmpty.cpp index 26054c342..941813b2e 100644 --- a/Source/Engine/Physics/PhysicsBackendEmpty.cpp +++ b/Source/Engine/Physics/PhysicsBackendEmpty.cpp @@ -115,7 +115,7 @@ void PhysicsBackend::AddSceneActor(void* scene, void* actor) { } -void PhysicsBackend::RemoveSceneActor(void* scene, void* actor) +void PhysicsBackend::RemoveSceneActor(void* scene, void* actor, bool immediately) { } diff --git a/Source/Engine/Renderer/AntiAliasing/TAA.cpp b/Source/Engine/Renderer/AntiAliasing/TAA.cpp index e83870d9d..38e38636a 100644 --- a/Source/Engine/Renderer/AntiAliasing/TAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/TAA.cpp @@ -149,4 +149,7 @@ void TAA::Render(const RenderContext& renderContext, GPUTexture* input, GPUTextu context->Draw(output); renderContext.Buffers->TemporalAA = outputHistory; } + + // Mark TAA jitter as resolved for future drawing + (bool&)renderContext.View.IsTaaResolved = true; } diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index af2495b67..01da9e2ea 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -652,6 +652,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL const auto* batchesData = list.Batches.Get(); const auto context = GPUDevice::Instance->GetMainContext(); bool useInstancing = list.CanUseInstancing && CanUseInstancing(renderContext.View.Pass) && GPUDevice::Instance->Limits.HasInstancing; + TaaJitterRemoveContext taaJitterRemove(renderContext.View); // Clear SR slots to prevent any resources binding issues (leftovers from the previous passes) context->ResetSR(); diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 3246b6d85..f3c4ac6d2 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -612,7 +612,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont // Color Grading LUT generation auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext); - // Post processing + // Post-processing EyeAdaptationPass::Instance()->Render(renderContext, frameBuffer); PostProcessingPass::Instance()->Render(renderContext, frameBuffer, tempBuffer, colorGradingLUT); RenderTargetPool::Release(colorGradingLUT); diff --git a/Source/Engine/Scripting/Attributes/Editor/ValueCategoryAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/ValueCategoryAttribute.cs new file mode 100644 index 000000000..4f51efbe3 --- /dev/null +++ b/Source/Engine/Scripting/Attributes/Editor/ValueCategoryAttribute.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// Specifies the value category of a numeric value as either as-is (a scalar), a distance (formatted as cm/m/km) or an angle (formatted with a degree sign). + /// + [Serializable] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ValueCategoryAttribute : Attribute + { + /// + /// The value category used for formatting. + /// + public Utils.ValueCategory Category; + + /// + /// Initializes a new instance of the class. + /// + private ValueCategoryAttribute() + { + Category = Utils.ValueCategory.None; + } + + /// + /// Initializes a new instance of the class. + /// + /// The value category. + public ValueCategoryAttribute(Utils.ValueCategory category) + { + Category = category; + } + } +} diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 053ab10e0..d2c4eaa94 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -2148,7 +2148,9 @@ bool TerrainPatch::CreateHeightField() if (collisionHeader->CheckOldMagicNumber != MAX_int32 || collisionHeader->Version != TerrainCollisionDataHeader::CurrentVersion) { // Reset height map - return InitializeHeightMap(); + PROFILE_CPU_NAMED("ResetHeightMap"); + const float* data = GetHeightmapData(); + return SetupHeightMap(_cachedHeightMap.Count(), data); } // Create heightfield object from the data @@ -2580,7 +2582,7 @@ void TerrainPatch::Deserialize(DeserializeStream& stream, ISerializeModifier* mo void TerrainPatch::OnPhysicsSceneChanged(PhysicsScene* previous) { - PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _physicsActor); + PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _physicsActor, true); void* scene = _terrain->GetPhysicsScene()->GetPhysicsScene(); PhysicsBackend::AddSceneActor(scene, _physicsActor); } diff --git a/Source/Engine/UI/GUI/CanvasScaler.cs b/Source/Engine/UI/GUI/CanvasScaler.cs index f0cc59b10..612de3f59 100644 --- a/Source/Engine/UI/GUI/CanvasScaler.cs +++ b/Source/Engine/UI/GUI/CanvasScaler.cs @@ -429,6 +429,13 @@ namespace FlaxEngine.GUI return ContainsPoint(ref location); } + /// + public override bool IntersectsChildContent(Control child, Float2 location, out Float2 childSpaceLocation) + { + location /= _scale; + return base.IntersectsChildContent(child, location, out childSpaceLocation); + } + /// public override bool ContainsPoint(ref Float2 location, bool precise = false) { @@ -462,97 +469,6 @@ namespace FlaxEngine.GUI return result; } - /// - public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) - { - location /= _scale; - return base.OnDragEnter(ref location, data); - } - - /// - public override DragDropEffect OnDragMove(ref Float2 location, DragData data) - { - location /= _scale; - return base.OnDragMove(ref location, data); - } - - /// - public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) - { - location /= _scale; - return base.OnDragDrop(ref location, data); - } - - /// - public override void OnMouseEnter(Float2 location) - { - location /= _scale; - base.OnMouseEnter(location); - } - - /// - public override void OnMouseMove(Float2 location) - { - location /= _scale; - base.OnMouseMove(location); - } - - /// - public override bool OnMouseDown(Float2 location, MouseButton button) - { - location /= _scale; - return base.OnMouseDown(location, button); - } - - /// - public override bool OnMouseUp(Float2 location, MouseButton button) - { - location /= _scale; - return base.OnMouseUp(location, button); - } - - /// - public override bool OnMouseDoubleClick(Float2 location, MouseButton button) - { - location /= _scale; - return base.OnMouseDoubleClick(location, button); - } - - /// - public override bool OnMouseWheel(Float2 location, float delta) - { - location /= _scale; - return base.OnMouseWheel(location, delta); - } - - /// - public override void OnTouchEnter(Float2 location, int pointerId) - { - location /= _scale; - base.OnTouchEnter(location, pointerId); - } - - /// - public override void OnTouchMove(Float2 location, int pointerId) - { - location /= _scale; - base.OnTouchMove(location, pointerId); - } - - /// - public override bool OnTouchDown(Float2 location, int pointerId) - { - location /= _scale; - return base.OnTouchDown(location, pointerId); - } - - /// - public override bool OnTouchUp(Float2 location, int pointerId) - { - location /= _scale; - return base.OnTouchUp(location, pointerId); - } - #endregion } } diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs index 917d200da..c53307c65 100644 --- a/Source/Engine/UI/GUI/ContainerControl.cs +++ b/Source/Engine/UI/GUI/ContainerControl.cs @@ -356,7 +356,7 @@ namespace FlaxEngine.GUI for (int i = _children.Count - 1; i >= 0; i--) { var child = _children[i]; - if (IntersectsChildContent(child, point, out var childLocation)) + if (child.Visible && IntersectsChildContent(child, point, out var childLocation)) { var containerControl = child as ContainerControl; var childAtRecursive = containerControl?.GetChildAtRecursive(childLocation); diff --git a/Source/Engine/UI/GUI/Control.Bounds.cs b/Source/Engine/UI/GUI/Control.Bounds.cs index d76f82308..cbb3ba80b 100644 --- a/Source/Engine/UI/GUI/Control.Bounds.cs +++ b/Source/Engine/UI/GUI/Control.Bounds.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; +using System.ComponentModel; namespace FlaxEngine.GUI { @@ -382,6 +383,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point. /// + [DefaultValue(typeof(Float2), "0,0")] [ExpandGroups, EditorDisplay("Transform"), EditorOrder(1040), Tooltip("The shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point.")] public Float2 Shear { @@ -398,6 +400,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default). /// + [DefaultValue(0.0f)] [ExpandGroups, EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default).")] public float Rotation { diff --git a/Source/Engine/Utilities/Utils.cs b/Source/Engine/Utilities/Utils.cs index acfa5a1dc..0767ab8f4 100644 --- a/Source/Engine/Utilities/Utils.cs +++ b/Source/Engine/Utilities/Utils.cs @@ -257,6 +257,7 @@ namespace FlaxEngine { public static FieldInfo itemsField; } + internal static T[] ExtractArrayFromList(List list) { if (list == null) @@ -1038,5 +1039,66 @@ namespace FlaxEngine parameterTypes = Array.Empty(); return parameterTypes; } + + /// + /// A category of number values used for formatting and input fields. + /// + public enum ValueCategory + { + /// + /// Nothing. + /// + None, + + /// + /// Distance (eg. meters). + /// + Distance, + + /// + /// Area (eg. m^2). + /// + Area, + + /// + /// Volume (eg. m^3). + /// + Volume, + + /// + /// Mass (eg. kilograms). + /// + Mass, + + /// + /// Angle (eg. degrees). + /// + Angle, + + /// + /// Speed (distance / time). + /// + Speed, + + /// + /// Acceleration (distance^2 / time). + /// + Acceleration, + + /// + /// Time (eg. seconds). + /// + Time, + + /// + /// Force (mass * distance / time^2). + /// + Force, + + /// + /// Torque (mass * distance^2 / time^2). + /// + Torque, + } } } diff --git a/Source/Shaders/DebugDraw.shader b/Source/Shaders/DebugDraw.shader index 6495cb1a3..d10dbbf06 100644 --- a/Source/Shaders/DebugDraw.shader +++ b/Source/Shaders/DebugDraw.shader @@ -4,7 +4,8 @@ META_CB_BEGIN(0, Data) float4x4 ViewProjection; -float3 Padding; +float2 Padding; +float ClipPosZBias; bool EnableDepthTest; META_CB_END @@ -23,6 +24,7 @@ VS2PS VS(float3 Position : POSITION, float4 Color : COLOR) { VS2PS output; output.Position = mul(float4(Position, 1), ViewProjection); + output.Position.z += ClipPosZBias; output.Color = Color; return output; } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 7a1963bd6..3bf3a2a2a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -24,7 +24,7 @@ namespace Flax.Build.Bindings private static readonly List CppAutoSerializeProperties = new List(); public static readonly HashSet CppIncludeFiles = new HashSet(); private static readonly List CppIncludeFilesList = new List(); - private static readonly HashSet CppVariantToTypes = new HashSet(); + private static readonly Dictionary CppVariantToTypes = new Dictionary(); private static readonly Dictionary CppVariantFromTypes = new Dictionary(); private static bool CppNonPodTypesConvertingGeneration = false; private static StringBuilder CppContentsEnd; @@ -231,13 +231,15 @@ namespace Flax.Build.Bindings throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) { - CppVariantToTypes.Add(typeInfo); - return $"MoveTemp(VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}({value}))"; + var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo); + CppVariantToTypes[wrapperName] = typeInfo; + return $"MoveTemp(VariantTo{wrapperName}({value}))"; } if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) { - CppVariantToTypes.Add(typeInfo); - return $"MoveTemp(VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}({value}))"; + var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo); + CppVariantToTypes[wrapperName] = typeInfo; + return $"MoveTemp(VariantTo{wrapperName}({value}))"; } if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null) { @@ -2790,12 +2792,14 @@ namespace Flax.Build.Bindings var header = GetStringBuilder(); // Variant converting helper methods - foreach (var typeInfo in CppVariantToTypes) + foreach (var e in CppVariantToTypes) { + var wrapperName = e.Key; + var typeInfo = e.Value; var name = typeInfo.ToString(false); header.AppendLine(); header.AppendLine("namespace {"); - header.Append($"{name} VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}(const Variant& v)").AppendLine(); + header.Append($"{name} VariantTo{wrapperName}(const Variant& v)").AppendLine(); header.Append('{').AppendLine(); header.Append($" {name} result;").AppendLine(); if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 5cf7bab62..8b4e7e6c9 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -839,10 +839,11 @@ namespace Flax.Build.Plugins module.GetType("System.IntPtr", out var intPtrType); module.GetType("FlaxEngine.Object", out var scriptingObjectType); var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr"); + TypeReference intPtr = module.ImportReference(intPtrType); var m = new MethodDefinition(name + "Native", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, context.VoidType); - m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtrType)); - m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtrType)); + m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtr)); + m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtr)); TypeReference networkStream = module.ImportReference(context.NetworkStreamType); ILProcessor il = m.Body.GetILProcessor(); il.Emit(OpCodes.Nop); @@ -1645,12 +1646,13 @@ namespace Flax.Build.Plugins module.GetType("FlaxEngine.Object", out var scriptingObjectType); var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr"); TypeReference networkStream = module.ImportReference(networkStreamType); + TypeReference intPtr = module.ImportReference(intPtrType); // Generate static method to execute RPC locally { var m = new MethodDefinition(method.Name + "_Execute", MethodAttributes.Static | MethodAttributes.Assembly | MethodAttributes.HideBySig, voidType); - m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtrType)); - m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, module.ImportReference(intPtrType))); + m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtr)); + m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtr)); ILProcessor ilp = m.Body.GetILProcessor(); var il = new DotnetIlContext(ilp, method); il.Emit(OpCodes.Nop);