From 0f5a177be2b2237f4e5c08c8e30a18b27dfbe88b Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 14 Oct 2023 21:32:58 -0500 Subject: [PATCH 001/103] Improve collection editor. --- .../CustomEditors/Editors/CollectionEditor.cs | 301 ++++++++++++++---- .../Editors/ModelInstanceEntryEditor.cs | 18 +- .../Core/Config/LayersAndTagsSettings.cs | 2 +- .../Localization/LocalizationSettings.h | 2 +- .../Attributes/CollectionAttribute.cs | 26 ++ 5 files changed, 281 insertions(+), 68 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 8922e2d25..281f6b206 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -3,9 +3,9 @@ using System; using System.Collections; using System.Linq; -using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.GUI; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.GUI.Input; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; @@ -34,6 +34,9 @@ namespace FlaxEditor.CustomEditors.Editors /// The index of the item (zero-based). /// public readonly int Index; + + private Image _moveUpImage; + private Image _moveDownImage; /// /// Initializes a new instance of the class. @@ -46,13 +49,61 @@ namespace FlaxEditor.CustomEditors.Editors Editor = editor; Index = index; + var icons = FlaxEditor.Editor.Instance.Icons; + + var style = FlaxEngine.GUI.Style.Current; + var imageSize = 18 - Margin.Height; + _moveDownImage = new Image + { + Brush = new SpriteBrush(icons.Down32), + TooltipText = "Move down", + IsScrollable = false, + AnchorPreset = AnchorPresets.MiddleLeft, + Bounds = new Rectangle(imageSize + 2, -Height * 0.5f, imageSize, imageSize), + Color = style.ForegroundGrey, + Margin = new Margin(1), + Parent = this, + }; + _moveDownImage.Clicked += MoveDownImageOnClicked; + _moveDownImage.Enabled = Index + 1 < Editor.Count; + + _moveUpImage = new Image + { + Brush = new SpriteBrush(icons.Up32), + TooltipText = "Move up", + IsScrollable = false, + AnchorPreset = AnchorPresets.MiddleLeft, + Bounds = new Rectangle(0, -Height * 0.5f, imageSize, imageSize), + Color = style.ForegroundGrey, + Margin = new Margin(1), + Parent = this, + }; + _moveUpImage.Clicked += MoveUpImageOnClicked; + _moveUpImage.Enabled = Index > 0; + + Margin = new Margin(_moveDownImage.Right + 2, Margin.Right, Margin.Top, Margin.Bottom); SetupContextMenu += OnSetupContextMenu; } + + private void MoveUpImageOnClicked(Image image, MouseButton button) + { + OnMoveUpClicked(); + } + + private void MoveDownImageOnClicked(Image image, MouseButton button) + { + OnMoveDownClicked(); + } private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor) { + menu.ItemsContainer.RemoveChildren(); + + menu.AddButton("Copy", linkedEditor.Copy); + var paste = menu.AddButton("Paste", linkedEditor.Paste); + paste.Enabled = linkedEditor.CanPaste; + menu.AddSeparator(); - var moveUpButton = menu.AddButton("Move up", OnMoveUpClicked); moveUpButton.Enabled = Index > 0; @@ -62,17 +113,140 @@ namespace FlaxEditor.CustomEditors.Editors menu.AddButton("Remove", OnRemoveClicked); } - private void OnMoveUpClicked(ContextMenuButton button) + private void OnMoveUpClicked() { Editor.Move(Index, Index - 1); } - private void OnMoveDownClicked(ContextMenuButton button) + private void OnMoveDownClicked() { Editor.Move(Index, Index + 1); } - private void OnRemoveClicked(ContextMenuButton button) + private void OnRemoveClicked() + { + Editor.Remove(Index); + } + } + + private class CollectionDropPanel : DropPanel + { + /// + /// The collection editor. + /// + public CollectionEditor Editor; + + /// + /// The index of the item (zero-based). + /// + public int Index { get; private set; } + + /// + /// The linked editor. + /// + public CustomEditor LinkedEditor; + + private bool _canReorder = true; + private Image _moveUpImage; + private Image _moveDownImage; + + public void Setup(CollectionEditor editor, int index, bool canReorder = true) + { + HeaderHeight = 18; + _canReorder = canReorder; + EnableDropDownIcon = true; + var icons = FlaxEditor.Editor.Instance.Icons; + ArrowImageClosed = new SpriteBrush(icons.ArrowRight12); + ArrowImageOpened = new SpriteBrush(icons.ArrowDown12); + HeaderText = $"Element {index}"; + IsClosed = false; + Editor = editor; + Index = index; + Offsets = new Margin(7, 7, 0, 0); + + MouseButtonRightClicked += OnMouseButtonRightClicked; + if (_canReorder) + { + var imageSize = HeaderHeight; + var style = FlaxEngine.GUI.Style.Current; + _moveDownImage = new Image + { + Brush = new SpriteBrush(icons.Down32), + TooltipText = "Move down", + IsScrollable = false, + Bounds = new Rectangle(imageSize * 2 + ItemsMargin.Left + 2, -HeaderHeight, imageSize, imageSize), + Color = style.ForegroundGrey, + Margin = new Margin(1), + Parent = this, + }; + _moveDownImage.Clicked += MoveDownImageOnClicked; + _moveDownImage.Enabled = Index + 1 < Editor.Count; + + _moveUpImage = new Image + { + Brush = new SpriteBrush(icons.Up32), + TooltipText = "Move up", + IsScrollable = false, + Bounds = new Rectangle(imageSize + ItemsMargin.Left, -HeaderHeight, imageSize, imageSize), + Color = style.ForegroundGrey, + Margin = new Margin(1), + Parent = this, + }; + _moveUpImage.Clicked += MoveUpImageOnClicked; + _moveUpImage.Enabled = Index > 0; + + HeaderTextMargin = new Margin(_moveDownImage.Right - 12, HeaderTextMargin.Right, HeaderTextMargin.Top, HeaderTextMargin.Bottom); + } + } + + private void MoveUpImageOnClicked(Image image, MouseButton button) + { + OnMoveUpClicked(); + } + + private void MoveDownImageOnClicked(Image image, MouseButton button) + { + OnMoveDownClicked(); + } + + private void OnMouseButtonRightClicked(DropPanel panel, Float2 location) + { + if (LinkedEditor == null) + return; + var linkedEditor = LinkedEditor; + var menu = new ContextMenu(); + + menu.AddButton("Copy", linkedEditor.Copy); + var paste = menu.AddButton("Paste", linkedEditor.Paste); + paste.Enabled = linkedEditor.CanPaste; + + if (_canReorder) + { + menu.AddSeparator(); + + var moveUpButton = menu.AddButton("Move up", OnMoveUpClicked); + moveUpButton.Enabled = Index > 0; + + var moveDownButton = menu.AddButton("Move down", OnMoveDownClicked); + moveDownButton.Enabled = Index + 1 < Editor.Count; + } + + menu.AddButton("Remove", OnRemoveClicked); + + menu.Show(panel, location); + } + + private void OnMoveUpClicked() + { + Editor.Move(Index, Index - 1); + } + + private void OnMoveDownClicked() + { + Editor.Move(Index, Index + 1); + } + + private void OnRemoveClicked() { Editor.Remove(Index); } @@ -82,12 +256,13 @@ namespace FlaxEditor.CustomEditors.Editors /// Determines if value of collection can be null. /// protected bool NotNullItems; - - private IntegerValueElement _size; + + private IntValueBox _sizeBox; private Color _background; private int _elementsCount; private bool _readOnly; private bool _canReorderItems; + private CollectionAttribute.DisplayType _displayType; /// /// Gets the length of the collection. @@ -117,12 +292,13 @@ namespace FlaxEditor.CustomEditors.Editors _readOnly = false; _canReorderItems = true; _background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor; + _displayType = CollectionAttribute.DisplayType.Header; NotNullItems = false; // Try get CollectionAttribute for collection editor meta var attributes = Values.GetAttributes(); Type overrideEditorType = null; - float spacing = 10.0f; + float spacing = 1.0f; var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute); if (collection != null) { @@ -133,20 +309,40 @@ namespace FlaxEditor.CustomEditors.Editors _background = collection.BackgroundColor.Value; overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type; spacing = collection.Spacing; + _displayType = collection.Display; } // Size - if (_readOnly || (NotNullItems && size == 0)) + if (layout.ContainerControl is DropPanel dropPanel) { - layout.Label("Size", size.ToString()); - } - else - { - _size = layout.IntegerValue("Size"); - _size.IntValue.MinValue = 0; - _size.IntValue.MaxValue = ushort.MaxValue; - _size.IntValue.Value = size; - _size.IntValue.EditEnd += OnSizeChanged; + var height = dropPanel.HeaderHeight - dropPanel.HeaderTextMargin.Height; + var y = -dropPanel.HeaderHeight + dropPanel.HeaderTextMargin.Top; + _sizeBox = new IntValueBox(size) + { + MinValue = 0, + MaxValue = ushort.MaxValue, + AnchorPreset = AnchorPresets.TopRight, + Bounds = new Rectangle(-40 - dropPanel.ItemsMargin.Right, y, 40, height), + Parent = dropPanel, + }; + + var label = new Label + { + Text = "Size", + AnchorPreset = AnchorPresets.TopRight, + Bounds = new Rectangle(-_sizeBox.Width - 40 - dropPanel.ItemsMargin.Right - 2, y, 40, height), + Parent = dropPanel + }; + + if (_readOnly || (NotNullItems && size == 0)) + { + _sizeBox.IsReadOnly = true; + _sizeBox.Enabled = false; + } + else + { + _sizeBox.EditEnd += OnSizeChanged; + } } // Elements @@ -155,55 +351,42 @@ namespace FlaxEditor.CustomEditors.Editors var panel = layout.VerticalPanel(); panel.Panel.BackgroundColor = _background; var elementType = ElementType; + bool single = elementType.IsPrimitive || + elementType.Equals(new ScriptType(typeof(string))) || + elementType.IsEnum || + (elementType.GetFields().Length == 1 && elementType.GetProperties().Length == 0) || + (elementType.GetProperties().Length == 1 && elementType.GetFields().Length == 0) || + elementType.Equals(new ScriptType(typeof(JsonAsset))) || + elementType.Equals(new ScriptType(typeof(SettingsBase))); // Use separate layout cells for each collection items to improve layout updates for them in separation var useSharedLayout = elementType.IsPrimitive || elementType.IsEnum; - if (_canReorderItems) + for (int i = 0; i < size; i++) { - for (int i = 0; i < size; i++) + if (i > 0 && i < size && spacing > 0) { - if (i != 0 && spacing > 0f) - { - if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement) - { - if (propertiesListElement.Labels.Count > 0) - { - var label = propertiesListElement.Labels[propertiesListElement.Labels.Count - 1]; - var margin = label.Margin; - margin.Bottom += spacing; - label.Margin = margin; - } - propertiesListElement.Space(spacing); - } - else - { - panel.Space(spacing); - } - } - - var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; - var property = panel.AddPropertyItem(new CollectionItemLabel(this, i)); - var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); - itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor); + panel.Space(spacing); } - } - else - { - for (int i = 0; i < size; i++) + var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; + if (_displayType == CollectionAttribute.DisplayType.Inline || (collection == null && single) || (_displayType == CollectionAttribute.DisplayType.Default && single)) { - if (i != 0 && spacing > 0f) - { - if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement) - propertiesListElement.Space(spacing); - else - panel.Space(spacing); - } - - var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; - var property = panel.AddPropertyItem("Element " + i); + PropertyNameLabel itemLabel; + if (_canReorderItems) + itemLabel = new CollectionItemLabel(this, i); + else + itemLabel = new PropertyNameLabel("Element " + i); + var property = panel.AddPropertyItem(itemLabel); var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); - itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor); + itemLabel.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor); + itemLabel.Parent.Offsets = new Margin(7, 7, 0, 0); + } + else if (_displayType == CollectionAttribute.DisplayType.Header || (_displayType == CollectionAttribute.DisplayType.Default && !single)) + { + var cdp = panel.CustomContainer(); + cdp.CustomControl.Setup(this, i, _canReorderItems); + var itemLayout = useSharedLayout ? (LayoutElementsContainer)cdp : cdp.VerticalPanel(); + cdp.CustomControl.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor); } } } @@ -269,7 +452,7 @@ namespace FlaxEditor.CustomEditors.Editors if (IsSetBlocked) return; - Resize(_size.IntValue.Value); + Resize(_sizeBox.Value); } /// diff --git a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs index 9607680f2..3f55506fb 100644 --- a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs @@ -4,6 +4,7 @@ using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.GUI; using FlaxEditor.Scripting; using FlaxEngine; +using FlaxEngine.GUI; namespace FlaxEditor.CustomEditors.Editors { @@ -13,7 +14,7 @@ namespace FlaxEditor.CustomEditors.Editors [CustomEditor(typeof(ModelInstanceEntry)), DefaultEditor] public sealed class ModelInstanceEntryEditor : GenericEditor { - private GroupElement _group; + private DropPanel _mainPanel; private bool _updateName; private int _entryIndex; private bool _isRefreshing; @@ -25,8 +26,11 @@ namespace FlaxEditor.CustomEditors.Editors public override void Initialize(LayoutElementsContainer layout) { _updateName = true; - var group = layout.Group("Entry"); - _group = group; + if (layout.ContainerControl.Parent is DropPanel panel) + { + _mainPanel = panel; + _mainPanel.HeaderText = "Entry"; + } if (ParentEditor == null) return; @@ -56,14 +60,14 @@ namespace FlaxEditor.CustomEditors.Editors // Create material picker var materialValue = new CustomValueContainer(new ScriptType(typeof(MaterialBase)), _material, (instance, index) => _material, (instance, index, value) => _material = value as MaterialBase); - var materialEditor = (AssetRefEditor)_group.Property(materialLabel, materialValue); + var materialEditor = (AssetRefEditor)layout.Property(materialLabel, materialValue); materialEditor.Values.SetDefaultValue(defaultValue); materialEditor.RefreshDefaultValue(); materialEditor.Picker.SelectedItemChanged += OnSelectedMaterialChanged; _materialEditor = materialEditor; } - base.Initialize(group); + base.Initialize(layout); } private void OnSelectedMaterialChanged() @@ -116,7 +120,7 @@ namespace FlaxEditor.CustomEditors.Editors { // Update panel title to match material slot name if (_updateName && - _group != null && + _mainPanel != null && ParentEditor?.ParentEditor != null && ParentEditor.ParentEditor.Values.Count > 0) { @@ -127,7 +131,7 @@ namespace FlaxEditor.CustomEditors.Editors if (slots != null && slots.Length > entryIndex) { _updateName = false; - _group.Panel.HeaderText = "Entry " + slots[entryIndex].Name; + _mainPanel.HeaderText = "Entry " + slots[entryIndex].Name; } } } diff --git a/Source/Engine/Core/Config/LayersAndTagsSettings.cs b/Source/Engine/Core/Config/LayersAndTagsSettings.cs index 008f70504..d5ebbe19b 100644 --- a/Source/Engine/Core/Config/LayersAndTagsSettings.cs +++ b/Source/Engine/Core/Config/LayersAndTagsSettings.cs @@ -18,7 +18,7 @@ namespace FlaxEditor.Content.Settings /// /// The layers names. /// - [EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true)] + [EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true, Display = CollectionAttribute.DisplayType.Inline)] public string[] Layers = new string[32]; /// diff --git a/Source/Engine/Localization/LocalizationSettings.h b/Source/Engine/Localization/LocalizationSettings.h index e2c546c8b..22a4f03e4 100644 --- a/Source/Engine/Localization/LocalizationSettings.h +++ b/Source/Engine/Localization/LocalizationSettings.h @@ -16,7 +16,7 @@ public: /// /// The list of the string localization tables used by the game. /// - API_FIELD() + API_FIELD(Attributes="Collection(Display = CollectionAttribute.DisplayType.Inline)") Array> LocalizedStringTables; /// diff --git a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs index ca8f45d39..cd2e962e6 100644 --- a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs +++ b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs @@ -10,6 +10,32 @@ namespace FlaxEngine [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class)] public sealed class CollectionAttribute : Attribute { + /// + /// The display type for collections. + /// + public enum DisplayType + { + /// + /// Displays the default display type. + /// + Default, + + /// + /// Displays a header. + /// + Header, + + /// + /// Displays inline. + /// + Inline, + } + + /// + /// Gets or sets the display type. + /// + public DisplayType Display; + /// /// Gets or sets whether this collection is read-only. If true, applications using this collection should not allow to add or remove items. /// From 9c60da278f7ac374e9e108e78b1d10ee05ac99e7 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 15 Oct 2023 13:31:35 -0500 Subject: [PATCH 002/103] Small change to dictionary. Small changes to collections. --- .../CustomEditors/Editors/CollectionEditor.cs | 35 ++++----- .../CustomEditors/Editors/DictionaryEditor.cs | 73 ++++++++++++------- 2 files changed, 60 insertions(+), 48 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 281f6b206..eed453054 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -50,30 +50,29 @@ namespace FlaxEditor.CustomEditors.Editors Index = index; var icons = FlaxEditor.Editor.Instance.Icons; - var style = FlaxEngine.GUI.Style.Current; - var imageSize = 18 - Margin.Height; + var imageSize = 18; + var indexAmount = Index == 0 ? 2 : 0; _moveDownImage = new Image { Brush = new SpriteBrush(icons.Down32), TooltipText = "Move down", IsScrollable = false, - AnchorPreset = AnchorPresets.MiddleLeft, - Bounds = new Rectangle(imageSize + 2, -Height * 0.5f, imageSize, imageSize), + AnchorPreset = AnchorPresets.MiddleRight, + Bounds = new Rectangle(-imageSize, -Height * 0.5f, imageSize, imageSize), Color = style.ForegroundGrey, Margin = new Margin(1), Parent = this, }; _moveDownImage.Clicked += MoveDownImageOnClicked; _moveDownImage.Enabled = Index + 1 < Editor.Count; - _moveUpImage = new Image { Brush = new SpriteBrush(icons.Up32), TooltipText = "Move up", IsScrollable = false, - AnchorPreset = AnchorPresets.MiddleLeft, - Bounds = new Rectangle(0, -Height * 0.5f, imageSize, imageSize), + AnchorPreset = AnchorPresets.MiddleRight, + Bounds = new Rectangle(-(imageSize * 2 + 2), -Height * 0.5f, imageSize, imageSize), Color = style.ForegroundGrey, Margin = new Margin(1), Parent = this, @@ -81,7 +80,6 @@ namespace FlaxEditor.CustomEditors.Editors _moveUpImage.Clicked += MoveUpImageOnClicked; _moveUpImage.Enabled = Index > 0; - Margin = new Margin(_moveDownImage.Right + 2, Margin.Right, Margin.Top, Margin.Bottom); SetupContextMenu += OnSetupContextMenu; } @@ -174,7 +172,8 @@ namespace FlaxEditor.CustomEditors.Editors Brush = new SpriteBrush(icons.Down32), TooltipText = "Move down", IsScrollable = false, - Bounds = new Rectangle(imageSize * 2 + ItemsMargin.Left + 2, -HeaderHeight, imageSize, imageSize), + AnchorPreset = AnchorPresets.TopRight, + Bounds = new Rectangle(-(imageSize + ItemsMargin.Right + 2), -HeaderHeight, imageSize, imageSize), Color = style.ForegroundGrey, Margin = new Margin(1), Parent = this, @@ -187,15 +186,14 @@ namespace FlaxEditor.CustomEditors.Editors Brush = new SpriteBrush(icons.Up32), TooltipText = "Move up", IsScrollable = false, - Bounds = new Rectangle(imageSize + ItemsMargin.Left, -HeaderHeight, imageSize, imageSize), + AnchorPreset = AnchorPresets.TopRight, + Bounds = new Rectangle(-(imageSize * 2 + ItemsMargin.Right + 2), -HeaderHeight, imageSize, imageSize), Color = style.ForegroundGrey, Margin = new Margin(1), Parent = this, }; _moveUpImage.Clicked += MoveUpImageOnClicked; _moveUpImage.Enabled = Index > 0; - - HeaderTextMargin = new Margin(_moveDownImage.Right - 12, HeaderTextMargin.Right, HeaderTextMargin.Top, HeaderTextMargin.Bottom); } } @@ -349,6 +347,7 @@ namespace FlaxEditor.CustomEditors.Editors if (size > 0) { var panel = layout.VerticalPanel(); + panel.Panel.Offsets = new Margin(7, 7, 0, 0); panel.Panel.BackgroundColor = _background; var elementType = ElementType; bool single = elementType.IsPrimitive || @@ -359,15 +358,12 @@ namespace FlaxEditor.CustomEditors.Editors elementType.Equals(new ScriptType(typeof(JsonAsset))) || elementType.Equals(new ScriptType(typeof(SettingsBase))); - // Use separate layout cells for each collection items to improve layout updates for them in separation - var useSharedLayout = elementType.IsPrimitive || elementType.IsEnum; - for (int i = 0; i < size; i++) { + // Apply spacing if (i > 0 && i < size && spacing > 0) - { panel.Space(spacing); - } + var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; if (_displayType == CollectionAttribute.DisplayType.Inline || (collection == null && single) || (_displayType == CollectionAttribute.DisplayType.Default && single)) { @@ -377,15 +373,14 @@ namespace FlaxEditor.CustomEditors.Editors else itemLabel = new PropertyNameLabel("Element " + i); var property = panel.AddPropertyItem(itemLabel); - var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel(); + var itemLayout = property.VerticalPanel(); itemLabel.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor); - itemLabel.Parent.Offsets = new Margin(7, 7, 0, 0); } else if (_displayType == CollectionAttribute.DisplayType.Header || (_displayType == CollectionAttribute.DisplayType.Default && !single)) { var cdp = panel.CustomContainer(); cdp.CustomControl.Setup(this, i, _canReorderItems); - var itemLayout = useSharedLayout ? (LayoutElementsContainer)cdp : cdp.VerticalPanel(); + var itemLayout = cdp.VerticalPanel(); cdp.CustomControl.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor); } } diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs index 73089ff04..774f9b6b3 100644 --- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs @@ -7,6 +7,7 @@ using FlaxEditor.CustomEditors.Elements; using FlaxEditor.CustomEditors.GUI; using FlaxEditor.GUI; using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.GUI.Input; using FlaxEditor.Scripting; using FlaxEngine; using FlaxEngine.GUI; @@ -149,13 +150,14 @@ namespace FlaxEditor.CustomEditors.Editors } } - private IntegerValueElement _size; + private IntValueBox _sizeBox; private Color _background; private int _elementsCount; private bool _readOnly; private bool _notNullItems; private bool _canEditKeys; private bool _keyEdited; + private CollectionAttribute.DisplayType _displayType; /// /// Gets the length of the collection. @@ -178,6 +180,7 @@ namespace FlaxEditor.CustomEditors.Editors _background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor; _readOnly = false; _notNullItems = false; + _displayType = CollectionAttribute.DisplayType.Header; // Try get CollectionAttribute for collection editor meta var attributes = Values.GetAttributes(); @@ -192,20 +195,40 @@ namespace FlaxEditor.CustomEditors.Editors _background = collection.BackgroundColor.Value; overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type; spacing = collection.Spacing; + _displayType = collection.Display; } // Size - if (_readOnly || !_canEditKeys) + if (layout.ContainerControl is DropPanel dropPanel) { - layout.Label("Size", size.ToString()); - } - else - { - _size = layout.IntegerValue("Size"); - _size.IntValue.MinValue = 0; - _size.IntValue.MaxValue = _notNullItems ? size : ushort.MaxValue; - _size.IntValue.Value = size; - _size.IntValue.EditEnd += OnSizeChanged; + var height = dropPanel.HeaderHeight - dropPanel.HeaderTextMargin.Height; + var y = -dropPanel.HeaderHeight + dropPanel.HeaderTextMargin.Top; + _sizeBox = new IntValueBox(size) + { + MinValue = 0, + MaxValue = _notNullItems ? size : ushort.MaxValue, + AnchorPreset = AnchorPresets.TopRight, + Bounds = new Rectangle(-40 - dropPanel.ItemsMargin.Right, y, 40, height), + Parent = dropPanel, + }; + + var label = new Label + { + Text = "Size", + AnchorPreset = AnchorPresets.TopRight, + Bounds = new Rectangle(-_sizeBox.Width - 40 - dropPanel.ItemsMargin.Right - 2, y, 40, height), + Parent = dropPanel + }; + + if (_readOnly || !_canEditKeys) + { + _sizeBox.IsReadOnly = true; + _sizeBox.Enabled = false; + } + else + { + _sizeBox.EditEnd += OnSizeChanged; + } } // Elements @@ -216,29 +239,23 @@ namespace FlaxEditor.CustomEditors.Editors var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType(); var keys = keysEnumerable as object[] ?? keysEnumerable.ToArray(); var valuesType = new ScriptType(valueType); + + bool single = valuesType.IsPrimitive || + valuesType.Equals(new ScriptType(typeof(string))) || + valuesType.IsEnum || + (valuesType.GetFields().Length == 1 && valuesType.GetProperties().Length == 0) || + (valuesType.GetProperties().Length == 1 && valuesType.GetFields().Length == 0) || + valuesType.Equals(new ScriptType(typeof(JsonAsset))) || + valuesType.Equals(new ScriptType(typeof(SettingsBase))); // Use separate layout cells for each collection items to improve layout updates for them in separation var useSharedLayout = valueType.IsPrimitive || valueType.IsEnum; for (int i = 0; i < size; i++) { - if (i != 0 && spacing > 0f) + if (i > 0 && i < size && spacing > 0) { - if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement) - { - if (propertiesListElement.Labels.Count > 0) - { - var label = propertiesListElement.Labels[propertiesListElement.Labels.Count - 1]; - var margin = label.Margin; - margin.Bottom += spacing; - label.Margin = margin; - } - propertiesListElement.Space(spacing); - } - else - { - panel.Space(spacing); - } + panel.Space(spacing); } var key = keys.ElementAt(i); @@ -310,7 +327,7 @@ namespace FlaxEditor.CustomEditors.Editors if (IsSetBlocked) return; - Resize(_size.IntValue.Value); + Resize(_sizeBox.Value); } /// From 86c2406d5200075bb14482895e3fab9dd7c714ef Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 15 Oct 2023 13:34:09 -0500 Subject: [PATCH 003/103] Clean up code. --- Source/Editor/CustomEditors/Editors/CollectionEditor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index eed453054..e48d72003 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -52,7 +52,6 @@ namespace FlaxEditor.CustomEditors.Editors var icons = FlaxEditor.Editor.Instance.Icons; var style = FlaxEngine.GUI.Style.Current; var imageSize = 18; - var indexAmount = Index == 0 ? 2 : 0; _moveDownImage = new Image { Brush = new SpriteBrush(icons.Down32), From 35becc674a449956c49ab6086ffb05ff16ce698d Mon Sep 17 00:00:00 2001 From: Preben Eriksen Date: Mon, 25 Dec 2023 12:08:34 +0100 Subject: [PATCH 004/103] PE: Fix problem with Procedural Texture Sample flicker if using scaled UVs. --- .../MaterialGenerator.Textures.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index ed79b5edc..475809b6c 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -584,7 +584,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) { // Procedural Texture Sample textureBox->Cache = writeLocal(Value::InitForZero(ValueType::Float4), node); - createGradients(node); + // createGradients(node); //PE: Not needed should always use the scaled or not scaled uv. auto proceduralSample = String::Format(TEXT( " {{\n" " float3 weights;\n" @@ -613,19 +613,19 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) " uv1 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex1)) * 43758.5453);\n" " uv2 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex2)) * 43758.5453);\n" " uv3 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex3)) * 43758.5453);\n" - " float4 tex1 = {1}.SampleGrad({4}, uv1, {2}, {3}, {6}) * weights.x;\n" - " float4 tex2 = {1}.SampleGrad({4}, uv2, {2}, {3}, {6}) * weights.y;\n" - " float4 tex3 = {1}.SampleGrad({4}, uv3, {2}, {3}, {6}) * weights.z;\n" - " {5} = tex1 + tex2 + tex3;\n" + " float2 fdx = ddx({0});\n" + " float2 fdy = ddy({0});\n" + " float4 tex1 = {1}.SampleGrad({2}, uv1, fdx, fdy, {4}) * weights.x;\n" + " float4 tex2 = {1}.SampleGrad({2}, uv2, fdx, fdy, {4}) * weights.y;\n" + " float4 tex3 = {1}.SampleGrad({2}, uv3, fdx, fdy, {4}) * weights.z;\n" + " {3} = tex1 + tex2 + tex3;\n" " }}\n" ), uvs.Value, // {0} texture.Value, // {1} - _ddx.Value, // {2} - _ddy.Value, // {3} - samplerName, // {4} - textureBox->Cache.Value, // {5} - offset.Value // {6} + samplerName, // {2} + textureBox->Cache.Value, // {3} + offset.Value // {4} ); _writer.Write(*proceduralSample); From 8174e8ab775267ce69e7061a42f56db93c3174a3 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Thu, 28 Dec 2023 22:24:34 +0100 Subject: [PATCH 005/103] fix getting file access time for Linux --- Source/Engine/Platform/Linux/LinuxFileSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index 97cde4a1c..c56027cd2 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -657,7 +657,7 @@ DateTime LinuxFileSystem::GetFileLastEditTime(const StringView& path) return DateTime::MinValue(); } - const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime); + const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime); return UnixEpoch + timeSinceEpoch; } From 21c4667f8c8fdc4870df1225719fb85d1264bce6 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Thu, 28 Dec 2023 22:36:33 +0100 Subject: [PATCH 006/103] same problem for Android and Apple --- Source/Engine/Platform/Android/AndroidFileSystem.cpp | 2 +- Source/Engine/Platform/Apple/AppleFileSystem.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Platform/Android/AndroidFileSystem.cpp b/Source/Engine/Platform/Android/AndroidFileSystem.cpp index a04fc0229..a939124f6 100644 --- a/Source/Engine/Platform/Android/AndroidFileSystem.cpp +++ b/Source/Engine/Platform/Android/AndroidFileSystem.cpp @@ -517,7 +517,7 @@ DateTime AndroidFileSystem::GetFileLastEditTime(const StringView& path) const StringAsANSI<> pathANSI(*path, path.Length()); if (stat(pathANSI.Get(), &fileInfo) == -1) return DateTime::MinValue(); - const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime); + const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime); const DateTime UnixEpoch(1970, 1, 1); return UnixEpoch + timeSinceEpoch; } diff --git a/Source/Engine/Platform/Apple/AppleFileSystem.cpp b/Source/Engine/Platform/Apple/AppleFileSystem.cpp index 9e306019a..3fd52a54b 100644 --- a/Source/Engine/Platform/Apple/AppleFileSystem.cpp +++ b/Source/Engine/Platform/Apple/AppleFileSystem.cpp @@ -498,7 +498,7 @@ DateTime AppleFileSystem::GetFileLastEditTime(const StringView& path) return DateTime::MinValue(); } - const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime); + const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime); return UnixEpoch + timeSinceEpoch; } From e851d18227fcbf26aca8968c2e2a1b69b9c9cb29 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Fri, 29 Dec 2023 01:20:17 +0100 Subject: [PATCH 007/103] same in UnixFile.cpp --- Source/Engine/Platform/Unix/UnixFile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Unix/UnixFile.cpp b/Source/Engine/Platform/Unix/UnixFile.cpp index 5fbc75f2e..9cf629d2c 100644 --- a/Source/Engine/Platform/Unix/UnixFile.cpp +++ b/Source/Engine/Platform/Unix/UnixFile.cpp @@ -137,7 +137,7 @@ DateTime UnixFile::GetLastWriteTime() const { return DateTime::MinValue(); } - const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime); + const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime); const DateTime unixEpoch(1970, 1, 1); return unixEpoch + timeSinceEpoch; } From f10840efb483739be6b048c0204bf0478e57d8f0 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 29 Dec 2023 19:24:00 +0200 Subject: [PATCH 008/103] Fix uninitialized value of `NetworkConfig` `NetworkDriver` field --- Source/Engine/Networking/NetworkConfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Networking/NetworkConfig.h b/Source/Engine/Networking/NetworkConfig.h index b92d822cb..0768d3402 100644 --- a/Source/Engine/Networking/NetworkConfig.h +++ b/Source/Engine/Networking/NetworkConfig.h @@ -43,7 +43,7 @@ API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkConfi /// /// Object is managed by the created network peer (will be deleted on peer shutdown). API_FIELD() - ScriptingObject* NetworkDriver; + ScriptingObject* NetworkDriver = nullptr; /// /// The upper limit on how many peers can join when we're listening. From 8841a603f650757d89a2032c2ef46074de62c849 Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Fri, 29 Dec 2023 23:15:03 +0100 Subject: [PATCH 009/103] Virtual proxy --- Source/Editor/Content/Proxy/AssetProxy.cs | 16 +++++++++++ .../Editor/Modules/ContentDatabaseModule.cs | 28 ++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Content/Proxy/AssetProxy.cs b/Source/Editor/Content/Proxy/AssetProxy.cs index 3bd23ad03..7dbd178a9 100644 --- a/Source/Editor/Content/Proxy/AssetProxy.cs +++ b/Source/Editor/Content/Proxy/AssetProxy.cs @@ -22,6 +22,22 @@ namespace FlaxEditor.Content /// public abstract string TypeName { get; } + /// + /// Gets a value indicating whether this instance is virtual Proxy not linked to any asset. + /// + protected virtual bool IsVirtual { get; } + + /// + /// Determines whether [is virtual proxy]. + /// + /// + /// true if [is virtual proxy]; otherwise, false. + /// + public bool IsVirtualProxy() + { + return IsVirtual && CanExport == false; + } + /// /// Checks if this proxy supports the given asset type id at the given path. /// diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index 80f419a6f..fa023bf7b 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -196,6 +196,25 @@ namespace FlaxEditor.Modules return null; } + /// + /// Gets the virtual proxy object from given path. + ///

use case if the asset u trying to display is not a flax asset but u like to add custom functionality + ///

to context menu,or display it the asset + ///
+ /// The asset path. + /// Asset proxy or null if cannot find. + public AssetProxy GetAssetVirtuallProxy(string path) + { + for (int i = 0; i < Proxy.Count; i++) + { + if (Proxy[i] is AssetProxy proxy && proxy.IsVirtualProxy() && path.EndsWith(proxy.FileExtension, StringComparison.OrdinalIgnoreCase)) + { + return proxy; + } + } + + return null; + } /// /// Refreshes the given item folder. Tries to find new content items and remove not existing ones. @@ -996,7 +1015,14 @@ namespace FlaxEditor.Modules item = proxy?.ConstructItem(path, assetInfo.TypeName, ref assetInfo.ID); } if (item == null) - item = new FileItem(path); + { + var proxy = GetAssetVirtuallProxy(path); + item = proxy?.ConstructItem(path, assetInfo.TypeName, ref assetInfo.ID); + if (item == null) + { + item = new FileItem(path); + } + } // Link item.ParentFolder = parent.Folder; From 343d7b4973f8e6d32905c14e89ad817d4b8a790e Mon Sep 17 00:00:00 2001 From: MineBill Date: Sat, 30 Dec 2023 18:30:26 +0200 Subject: [PATCH 010/103] Save the dock state of custom editor windows between script reloads. --- Source/Editor/CustomEditorWindow.cs | 9 ++++-- Source/Editor/GUI/Docking/DockPanel.cs | 12 ++++---- Source/Editor/GUI/Docking/DockWindow.cs | 6 ++-- Source/Editor/Modules/WindowsModule.cs | 40 +++++++++++++++++++++++-- 4 files changed, 54 insertions(+), 13 deletions(-) diff --git a/Source/Editor/CustomEditorWindow.cs b/Source/Editor/CustomEditorWindow.cs index 242b4d28d..5c3fedd6c 100644 --- a/Source/Editor/CustomEditorWindow.cs +++ b/Source/Editor/CustomEditorWindow.cs @@ -1,9 +1,9 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using FlaxEditor.CustomEditors; +using FlaxEditor.GUI.Docking; using FlaxEditor.Windows; using FlaxEngine.GUI; -using DockState = FlaxEditor.GUI.Docking.DockState; namespace FlaxEditor { @@ -97,9 +97,12 @@ namespace FlaxEditor /// Shows the window. /// /// Initial window state. - public void Show(DockState state = DockState.Float) + /// The panel to dock to, if any. + /// Only used if is set. If true the window will be selected after docking it. + /// The splitter value to use if toDock is not null. If not specified, a default value will be used. + public void Show(DockState state = DockState.Float, DockPanel toDock = null, bool autoSelect = true, float? splitterValue = null) { - _win.Show(state); + _win.Show(state, toDock, autoSelect, splitterValue); } } } diff --git a/Source/Editor/GUI/Docking/DockPanel.cs b/Source/Editor/GUI/Docking/DockPanel.cs index b04aad08c..df6a75bef 100644 --- a/Source/Editor/GUI/Docking/DockPanel.cs +++ b/Source/Editor/GUI/Docking/DockPanel.cs @@ -518,9 +518,9 @@ namespace FlaxEditor.GUI.Docking } } - internal virtual void DockWindowInternal(DockState state, DockWindow window) + internal virtual void DockWindowInternal(DockState state, DockWindow window, bool autoSelect = true, float? splitterValue = null) { - DockWindow(state, window); + DockWindow(state, window, autoSelect, splitterValue); } /// @@ -528,7 +528,9 @@ namespace FlaxEditor.GUI.Docking /// /// The state. /// The window. - protected virtual void DockWindow(DockState state, DockWindow window) + /// Whether or not to automatically select the window after docking it. + /// The splitter value to use when docking to window. + protected virtual void DockWindow(DockState state, DockWindow window, bool autoSelect = true, float? splitterValue = null) { CreateTabsProxy(); @@ -536,12 +538,12 @@ namespace FlaxEditor.GUI.Docking if (state == DockState.DockFill) { // Add tab - AddTab(window); + AddTab(window, autoSelect); } else { // Create child panel - var dockPanel = CreateChildPanel(state, DefaultSplitterValue); + var dockPanel = CreateChildPanel(state, splitterValue ?? DefaultSplitterValue); // Dock window as a tab in a child panel dockPanel.DockWindow(DockState.DockFill, window); diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index 72c74ad9e..15608d3e1 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -214,7 +214,9 @@ namespace FlaxEditor.GUI.Docking /// /// Initial window state. /// Panel to dock to it. - public void Show(DockState state = DockState.Float, DockPanel toDock = null) + /// Only used if is set. If true the window will be selected after docking it. + /// Only used if is set. The splitter value to use. If not specified, a default value will be used. + public void Show(DockState state = DockState.Float, DockPanel toDock = null, bool autoSelect = true, float? splitterValue = null) { if (state == DockState.Hidden) { @@ -232,7 +234,7 @@ namespace FlaxEditor.GUI.Docking Undock(); // Then dock - (toDock ?? _masterPanel).DockWindowInternal(state, this); + (toDock ?? _masterPanel).DockWindowInternal(state, this, autoSelect, splitterValue); OnShow(); PerformLayout(); } diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 245fd4d5d..3e88a379c 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -36,6 +36,17 @@ namespace FlaxEditor.Modules { public string AssemblyName; public string TypeName; + + public DockState DockState; + public DockPanel DockedTo; + public float? SplitterValue = null; + + public bool SelectOnShow = false; + + // Constructor, to allow for default values + public WindowRestoreData() + { + } } private readonly List _restoreWindows = new List(); @@ -802,10 +813,33 @@ namespace FlaxEditor.Modules if (constructor == null || type.IsGenericType) return; - WindowRestoreData winData; + var winData = new WindowRestoreData(); + var panel = win.Window.ParentDockPanel; + + // Ensure that this window is only selected following recompilation + // if it was the active tab in its dock panel. Otherwise, there is a + // risk of interrupting the user's workflow by potentially selecting + // background tabs. + winData.SelectOnShow = panel.SelectedTab == win.Window; + if (panel is FloatWindowDockPanel) + { + winData.DockState = DockState.Float; + } + else + { + if (panel.TabsCount > 1) + { + winData.DockState = DockState.DockFill; + winData.DockedTo = panel; + }else + { + winData.DockState = panel.TryGetDockState(out var splitterValue); + winData.DockedTo = panel.ParentDockPanel; + winData.SplitterValue = splitterValue; + } + } winData.AssemblyName = type.Assembly.GetName().Name; winData.TypeName = type.FullName; - // TODO: cache and restore docking info _restoreWindows.Add(winData); } @@ -824,7 +858,7 @@ namespace FlaxEditor.Modules if (type != null) { var win = (CustomEditorWindow)Activator.CreateInstance(type); - win.Show(); + win.Show(winData.DockState, winData.DockedTo, winData.SelectOnShow, winData.SplitterValue); } } } From 1e797c244f24d685f5cc4aa2643f0aeaef391ddf Mon Sep 17 00:00:00 2001 From: MineBill Date: Sat, 30 Dec 2023 18:57:33 +0200 Subject: [PATCH 011/103] Restore floating window data. --- Source/Editor/Modules/WindowsModule.cs | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 3e88a379c..a935e73f2 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -43,6 +43,11 @@ namespace FlaxEditor.Modules public bool SelectOnShow = false; + public bool Maximize; + public bool Minimize; + public Float2 FloatSize; + public Float2 FloatPosition; + // Constructor, to allow for default values public WindowRestoreData() { @@ -824,6 +829,11 @@ namespace FlaxEditor.Modules if (panel is FloatWindowDockPanel) { winData.DockState = DockState.Float; + var window = win.Window.RootWindow.Window; + winData.FloatPosition = window.Position; + winData.FloatSize = window.ClientSize; + winData.Maximize = window.IsMaximized; + winData.Minimize = window.IsMinimized; } else { @@ -859,6 +869,23 @@ namespace FlaxEditor.Modules { var win = (CustomEditorWindow)Activator.CreateInstance(type); win.Show(winData.DockState, winData.DockedTo, winData.SelectOnShow, winData.SplitterValue); + if (winData.DockState == DockState.Float) + { + var window = win.Window.RootWindow.Window; + window.Position = winData.FloatPosition; + if (winData.Maximize) + { + window.Maximize(); + } + else if (winData.Minimize) + { + window.Minimize(); + } + else + { + window.ClientSize = winData.FloatSize; + } + } } } } From 4b6ada72898980bd6e96793c511af2d58fe6736f Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 31 Dec 2023 13:10:20 +0200 Subject: [PATCH 012/103] Fix profiling hotkeys not working while profiler window is closed --- Source/Editor/Utilities/Utils.cs | 13 +++++++++++-- Source/Editor/Windows/Profiler/ProfilerWindow.cs | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 7184391d5..3d790ff81 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -1310,8 +1310,17 @@ namespace FlaxEditor.Utilities inputActions.Add(options => options.BuildSDF, Editor.Instance.BuildAllMeshesSDF); inputActions.Add(options => options.TakeScreenshot, Editor.Instance.Windows.TakeScreenshot); inputActions.Add(options => options.ProfilerWindow, () => Editor.Instance.Windows.ProfilerWin.FocusOrShow()); - inputActions.Add(options => options.ProfilerStartStop, () => { Editor.Instance.Windows.ProfilerWin.LiveRecording = !Editor.Instance.Windows.ProfilerWin.LiveRecording; Editor.Instance.UI.AddStatusMessage($"Profiling {(Editor.Instance.Windows.ProfilerWin.LiveRecording ? "started" : "stopped")}."); }); - inputActions.Add(options => options.ProfilerClear, () => { Editor.Instance.Windows.ProfilerWin.Clear(); Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); }); + inputActions.Add(options => options.ProfilerStartStop, () => + { + bool recording = !Editor.Instance.Windows.ProfilerWin.LiveRecording; + Editor.Instance.Windows.ProfilerWin.LiveRecording = recording; + Editor.Instance.UI.AddStatusMessage($"Profiling {(recording ? "started" : "stopped")}."); + }); + inputActions.Add(options => options.ProfilerClear, () => + { + Editor.Instance.Windows.ProfilerWin.Clear(); + Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); + }); inputActions.Add(options => options.SaveScenes, () => Editor.Instance.Scene.SaveScenes()); inputActions.Add(options => options.CloseScenes, () => Editor.Instance.Scene.CloseAllScenes()); inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution()); diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs index f97e943ad..7a3924d1b 100644 --- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs +++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs @@ -38,6 +38,7 @@ namespace FlaxEditor.Windows.Profiler if (value != LiveRecording) { _liveRecordingButton.Checked = value; + OnLiveRecordingChanged(); } } } From 9077e0cf226acf953fe62cca66d9c09a4de3f97d Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 31 Dec 2023 14:36:04 +0200 Subject: [PATCH 013/103] Fix crash while moving simulation disabled kinematic actors --- Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index cbc381142..af6c6403b 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -2260,7 +2260,13 @@ void PhysicsBackend::SetRigidActorPose(void* actor, const Vector3& position, con if (kinematic) { auto actorPhysX = (PxRigidDynamic*)actor; - actorPhysX->setKinematicTarget(trans); + if (actorPhysX->getActorFlags() & PxActorFlag::eDISABLE_SIMULATION) + { + // Ensures the disabled kinematic actor ends up in the correct pose after enabling simulation + actorPhysX->setGlobalPose(trans, wakeUp); + } + else + actorPhysX->setKinematicTarget(trans); } else { From bf6cb90a78b2c493dcb21e54318173be96e86dc0 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Sun, 31 Dec 2023 15:09:07 -0500 Subject: [PATCH 014/103] Expose partition mode, and cascade spacing. --- .../SceneGraph/Actors/DirectionalLightNode.cs | 11 +++++++ Source/Engine/Graphics/Enums.h | 21 +++++++++++++ .../Engine/Level/Actors/DirectionalLight.cpp | 18 +++++++++++ Source/Engine/Level/Actors/DirectionalLight.h | 30 +++++++++++++++++++ Source/Engine/Renderer/RenderList.h | 6 ++++ Source/Engine/Renderer/ShadowsPass.cpp | 17 ++++------- 6 files changed, 91 insertions(+), 12 deletions(-) diff --git a/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs b/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs index 5363d1f55..1af3812f7 100644 --- a/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs +++ b/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs @@ -2,6 +2,17 @@ using FlaxEngine; +namespace FlaxEngine +{ + public partial class DirectionalLight + { + bool ShowCascade1 => CascadeCount >= 1 && PartitionMode == PartitionMode.Manual; + bool ShowCascade2 => CascadeCount >= 2 && PartitionMode == PartitionMode.Manual; + bool ShowCascade3 => CascadeCount >= 3 && PartitionMode == PartitionMode.Manual; + bool ShowCascade4 => CascadeCount >= 4 && PartitionMode == PartitionMode.Manual; + } +} + namespace FlaxEditor.SceneGraph.Actors { /// diff --git a/Source/Engine/Graphics/Enums.h b/Source/Engine/Graphics/Enums.h index bbf9c0de8..6ae9ad4ec 100644 --- a/Source/Engine/Graphics/Enums.h +++ b/Source/Engine/Graphics/Enums.h @@ -236,6 +236,27 @@ API_ENUM(Attributes="Flags") enum class ShadowsCastingMode DECLARE_ENUM_OPERATORS(ShadowsCastingMode); +/// +/// The partitioning mode for shadow cascades. +/// +API_ENUM() enum class PartitionMode +{ + /// + /// Internally defined cascade splits. + /// + Manual = 0, + + /// + /// Logarithmic cascade splits. + /// + Logarithmic = 1, + + /// + /// PSSM cascade splits. + /// + PSSM = 2, +}; + /// /// Identifies expected GPU resource use during rendering. The usage directly reflects whether a resource is accessible by the CPU and/or the GPU. /// diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp index 43450fd8e..32bb61653 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.cpp +++ b/Source/Engine/Level/Actors/DirectionalLight.cpp @@ -41,6 +41,12 @@ void DirectionalLight::Draw(RenderContext& renderContext) data.RenderedVolumetricFog = 0; data.ShadowsMode = ShadowsMode; data.CascadeCount = CascadeCount; + data.Cascade1Spacing = Cascade1Spacing; + data.Cascade2Spacing = Cascade2Spacing; + data.Cascade3Spacing = Cascade3Spacing; + data.Cascade4Spacing = Cascade4Spacing; + + data.PartitionMode = PartitionMode; data.ContactShadowsLength = ContactShadowsLength; data.StaticFlags = GetStaticFlags(); data.ID = GetID(); @@ -56,6 +62,12 @@ void DirectionalLight::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_GET_OTHER_OBJ(DirectionalLight); SERIALIZE(CascadeCount); + SERIALIZE(Cascade1Spacing); + SERIALIZE(Cascade2Spacing); + SERIALIZE(Cascade3Spacing); + SERIALIZE(Cascade4Spacing); + + SERIALIZE(PartitionMode); } void DirectionalLight::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -64,6 +76,12 @@ void DirectionalLight::Deserialize(DeserializeStream& stream, ISerializeModifier LightWithShadow::Deserialize(stream, modifier); DESERIALIZE(CascadeCount); + DESERIALIZE(Cascade1Spacing); + DESERIALIZE(Cascade2Spacing); + DESERIALIZE(Cascade3Spacing); + DESERIALIZE(Cascade4Spacing); + + DESERIALIZE(PartitionMode); } bool DirectionalLight::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) diff --git a/Source/Engine/Level/Actors/DirectionalLight.h b/Source/Engine/Level/Actors/DirectionalLight.h index d5f31324a..cb29112c0 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.h +++ b/Source/Engine/Level/Actors/DirectionalLight.h @@ -12,12 +12,42 @@ class FLAXENGINE_API DirectionalLight : public LightWithShadow { DECLARE_SCENE_OBJECT(DirectionalLight); public: + /// + /// The partitioning mode for the shadow cascades. + /// + API_FIELD(Attributes = "EditorOrder(64), DefaultValue(PartitionMode.Manual), EditorDisplay(\"Shadow\")") + PartitionMode PartitionMode = PartitionMode::Manual; + /// /// The number of cascades used for slicing the range of depth covered by the light during shadow rendering. Values are 1, 2 or 4 cascades; a typical scene uses 4 cascades. /// API_FIELD(Attributes="EditorOrder(65), DefaultValue(4), Limit(1, 4), EditorDisplay(\"Shadow\")") int32 CascadeCount = 4; + /// + /// Percentage of the shadow distance used by the first cascade. + /// + API_FIELD(Attributes = "EditorOrder(66), DefaultValue(0.05f), VisibleIf(nameof(ShowCascade1)), Limit(0, 1, 0.001f), EditorDisplay(\"Shadow\")") + float Cascade1Spacing = 0.05f; + + /// + /// Percentage of the shadow distance used by the second cascade. + /// + API_FIELD(Attributes = "EditorOrder(67), DefaultValue(0.15f), VisibleIf(nameof(ShowCascade2)), Limit(0, 1, 0.001f), EditorDisplay(\"Shadow\")") + float Cascade2Spacing = 0.15f; + + /// + /// Percentage of the shadow distance used by the third cascade. + /// + API_FIELD(Attributes = "EditorOrder(68), DefaultValue(0.50f), VisibleIf(nameof(ShowCascade3)), Limit(0, 1, 0.001f), EditorDisplay(\"Shadow\")") + float Cascade3Spacing = 0.50f; + + /// + /// Percentage of the shadow distance used by the fourth cascade. + /// + API_FIELD(Attributes = "EditorOrder(69), DefaultValue(1.0f), VisibleIf(nameof(ShowCascade4)), Limit(0, 1, 0.001f), EditorDisplay(\"Shadow\")") + float Cascade4Spacing = 1.0f; + public: // [LightWithShadow] void Draw(RenderContext& renderContext) override; diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index a2cd48696..3f9a25694 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -46,6 +46,12 @@ struct RendererDirectionalLightData float ShadowsDistance; int32 CascadeCount; + float Cascade1Spacing; + float Cascade2Spacing; + float Cascade3Spacing; + float Cascade4Spacing; + + PartitionMode PartitionMode; float ContactShadowsLength; ShadowsCastingMode ShadowsMode; diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index ba326b728..937cd9e99 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -247,19 +247,12 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r minDistance = cameraNear; maxDistance = cameraNear + shadowsDistance; - // TODO: expose partition mode? - enum class PartitionMode - { - Manual = 0, - Logarithmic = 1, - PSSM = 2, - }; - PartitionMode partitionMode = PartitionMode::Manual; + PartitionMode partitionMode = light.PartitionMode; float pssmFactor = 0.5f; - float splitDistance0 = 0.05f; - float splitDistance1 = 0.15f; - float splitDistance2 = 0.50f; - float splitDistance3 = 1.00f; + float splitDistance0 = light.Cascade1Spacing; + float splitDistance1 = Math::Max(splitDistance0, light.Cascade2Spacing); + float splitDistance2 = Math::Max(splitDistance1, light.Cascade3Spacing); + float splitDistance3 = Math::Max(splitDistance2, light.Cascade4Spacing); // Compute the split distances based on the partitioning mode if (partitionMode == PartitionMode::Manual) From 826a330f636dacc3c9947bb7035a30e9daea0396 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 31 Dec 2023 16:06:38 -0600 Subject: [PATCH 015/103] Add asset reference to Guid editor for easy picking of specific Guid based on type in editor. --- .../CustomEditors/Editors/GuidEditor.cs | 82 +++++++++++++++++-- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/GuidEditor.cs b/Source/Editor/CustomEditors/Editors/GuidEditor.cs index 28d93aa8e..adba58e26 100644 --- a/Source/Editor/CustomEditors/Editors/GuidEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GuidEditor.cs @@ -1,8 +1,12 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEditor.CustomEditors.Elements; +using FlaxEditor.GUI; +using FlaxEditor.Scripting; using FlaxEngine; +using FlaxEngine.Utilities; namespace FlaxEditor.CustomEditors.Editors { @@ -13,6 +17,9 @@ namespace FlaxEditor.CustomEditors.Editors public sealed class GuidEditor : CustomEditor { private TextBoxElement _element; + private AssetPicker _picker; + private bool _isReference; + private bool _isRefreshing; /// public override DisplayStyle Style => DisplayStyle.Inline; @@ -20,8 +27,57 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - _element = layout.TextBox(); - _element.TextBox.EditEnd += OnEditEnd; + + var attributes = Values.GetAttributes(); + var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute); + if (assetReference != null) + { + _picker = layout.Custom().CustomControl; + ScriptType assetType = new ScriptType(); + + float height = 48; + if (assetReference.UseSmallPicker) + height = 32; + + if (string.IsNullOrEmpty(assetReference.TypeName)) + { + assetType = ScriptType.Void; + } + else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.') + { + // Generic file picker + assetType = ScriptType.Null; + _picker.Validator.FileExtension = assetReference.TypeName; + } + else + { + var customType = TypeUtils.GetType(assetReference.TypeName); + if (customType != ScriptType.Null) + assetType = customType; + else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName)) + Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName)); + else + assetType = ScriptType.Void; + } + + _picker.Validator.AssetType = assetType; + _picker.Height = height; + _picker.SelectedItemChanged += OnSelectedItemChanged; + _isReference = true; + + } + else + { + _element = layout.TextBox(); + _element.TextBox.EditEnd += OnEditEnd; + } + } + + private void OnSelectedItemChanged() + { + if (_isRefreshing) + return; + SetValue(_picker.Validator.SelectedID); } private void OnEditEnd() @@ -39,13 +95,27 @@ namespace FlaxEditor.CustomEditors.Editors if (HasDifferentValues) { - _element.TextBox.Text = string.Empty; - _element.TextBox.WatermarkText = "Different values"; + if (_isReference) + { + //_picker.Validator.SelectedID = (Guid)Values[0]; + } + else + { + _element.TextBox.Text = string.Empty; + _element.TextBox.WatermarkText = "Different values"; + } } else { - _element.TextBox.Text = ((Guid)Values[0]).ToString("D"); - _element.TextBox.WatermarkText = string.Empty; + if (_isReference) + { + _picker.Validator.SelectedID = (Guid)Values[0]; + } + else + { + _element.TextBox.Text = ((Guid)Values[0]).ToString("D"); + _element.TextBox.WatermarkText = string.Empty; + } } } } From 57d4f0ff7ddcc016675c5329ec792436d1d5c545 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 31 Dec 2023 16:09:32 -0600 Subject: [PATCH 016/103] Clean up code --- Source/Editor/CustomEditors/Editors/GuidEditor.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/GuidEditor.cs b/Source/Editor/CustomEditors/Editors/GuidEditor.cs index adba58e26..a151f308a 100644 --- a/Source/Editor/CustomEditors/Editors/GuidEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GuidEditor.cs @@ -92,12 +92,12 @@ namespace FlaxEditor.CustomEditors.Editors public override void Refresh() { base.Refresh(); - + _isRefreshing = true; if (HasDifferentValues) { if (_isReference) { - //_picker.Validator.SelectedID = (Guid)Values[0]; + // Not supported } else { @@ -117,6 +117,7 @@ namespace FlaxEditor.CustomEditors.Editors _element.TextBox.WatermarkText = string.Empty; } } + _isRefreshing = false; } } } From 8bff9556defcaa66c28024c5f27f512261ff3be2 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 31 Dec 2023 16:36:34 -0600 Subject: [PATCH 017/103] Code cleanup --- Source/Editor/CustomEditors/Editors/GuidEditor.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/GuidEditor.cs b/Source/Editor/CustomEditors/Editors/GuidEditor.cs index a151f308a..4573def77 100644 --- a/Source/Editor/CustomEditors/Editors/GuidEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GuidEditor.cs @@ -27,7 +27,6 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - var attributes = Values.GetAttributes(); var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute); if (assetReference != null) @@ -64,7 +63,6 @@ namespace FlaxEditor.CustomEditors.Editors _picker.Height = height; _picker.SelectedItemChanged += OnSelectedItemChanged; _isReference = true; - } else { From 4839ef5ddc6e913c398ffce621eb73c1f7171f08 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Sun, 31 Dec 2023 19:42:13 -0500 Subject: [PATCH 018/103] Move all ShowCascadeX definitions into an engine class instead of an editor class to allow game builds. --- .../Editor/SceneGraph/Actors/DirectionalLightNode.cs | 11 ----------- Source/Engine/Level/DirectionalLight.cs | 10 ++++++++++ 2 files changed, 10 insertions(+), 11 deletions(-) create mode 100644 Source/Engine/Level/DirectionalLight.cs diff --git a/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs b/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs index 1af3812f7..5363d1f55 100644 --- a/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs +++ b/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs @@ -2,17 +2,6 @@ using FlaxEngine; -namespace FlaxEngine -{ - public partial class DirectionalLight - { - bool ShowCascade1 => CascadeCount >= 1 && PartitionMode == PartitionMode.Manual; - bool ShowCascade2 => CascadeCount >= 2 && PartitionMode == PartitionMode.Manual; - bool ShowCascade3 => CascadeCount >= 3 && PartitionMode == PartitionMode.Manual; - bool ShowCascade4 => CascadeCount >= 4 && PartitionMode == PartitionMode.Manual; - } -} - namespace FlaxEditor.SceneGraph.Actors { /// diff --git a/Source/Engine/Level/DirectionalLight.cs b/Source/Engine/Level/DirectionalLight.cs new file mode 100644 index 000000000..201d35dc1 --- /dev/null +++ b/Source/Engine/Level/DirectionalLight.cs @@ -0,0 +1,10 @@ +namespace FlaxEngine +{ + public partial class DirectionalLight + { + bool ShowCascade1 => CascadeCount >= 1 && PartitionMode == PartitionMode.Manual; + bool ShowCascade2 => CascadeCount >= 2 && PartitionMode == PartitionMode.Manual; + bool ShowCascade3 => CascadeCount >= 3 && PartitionMode == PartitionMode.Manual; + bool ShowCascade4 => CascadeCount >= 4 && PartitionMode == PartitionMode.Manual; + } +} From 5cf20b8eeeb647a59be05cad7d8598652395101b Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Sun, 31 Dec 2023 21:11:39 -0500 Subject: [PATCH 019/103] Add an option to skip existing materials when reimporting. --- Source/Engine/Tools/ModelTool/ModelTool.cpp | 14 ++++++++++++++ Source/Engine/Tools/ModelTool/ModelTool.h | 7 +++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index e2b2f5d8d..d0aa537f6 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -374,6 +374,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(InstanceToImportAs); SERIALIZE(ImportTextures); SERIALIZE(RestoreMaterialsOnReimport); + SERIALIZE(SkipExistingMaterialsOnReimport); SERIALIZE(GenerateSDF); SERIALIZE(SDFResolution); SERIALIZE(SplitObjects); @@ -422,6 +423,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(InstanceToImportAs); DESERIALIZE(ImportTextures); DESERIALIZE(RestoreMaterialsOnReimport); + DESERIALIZE(SkipExistingMaterialsOnReimport); DESERIALIZE(GenerateSDF); DESERIALIZE(SDFResolution); DESERIALIZE(SplitObjects); @@ -1154,6 +1156,18 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option continue; } + // Skip any materials that already exist from the model. + // This allows the use of "import as material instances" without material properties getting overridden on each import. + if (options.SkipExistingMaterialsOnReimport) + { + AssetInfo info; + if (Content::GetAssetInfo(assetPath, info)) + { + material.AssetID = info.ID; + continue; + } + } + if (options.ImportMaterialsAsInstances) { // Create material instance diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 416505631..629da7a3b 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -268,9 +268,12 @@ public: // If checked, the importer will import texture files used by the model and any embedded texture resources. API_FIELD(Attributes="EditorOrder(410), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))") bool ImportTextures = true; - // If checked, the importer will try to keep the model's current material slots, instead of importing materials from the source file. - API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Keep Material Slots on Reimport\"), VisibleIf(nameof(ShowGeometry))") + // If checked, the importer will try to keep the model's current overridden material slots, instead of importing materials from the source file. + API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Keep Overridden Materials\"), VisibleIf(nameof(ShowGeometry))") bool RestoreMaterialsOnReimport = true; + // If checked, the importer will not reimport any material from this model which already exist in the sub-asset folder. + API_FIELD(Attributes = "EditorOrder(421), EditorDisplay(\"Materials\", \"Skip Existing Materials\"), VisibleIf(nameof(ShowGeometry))") + bool SkipExistingMaterialsOnReimport = true; public: // SDF From 8bcc526dd61d775accc79abb160281ce52c14273 Mon Sep 17 00:00:00 2001 From: Menotdan <32620310+Menotdan@users.noreply.github.com> Date: Mon, 1 Jan 2024 01:24:36 -0500 Subject: [PATCH 020/103] Add support for multiple VisibleIf attributes, and properly set up the VisibleIf entries for ImportMaterialsAsInstances and InstanceToImportAs. --- .../CustomEditors/Editors/GenericEditor.cs | 91 +++++++++++-------- .../Attributes/Editor/VisibleIfAttribute.cs | 4 +- Source/Engine/Tools/ModelTool/ModelTool.h | 4 +- 3 files changed, 57 insertions(+), 42 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs index 68c24a675..3359054fe 100644 --- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs @@ -66,9 +66,9 @@ namespace FlaxEditor.CustomEditors.Editors public HeaderAttribute Header; /// - /// The visible if attribute. + /// The visible if attributes. /// - public VisibleIfAttribute VisibleIf; + public VisibleIfAttribute[] VisibleIfs; /// /// The read-only attribute usage flag. @@ -128,7 +128,7 @@ namespace FlaxEditor.CustomEditors.Editors CustomEditorAlias = (CustomEditorAliasAttribute)attributes.FirstOrDefault(x => x is CustomEditorAliasAttribute); Space = (SpaceAttribute)attributes.FirstOrDefault(x => x is SpaceAttribute); Header = (HeaderAttribute)attributes.FirstOrDefault(x => x is HeaderAttribute); - VisibleIf = (VisibleIfAttribute)attributes.FirstOrDefault(x => x is VisibleIfAttribute); + VisibleIfs = attributes.OfType().ToArray(); IsReadOnly = attributes.FirstOrDefault(x => x is ReadOnlyAttribute) != null; ExpandGroups = attributes.FirstOrDefault(x => x is ExpandGroupsAttribute) != null; @@ -210,17 +210,24 @@ namespace FlaxEditor.CustomEditors.Editors private struct VisibleIfCache { public ScriptMemberInfo Target; - public ScriptMemberInfo Source; + public ScriptMemberInfo[] Sources; public PropertiesListElement PropertiesList; public GroupElement Group; - public bool Invert; + public bool[] InversionList; public int LabelIndex; public bool GetValue(object instance) { - var value = (bool)Source.GetValue(instance); - if (Invert) - value = !value; + bool value = true; + + for (int i = 0; i < Sources.Length; i++) + { + bool currentValue = (bool)Sources[i].GetValue(instance); + if (InversionList[i]) + currentValue = !currentValue; + + value = value && currentValue; + } return value; } } @@ -298,40 +305,48 @@ namespace FlaxEditor.CustomEditors.Editors return items; } - private static ScriptMemberInfo GetVisibleIfSource(ScriptType type, VisibleIfAttribute visibleIf) + private static ScriptMemberInfo[] GetVisibleIfSources(ScriptType type, VisibleIfAttribute[] visibleIfs) { - var property = type.GetProperty(visibleIf.MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); - if (property != ScriptMemberInfo.Null) + ScriptMemberInfo[] members = Array.Empty(); + + for (int i = 0; i < visibleIfs.Length; i++) { - if (!property.HasGet) + var property = type.GetProperty(visibleIfs[i].MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + if (property != ScriptMemberInfo.Null) { - Debug.LogError("Invalid VisibleIf rule. Property has missing getter " + visibleIf.MemberName); - return ScriptMemberInfo.Null; + if (!property.HasGet) + { + Debug.LogError("Invalid VisibleIf rule. Property has missing getter " + visibleIfs[i].MemberName); + continue; + } + + if (property.ValueType.Type != typeof(bool)) + { + Debug.LogError("Invalid VisibleIf rule. Property has to return bool type " + visibleIfs[i].MemberName); + continue; + } + + members = members.Append(property).ToArray(); + continue; } - if (property.ValueType.Type != typeof(bool)) + var field = type.GetField(visibleIfs[i].MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + if (field != ScriptMemberInfo.Null) { - Debug.LogError("Invalid VisibleIf rule. Property has to return bool type " + visibleIf.MemberName); - return ScriptMemberInfo.Null; + if (field.ValueType.Type != typeof(bool)) + { + Debug.LogError("Invalid VisibleIf rule. Field has to be bool type " + visibleIfs[i].MemberName); + continue; + } + + members = members.Append(field).ToArray(); + continue; } - return property; + Debug.LogError("Invalid VisibleIf rule. Cannot find member " + visibleIfs[i].MemberName); } - var field = type.GetField(visibleIf.MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); - if (field != ScriptMemberInfo.Null) - { - if (field.ValueType.Type != typeof(bool)) - { - Debug.LogError("Invalid VisibleIf rule. Field has to be bool type " + visibleIf.MemberName); - return ScriptMemberInfo.Null; - } - - return field; - } - - Debug.LogError("Invalid VisibleIf rule. Cannot find member " + visibleIf.MemberName); - return ScriptMemberInfo.Null; + return members; } private static void GroupPanelCheckIfCanRevert(LayoutElementsContainer layout, ref bool canRevertReference, ref bool canRevertDefault) @@ -575,7 +590,7 @@ namespace FlaxEditor.CustomEditors.Editors protected virtual void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item) { int labelIndex = 0; - if ((item.IsReadOnly || item.VisibleIf != null) && + if ((item.IsReadOnly || item.VisibleIfs.Length > 0) && itemLayout.Children.Count > 0 && itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement propertiesListElement) { @@ -616,7 +631,7 @@ namespace FlaxEditor.CustomEditors.Editors } } } - if (item.VisibleIf != null && itemLayout.Children.Count > 0) + if (item.VisibleIfs.Length > 0 && itemLayout.Children.Count > 0) { PropertiesListElement list = null; GroupElement group = null; @@ -628,8 +643,8 @@ namespace FlaxEditor.CustomEditors.Editors return; // Get source member used to check rule - var sourceMember = GetVisibleIfSource(item.Info.DeclaringType, item.VisibleIf); - if (sourceMember == ScriptType.Null) + var sourceMembers = GetVisibleIfSources(item.Info.DeclaringType, item.VisibleIfs); + if (sourceMembers.Length == 0) return; // Resize cache @@ -645,11 +660,11 @@ namespace FlaxEditor.CustomEditors.Editors _visibleIfCaches[count] = new VisibleIfCache { Target = item.Info, - Source = sourceMember, + Sources = sourceMembers, PropertiesList = list, Group = group, LabelIndex = labelIndex, - Invert = item.VisibleIf.Invert, + InversionList = item.VisibleIfs.Select((x, i) => x.Invert).ToArray(), }; } } diff --git a/Source/Engine/Scripting/Attributes/Editor/VisibleIfAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/VisibleIfAttribute.cs index 5e690caa8..3d606370a 100644 --- a/Source/Engine/Scripting/Attributes/Editor/VisibleIfAttribute.cs +++ b/Source/Engine/Scripting/Attributes/Editor/VisibleIfAttribute.cs @@ -5,11 +5,11 @@ using System; namespace FlaxEngine { /// - /// Shows property/field in the editor only if the specified member has a given value. Can be used to hide properties based on other properties (also private properties). The given member has to be bool type. + /// Shows property/field in the editor only if the specified member has a given value. Can be used to hide properties based on other properties (also private properties). The given member has to be bool type. Multiple VisibleIf attributes can be added for additional conditions to be met. /// /// [Serializable] - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] public sealed class VisibleIfAttribute : Attribute { /// diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 416505631..7ae199136 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -260,10 +260,10 @@ public: API_FIELD(Attributes="EditorOrder(400), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))") bool ImportMaterials = true; // If checked, the importer will create the model's materials as instances of a base material. - API_FIELD(Attributes = "EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials))") + API_FIELD(Attributes = "EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials)), VisibleIf(nameof(ShowGeometry))") bool ImportMaterialsAsInstances = false; // The material used as the base material that will be instanced as the imported model's material. - API_FIELD(Attributes = "EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances))") + API_FIELD(Attributes = "EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances)), VisibleIf(nameof(ShowGeometry))") AssetReference InstanceToImportAs; // If checked, the importer will import texture files used by the model and any embedded texture resources. API_FIELD(Attributes="EditorOrder(410), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))") From a8913d89acc88808765df58e37afb44587b1d476 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 1 Jan 2024 17:46:33 +0200 Subject: [PATCH 021/103] Fix C++ Intellisense not working with latest version of Rider Project `IncludePath` doesn't seem to include NMake include paths anymore in Rider, populate the property with same paths to work around the issue. --- .../Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs index 4eba06cb0..4facc73c0 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs @@ -179,7 +179,10 @@ namespace Flax.Build.Projects.VisualStudio customizer.WriteVisualStudioBuildProperties(vsProject, platform, toolchain, configuration, vcProjectFileContent, vcFiltersFileContent, vcUserFileContent); vcProjectFileContent.AppendLine(string.Format(" {0}", targetBuildOptions.IntermediateFolder)); vcProjectFileContent.AppendLine(string.Format(" {0}", targetBuildOptions.OutputFolder)); - vcProjectFileContent.AppendLine(" "); + if (includePaths.Count != 0) + vcProjectFileContent.AppendLine(string.Format(" $(IncludePath);{0}", string.Join(";", includePaths))); + else + vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(" "); From e0cb94ec747436c96c44971209f3dce6f52aab74 Mon Sep 17 00:00:00 2001 From: z1dev Date: Tue, 2 Jan 2024 00:08:00 +0100 Subject: [PATCH 022/103] Fix for editor view's cached "CameraNearPlaneValue" getting overwritten. Changing the editor's camera sometimes results in empty views in editors. It was caused by writing the _farPlane with the wrong name in the cache. --- Source/Editor/Viewport/EditorViewport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 9ac9b5571..526a84c45 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -1113,7 +1113,7 @@ namespace FlaxEditor.Viewport private void OnFarPlaneChanged(FloatValueBox control) { _farPlane = control.Value; - _editor.ProjectCache.SetCustomData("CameraNearPlaneValue", _farPlane.ToString()); + _editor.ProjectCache.SetCustomData("CameraFarPlaneValue", _farPlane.ToString()); } /// From 329bab11020b1dfee9641617c1615ed3bdfb1ee0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 2 Jan 2024 11:06:54 +0100 Subject: [PATCH 023/103] Fix minor issue --- Source/Editor/CustomEditors/Editors/FloatEditor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Editor/CustomEditors/Editors/FloatEditor.cs b/Source/Editor/CustomEditors/Editors/FloatEditor.cs index a79dc036f..07c07030e 100644 --- a/Source/Editor/CustomEditors/Editors/FloatEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FloatEditor.cs @@ -88,6 +88,8 @@ namespace FlaxEditor.CustomEditors.Editors _element.Value = asFloat; else if (value is double asDouble) _element.Value = (float)asDouble; + else if (value is int asInt) + _element.Value = (float)asInt; else throw new Exception(string.Format("Invalid value type {0}.", value?.GetType().ToString() ?? "")); } From a5d0916f93387a7bd62650d07f903f72574b8b3a Mon Sep 17 00:00:00 2001 From: MineBill Date: Tue, 2 Jan 2024 19:36:27 +0200 Subject: [PATCH 024/103] Add custom editor for buttons that allow listening for them inside the editor. --- .../Editors/BindableButtonEditor.cs | 96 +++++++++++++++++++ .../Editors/GamepadButtonEditor.cs | 51 ++++++++++ .../Editors/KeyboardKeysEditor.cs | 36 +++++++ .../Editors/MouseButtonEditor.cs | 37 +++++++ 4 files changed, 220 insertions(+) create mode 100644 Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs create mode 100644 Source/Editor/CustomEditors/Editors/GamepadButtonEditor.cs create mode 100644 Source/Editor/CustomEditors/Editors/KeyboardKeysEditor.cs create mode 100644 Source/Editor/CustomEditors/Editors/MouseButtonEditor.cs diff --git a/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs b/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs new file mode 100644 index 000000000..80d873cdf --- /dev/null +++ b/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs @@ -0,0 +1,96 @@ +using FlaxEditor.GUI; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Editors +{ + /// + /// Base class for custom button editors. + /// See , and . + /// + public class BindableButtonEditor : EnumEditor + { + private bool _isListeningForInput; + private Button _button; + + /// + /// Where or not we are currently listening for any input. + /// + protected bool IsListeningForInput + { + get => _isListeningForInput; + set + { + _isListeningForInput = value; + if (_isListeningForInput) + SetupButton(); + else + ResetButton(); + } + } + + /// + /// The window this editor is attached to. + /// Useful to hook into for key pressed, mouse buttons etc. + /// + protected Window Window { get; private set; } + + /// + public override void Initialize(LayoutElementsContainer layout) + { + Window = layout.Control.RootWindow.Window; + + var panel = layout.CustomContainer(); + panel.CustomControl.SlotsHorizontally = 2; + panel.CustomControl.SlotsVertically = 1; + + var button = panel.Button("Listen"); + _button = button.Button; + _button.Clicked += OnButtonClicked; + ResetButton(); + + var padding = panel.CustomControl.SlotPadding; + panel.CustomControl.Height = ComboBox.DefaultHeight + padding.Height; + + base.Initialize(panel); + } + + /// + protected override void Deinitialize() + { + _button.Clicked -= OnButtonClicked; + base.Deinitialize(); + } + + private void ResetButton() + { + _button.Text = "Listen"; + _button.BorderThickness = 1; + + var style = FlaxEngine.GUI.Style.Current; + _button.BorderColor = style.BorderNormal; + _button.BorderColorHighlighted = style.BorderHighlighted; + _button.BorderColorSelected = style.BorderSelected; + } + + private void SetupButton() + { + _button.Text = "Listening..."; + _button.BorderThickness = 2; + + var color = FlaxEngine.GUI.Style.Current.ProgressNormal; + _button.BorderColor = color; + _button.BorderColorHighlighted = color; + _button.BorderColorSelected = color; + } + + private void OnButtonClicked() + { + _isListeningForInput = !_isListeningForInput; + if (_isListeningForInput) + SetupButton(); + else + ResetButton(); + } + } +} diff --git a/Source/Editor/CustomEditors/Editors/GamepadButtonEditor.cs b/Source/Editor/CustomEditors/Editors/GamepadButtonEditor.cs new file mode 100644 index 000000000..7bae24922 --- /dev/null +++ b/Source/Editor/CustomEditors/Editors/GamepadButtonEditor.cs @@ -0,0 +1,51 @@ +using System; +using FlaxEngine; + +namespace FlaxEditor.CustomEditors.Editors +{ + /// + /// Custom editor for . + /// Allows capturing gamepad buttons and assigning them + /// to the edited value. + /// + [CustomEditor(typeof(GamepadButton))] + public class GamepadButtonEditor : BindableButtonEditor + { + /// + public override void Initialize(LayoutElementsContainer layout) + { + base.Initialize(layout); + FlaxEngine.Scripting.Update += ScriptingOnUpdate; + } + + /// + protected override void Deinitialize() + { + FlaxEngine.Scripting.Update -= ScriptingOnUpdate; + base.Deinitialize(); + } + + private void ScriptingOnUpdate() + { + if (!IsListeningForInput) + return; + + // Since there is no way to get an event about + // which gamepad pressed what button, we have + // to poll all gamepads and buttons manually. + for (var i = 0; i < Input.GamepadsCount; i++) + { + var pad = Input.Gamepads[i]; + foreach (var btn in Enum.GetValues()) + { + if (pad.GetButtonUp(btn)) + { + IsListeningForInput = false; + SetValue(btn); + return; + } + } + } + } + } +} diff --git a/Source/Editor/CustomEditors/Editors/KeyboardKeysEditor.cs b/Source/Editor/CustomEditors/Editors/KeyboardKeysEditor.cs new file mode 100644 index 000000000..765527b7e --- /dev/null +++ b/Source/Editor/CustomEditors/Editors/KeyboardKeysEditor.cs @@ -0,0 +1,36 @@ +using FlaxEngine; + +namespace FlaxEditor.CustomEditors.Editors +{ + /// + /// Custom editor for . + /// Allows capturing key presses and assigning them + /// to the edited value. + /// + [CustomEditor(typeof(KeyboardKeys))] + public class KeyboardKeysEditor : BindableButtonEditor + { + /// + public override void Initialize(LayoutElementsContainer layout) + { + base.Initialize(layout); + Window.KeyUp += WindowOnKeyUp; + } + + /// + protected override void Deinitialize() + { + Window.KeyUp -= WindowOnKeyUp; + base.Deinitialize(); + } + + private void WindowOnKeyUp(KeyboardKeys key) + { + if (!IsListeningForInput) + return; + IsListeningForInput = false; + + SetValue(key); + } + } +} diff --git a/Source/Editor/CustomEditors/Editors/MouseButtonEditor.cs b/Source/Editor/CustomEditors/Editors/MouseButtonEditor.cs new file mode 100644 index 000000000..7964d3a4d --- /dev/null +++ b/Source/Editor/CustomEditors/Editors/MouseButtonEditor.cs @@ -0,0 +1,37 @@ +using FlaxEngine; + +namespace FlaxEditor.CustomEditors.Editors +{ + /// + /// Custom editor for . + /// Allows capturing mouse button presses and assigning them + /// to the edited value. + /// + [CustomEditor(typeof(MouseButton))] + public class MouseButtonEditor : BindableButtonEditor + { + /// + public override void Initialize(LayoutElementsContainer layout) + { + base.Initialize(layout); + Window.MouseUp += WindowOnMouseUp; + } + + /// + protected override void Deinitialize() + { + Window.MouseUp -= WindowOnMouseUp; + base.Deinitialize(); + } + + private void WindowOnMouseUp(ref Float2 mouse, MouseButton button, ref bool handled) + { + if (!IsListeningForInput) + return; + IsListeningForInput = false; + + SetValue(button); + handled = true; + } + } +} From 5158de4ac683578a61ef59f8edf0ed4de8dca656 Mon Sep 17 00:00:00 2001 From: MineBill Date: Tue, 2 Jan 2024 19:46:08 +0200 Subject: [PATCH 025/103] Added a tooltip for the listen button. --- Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs b/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs index 80d873cdf..2d4bf0df3 100644 --- a/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs +++ b/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs @@ -44,7 +44,7 @@ namespace FlaxEditor.CustomEditors.Editors panel.CustomControl.SlotsHorizontally = 2; panel.CustomControl.SlotsVertically = 1; - var button = panel.Button("Listen"); + var button = panel.Button("Listen", "Press to listen for input events"); _button = button.Button; _button.Clicked += OnButtonClicked; ResetButton(); From 8e331e4d1344b821f47b6cc6374b0f312c20d3a3 Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Thu, 4 Jan 2024 08:54:04 +0300 Subject: [PATCH 026/103] Silently drop unreliable RPC calls if we failed to find an object --- .../Engine/Networking/NetworkReplicator.cpp | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index c6311cd7a..f9efba4a5 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -2151,6 +2151,18 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie NetworkMessageObjectRpc msgData; event.Message.ReadStructure(msgData); ScopeLock lock(ObjectsLock); + + // Find RPC info + NetworkRpcName name; + name.First = Scripting::FindScriptingType(msgData.RpcTypeName); + name.Second = msgData.RpcName; + const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name); + if (!info) + { + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(msgData.RpcTypeName), String(msgData.RpcName), msgData.ObjectId); + return; + } + NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId, msgData.ParentId, msgData.ObjectTypeName); if (e) { @@ -2159,17 +2171,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie if (!obj) return; - // Find RPC info - NetworkRpcName name; - name.First = Scripting::FindScriptingType(msgData.RpcTypeName); - name.Second = msgData.RpcName; - const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name); - if (!info) - { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(msgData.RpcTypeName), String(msgData.RpcName), msgData.ObjectId); - return; - } - + // Validate RPC if (info->Server && NetworkManager::IsClient()) { @@ -2192,7 +2194,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie // Execute RPC info->Execute(obj, stream, info->Tag); } - else + else if(info->Channel != static_cast(NetworkChannelType::Unreliable) && info->Channel != static_cast(NetworkChannelType::UnreliableOrdered)) { NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgData.ObjectId, String(msgData.RpcTypeName), String(msgData.RpcName)); } From 284c971ec0e1242664aa25280cc89cd3e260b4bb Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Fri, 5 Jan 2024 05:07:08 +0100 Subject: [PATCH 027/103] don't rotate a rigidbody around locked axis --- Source/Engine/Level/Actors/AnimatedModel.cpp | 29 +++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index c1b5af398..2ff4def5f 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -15,6 +15,7 @@ #include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/SceneObjectsFactory.h" +#include "Engine/Physics/Actors/RigidBody.h" #include "Engine/Serialization/Serialization.h" AnimatedModel::AnimatedModel(const SpawnParams& params) @@ -548,7 +549,33 @@ void AnimatedModel::ApplyRootMotion(const Transform& rootMotionDelta) // Apply movement Actor* target = RootMotionTarget ? RootMotionTarget.Get() : this; - target->AddMovement(translation, rootMotionDelta.Orientation); + // filter rotation according to constraints if the target is a rigidbody + const auto rigidBody = dynamic_cast(target); + auto rotation = rootMotionDelta.Orientation; + if (rigidBody) + { + if (static_cast(rigidBody->GetConstraints()) & static_cast(RigidbodyConstraints::LockRotation)) + rotation = Quaternion::Identity; + else + { + Float3 euler = rotation.GetEuler(); + const auto constraints = static_cast(rigidBody->GetConstraints()); + if (constraints & static_cast(RigidbodyConstraints::LockRotationX)) + { + euler.X = 0; + } + if (constraints & static_cast(RigidbodyConstraints::LockRotationY)) + { + euler.Y = 0; + } + if (constraints & static_cast(RigidbodyConstraints::LockRotationZ)) + { + euler.Z = 0; + } + rotation = Quaternion::Euler(euler); + } + } + target->AddMovement(translation, rotation); } void AnimatedModel::SyncParameters() From 976d0992df59046a30d7fe07c29f09bf683b95b3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Jan 2024 11:13:41 +0100 Subject: [PATCH 028/103] Fix `DateTime::GetDate` calculations #2089 #2086 --- Source/Engine/Core/Types/DateTime.cpp | 4 +--- Source/Engine/Tests/TestTime.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 Source/Engine/Tests/TestTime.cpp diff --git a/Source/Engine/Core/Types/DateTime.cpp b/Source/Engine/Core/Types/DateTime.cpp index a0d873b43..3141cd52d 100644 --- a/Source/Engine/Core/Types/DateTime.cpp +++ b/Source/Engine/Core/Types/DateTime.cpp @@ -35,8 +35,7 @@ void DateTime::GetDate(int32& year, int32& month, int32& day) const // Based on: // Fliegel, H. F. and van Flandern, T. C., // Communications of the ACM, Vol. 11, No. 10 (October 1968). - - int32 l = Math::FloorToInt(static_cast(GetJulianDay() + 0.5)) + 68569; + int32 l = Math::FloorToInt((float)(GetDate().GetJulianDay() + 0.5)) + 68569; const int32 n = 4 * l / 146097; l = l - (146097 * n + 3) / 4; int32 i = 4000 * (l + 1) / 1461001; @@ -46,7 +45,6 @@ void DateTime::GetDate(int32& year, int32& month, int32& day) const l = j / 11; j = j + 2 - 12 * l; i = 100 * (n - 49) + i + l; - year = i; month = j; day = k; diff --git a/Source/Engine/Tests/TestTime.cpp b/Source/Engine/Tests/TestTime.cpp new file mode 100644 index 000000000..0419c5e13 --- /dev/null +++ b/Source/Engine/Tests/TestTime.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#include "Engine/Core/Types/DateTime.h" +#include + +TEST_CASE("DateTime") +{ + SECTION("Test Convertion") + { + constexpr int year = 2023; + constexpr int month = 12; + constexpr int day = 16; + constexpr int hour = 23; + constexpr int minute = 50; + constexpr int second = 13; + constexpr int millisecond = 5; + const DateTime dt1(year, month, day, hour, minute, second, millisecond); + CHECK(dt1.GetYear() == year); + CHECK(dt1.GetMonth() == month); + CHECK(dt1.GetDay() == day); + CHECK(dt1.GetHour() == hour); + CHECK(dt1.GetMinute() == minute); + CHECK(dt1.GetSecond() == second); + CHECK(dt1.GetMillisecond() == millisecond); + } +} From 37da55896c47b93f34ae20b60901e901fe07aab1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Jan 2024 11:15:32 +0100 Subject: [PATCH 029/103] Remove unused DateTime apis and cleanup constants --- Source/Engine/Core/Types/DateTime.cpp | 49 ++++++----------- Source/Engine/Core/Types/DateTime.h | 19 ------- Source/Engine/Core/Types/TimeSpan.cpp | 27 +++++++--- Source/Engine/Core/Types/TimeSpan.h | 78 +++++++++++---------------- Source/Engine/Engine/Time.cpp | 4 +- 5 files changed, 72 insertions(+), 105 deletions(-) diff --git a/Source/Engine/Core/Types/DateTime.cpp b/Source/Engine/Core/Types/DateTime.cpp index 3141cd52d..85b42378c 100644 --- a/Source/Engine/Core/Types/DateTime.cpp +++ b/Source/Engine/Core/Types/DateTime.cpp @@ -12,22 +12,22 @@ const int32 CachedDaysToMonth[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, DateTime::DateTime(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second, int32 millisecond) { ASSERT_LOW_LAYER(Validate(year, month, day, hour, minute, second, millisecond)); - int32 totalDays = 0; + int32 daysSum = 0; if (month > 2 && IsLeapYear(year)) - totalDays++; + daysSum++; year--; month--; - totalDays += year * 365 + year / 4 - year / 100 + year / 400 + CachedDaysToMonth[month] + day - 1; - Ticks = totalDays * Constants::TicksPerDay - + hour * Constants::TicksPerHour - + minute * Constants::TicksPerMinute - + second * Constants::TicksPerSecond - + millisecond * Constants::TicksPerMillisecond; + daysSum += year * 365 + year / 4 - year / 100 + year / 400 + CachedDaysToMonth[month] + day - 1; + Ticks = daysSum * TimeSpan::TicksPerDay + + hour * TimeSpan::TicksPerHour + + minute * TimeSpan::TicksPerMinute + + second * TimeSpan::TicksPerSecond + + millisecond * TimeSpan::TicksPerMillisecond; } DateTime DateTime::GetDate() const { - return DateTime(Ticks - Ticks % Constants::TicksPerDay); + return DateTime(Ticks - Ticks % TimeSpan::TicksPerDay); } void DateTime::GetDate(int32& year, int32& month, int32& day) const @@ -59,7 +59,7 @@ int32 DateTime::GetDay() const DayOfWeek DateTime::GetDayOfWeek() const { - return static_cast((Ticks / Constants::TicksPerDay) % 7); + return static_cast((Ticks / TimeSpan::TicksPerDay) % 7); } int32 DateTime::GetDayOfYear() const @@ -73,7 +73,7 @@ int32 DateTime::GetDayOfYear() const int32 DateTime::GetHour() const { - return static_cast(Ticks / Constants::TicksPerHour % 24); + return static_cast(Ticks / TimeSpan::TicksPerHour % 24); } int32 DateTime::GetHour12() const @@ -88,7 +88,7 @@ int32 DateTime::GetHour12() const double DateTime::GetJulianDay() const { - return 1721425.5 + static_cast(Ticks) / Constants::TicksPerDay; + return 1721425.5 + static_cast(Ticks) / TimeSpan::TicksPerDay; } double DateTime::GetModifiedJulianDay() const @@ -98,12 +98,12 @@ double DateTime::GetModifiedJulianDay() const int32 DateTime::GetMillisecond() const { - return static_cast(Ticks / Constants::TicksPerMillisecond % 1000); + return static_cast(Ticks / TimeSpan::TicksPerMillisecond % 1000); } int32 DateTime::GetMinute() const { - return static_cast(Ticks / Constants::TicksPerMinute % 60); + return static_cast(Ticks / TimeSpan::TicksPerMinute % 60); } int32 DateTime::GetMonth() const @@ -120,12 +120,12 @@ MonthOfYear DateTime::GetMonthOfYear() const int32 DateTime::GetSecond() const { - return static_cast(Ticks / Constants::TicksPerSecond % 60); + return static_cast(Ticks / TimeSpan::TicksPerSecond % 60); } TimeSpan DateTime::GetTimeOfDay() const { - return TimeSpan(Ticks % Constants::TicksPerDay); + return TimeSpan(Ticks % TimeSpan::TicksPerDay); } int32 DateTime::GetYear() const @@ -135,11 +135,6 @@ int32 DateTime::GetYear() const return year; } -int32 DateTime::ToUnixTimestamp() const -{ - return static_cast((Ticks - DateTime(1970, 1, 1).Ticks) / Constants::TicksPerSecond); -} - int32 DateTime::DaysInMonth(int32 year, int32 month) { ASSERT_LOW_LAYER((month >= 1) && (month <= 12)); @@ -153,16 +148,6 @@ int32 DateTime::DaysInYear(int32 year) return IsLeapYear(year) ? 366 : 365; } -DateTime DateTime::FromJulianDay(double julianDay) -{ - return DateTime(static_cast((julianDay - 1721425.5) * Constants::TicksPerDay)); -} - -DateTime DateTime::FromUnixTimestamp(int32 unixTime) -{ - return DateTime(1970, 1, 1) + TimeSpan(static_cast(unixTime) * Constants::TicksPerSecond); -} - bool DateTime::IsLeapYear(int32 year) { if ((year % 4) == 0) @@ -174,7 +159,7 @@ bool DateTime::IsLeapYear(int32 year) DateTime DateTime::MaxValue() { - return DateTime(3652059 * Constants::TicksPerDay - 1); + return DateTime(3652059 * TimeSpan::TicksPerDay - 1); } DateTime DateTime::Now() diff --git a/Source/Engine/Core/Types/DateTime.h b/Source/Engine/Core/Types/DateTime.h index 67b2d70ff..a89a69fcf 100644 --- a/Source/Engine/Core/Types/DateTime.h +++ b/Source/Engine/Core/Types/DateTime.h @@ -199,11 +199,6 @@ public: /// int32 GetYear() const; - /// - /// Gets this date as the number of seconds since the Unix Epoch (January 1st of 1970). - /// - int32 ToUnixTimestamp() const; - public: /// /// Gets the number of days in the year and month. @@ -220,20 +215,6 @@ public: /// The number of days. static int32 DaysInYear(int32 year); - /// - /// Returns the proleptic Gregorian date for the given Julian Day. - /// - /// The Julian Day. - /// Gregorian date and time. - static DateTime FromJulianDay(double julianDay); - - /// - /// Returns the date from Unix time (seconds from midnight 1970-01-01). - /// - /// The Unix time (seconds from midnight 1970-01-01). - /// The Gregorian date and time. - static DateTime FromUnixTimestamp(int32 unixTime); - /// /// Determines whether the specified year is a leap year. /// diff --git a/Source/Engine/Core/Types/TimeSpan.cpp b/Source/Engine/Core/Types/TimeSpan.cpp index 0e4aed407..287097672 100644 --- a/Source/Engine/Core/Types/TimeSpan.cpp +++ b/Source/Engine/Core/Types/TimeSpan.cpp @@ -6,38 +6,53 @@ TimeSpan TimeSpan::FromDays(double days) { ASSERT_LOW_LAYER((days >= MinValue().GetTotalDays()) && (days <= MaxValue().GetTotalDays())); - return TimeSpan(static_cast(days * Constants::TicksPerDay)); + return TimeSpan(static_cast(days * TicksPerDay)); } TimeSpan TimeSpan::FromHours(double hours) { ASSERT_LOW_LAYER((hours >= MinValue().GetTotalHours()) && (hours <= MaxValue().GetTotalHours())); - return TimeSpan(static_cast(hours * Constants::TicksPerHour)); + return TimeSpan(static_cast(hours * TicksPerHour)); } TimeSpan TimeSpan::FromMilliseconds(double milliseconds) { ASSERT_LOW_LAYER((milliseconds >= MinValue().GetTotalMilliseconds()) && (milliseconds <= MaxValue().GetTotalMilliseconds())); - return TimeSpan(static_cast(milliseconds * Constants::TicksPerMillisecond)); + return TimeSpan(static_cast(milliseconds * TicksPerMillisecond)); } TimeSpan TimeSpan::FromMinutes(double minutes) { ASSERT_LOW_LAYER((minutes >= MinValue().GetTotalMinutes()) && (minutes <= MaxValue().GetTotalMinutes())); - return TimeSpan(static_cast(minutes * Constants::TicksPerMinute)); + return TimeSpan(static_cast(minutes * TicksPerMinute)); } TimeSpan TimeSpan::FromSeconds(double seconds) { ASSERT_LOW_LAYER((seconds >= MinValue().GetTotalSeconds()) && (seconds <= MaxValue().GetTotalSeconds())); - return TimeSpan(static_cast(seconds * Constants::TicksPerSecond)); + return TimeSpan(static_cast(seconds * TicksPerSecond)); +} + +TimeSpan TimeSpan::MaxValue() +{ + return TimeSpan(9223372036854775807); +} + +TimeSpan TimeSpan::MinValue() +{ + return TimeSpan(-9223372036854775807 - 1); +} + +TimeSpan TimeSpan::Zero() +{ + return TimeSpan(0); } void TimeSpan::Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds) { const int64 totalMs = 1000 * (60 * 60 * 24 * (int64)days + 60 * 60 * (int64)hours + 60 * (int64)minutes + (int64)seconds) + (int64)milliseconds; ASSERT_LOW_LAYER((totalMs >= MinValue().GetTotalMilliseconds()) && (totalMs <= MaxValue().GetTotalMilliseconds())); - Ticks = totalMs * Constants::TicksPerMillisecond; + Ticks = totalMs * TicksPerMillisecond; } String TimeSpan::ToString() const diff --git a/Source/Engine/Core/Types/TimeSpan.h b/Source/Engine/Core/Types/TimeSpan.h index 7545a21c0..f14a622a0 100644 --- a/Source/Engine/Core/Types/TimeSpan.h +++ b/Source/Engine/Core/Types/TimeSpan.h @@ -6,32 +6,30 @@ #include "Engine/Core/Formatting.h" #include "Engine/Core/Templates.h" -namespace Constants -{ - // The number of timespan ticks per day. - const int64 TicksPerDay = 864000000000; - - // The number of timespan ticks per hour. - const int64 TicksPerHour = 36000000000; - - // The number of timespan ticks per millisecond. - const int64 TicksPerMillisecond = 10000; - - // The number of timespan ticks per minute. - const int64 TicksPerMinute = 600000000; - - // The number of timespan ticks per second. - const int64 TicksPerSecond = 10000000; - - // The number of timespan ticks per week. - const int64 TicksPerWeek = 6048000000000; -} - /// /// Represents the difference between two dates and times. /// API_STRUCT(InBuild, Namespace="System") struct FLAXENGINE_API TimeSpan { +public: + // The number of timespan ticks per day. + static constexpr int64 TicksPerDay = 864000000000; + + // The number of timespan ticks per hour. + static constexpr int64 TicksPerHour = 36000000000; + + // The number of timespan ticks per millisecond. + static constexpr int64 TicksPerMillisecond = 10000; + + // The number of timespan ticks per minute. + static constexpr int64 TicksPerMinute = 600000000; + + // The number of timespan ticks per second. + static constexpr int64 TicksPerSecond = 10000000; + + // The number of timespan ticks per week. + static constexpr int64 TicksPerWeek = 6048000000000; + public: /// /// Time span in 100 nanoseconds resolution. @@ -170,7 +168,7 @@ public: /// FORCE_INLINE int32 GetDays() const { - return (int32)(Ticks / Constants::TicksPerDay); + return (int32)(Ticks / TicksPerDay); } /// @@ -186,7 +184,7 @@ public: /// FORCE_INLINE int32 GetHours() const { - return (int32)(Ticks / Constants::TicksPerHour % 24); + return (int32)(Ticks / TicksPerHour % 24); } /// @@ -194,7 +192,7 @@ public: /// FORCE_INLINE int32 GetMilliseconds() const { - return (int32)(Ticks / Constants::TicksPerMillisecond % 1000); + return (int32)(Ticks / TicksPerMillisecond % 1000); } /// @@ -202,7 +200,7 @@ public: /// FORCE_INLINE int32 GetMinutes() const { - return (int32)(Ticks / Constants::TicksPerMinute % 60); + return (int32)(Ticks / TicksPerMinute % 60); } /// @@ -210,7 +208,7 @@ public: /// FORCE_INLINE int32 GetSeconds() const { - return (int32)(Ticks / Constants::TicksPerSecond % 60); + return (int32)(Ticks / TicksPerSecond % 60); } /// @@ -218,7 +216,7 @@ public: /// FORCE_INLINE double GetTotalDays() const { - return (double)Ticks / Constants::TicksPerDay; + return (double)Ticks / TicksPerDay; } /// @@ -226,7 +224,7 @@ public: /// FORCE_INLINE double GetTotalHours() const { - return (double)Ticks / Constants::TicksPerHour; + return (double)Ticks / TicksPerHour; } /// @@ -234,7 +232,7 @@ public: /// FORCE_INLINE double GetTotalMilliseconds() const { - return (double)Ticks / Constants::TicksPerMillisecond; + return (double)Ticks / TicksPerMillisecond; } /// @@ -242,7 +240,7 @@ public: /// FORCE_INLINE double GetTotalMinutes() const { - return (double)Ticks / Constants::TicksPerMinute; + return (double)Ticks / TicksPerMinute; } /// @@ -250,7 +248,7 @@ public: /// FORCE_INLINE float GetTotalSeconds() const { - return static_cast(Ticks) / Constants::TicksPerSecond; + return static_cast(Ticks) / TicksPerSecond; } public: @@ -293,29 +291,17 @@ public: /// /// Returns the maximum time span value. /// - /// The time span. - static TimeSpan MaxValue() - { - return TimeSpan(9223372036854775807); - } + static TimeSpan MaxValue(); /// /// Returns the minimum time span value. /// - /// The time span. - static TimeSpan MinValue() - { - return TimeSpan(-9223372036854775807 - 1); - } + static TimeSpan MinValue(); /// /// Returns the zero time span value. /// - /// The time span. - static TimeSpan Zero() - { - return TimeSpan(0); - } + static TimeSpan Zero(); private: void Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds); diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp index 3adccdbd9..bb0c856f8 100644 --- a/Source/Engine/Engine/Time.cpp +++ b/Source/Engine/Engine/Time.cpp @@ -67,7 +67,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime) { Time = UnscaledTime = TimeSpan::Zero(); DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero(); - LastLength = static_cast(DeltaTime.Ticks) / Constants::TicksPerSecond; + LastLength = static_cast(DeltaTime.Ticks) / TimeSpan::TicksPerSecond; LastBegin = currentTime - LastLength; LastEnd = currentTime; NextBegin = targetFps > ZeroTolerance ? LastBegin + (1.0f / targetFps) : 0.0; @@ -76,7 +76,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime) void Time::TickData::OnReset(float targetFps, double currentTime) { DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero(); - LastLength = static_cast(DeltaTime.Ticks) / Constants::TicksPerSecond; + LastLength = static_cast(DeltaTime.Ticks) / TimeSpan::TicksPerSecond; LastBegin = currentTime - LastLength; LastEnd = currentTime; } From d9ca3e5b57df31bbb0116369f6de166c5df1a8c6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Jan 2024 11:29:53 +0100 Subject: [PATCH 030/103] Add sanity check to prevent crashes when Animated Model has NaN in skeleton pose #2118 --- Source/Engine/Animations/Graph/AnimGraph.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 5aa720d31..7c4671a51 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -282,6 +282,20 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) } } } +#if !BUILD_RELEASE + { + // Perform sanity check on nodes pose to prevent crashes due to NaNs + bool anyInvalid = animResult->RootMotion.IsNanOrInfinity(); + for (int32 i = 0; i < animResult->Nodes.Count(); i++) + anyInvalid |= animResult->Nodes.Get()[i].IsNanOrInfinity(); + if (anyInvalid) + { + LOG(Error, "Animated Model pose contains NaNs due to animations sampling/blending bug."); + context.Data = nullptr; + return; + } + } +#endif SkeletonData* animResultSkeleton = &skeleton; // Retarget animation when using output pose from other skeleton From 60fd4702a626241ef80293250f7be083ee9decb5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Jan 2024 11:52:28 +0100 Subject: [PATCH 031/103] Fix crash when using degenerated triangle in Multi Blend 2D to properly sample animation #2118 --- .../Animations/Graph/AnimGroup.Animation.cpp | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 8a7751ecb..1058ea301 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -1367,33 +1367,29 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } // Use 1D blend if points are on the same line (degenerated triangle) - // TODO: simplify this code + struct BlendData + { + float AlphaX, AlphaY; + Animation* AnimA, *AnimB; + const Float4* AnimAd, *AnimBd; + }; + BlendData blendData; if (v1.Y >= v0.Y) { if (p.Y < v0.Y && v1.Y >= v0.Y) - { - const float alpha = p.Y / v0.Y; - value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, bAnim, aData.W, bData.W, alpha); - } + blendData = { p.Y, v0.Y, aAnim, bAnim, &aData, &bData }; else - { - const float alpha = (p.Y - v0.Y) / (v1.Y - v0.Y); - value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, bAnim, cAnim, bData.W, cData.W, alpha); - } + blendData = { p.Y - v0.Y, v1.Y - v0.Y, bAnim, cAnim, &bData, &cData }; } else { if (p.Y < v1.Y) - { - const float alpha = p.Y / v1.Y; - value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, cAnim, aData.W, cData.W, alpha); - } + blendData = { p.Y, v1.Y, aAnim, cAnim, &aData, &cData }; else - { - const float alpha = (p.Y - v1.Y) / (v0.Y - v1.Y); - value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, cAnim, bAnim, cData.W, bData.W, alpha); - } + blendData = { p.Y - v1.Y, v0.Y - v1.Y, cAnim, bAnim, &cData, &bData }; } + const float alpha = Math::IsZero(blendData.AlphaY) ? 0.0f : blendData.AlphaX / blendData.AlphaY; + value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, blendData.AnimA, blendData.AnimB, blendData.AnimAd->W, blendData.AnimBd->W, alpha); } else { From 78b13ace5c73b184a5206c186752e4918e3492a1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Jan 2024 12:19:37 +0100 Subject: [PATCH 032/103] Minor tweak --- .../Tools/MaterialGenerator/MaterialGenerator.Textures.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index 475809b6c..a24c5e108 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -584,7 +584,6 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) { // Procedural Texture Sample textureBox->Cache = writeLocal(Value::InitForZero(ValueType::Float4), node); - // createGradients(node); //PE: Not needed should always use the scaled or not scaled uv. auto proceduralSample = String::Format(TEXT( " {{\n" " float3 weights;\n" From 7f4d2d8db6718f3e4b8a7eb8ff3fe6b2fe46d239 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Jan 2024 12:53:48 +0100 Subject: [PATCH 033/103] Fix GPU profiler event percentage calculation and add tint highlight to spot slow entries --- Source/Editor/Windows/Profiler/GPU.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Windows/Profiler/GPU.cs b/Source/Editor/Windows/Profiler/GPU.cs index d2c34d335..6ddd5e704 100644 --- a/Source/Editor/Windows/Profiler/GPU.cs +++ b/Source/Editor/Windows/Profiler/GPU.cs @@ -321,8 +321,7 @@ namespace FlaxEditor.Windows.Profiler var data = _events.Get(_drawTimeCPU.SelectedSampleIndex); if (data == null || data.Length == 0) return; - - float totalTimeMs = _drawTimeCPU.SelectedSample; + float totalTimeMs = _drawTimeGPU.SelectedSample; // Add rows var rowColor2 = Style.Current.Background * 1.4f; @@ -343,14 +342,19 @@ namespace FlaxEditor.Windows.Profiler row = new Row { Values = new object[6], + BackgroundColors = new Color[6], }; + for (int k = 0; k < row.BackgroundColors.Length; k++) + row.BackgroundColors[k] = Color.Transparent; } { // Event row.Values[0] = name; // Total (%) - row.Values[1] = (int)(e.Time / totalTimeMs * 1000.0f) / 10.0f; + float rowTimePerc = (float)(e.Time / totalTimeMs); + row.Values[1] = (int)(rowTimePerc * 1000.0f) / 10.0f; + row.BackgroundColors[1] = Color.Red.AlphaMultiplied(Mathf.Min(1, rowTimePerc) * 0.5f); // GPU ms row.Values[2] = (e.Time * 10000.0f) / 10000.0f; From b6337c748c25f91c195ea7aa1825facab7637e7d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 27 Dec 2023 23:45:55 +0100 Subject: [PATCH 034/103] Fix stopping slot animations and playing the same frame #1927 --- Source/Engine/Animations/Graph/AnimGraph.h | 1 + Source/Engine/Animations/Graph/AnimGroup.Animation.cpp | 6 ++++++ Source/Engine/Level/Actors/AnimatedModel.cpp | 1 + 3 files changed, 8 insertions(+) diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index afaa4441b..30ac5a776 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -197,6 +197,7 @@ struct FLAXENGINE_API AnimGraphSlot float BlendOutTime = 0.0f; int32 LoopCount = 0; bool Pause = false; + bool Reset = false; }; /// diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 1058ea301..fc7cf6dbc 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -2098,6 +2098,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu auto& slot = slots[bucket.Index]; Animation* anim = slot.Animation; ASSERT(slot.Animation && slot.Animation->IsLoaded()); + if (slot.Reset) + { + // Start from the begining + slot.Reset = false; + bucket.TimePosition = 0.0f; + } const float deltaTime = slot.Pause ? 0.0f : context.DeltaTime * slot.Speed; const float length = anim->GetLength(); const bool loop = bucket.LoopsLeft != 0; diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index c1b5af398..17f117173 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -494,6 +494,7 @@ void AnimatedModel::StopSlotAnimation(const StringView& slotName, Animation* ani if (slot.Animation == anim && slot.Name == slotName) { slot.Animation = nullptr; + slot.Reset = true; break; } } From 6d5888345404cd4ed8e80a21075b78e6127b0dc2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Jan 2024 21:42:28 +0100 Subject: [PATCH 035/103] Add events tracing feature to Animated Model for animation playback insights --- Source/Engine/Animations/Graph/AnimGraph.cpp | 2 ++ Source/Engine/Animations/Graph/AnimGraph.h | 21 +++++++++++++++++++ .../Animations/Graph/AnimGroup.Animation.cpp | 10 +++++++++ Source/Engine/Level/Actors/AnimatedModel.cpp | 11 ++++++++++ Source/Engine/Level/Actors/AnimatedModel.h | 21 +++++++++++++++++++ 5 files changed, 65 insertions(+) diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 7c4671a51..fa039c7e9 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -56,6 +56,7 @@ void AnimGraphInstanceData::ClearState() State.Resize(0); NodesPose.Resize(0); Slots.Clear(); + TraceEvents.Clear(); } void AnimGraphInstanceData::Invalidate() @@ -238,6 +239,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) } for (auto& e : data.ActiveEvents) e.Hit = false; + data.TraceEvents.Clear(); // Init empty nodes data context.EmptyNodes.RootMotion = Transform::Identity; diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index 30ac5a776..2a8a7f7ab 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -200,6 +200,21 @@ struct FLAXENGINE_API AnimGraphSlot bool Reset = false; }; +/// +/// The animation graph state container for a single node playback trace (eg. animation sample info or state transition). Can be used by Anim Graph debugging or custom scripting. +/// +API_STRUCT() struct FLAXENGINE_API AnimGraphTraceEvent +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(AnimGraphTraceEvent); + + // Contextual asset used. For example, sampled animation. + API_FIELD() Asset* Asset = nullptr; + // Generic value contextual to playback type (eg. animation sample position). + API_FIELD() float Value = 0; + // Identifier of the node in the graph. + API_FIELD() uint32 NodeId = 0; +}; + /// /// The animation graph instance data storage. Required to update the animation graph. /// @@ -358,6 +373,12 @@ public: /// void InvokeAnimEvents(); +public: + // Anim Graph logic tracing feature that allows to collect insights of animations sampling and skeleton poses operations. + bool EnableTracing = false; + // Trace events collected when using EnableTracing option. + Array TraceEvents; + private: struct OutgoingEvent { diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index fc7cf6dbc..598e55da1 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -219,6 +219,16 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* const float animPos = GetAnimSamplePos(length, anim, pos, speed); const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed); + // Add to trace + auto& context = Context.Get(); + if (context.Data->EnableTracing) + { + auto& trace = context.Data->TraceEvents.AddOne(); + trace.Asset = anim; + trace.Value = animPos; + trace.NodeId = node->ID; + } + // Evaluate nested animations bool hasNested = false; if (anim->NestedAnims.Count() != 0) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 17f117173..319def475 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -225,6 +225,17 @@ void AnimatedModel::SetMasterPoseModel(AnimatedModel* masterPose) _masterPose->AnimationUpdated.Bind(this); } +const Array& AnimatedModel::GetTraceEvents() const +{ +#if !BUILD_RELEASE + if (!GetEnableTracing()) + { + LOG(Warning, "Accessing AnimatedModel.TraceEvents with tracing disabled."); + } +#endif + return GraphInstance.TraceEvents; +} + #define CHECK_ANIM_GRAPH_PARAM_ACCESS() \ if (!AnimationGraph) \ { \ diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 029e17b62..bd03e824e 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -259,6 +259,27 @@ public: /// The master pose actor to use. API_FUNCTION() void SetMasterPoseModel(AnimatedModel* masterPose); + /// + /// Enables extracting animation playback insights for debugging or custom scripting. + /// + API_PROPERTY(Attributes="HideInEditor, NoSerialize") bool GetEnableTracing() const + { + return GraphInstance.EnableTracing; + } + + /// + /// Enables extracting animation playback insights for debugging or custom scripting. + /// + API_PROPERTY() void SetEnableTracing(bool value) + { + GraphInstance.EnableTracing = value; + } + + /// + /// Gets the trace events from the last animation update. Valid only when EnableTracing is active. + /// + API_PROPERTY(Attributes="HideInEditor, NoSerialize") const Array& GetTraceEvents() const; + public: /// /// Gets the anim graph instance parameters collection. From af0439f3ce5903a9f96479dd821bee9244f6d0fd Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Jan 2024 22:06:44 +0100 Subject: [PATCH 036/103] Add displaying playback position of animation in Anim Graph window --- Source/Editor/Surface/AnimGraphSurface.cs | 20 ++++++++++++ Source/Editor/Surface/Archetypes/Animation.cs | 31 +++++++++++++++++++ .../Windows/Assets/AnimationGraphWindow.cs | 12 +++++++ 3 files changed, 63 insertions(+) diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs index d7746662f..ccd78412a 100644 --- a/Source/Editor/Surface/AnimGraphSurface.cs +++ b/Source/Editor/Surface/AnimGraphSurface.cs @@ -113,6 +113,25 @@ namespace FlaxEditor.Surface ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; } + internal AnimGraphTraceEvent[] LastTraceEvents; + + internal bool TryGetTraceEvent(SurfaceNode node, out AnimGraphTraceEvent traceEvent) + { + if (LastTraceEvents != null) + { + foreach (var e in LastTraceEvents) + { + if (e.NodeId == node.ID) + { + traceEvent = e; + return true; + } + } + } + traceEvent = default; + return false; + } + private static SurfaceStyle CreateStyle() { var editor = Editor.Instance; @@ -383,6 +402,7 @@ namespace FlaxEditor.Surface } ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; _nodesCache.Wait(); + LastTraceEvents = null; base.OnDestroy(); } diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index 446883406..c659b475a 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -36,6 +36,7 @@ namespace FlaxEditor.Surface.Archetypes { private AssetSelect _assetSelect; private Box _assetBox; + private ProgressBar _playbackPos; /// public Sample(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) @@ -93,6 +94,36 @@ namespace FlaxEditor.Surface.Archetypes _assetSelect.Visible = !box.HasAnyConnection; UpdateTitle(); } + + /// + public override void Update(float deltaTime) + { + // Debug current playback position + if (((AnimGraphSurface)Surface).TryGetTraceEvent(this, out var traceEvent) && traceEvent.Asset is FlaxEngine.Animation anim) + { + if (_playbackPos == null) + { + _playbackPos = new ProgressBar + { + SmoothingScale = 0.0f, + Offsets = Margin.Zero, + AnchorPreset = AnchorPresets.HorizontalStretchBottom, + Parent = this, + Height = 12.0f, + }; + _playbackPos.Y -= 16.0f; + } + _playbackPos.Visible = true; + _playbackPos.Maximum = anim.Duration; + _playbackPos.Value = traceEvent.Value; // AnimGraph reports position in animation frames, not time + } + else if (_playbackPos != null) + { + _playbackPos.Visible = false; + } + + base.Update(deltaTime); + } } /// diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs index 12eac22b5..e0d8c9ded 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs @@ -396,6 +396,16 @@ namespace FlaxEditor.Windows.Assets /// public override void OnUpdate() { + // Extract animations playback state from the events tracing + var debugActor = _debugPicker.Value as AnimatedModel; + if (debugActor == null) + debugActor = _preview.PreviewActor; + if (debugActor != null) + { + debugActor.EnableTracing = true; + Surface.LastTraceEvents = debugActor.TraceEvents; + } + base.OnUpdate(); // Update graph execution flow debugging visualization @@ -416,6 +426,8 @@ namespace FlaxEditor.Windows.Assets /// public override void OnDestroy() { + if (IsDisposing) + return; Animations.DebugFlow -= OnDebugFlow; _properties = null; From a24a9d209433d3f79a14be6f34a4ab340ec39bf5 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Sat, 6 Jan 2024 12:40:24 +0100 Subject: [PATCH 037/103] move constraints checks to RigidBody --- Source/Engine/Level/Actors/AnimatedModel.cpp | 28 +------------- Source/Engine/Physics/Actors/RigidBody.cpp | 40 ++++++++++++++++++++ Source/Engine/Physics/Actors/RigidBody.h | 6 +++ 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 2ff4def5f..3a40dbf46 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -549,33 +549,7 @@ void AnimatedModel::ApplyRootMotion(const Transform& rootMotionDelta) // Apply movement Actor* target = RootMotionTarget ? RootMotionTarget.Get() : this; - // filter rotation according to constraints if the target is a rigidbody - const auto rigidBody = dynamic_cast(target); - auto rotation = rootMotionDelta.Orientation; - if (rigidBody) - { - if (static_cast(rigidBody->GetConstraints()) & static_cast(RigidbodyConstraints::LockRotation)) - rotation = Quaternion::Identity; - else - { - Float3 euler = rotation.GetEuler(); - const auto constraints = static_cast(rigidBody->GetConstraints()); - if (constraints & static_cast(RigidbodyConstraints::LockRotationX)) - { - euler.X = 0; - } - if (constraints & static_cast(RigidbodyConstraints::LockRotationY)) - { - euler.Y = 0; - } - if (constraints & static_cast(RigidbodyConstraints::LockRotationZ)) - { - euler.Z = 0; - } - rotation = Quaternion::Euler(euler); - } - } - target->AddMovement(translation, rotation); + target->AddMovement(translation, rootMotionDelta.Orientation); } void AnimatedModel::SyncParameters() diff --git a/Source/Engine/Physics/Actors/RigidBody.cpp b/Source/Engine/Physics/Actors/RigidBody.cpp index afbac6611..15ea95075 100644 --- a/Source/Engine/Physics/Actors/RigidBody.cpp +++ b/Source/Engine/Physics/Actors/RigidBody.cpp @@ -300,6 +300,46 @@ void RigidBody::ClosestPoint(const Vector3& position, Vector3& result) const } } +void RigidBody::AddMovement(const Vector3& translation, const Quaternion& rotation) +{ + // filter rotation according to constraints + Quaternion allowedRotation; + if (static_cast(GetConstraints()) & static_cast(RigidbodyConstraints::LockRotation)) + allowedRotation = Quaternion::Identity; + else + { + Float3 euler = rotation.GetEuler(); + const auto constraints = static_cast(GetConstraints()); + if (constraints & static_cast(RigidbodyConstraints::LockRotationX)) + euler.X = 0; + if (constraints & static_cast(RigidbodyConstraints::LockRotationY)) + euler.Y = 0; + if (constraints & static_cast(RigidbodyConstraints::LockRotationZ)) + euler.Z = 0; + allowedRotation = Quaternion::Euler(euler); + } + + // filter translation according to the constraints + auto allowedTranslation = translation; + if (static_cast(GetConstraints()) & static_cast(RigidbodyConstraints::LockPosition)) + allowedTranslation = Vector3::Zero; + else + { + const auto constraints = static_cast(GetConstraints()); + if (constraints & static_cast(RigidbodyConstraints::LockPositionX)) + allowedTranslation.X = 0; + if (constraints & static_cast(RigidbodyConstraints::LockPositionY)) + allowedTranslation.Y = 0; + if (constraints & static_cast(RigidbodyConstraints::LockPositionZ)) + allowedTranslation.Z = 0; + } + Transform t; + t.Translation = _transform.Translation + allowedTranslation; + t.Orientation = _transform.Orientation * allowedRotation; + t.Scale = _transform.Scale; + SetTransform(t); +} + void RigidBody::OnCollisionEnter(const Collision& c) { CollisionEnter(c); diff --git a/Source/Engine/Physics/Actors/RigidBody.h b/Source/Engine/Physics/Actors/RigidBody.h index 2ef50ca7a..b8c7d0208 100644 --- a/Source/Engine/Physics/Actors/RigidBody.h +++ b/Source/Engine/Physics/Actors/RigidBody.h @@ -430,6 +430,12 @@ public: /// The result point on the rigidbody shape that is closest to the specified location. API_FUNCTION() void ClosestPoint(const Vector3& position, API_PARAM(Out) Vector3& result) const; + /// + /// Moves and rotates the rigidbody in world space within the limits of defined constraints. + /// + /// The translation vector. + /// The rotation quaternion. + API_FUNCTION() void AddMovement(const Vector3& translation, const Quaternion& rotation) override; public: /// /// Occurs when a collision start gets registered for this rigidbody (it collides with something). From 8c2a156e1f124479d0aaed5e26ab22e58577b1e0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 7 Jan 2024 18:34:15 +0100 Subject: [PATCH 038/103] Add SourceState and DestinationState modes to State Machine interruption modes in Anim Graph #1735 --- .../Archetypes/Animation.StateMachine.cs | 24 ++- .../Editor/Surface/Undo/ConnectBoxesAction.cs | 2 + .../Animations/Graph/AnimGraph.Base.cpp | 5 +- Source/Engine/Animations/Graph/AnimGraph.h | 9 +- .../Animations/Graph/AnimGroup.Animation.cpp | 180 ++++++++++++------ 5 files changed, 153 insertions(+), 67 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index 9d4367303..9ec1bab19 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -1583,14 +1583,24 @@ namespace FlaxEditor.Surface.Archetypes None = 0, /// - /// Transition rule will be rechecked during active transition with option to interrupt transition. + /// Transition rule will be rechecked during active transition with option to interrupt transition (to go back to the source state). /// RuleRechecking = 1, /// - /// Interrupted transition is immediately stopped without blending out. + /// Interrupted transition is immediately stopped without blending out (back to the source/destination state). /// Instant = 2, + + /// + /// Enables checking other transitions in the source state that might interrupt this one. + /// + SourceState = 4, + + /// + /// Enables checking transitions in the destination state that might interrupt this one. + /// + DestinationState = 8, } /// @@ -1613,6 +1623,8 @@ namespace FlaxEditor.Surface.Archetypes UseDefaultRule = 4, InterruptionRuleRechecking = 8, InterruptionInstant = 16, + InterruptionSourceState = 32, + InterruptionDestinationState = 64, } /// @@ -1773,7 +1785,7 @@ namespace FlaxEditor.Surface.Archetypes } /// - /// Transition interruption options. + /// Transition interruption options (flags, can select multiple values). /// [EditorOrder(70), DefaultValue(InterruptionFlags.None)] public InterruptionFlags Interruption @@ -1785,12 +1797,18 @@ namespace FlaxEditor.Surface.Archetypes flags |= InterruptionFlags.RuleRechecking; if (_data.HasFlag(Data.FlagTypes.InterruptionInstant)) flags |= InterruptionFlags.Instant; + if (_data.HasFlag(Data.FlagTypes.InterruptionSourceState)) + flags |= InterruptionFlags.SourceState; + if (_data.HasFlag(Data.FlagTypes.InterruptionDestinationState)) + flags |= InterruptionFlags.DestinationState; return flags; } set { _data.SetFlag(Data.FlagTypes.InterruptionRuleRechecking, value.HasFlag(InterruptionFlags.RuleRechecking)); _data.SetFlag(Data.FlagTypes.InterruptionInstant, value.HasFlag(InterruptionFlags.Instant)); + _data.SetFlag(Data.FlagTypes.InterruptionSourceState, value.HasFlag(InterruptionFlags.SourceState)); + _data.SetFlag(Data.FlagTypes.InterruptionDestinationState, value.HasFlag(InterruptionFlags.DestinationState)); SourceState.SaveTransitions(true); } } diff --git a/Source/Editor/Surface/Undo/ConnectBoxesAction.cs b/Source/Editor/Surface/Undo/ConnectBoxesAction.cs index 72b9242a4..07118228e 100644 --- a/Source/Editor/Surface/Undo/ConnectBoxesAction.cs +++ b/Source/Editor/Surface/Undo/ConnectBoxesAction.cs @@ -24,6 +24,8 @@ namespace FlaxEditor.Surface.Undo public ConnectBoxesAction(InputBox iB, OutputBox oB, bool connect) { + if (iB == null || oB == null || iB.ParentNode == null || oB.ParentNode == null) + throw new System.ArgumentNullException(); _surface = iB.Surface; _context = new ContextHandle(iB.ParentNode.Context); _connect = connect; diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index 48fa7fed1..c05b82b70 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -99,10 +99,7 @@ void BlendPoseBucketInit(AnimGraphInstanceData::Bucket& bucket) void StateMachineBucketInit(AnimGraphInstanceData::Bucket& bucket) { - bucket.StateMachine.LastUpdateFrame = 0; - bucket.StateMachine.CurrentState = nullptr; - bucket.StateMachine.ActiveTransition = nullptr; - bucket.StateMachine.TransitionPosition = 0.0f; + Platform::MemoryClear(&bucket.StateMachine, sizeof(bucket.StateMachine)); } void SlotBucketInit(AnimGraphInstanceData::Bucket& bucket) diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index 2a8a7f7ab..c160b671c 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -129,6 +129,8 @@ public: UseDefaultRule = 4, InterruptionRuleRechecking = 8, InterruptionInstant = 16, + InterruptionSourceState = 32, + InterruptionDestinationState = 64, }; public: @@ -256,7 +258,10 @@ public: uint64 LastUpdateFrame; AnimGraphNode* CurrentState; AnimGraphStateTransition* ActiveTransition; + AnimGraphStateTransition* BaseTransition; + AnimGraphNode* BaseTransitionState; float TransitionPosition; + float BaseTransitionPosition; }; struct SlotBucket @@ -858,7 +863,7 @@ public: } /// - /// Resets all the state bucket used by the given graph including sub-graphs (total). Can eb used to reset the animation state of the nested graph (including children). + /// Resets all the state bucket used by the given graph including sub-graphs (total). Can be used to reset the animation state of the nested graph (including children). /// void ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph); @@ -887,5 +892,7 @@ private: Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC); Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode); Variant SampleState(AnimGraphNode* state); + void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr); + AnimGraphStateTransition* UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState = nullptr); void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData); }; diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 598e55da1..cdf1ebd84 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -470,10 +470,12 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const { ANIM_GRAPH_PROFILE_EVENT("Blend Pose"); + if (isnan(alpha) || isinf(alpha)) + alpha = 0; + alpha = Math::Saturate(alpha); alpha = AlphaBlend::Process(alpha, alphaMode); const auto nodes = node->GetNodes(this); - auto nodesA = static_cast(poseA.AsPointer); auto nodesB = static_cast(poseB.AsPointer); if (!ANIM_GRAPH_IS_VALID_PTR(poseA)) @@ -494,32 +496,40 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const Variant AnimGraphExecutor::SampleState(AnimGraphNode* state) { - // Prepare auto& data = state->Data.State; if (data.Graph == nullptr || data.Graph->GetRootNode() == nullptr) - { - // Invalid state graph return Value::Null; - } - ANIM_GRAPH_PROFILE_EVENT("Evaluate State"); - - // Evaluate state auto rootNode = data.Graph->GetRootNode(); - auto result = eatBox((Node*)rootNode, &rootNode->Boxes[0]); - - return result; + return eatBox((Node*)rootNode, &rootNode->Boxes[0]); } -void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData) +void AnimGraphExecutor::InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition) +{ + // Reset transiton + stateMachineBucket.ActiveTransition = transition; + stateMachineBucket.TransitionPosition = 0.0f; + + // End base transition + if (stateMachineBucket.BaseTransition) + { + ResetBuckets(context, stateMachineBucket.BaseTransitionState->Data.State.Graph); + stateMachineBucket.BaseTransition = nullptr; + stateMachineBucket.BaseTransitionState = nullptr; + stateMachineBucket.BaseTransitionPosition = 0.0f; + } +} + +AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState) { int32 transitionIndex = 0; + const AnimGraphNode::StateBaseData& stateData = state->Data.State; while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex) { const uint16 idx = stateData.Transitions[transitionIndex]; ASSERT(idx < stateMachineData.Graph->StateTransitions.Count()); auto& transition = stateMachineData.Graph->StateTransitions[idx]; - if (transition.Destination == stateMachineBucket.CurrentState) + if (transition.Destination == state || transition.Destination == ignoreState) { // Ignore transition to the current state transitionIndex++; @@ -527,7 +537,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const } // Evaluate source state transition data (position, length, etc.) - const Value sourceStatePtr = SampleState(stateMachineBucket.CurrentState); + const Value sourceStatePtr = SampleState(state); auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule? if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr)) { @@ -548,6 +558,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const if (transition.RuleGraph && !useDefaultRule) { // Execute transition rule + ANIM_GRAPH_PROFILE_EVENT("Rule"); auto rootNode = transition.RuleGraph->GetRootNode(); ASSERT(rootNode); if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0])) @@ -570,10 +581,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const canEnter = true; if (canEnter) { - // Start transition - stateMachineBucket.ActiveTransition = &transition; - stateMachineBucket.TransitionPosition = 0.0f; - break; + return &transition; } // Skip after Solo transition @@ -583,6 +591,18 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const transitionIndex++; } + + // No transition + return nullptr; +} + +void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData) +{ + AnimGraphStateTransition* transition = UpdateStateTransitions(context, stateMachineData, stateMachineBucket.CurrentState); + if (transition) + { + InitStateTransition(context, stateMachineBucket, transition); + } } void ComputeMultiBlendLength(float& length, AnimGraphNode* node) @@ -1511,10 +1531,9 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Blend two animations { - const float alpha = Math::Saturate(bucket.TransitionPosition / blendDuration); + const float alpha = bucket.TransitionPosition / blendDuration; const auto valueA = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.PreviousBlendPoseIndex), Value::Null); const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null); - value = Blend(node, valueA, valueB, alpha, mode); } @@ -1620,22 +1639,21 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Enter to the first state pointed by the Entry node (without transitions) bucket.CurrentState = data.Graph->GetRootNode(); - bucket.ActiveTransition = nullptr; - bucket.TransitionPosition = 0.0f; + InitStateTransition(context, bucket); - // Reset all state buckets pof the graphs and nodes included inside the state machine + // Reset all state buckets of the graphs and nodes included inside the state machine ResetBuckets(context, data.Graph); } #define END_TRANSITION() \ ResetBuckets(context, bucket.CurrentState->Data.State.Graph); \ bucket.CurrentState = bucket.ActiveTransition->Destination; \ - bucket.ActiveTransition = nullptr; \ - bucket.TransitionPosition = 0.0f + InitStateTransition(context, bucket) // Update the active transition if (bucket.ActiveTransition) { bucket.TransitionPosition += context.DeltaTime; + ASSERT(bucket.CurrentState); // Check for transition end if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration) @@ -1643,38 +1661,70 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu END_TRANSITION(); } // Check for transition interruption - else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking)) + else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking) && + EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule) && + bucket.ActiveTransition->RuleGraph) { - const bool useDefaultRule = EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule); - if (bucket.ActiveTransition->RuleGraph && !useDefaultRule) + // Execute transition rule + auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode(); + if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0])) { - // Execute transition rule - auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode(); - if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0])) + bool cancelTransition = false; + if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant)) { - bool cancelTransition = false; - if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant)) + cancelTransition = true; + } + else + { + // Blend back to the source state (remove currently applied delta and rewind transition) + bucket.TransitionPosition -= context.DeltaTime; + bucket.TransitionPosition -= context.DeltaTime; + if (bucket.TransitionPosition <= ZeroTolerance) { cancelTransition = true; } - else - { - // Blend back to the source state (remove currently applied delta and rewind transition) - bucket.TransitionPosition -= context.DeltaTime; - bucket.TransitionPosition -= context.DeltaTime; - if (bucket.TransitionPosition <= ZeroTolerance) - { - cancelTransition = true; - } - } - if (cancelTransition) - { - // Go back to the source state - ResetBuckets(context, bucket.CurrentState->Data.State.Graph); - bucket.ActiveTransition = nullptr; - bucket.TransitionPosition = 0.0f; - } } + if (cancelTransition) + { + // Go back to the source state + ResetBuckets(context, bucket.CurrentState->Data.State.Graph); + InitStateTransition(context, bucket); + } + } + } + if (bucket.ActiveTransition && !bucket.BaseTransition && EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionSourceState)) + { + // Try to interrupt with any other transition in the source state (except the current transition) + if (AnimGraphStateTransition* transition = UpdateStateTransitions(context, data, bucket.CurrentState, bucket.ActiveTransition->Destination)) + { + // Change active transition to the interrupted one + if (EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant)) + { + // Cache the current blending state to be used as a base when blending towards new destination state (seamless blending after interruption) + bucket.BaseTransition = bucket.ActiveTransition; + bucket.BaseTransitionState = bucket.CurrentState; + bucket.BaseTransitionPosition = bucket.TransitionPosition; + } + bucket.ActiveTransition = transition; + bucket.TransitionPosition = 0.0f; + } + } + if (bucket.ActiveTransition && !bucket.BaseTransition && EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionDestinationState)) + { + // Try to interrupt with any other transition in the destination state (except the transition back to the current state if exists) + if (AnimGraphStateTransition* transition = UpdateStateTransitions(context, data, bucket.ActiveTransition->Destination, bucket.CurrentState)) + { + // Change active transition to the interrupted one + if (EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant)) + { + // Cache the current blending state to be used as a base when blending towards new destination state (seamless blending after interruption) + bucket.BaseTransition = bucket.ActiveTransition; + bucket.BaseTransitionState = bucket.CurrentState; + bucket.BaseTransitionPosition = bucket.TransitionPosition; + } + bucket.CurrentState = bucket.ActiveTransition->Destination; + bucket.ActiveTransition = transition; + bucket.TransitionPosition = 0.0f; } } } @@ -1703,9 +1753,23 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } } - // Sample the current state - const auto currentState = SampleState(bucket.CurrentState); - value = currentState; + if (bucket.BaseTransitionState) + { + // Sample the other state (eg. when blending from interrupted state to the another state from the old destination) + value = SampleState(bucket.BaseTransitionState); + if (bucket.BaseTransition) + { + // Evaluate the base pose from the time when transition was interrupted + const auto destinationState = SampleState(bucket.BaseTransition->Destination); + const float alpha = bucket.BaseTransitionPosition / bucket.BaseTransition->BlendDuration; + value = Blend(node, value, destinationState, alpha, bucket.BaseTransition->BlendMode); + } + } + else + { + // Sample the current state + value = SampleState(bucket.CurrentState); + } // Handle active transition blending if (bucket.ActiveTransition) @@ -1714,14 +1778,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu const auto destinationState = SampleState(bucket.ActiveTransition->Destination); // Perform blending - const float alpha = Math::Saturate(bucket.TransitionPosition / bucket.ActiveTransition->BlendDuration); - value = Blend(node, currentState, destinationState, alpha, bucket.ActiveTransition->BlendMode); + const float alpha = bucket.TransitionPosition / bucket.ActiveTransition->BlendDuration; + value = Blend(node, value, destinationState, alpha, bucket.ActiveTransition->BlendMode); } - // Update bucket bucket.LastUpdateFrame = context.CurrentFrameIndex; #undef END_TRANSITION - break; } // Entry @@ -2142,7 +2204,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Blend out auto input = tryGetValue(node->GetBox(1), Value::Null); bucket.BlendOutPosition += deltaTime; - const float alpha = Math::Saturate(bucket.BlendOutPosition / slot.BlendOutTime); + const float alpha = bucket.BlendOutPosition / slot.BlendOutTime; value = Blend(node, value, input, alpha, AlphaBlendMode::HermiteCubic); } else if (bucket.LoopsDone == 0 && slot.BlendInTime > 0.0f && bucket.BlendInPosition < slot.BlendInTime) @@ -2150,7 +2212,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Blend in auto input = tryGetValue(node->GetBox(1), Value::Null); bucket.BlendInPosition += deltaTime; - const float alpha = Math::Saturate(bucket.BlendInPosition / slot.BlendInTime); + const float alpha = bucket.BlendInPosition / slot.BlendInTime; value = Blend(node, input, value, alpha, AlphaBlendMode::HermiteCubic); } break; From 0b7c18763006a7442a84c03ba43b60c96d3b9408 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 7 Jan 2024 22:35:17 +0100 Subject: [PATCH 039/103] Fix Animated Model slot animations clearing on start #1803 --- Source/Engine/Animations/Graph/AnimGraph.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index fa039c7e9..f7b883bd7 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -39,6 +39,7 @@ void AnimGraphImpulse::SetNodeModelTransformation(SkeletonData& skeleton, int32 void AnimGraphInstanceData::Clear() { ClearState(); + Slots.Clear(); Parameters.Resize(0); } @@ -55,7 +56,6 @@ void AnimGraphInstanceData::ClearState() RootMotion = Transform::Identity; State.Resize(0); NodesPose.Resize(0); - Slots.Clear(); TraceEvents.Clear(); } From 9fd7a231ca0e4e027a68d1b5aed3245d60772fbb Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 8 Jan 2024 20:45:10 +0200 Subject: [PATCH 040/103] Fix invalid Visual Studio solution folder nesting Fixes fatal error when loading generated solution files with Rider, also fixes folders with identical names getting mapped to same folder (Plugins folders within plugin projects). --- .../Projects/VisualStudio/VisualStudioProjectGenerator.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index 5cc1b2205..3438f8960 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -296,7 +296,7 @@ namespace Flax.Build.Projects.VisualStudio var folderIdMatches = new Regex("Project\\(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\"\\) = \"(.*?)\", \"(.*?)\", \"{(.*?)}\"").Matches(contents); foreach (Match match in folderIdMatches) { - var folder = match.Groups[1].Value; + var folder = match.Groups[2].Value; var folderId = Guid.ParseExact(match.Groups[3].Value, "D"); folderIds[folder] = folderId; } @@ -385,8 +385,7 @@ namespace Flax.Build.Projects.VisualStudio { if (!folderIds.TryGetValue(folderPath, out project.FolderGuid)) { - if (!folderIds.TryGetValue(folderParents[i], out project.FolderGuid)) - project.FolderGuid = Guid.NewGuid(); + project.FolderGuid = Guid.NewGuid(); folderIds.Add(folderPath, project.FolderGuid); } folderNames.Add(folderPath); @@ -401,7 +400,7 @@ namespace Flax.Build.Projects.VisualStudio var lastSplit = folder.LastIndexOf('\\'); var name = lastSplit != -1 ? folder.Substring(lastSplit + 1) : folder; - vcSolutionFileContent.AppendLine(string.Format("Project(\"{0}\") = \"{1}\", \"{2}\", \"{3}\"", typeGuid, name, name, folderGuid)); + vcSolutionFileContent.AppendLine(string.Format("Project(\"{0}\") = \"{1}\", \"{2}\", \"{3}\"", typeGuid, name, folder, folderGuid)); vcSolutionFileContent.AppendLine("EndProject"); } } From afb5edb82451fe1525b40b86cc0b8059b2e6a8c5 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 8 Jan 2024 19:45:54 -0600 Subject: [PATCH 041/103] Adjust `ContextMenu` ShortKeys as needed to accommodate for scrollbar when visible. --- Source/Editor/GUI/ContextMenu/ContextMenu.cs | 4 ++-- Source/Editor/GUI/ContextMenu/ContextMenuButton.cs | 7 ++++++- Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs | 7 +------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Source/Editor/GUI/ContextMenu/ContextMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenu.cs index 25f45a1f8..072b01678 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenu.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenu.cs @@ -408,9 +408,9 @@ namespace FlaxEditor.GUI.ContextMenu { foreach (var child in _panel.Children) { - if (child is ContextMenuChildMenu item && item.Visible) + if (child is ContextMenuButton item && item.Visible) { - item.AdjustArrowAmount = -_panel.VScrollBar.Width; + item.ExtraAdjustmentAmount = -_panel.VScrollBar.Width; } } } diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs index e371f7c4b..e79d75e46 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs @@ -14,6 +14,11 @@ namespace FlaxEditor.GUI.ContextMenu public class ContextMenuButton : ContextMenuItem { private bool _isMouseDown; + + /// + /// The amount to adjust the short keys and arrow image by in x coordinates. + /// + public float ExtraAdjustmentAmount = 0; /// /// Event fired when user clicks on the button. @@ -133,7 +138,7 @@ namespace FlaxEditor.GUI.ContextMenu if (!string.IsNullOrEmpty(ShortKeys)) { // Draw short keys - Render2D.DrawText(style.FontMedium, ShortKeys, textRect, textColor, TextAlignment.Far, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, ShortKeys, new Rectangle(ExtraAdjustmentAmount, textRect.Y, textRect.Width, textRect.Height), textColor, TextAlignment.Far, TextAlignment.Center); } // Draw icon diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs index 49a60a04e..d71166196 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs @@ -17,11 +17,6 @@ namespace FlaxEditor.GUI.ContextMenu /// public readonly ContextMenu ContextMenu = new ContextMenu(); - /// - /// The amount to adjust the arrow image by in x coordinates. - /// - public float AdjustArrowAmount = 0; - /// /// Initializes a new instance of the class. /// @@ -49,7 +44,7 @@ namespace FlaxEditor.GUI.ContextMenu // Draw arrow if (ContextMenu.HasChildren) - Render2D.DrawSprite(style.ArrowRight, new Rectangle(Width - 15 + AdjustArrowAmount, (Height - 12) / 2, 12, 12), Enabled ? isCMopened ? style.BackgroundSelected : style.Foreground : style.ForegroundDisabled); + Render2D.DrawSprite(style.ArrowRight, new Rectangle(Width - 15 + ExtraAdjustmentAmount, (Height - 12) / 2, 12, 12), Enabled ? isCMopened ? style.BackgroundSelected : style.Foreground : style.ForegroundDisabled); } /// From 1e061b77afc18d7dc1c2559ad9e062961c35b8aa Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 8 Jan 2024 19:48:12 -0600 Subject: [PATCH 042/103] Add back in checking for textRect x location. --- Source/Editor/GUI/ContextMenu/ContextMenuButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs index e79d75e46..485cde1a7 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs @@ -138,7 +138,7 @@ namespace FlaxEditor.GUI.ContextMenu if (!string.IsNullOrEmpty(ShortKeys)) { // Draw short keys - Render2D.DrawText(style.FontMedium, ShortKeys, new Rectangle(ExtraAdjustmentAmount, textRect.Y, textRect.Width, textRect.Height), textColor, TextAlignment.Far, TextAlignment.Center); + Render2D.DrawText(style.FontMedium, ShortKeys, new Rectangle(textRect.X + ExtraAdjustmentAmount, textRect.Y, textRect.Width, textRect.Height), textColor, TextAlignment.Far, TextAlignment.Center); } // Draw icon From 6289b9d15a2c1c7a5e87f6bc69227bcfbf4fe626 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Jan 2024 10:58:39 +0100 Subject: [PATCH 043/103] Fix crash due to incorrect PhysX usage for vehicle setup #1530 #2129 --- Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index af6c6403b..99a5abc56 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -3312,7 +3312,6 @@ void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor) // Create vehicle drive auto drive4W = PxVehicleDrive4W::allocate(wheels.Count()); drive4W->setup(PhysX, actorPhysX, *wheelsSimData, driveSimData, Math::Max(wheels.Count() - 4, 0)); - drive4W->setToRestState(); drive4W->mDriveDynData.forceGearChange(PxVehicleGearsData::eFIRST); drive4W->mDriveDynData.setUseAutoGears(gearbox.AutoGear); vehicle = drive4W; @@ -3355,7 +3354,6 @@ void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor) // Create vehicle drive auto driveNW = PxVehicleDriveNW::allocate(wheels.Count()); driveNW->setup(PhysX, actorPhysX, *wheelsSimData, driveSimData, wheels.Count()); - driveNW->setToRestState(); driveNW->mDriveDynData.forceGearChange(PxVehicleGearsData::eFIRST); driveNW->mDriveDynData.setUseAutoGears(gearbox.AutoGear); vehicle = driveNW; @@ -3366,7 +3364,6 @@ void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor) // Create vehicle drive auto driveNo = PxVehicleNoDrive::allocate(wheels.Count()); driveNo->setup(PhysX, actorPhysX, *wheelsSimData); - driveNo->setToRestState(); vehicle = driveNo; break; } From d807e9bfe066cfb092e1701a82581ab9c288d52d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Jan 2024 11:48:08 +0100 Subject: [PATCH 044/103] Fix crash when setting maanaged structure data via `Variant` #2163 --- Source/Engine/Core/Types/Variant.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 952e648e6..66e17b0a2 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -3985,15 +3985,32 @@ void Variant::CopyStructure(void* src) { if (AsBlob.Data && src) { - const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(StringAnsiView(Type.TypeName)); + const StringAnsiView typeName(Type.TypeName); + const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName); if (typeHandle) { auto& type = typeHandle.GetType(); type.Struct.Copy(AsBlob.Data, src); } +#if USE_CSHARP + else if (const auto mclass = Scripting::FindClass(typeName)) + { + // Fallback to C#-only types + MCore::Thread::Attach(); + if (MANAGED_GC_HANDLE && mclass->IsValueType()) + { + MObject* instance = MCore::GCHandle::GetTarget(MANAGED_GC_HANDLE); + void* data = MCore::Object::Unbox(instance); + Platform::MemoryCopy(data, src, mclass->GetInstanceSize()); + } + } +#endif else { - Platform::MemoryCopy(AsBlob.Data, src, AsBlob.Length); + if (typeName.Length() != 0) + { + LOG(Warning, "Missing scripting type \'{0}\'", String(typeName)); + } } } } From b7cc4c768f4ba186376f0e03d804d3aa052970e4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Jan 2024 12:08:01 +0100 Subject: [PATCH 045/103] Fix unpacking `Variant` structure if input value is a scalar #2163 --- Source/Engine/Core/Types/Variant.cpp | 18 ++++++++++++++++++ Source/Engine/Visject/VisjectGraph.cpp | 8 +++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 66e17b0a2..9e8b69b2c 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -139,6 +139,24 @@ VariantType::VariantType(const StringAnsiView& typeName) return; } } + { + // Aliases + if (typeName == "FlaxEngine.Vector2") + { + new(this) VariantType(Vector2); + return; + } + if (typeName == "FlaxEngine.Vector3") + { + new(this) VariantType(Vector3); + return; + } + if (typeName == "FlaxEngine.Vector4") + { + new(this) VariantType(Vector4); + return; + } + } // Check case for array if (typeName.EndsWith(StringAnsiView("[]"), StringSearchCase::CaseSensitive)) diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index 64164ecec..ea2824e77 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -685,9 +685,9 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value) case 36: { // Get value with structure data - Variant structureValue = eatBox(node, node->GetBox(0)->FirstConnection()); if (!node->GetBox(0)->HasConnection()) return; + Variant structureValue = eatBox(node, node->GetBox(0)->FirstConnection()); // Find type const StringView typeName(node->Values[0]); @@ -741,6 +741,12 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value) return; } const ScriptingType& type = typeHandle.GetType(); + if (structureValue.Type.Type != VariantType::Structure) // If structureValue is eg. Float we can try to cast it to a required structure type + { + VariantType typeVariantType(typeNameAnsiView); + if (Variant::CanCast(structureValue, typeVariantType)) + structureValue = Variant::Cast(structureValue, typeVariantType); + } structureValue.InvertInline(); // Extract any Float3/Int32 into Structure type from inlined format const ScriptingTypeHandle structureValueTypeHandle = Scripting::FindScriptingType(structureValue.Type.GetTypeName()); if (structureValue.Type.Type != VariantType::Structure || typeHandle != structureValueTypeHandle) From 1094abce5a8ffe3f5bb88ab3a3864f820a29bd31 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Jan 2024 12:12:11 +0100 Subject: [PATCH 046/103] Fix crash when finding actor or level with empty name text #2161 --- Source/Engine/Level/Actor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index ff40cc208..7e2329389 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1359,7 +1359,7 @@ bool Actor::IsPrefabRoot() const Actor* Actor::FindActor(const StringView& name) const { Actor* result = nullptr; - if (StringUtils::Compare(*_name, *name) == 0) + if (_name == name) { result = const_cast(this); } @@ -1393,7 +1393,7 @@ Actor* Actor::FindActor(const MClass* type) const Actor* Actor::FindActor(const MClass* type, const StringView& name) const { CHECK_RETURN(type, nullptr); - if (GetClass()->IsSubClassOf(type) && StringUtils::Compare(*_name, *name) == 0) + if (GetClass()->IsSubClassOf(type) && _name == name) return const_cast(this); for (auto child : Children) { From 2c76785bf0fc8c206d7441c5b527b8da0676e99a Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Wed, 10 Jan 2024 18:31:13 +0100 Subject: [PATCH 047/103] remove unused import --- Source/Engine/Level/Actors/AnimatedModel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 3a40dbf46..c1b5af398 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -15,7 +15,6 @@ #include "Engine/Graphics/Models/MeshDeformation.h" #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/SceneObjectsFactory.h" -#include "Engine/Physics/Actors/RigidBody.h" #include "Engine/Serialization/Serialization.h" AnimatedModel::AnimatedModel(const SpawnParams& params) From d126f5bc55b2a0f8d2b2db6a983d96956401eebe Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Wed, 10 Jan 2024 18:36:05 +0100 Subject: [PATCH 048/103] use enum helper functions --- Source/Engine/Physics/Actors/RigidBody.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Physics/Actors/RigidBody.cpp b/Source/Engine/Physics/Actors/RigidBody.cpp index 15ea95075..71151866a 100644 --- a/Source/Engine/Physics/Actors/RigidBody.cpp +++ b/Source/Engine/Physics/Actors/RigidBody.cpp @@ -304,33 +304,31 @@ void RigidBody::AddMovement(const Vector3& translation, const Quaternion& rotati { // filter rotation according to constraints Quaternion allowedRotation; - if (static_cast(GetConstraints()) & static_cast(RigidbodyConstraints::LockRotation)) + if (EnumHasAllFlags(GetConstraints(), RigidbodyConstraints::LockRotation)) allowedRotation = Quaternion::Identity; else { Float3 euler = rotation.GetEuler(); - const auto constraints = static_cast(GetConstraints()); - if (constraints & static_cast(RigidbodyConstraints::LockRotationX)) + if (EnumHasAnyFlags(GetConstraints(), RigidbodyConstraints::LockRotationX)) euler.X = 0; - if (constraints & static_cast(RigidbodyConstraints::LockRotationY)) + if (EnumHasAnyFlags(GetConstraints(), RigidbodyConstraints::LockRotationY)) euler.Y = 0; - if (constraints & static_cast(RigidbodyConstraints::LockRotationZ)) + if (EnumHasAnyFlags(GetConstraints(), RigidbodyConstraints::LockRotationZ)) euler.Z = 0; allowedRotation = Quaternion::Euler(euler); } // filter translation according to the constraints auto allowedTranslation = translation; - if (static_cast(GetConstraints()) & static_cast(RigidbodyConstraints::LockPosition)) + if (EnumHasAllFlags(GetConstraints(), RigidbodyConstraints::LockPosition)) allowedTranslation = Vector3::Zero; else { - const auto constraints = static_cast(GetConstraints()); - if (constraints & static_cast(RigidbodyConstraints::LockPositionX)) + if (EnumHasAnyFlags(GetConstraints(), RigidbodyConstraints::LockPositionX)) allowedTranslation.X = 0; - if (constraints & static_cast(RigidbodyConstraints::LockPositionY)) + if (EnumHasAnyFlags(GetConstraints(), RigidbodyConstraints::LockPositionY)) allowedTranslation.Y = 0; - if (constraints & static_cast(RigidbodyConstraints::LockPositionZ)) + if (EnumHasAnyFlags(GetConstraints(), RigidbodyConstraints::LockPositionZ)) allowedTranslation.Z = 0; } Transform t; From 8922b5cd7970321a0ee91389a343d6f6e0b8396b Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Wed, 10 Jan 2024 18:38:29 +0100 Subject: [PATCH 049/103] clean up include file --- Source/Engine/Physics/Actors/RigidBody.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Source/Engine/Physics/Actors/RigidBody.h b/Source/Engine/Physics/Actors/RigidBody.h index b8c7d0208..ca1428810 100644 --- a/Source/Engine/Physics/Actors/RigidBody.h +++ b/Source/Engine/Physics/Actors/RigidBody.h @@ -430,12 +430,6 @@ public: /// The result point on the rigidbody shape that is closest to the specified location. API_FUNCTION() void ClosestPoint(const Vector3& position, API_PARAM(Out) Vector3& result) const; - /// - /// Moves and rotates the rigidbody in world space within the limits of defined constraints. - /// - /// The translation vector. - /// The rotation quaternion. - API_FUNCTION() void AddMovement(const Vector3& translation, const Quaternion& rotation) override; public: /// /// Occurs when a collision start gets registered for this rigidbody (it collides with something). @@ -492,6 +486,7 @@ public: // [Actor] void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void AddMovement(const Vector3& translation, const Quaternion& rotation) override; // [IPhysicsActor] void* GetPhysicsActor() const override; From 76cf935583f1f6ea3c090316155853a02a4757fb Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Wed, 10 Jan 2024 22:00:20 -0600 Subject: [PATCH 050/103] Fix `Dropdown` panel scale correctly. --- Source/Engine/UI/GUI/Common/Dropdown.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index 31ea87948..90454b586 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -606,14 +606,15 @@ namespace FlaxEngine.GUI _popup.LostFocus += DestroyPopup; // Show dropdown popup - var locationRootSpace = Location + new Float2(0, Height); + var locationRootSpace = Location + new Float2(0, Height - Height * (1 - Scale.Y) / 2); var parent = Parent; while (parent != null && parent != root) { locationRootSpace = parent.PointToParent(ref locationRootSpace); parent = parent.Parent; } - _popup.Location = locationRootSpace; + _popup.Scale = Scale; + _popup.Location = locationRootSpace - new Float2(0, _popup.Height * (1 - _popup.Scale.Y) / 2); _popup.Parent = root; _popup.Focus(); _popup.StartMouseCapture(); From 82c8e39dfd3174e7f97e87fc178443e15fe55d99 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 12 Jan 2024 17:23:10 -0600 Subject: [PATCH 051/103] Fix `AlwaysShowScrollbars` to update enabled state of the scroll bars. #2165 --- Source/Engine/UI/GUI/Panels/Panel.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Source/Engine/UI/GUI/Panels/Panel.cs b/Source/Engine/UI/GUI/Panels/Panel.cs index bae165fba..d660d7b6f 100644 --- a/Source/Engine/UI/GUI/Panels/Panel.cs +++ b/Source/Engine/UI/GUI/Panels/Panel.cs @@ -132,6 +132,22 @@ namespace FlaxEngine.GUI if (_alwaysShowScrollbars != value) { _alwaysShowScrollbars = value; + switch (_scrollBars) + { + case ScrollBars.None: + break; + case ScrollBars.Horizontal: + HScrollBar.Enabled = true; + break; + case ScrollBars.Vertical: + VScrollBar.Enabled = true; + break; + case ScrollBars.Both: + HScrollBar.Enabled = true; + VScrollBar.Enabled = true; + break; + default: break; + } PerformLayout(); } } From 503bcdcf2795f535e92dcd640560e3dcb06a744b Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 12 Jan 2024 19:57:00 -0600 Subject: [PATCH 052/103] Change visibility instead of enable on scroll bars. --- Source/Engine/UI/GUI/Panels/Panel.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Engine/UI/GUI/Panels/Panel.cs b/Source/Engine/UI/GUI/Panels/Panel.cs index d660d7b6f..05cb9bd80 100644 --- a/Source/Engine/UI/GUI/Panels/Panel.cs +++ b/Source/Engine/UI/GUI/Panels/Panel.cs @@ -137,14 +137,14 @@ namespace FlaxEngine.GUI case ScrollBars.None: break; case ScrollBars.Horizontal: - HScrollBar.Enabled = true; + HScrollBar.Visible = value; break; case ScrollBars.Vertical: - VScrollBar.Enabled = true; + VScrollBar.Visible = value; break; case ScrollBars.Both: - HScrollBar.Enabled = true; - VScrollBar.Enabled = true; + HScrollBar.Visible = value; + VScrollBar.Visible = value; break; default: break; } From 2c5095e0a1c6e5157d20ade0a288ce6a4c6a6944 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 12 Jan 2024 20:35:59 -0600 Subject: [PATCH 053/103] Fix Plugin Project creation when the user types in symbols. --- Source/Editor/Windows/PluginsWindow.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index eb04f07b8..4145f200c 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -671,11 +671,11 @@ namespace FlaxEditor.Windows Editor.Log($"Using plugin code type name: {pluginCodeName}"); var oldPluginPath = Path.Combine(extractPath, "ExamplePlugin-master"); - var newPluginPath = Path.Combine(extractPath, pluginName); + var newPluginPath = Path.Combine(extractPath, pluginCodeName); Directory.Move(oldPluginPath, newPluginPath); var oldFlaxProjFile = Path.Combine(newPluginPath, "ExamplePlugin.flaxproj"); - var newFlaxProjFile = Path.Combine(newPluginPath, $"{pluginName}.flaxproj"); + var newFlaxProjFile = Path.Combine(newPluginPath, $"{pluginCodeName}.flaxproj"); File.Move(oldFlaxProjFile, newFlaxProjFile); var readme = Path.Combine(newPluginPath, "README.md"); @@ -687,7 +687,7 @@ namespace FlaxEditor.Windows // Flax plugin project file var flaxPluginProjContents = JsonSerializer.Deserialize(await File.ReadAllTextAsync(newFlaxProjFile)); - flaxPluginProjContents.Name = pluginName; + flaxPluginProjContents.Name = pluginCodeName; if (!string.IsNullOrEmpty(pluginVersion)) flaxPluginProjContents.Version = new Version(pluginVersion); if (!string.IsNullOrEmpty(companyName)) @@ -751,7 +751,7 @@ namespace FlaxEditor.Windows } Editor.Log($"Plugin project {pluginName} has successfully been created."); - await AddReferenceToProject(pluginName, pluginName); + await AddReferenceToProject(pluginCodeName, pluginCodeName); MessageBox.Show($"{pluginName} has been successfully created. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK); } From 40e75465a328fb14ac9051dbdffe76f9d8563f12 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Wed, 17 Jan 2024 22:46:49 +0100 Subject: [PATCH 054/103] fix plugin entry layout --- Source/Editor/Windows/PluginsWindow.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index eb04f07b8..f2fa2f197 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -95,6 +95,7 @@ namespace FlaxEditor.Windows Bounds = new Rectangle(nameLabel.X, tmp1, nameLabel.Width, Height - tmp1 - margin), }; + var xOffset = nameLabel.X + nameLabel.Width; string versionString = string.Empty; if (desc.IsAlpha) versionString = "ALPHA "; @@ -109,7 +110,7 @@ namespace FlaxEditor.Windows AnchorPreset = AnchorPresets.TopRight, Text = versionString, Parent = this, - Bounds = new Rectangle(Width - 140 - margin, margin, 140, 14), + Bounds = new Rectangle(Width - 140 - margin - xOffset, margin, 140, 14), }; string url = null; @@ -129,7 +130,7 @@ namespace FlaxEditor.Windows AnchorPreset = AnchorPresets.TopRight, Text = desc.Author, Parent = this, - Bounds = new Rectangle(Width - authorWidth - margin, versionLabel.Bottom + margin, authorWidth, 14), + Bounds = new Rectangle(Width - authorWidth - margin - xOffset, versionLabel.Bottom + margin, authorWidth, 14), }; if (url != null) { From 0d3b81b8ca8e82f3e39202efb3506651ab417680 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 22 Jan 2024 17:37:02 -0600 Subject: [PATCH 055/103] Change drop location of multiple nodes to be vertical. --- Source/Editor/Surface/MaterialSurface.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Surface/MaterialSurface.cs b/Source/Editor/Surface/MaterialSurface.cs index ba7f2e01e..a7fa6cb38 100644 --- a/Source/Editor/Surface/MaterialSurface.cs +++ b/Source/Editor/Surface/MaterialSurface.cs @@ -105,7 +105,7 @@ namespace FlaxEditor.Surface if (node != null) { - args.SurfaceLocation.X += node.Width + 10; + args.SurfaceLocation.Y += node.Height + 10; } } From ce658acb22a0ba9b5eb3ef41177b37cd3f9cabed Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 22 Jan 2024 18:03:14 -0600 Subject: [PATCH 056/103] Dont include editor modules in auto generated git plugin pulling. --- Source/Editor/Windows/PluginsWindow.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index eb04f07b8..5bb6ea492 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -775,8 +775,12 @@ namespace FlaxEditor.Windows var pluginModuleScriptPath = Path.Combine(subDir, pluginModuleName + ".Build.cs"); if (File.Exists(pluginModuleScriptPath)) { - gameScriptContents = gameScriptContents.Insert(insertLocation, $"\n options.PublicDependencies.Add(\"{pluginModuleName}\");"); - modifiedAny = true; + var text = await File.ReadAllTextAsync(pluginModuleScriptPath); + if (!text.Contains("GameEditorModule", StringComparison.OrdinalIgnoreCase)) + { + gameScriptContents = gameScriptContents.Insert(insertLocation, $"\n options.PublicDependencies.Add(\"{pluginModuleName}\");"); + modifiedAny = true; + } } } From d5e9ad2147a1b6088b3ba26bff082cbd48077b78 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Mon, 22 Jan 2024 21:01:04 -0600 Subject: [PATCH 057/103] Adjust combobox window position based on what direction it opens. --- Source/Editor/GUI/ComboBox.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs index 0417cc7e3..176e83374 100644 --- a/Source/Editor/GUI/ComboBox.cs +++ b/Source/Editor/GUI/ComboBox.cs @@ -428,6 +428,13 @@ namespace FlaxEditor.GUI // Show dropdown list _popupMenu.MinimumWidth = Width; _popupMenu.Show(this, new Float2(1, Height)); + + // Adjust menu position if it is not the down direction + if (_popupMenu.Direction == ContextMenuDirection.RightUp) + { + var position = _popupMenu.RootWindow.Window.Position; + _popupMenu.RootWindow.Window.Position = new Float2(position.X, position.Y - Height); + } } } From 045f49d2d62e36d7967f7031a9e84d2fc06b8294 Mon Sep 17 00:00:00 2001 From: envision3d Date: Mon, 22 Jan 2024 22:25:53 -0600 Subject: [PATCH 058/103] Fixes terrain smoothing separation issue --- .../Tools/Terrain/Sculpt/FlattenMode.cs | 2 +- .../Editor/Tools/Terrain/Sculpt/HolesMode.cs | 2 +- Source/Editor/Tools/Terrain/Sculpt/Mode.cs | 107 +++++++---- .../Editor/Tools/Terrain/Sculpt/NoiseMode.cs | 2 +- .../Editor/Tools/Terrain/Sculpt/SculptMode.cs | 2 +- .../Editor/Tools/Terrain/Sculpt/SmoothMode.cs | 166 ++++++++++++++---- 6 files changed, 214 insertions(+), 67 deletions(-) diff --git a/Source/Editor/Tools/Terrain/Sculpt/FlattenMode.cs b/Source/Editor/Tools/Terrain/Sculpt/FlattenMode.cs index 610a336b7..bf80eb34d 100644 --- a/Source/Editor/Tools/Terrain/Sculpt/FlattenMode.cs +++ b/Source/Editor/Tools/Terrain/Sculpt/FlattenMode.cs @@ -18,7 +18,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt public float TargetHeight = 0.0f; /// - public override unsafe void Apply(ref ApplyParams p) + public override unsafe void ApplyBrushToPatch(ref ApplyParams p) { // If used with invert mode pick the target height level if (p.Options.Invert) diff --git a/Source/Editor/Tools/Terrain/Sculpt/HolesMode.cs b/Source/Editor/Tools/Terrain/Sculpt/HolesMode.cs index d4c10f00e..3d75a56f2 100644 --- a/Source/Editor/Tools/Terrain/Sculpt/HolesMode.cs +++ b/Source/Editor/Tools/Terrain/Sculpt/HolesMode.cs @@ -26,7 +26,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt } /// - public override unsafe void Apply(ref ApplyParams p) + public override unsafe void ApplyBrushToPatch(ref ApplyParams p) { var strength = p.Strength * -10.0f; var brushPosition = p.Gizmo.CursorPosition; diff --git a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs index 9a42d2ae8..a92b899e1 100644 --- a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs +++ b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; using FlaxEditor.Tools.Terrain.Brushes; using FlaxEngine; @@ -50,18 +51,20 @@ namespace FlaxEditor.Tools.Terrain.Sculpt public virtual bool EditHoles => false; /// - /// Applies the modification to the terrain. + /// Gets all patches that will be affected by the brush /// - /// The brush. - /// The options. - /// The gizmo. - /// The terrain. - public unsafe void Apply(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain) + /// + /// + /// + /// + public unsafe virtual List GetAffectedPatches(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain) { + List affectedPatches = new(); + // Combine final apply strength float strength = Strength * options.Strength * options.DeltaTime; if (strength <= 0.0f) - return; + return affectedPatches; if (options.Invert && SupportsNegativeApply) strength *= -1; @@ -72,20 +75,10 @@ namespace FlaxEditor.Tools.Terrain.Sculpt var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount; var tempBuffer = (float*)gizmo.GetHeightmapTempBuffer(heightmapLength * sizeof(float)).ToPointer(); var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex; - ApplyParams p = new ApplyParams - { - Terrain = terrain, - Brush = brush, - Gizmo = gizmo, - Options = options, - Strength = strength, - HeightmapSize = heightmapSize, - TempBuffer = tempBuffer, - }; // Get brush bounds in terrain local space var brushBounds = gizmo.CursorBrushBounds; - terrain.GetLocalToWorldMatrix(out p.TerrainWorld); + terrain.GetLocalToWorldMatrix(out var terrainWorld); terrain.GetWorldToLocalMatrix(out var terrainInvWorld); BoundingBox.Transform(ref brushBounds, ref terrainInvWorld, out var brushBoundsLocal); @@ -131,26 +124,78 @@ namespace FlaxEditor.Tools.Terrain.Sculpt if (sourceHeights == null && sourceHoles == null) throw new Exception("Cannot modify terrain. Loading heightmap failed. See log for more info."); - // Record patch data before editing it - if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord)) + ApplyParams p = new ApplyParams { - gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord); - } + Terrain = terrain, + TerrainWorld = terrainWorld, + Brush = brush, + Gizmo = gizmo, + Options = options, + Strength = strength, + HeightmapSize = heightmapSize, + TempBuffer = tempBuffer, + ModifiedOffset = modifiedOffset, + ModifiedSize = modifiedSize, + PatchCoord = patch.PatchCoord, + PatchPositionLocal = patchPositionLocal, + SourceHeightMap = sourceHeights, + SourceHolesMask = sourceHoles, + }; - // Apply modification - p.ModifiedOffset = modifiedOffset; - p.ModifiedSize = modifiedSize; - p.PatchCoord = patch.PatchCoord; - p.PatchPositionLocal = patchPositionLocal; - p.SourceHeightMap = sourceHeights; - p.SourceHolesMask = sourceHoles; - Apply(ref p); + affectedPatches.Add(p); } + return affectedPatches; + } + + /// + /// Applies the modification to the terrain. + /// + /// The brush. + /// The options. + /// The gizmo. + /// The terrain. + public unsafe void Apply(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain) + { + var affectedPatches = GetAffectedPatches(brush, ref options, gizmo, terrain); + + if (affectedPatches.Count == 0) + { + return; + } + + ApplyBrush(gizmo, affectedPatches); + // Auto NavMesh rebuild + var brushBounds = gizmo.CursorBrushBounds; gizmo.CurrentEditUndoAction.AddDirtyBounds(ref brushBounds); } + /// + /// Applies the brush to all affected patches + /// + /// + /// + public unsafe virtual void ApplyBrush(SculptTerrainGizmoMode gizmo, List affectedPatches) + { + for (int i = 0; i < affectedPatches.Count; i++) + { + ApplyParams patchApplyParams = affectedPatches[i]; + + // Record patch data before editing it + if (!gizmo.CurrentEditUndoAction.HashPatch(ref patchApplyParams.PatchCoord)) + { + gizmo.CurrentEditUndoAction.AddPatch(ref patchApplyParams.PatchCoord); + } + + ApplyBrushToPatch(ref patchApplyParams); + + // Auto NavMesh rebuild + var brushBounds = gizmo.CursorBrushBounds; + gizmo.CurrentEditUndoAction.AddDirtyBounds(ref brushBounds); + } + } + /// /// The mode apply parameters. /// @@ -231,6 +276,6 @@ namespace FlaxEditor.Tools.Terrain.Sculpt /// Applies the modification to the terrain. /// /// The parameters to use. - public abstract void Apply(ref ApplyParams p); + public abstract void ApplyBrushToPatch(ref ApplyParams p); } } diff --git a/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs b/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs index 32d02bb3f..7fcb09288 100644 --- a/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs +++ b/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs @@ -29,7 +29,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt public override bool SupportsNegativeApply => true; /// - public override unsafe void Apply(ref ApplyParams p) + public override unsafe void ApplyBrushToPatch(ref ApplyParams p) { // Prepare var brushPosition = p.Gizmo.CursorPosition; diff --git a/Source/Editor/Tools/Terrain/Sculpt/SculptMode.cs b/Source/Editor/Tools/Terrain/Sculpt/SculptMode.cs index c3409f30c..381b39ebd 100644 --- a/Source/Editor/Tools/Terrain/Sculpt/SculptMode.cs +++ b/Source/Editor/Tools/Terrain/Sculpt/SculptMode.cs @@ -15,7 +15,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt public override bool SupportsNegativeApply => true; /// - public override unsafe void Apply(ref ApplyParams p) + public override unsafe void ApplyBrushToPatch(ref ApplyParams p) { var strength = p.Strength * 1000.0f; var brushPosition = p.Gizmo.CursorPosition; diff --git a/Source/Editor/Tools/Terrain/Sculpt/SmoothMode.cs b/Source/Editor/Tools/Terrain/Sculpt/SmoothMode.cs index d23eae8dd..49ccfe0de 100644 --- a/Source/Editor/Tools/Terrain/Sculpt/SmoothMode.cs +++ b/Source/Editor/Tools/Terrain/Sculpt/SmoothMode.cs @@ -1,7 +1,9 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using System; +using System.Collections.Generic; using FlaxEngine; +using FlaxEditor.Tools.Terrain.Brushes; +using System; namespace FlaxEditor.Tools.Terrain.Sculpt { @@ -19,43 +21,135 @@ namespace FlaxEditor.Tools.Terrain.Sculpt public float FilterRadius = 0.4f; /// - public override unsafe void Apply(ref ApplyParams p) + public override unsafe void ApplyBrush(SculptTerrainGizmoMode gizmo, List affectedPatches) { - // Prepare - var brushPosition = p.Gizmo.CursorPosition; - var radius = Mathf.Max(Mathf.CeilToInt(FilterRadius * 0.01f * p.Brush.Size), 2); - var max = p.HeightmapSize - 1; - var strength = Mathf.Saturate(p.Strength); - - // Apply brush modification Profiler.BeginEvent("Apply Brush"); - for (int z = 0; z < p.ModifiedSize.Y; z++) + + // TODO: don't need these on each patch; just need them once + var heightmapSize = affectedPatches[0].HeightmapSize; + var radius = Mathf.Max(Mathf.CeilToInt(FilterRadius * 0.01f * affectedPatches[0].Brush.Size), 2); + + + ///// + /// Calculate bounding coordinates of the total affected area + /// + + + Int2 modifieedAreaMinCoord = Int2.Maximum; + Int2 modifiedAreaMaxCoord = Int2.Minimum; + + for (int i = 0; i < affectedPatches.Count; i++) { - var zz = z + p.ModifiedOffset.Y; - for (int x = 0; x < p.ModifiedSize.X; x++) + var patch = affectedPatches[i]; + + var tl = (patch.PatchCoord * (heightmapSize - 1)) + patch.ModifiedOffset; + var br = tl + patch.ModifiedSize; + + if (tl.X <= modifieedAreaMinCoord.X && tl.Y <= modifieedAreaMinCoord.Y) { - var xx = x + p.ModifiedOffset.X; - var sourceHeight = p.SourceHeightMap[zz * p.HeightmapSize + xx]; + modifieedAreaMinCoord = tl; + } - var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, sourceHeight, zz * FlaxEngine.Terrain.UnitsPerVertex); - Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); + if (br.X >= modifiedAreaMaxCoord.X && br.Y >= modifiedAreaMaxCoord.Y) + { + modifiedAreaMaxCoord = br; + } + } - var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength; - if (paintAmount > 0) + var totalModifiedSize = modifiedAreaMaxCoord - modifieedAreaMinCoord; + + + ///// + /// Build map of heights in affected area + /// + + + var modifiedHeights = new float[totalModifiedSize.X * totalModifiedSize.Y]; + + for (int i = 0; i < affectedPatches.Count; i++) + { + var patch = affectedPatches[i]; + + for (int z = 0; z < patch.ModifiedSize.Y; z++) + { + for (int x = 0; x < patch.ModifiedSize.X; x++) { + // read height from current patch + var localCoordX = (x + patch.ModifiedOffset.X); + var localCoordY = (z + patch.ModifiedOffset.Y); + var coordHeight = patch.SourceHeightMap[(localCoordY * heightmapSize) + localCoordX]; + + // calculate the absolute coordinate of the terrain point + var absoluteCurrentPointCoord = patch.PatchCoord * (heightmapSize - 1) + patch.ModifiedOffset + new Int2(x, z); + var currentPointCoordRelativeToModifiedArea = absoluteCurrentPointCoord - modifieedAreaMinCoord; + + // store height + var index = (currentPointCoordRelativeToModifiedArea.Y * totalModifiedSize.X) + currentPointCoordRelativeToModifiedArea.X; + modifiedHeights[index] = coordHeight; + } + } + } + + + ///// + /// Iterate through modified points and smooth now that we have height information for all necessary points + /// + + + for (int i = 0; i < affectedPatches.Count; i++) + { + var patch = affectedPatches[i]; + + var brushPosition = patch.Gizmo.CursorPosition; + var strength = Mathf.Saturate(patch.Strength); + + for (int z = 0; z < patch.ModifiedSize.Y; z++) + { + for (int x = 0; x < patch.ModifiedSize.X; x++) + { + // read height from current patch + var localCoordX = (x + patch.ModifiedOffset.X); + var localCoordY = (z + patch.ModifiedOffset.Y); + var coordHeight = patch.SourceHeightMap[(localCoordY * heightmapSize) + localCoordX]; + + // calculate the absolute coordinate of the terrain point + var absoluteCurrentPointCoord = patch.PatchCoord * (heightmapSize - 1) + patch.ModifiedOffset + new Int2(x, z); + var currentPointCoordRelativeToModifiedArea = absoluteCurrentPointCoord - modifieedAreaMinCoord; + + // calculate brush influence at the current position + var samplePositionLocal = patch.PatchPositionLocal + new Vector3(localCoordX * FlaxEngine.Terrain.UnitsPerVertex, coordHeight, localCoordY * FlaxEngine.Terrain.UnitsPerVertex); + Vector3.Transform(ref samplePositionLocal, ref patch.TerrainWorld, out Vector3 samplePositionWorld); + var paintAmount = patch.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength; + + if (paintAmount == 0) + { + patch.TempBuffer[z * patch.ModifiedSize.X + x] = coordHeight; + continue; + } + + // Record patch data before editing it + if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord)) + { + gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord); + } + // Sum the nearby values float smoothValue = 0; int smoothValueSamples = 0; - int minX = Math.Max(x - radius + p.ModifiedOffset.X, 0); - int minZ = Math.Max(z - radius + p.ModifiedOffset.Y, 0); - int maxX = Math.Min(x + radius + p.ModifiedOffset.X, max); - int maxZ = Math.Min(z + radius + p.ModifiedOffset.Y, max); + + var minX = Math.Max(0, currentPointCoordRelativeToModifiedArea.X - radius); + var maxX = Math.Min(totalModifiedSize.X - 1, currentPointCoordRelativeToModifiedArea.X + radius); + var minZ = Math.Max(0, currentPointCoordRelativeToModifiedArea.Y - radius); + var maxZ = Math.Min(totalModifiedSize.Y - 1, currentPointCoordRelativeToModifiedArea.Y + radius); + for (int dz = minZ; dz <= maxZ; dz++) { for (int dx = minX; dx <= maxX; dx++) { - var height = p.SourceHeightMap[dz * p.HeightmapSize + dx]; + var coordIndex = (dz * totalModifiedSize.X) + dx; + var height = modifiedHeights[coordIndex]; + smoothValue += height; smoothValueSamples++; } @@ -65,18 +159,26 @@ namespace FlaxEditor.Tools.Terrain.Sculpt smoothValue /= smoothValueSamples; // Blend between the height and smooth value - p.TempBuffer[z * p.ModifiedSize.X + x] = Mathf.Lerp(sourceHeight, smoothValue, paintAmount); - } - else - { - p.TempBuffer[z * p.ModifiedSize.X + x] = sourceHeight; + var newHeight = Mathf.Lerp(coordHeight, smoothValue, paintAmount); + patch.TempBuffer[z * patch.ModifiedSize.X + x] = newHeight; } } - } - Profiler.EndEvent(); - // Update terrain patch - TerrainTools.ModifyHeightMap(p.Terrain, ref p.PatchCoord, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize); + // Update terrain patch + TerrainTools.ModifyHeightMap(patch.Terrain, ref patch.PatchCoord, patch.TempBuffer, ref patch.ModifiedOffset, ref patch.ModifiedSize); + } + + // Auto NavMesh rebuild + var brushBounds = gizmo.CursorBrushBounds; + gizmo.CurrentEditUndoAction.AddDirtyBounds(ref brushBounds); + + Profiler.EndEvent(); + } + + /// + public override unsafe void ApplyBrushToPatch(ref ApplyParams p) + { + // noop; unused } } } From f306d34a6e953e81182617ea9c164036fa98d897 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 23 Jan 2024 08:34:15 -0600 Subject: [PATCH 059/103] Add drag drop for actor script items into scenes and prefabs. --- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 46 +++++++++++++++++++ .../Viewport/MainEditorGizmoViewport.cs | 11 ++++- .../Editor/Viewport/PrefabWindowViewport.cs | 11 ++++- .../Editor/Viewport/ViewportDraggingHelper.cs | 24 +++++++++- .../Windows/Assets/PrefabWindow.Hierarchy.cs | 40 ++++++++++++++++ Source/Editor/Windows/SceneTreeWindow.cs | 42 +++++++++++++++++ 6 files changed, 170 insertions(+), 4 deletions(-) diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 9cfe5b7bd..d31955acc 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -29,6 +29,7 @@ namespace FlaxEditor.SceneGraph.GUI private DragScripts _dragScripts; private DragAssets _dragAssets; private DragActorType _dragActorType; + private DragScriptItems _dragScriptItems; private DragHandlers _dragHandlers; private List _highlights; private bool _hasSearchFilter; @@ -395,6 +396,13 @@ namespace FlaxEditor.SceneGraph.GUI } if (_dragActorType.OnDragEnter(data)) return _dragActorType.Effect; + if (_dragScriptItems == null) + { + _dragScriptItems = new DragScriptItems(ValidateDragScriptItem); + _dragHandlers.Add(_dragScriptItems); + } + if (_dragScriptItems.OnDragEnter(data)) + return _dragScriptItems.Effect; return DragDropEffect.None; } @@ -673,7 +681,37 @@ namespace FlaxEditor.SceneGraph.GUI actor.Transform = Actor.Transform; ActorNode.Root.Spawn(actor, Actor); } + result = DragDropEffect.Move; + } + // Drag script item + else if (_dragScriptItems != null && _dragScriptItems.HasValidDrag) + { + var spawnParent = myActor; + if (DragOverMode == DragItemPositioning.Above || DragOverMode == DragItemPositioning.Below) + spawnParent = newParent; + for (int i = 0; i < _dragScriptItems.Objects.Count; i++) + { + var item = _dragScriptItems.Objects[i]; + // Find actors with the same content item and spawn them. + foreach (var actorType in Editor.Instance.CodeEditing.Actors.Get()) + { + if (actorType.ContentItem != item) + continue; + + var actor = actorType.CreateInstance() as Actor; + if (actor == null) + { + Editor.LogWarning("Failed to spawn actor of type " + actorType.TypeName); + continue; + } + actor.StaticFlags = spawnParent.StaticFlags; + actor.Name = actorType.Name; + actor.Transform = spawnParent.Transform; + ActorNode.Root.Spawn(actor, spawnParent); + actor.OrderInParent = newOrder; + } + } result = DragDropEffect.Move; } @@ -728,6 +766,14 @@ namespace FlaxEditor.SceneGraph.GUI return true; } + private static bool ValidateDragScriptItem(ScriptItem script) + { + var actors = Editor.Instance.CodeEditing.Actors.Get(); + if (actors.Any(x => x.ContentItem == script)) + return true; + return false; + } + /// protected override void DoDragDrop() { diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 24c8f1c4c..c501def33 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.Content; using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; @@ -194,7 +195,7 @@ namespace FlaxEditor.Viewport : base(Object.New(), editor.Undo, editor.Scene.Root) { _editor = editor; - DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType); + DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem); var inputOptions = editor.Options.Options.Input; // Prepare rendering task @@ -940,6 +941,14 @@ namespace FlaxEditor.Viewport return Level.IsAnySceneLoaded; } + private static bool ValidateDragScriptItem(ScriptItem script) + { + var actors = Editor.Instance.CodeEditing.Actors.Get(); + if (actors.Any(x => x.ContentItem == script)) + return true; + return false; + } + /// public override DragDropEffect OnDragMove(ref Float2 location, DragData data) { diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 6e111f588..a46002028 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.Content; using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; @@ -81,7 +82,7 @@ namespace FlaxEditor.Viewport _window.SelectionChanged += OnSelectionChanged; Undo = window.Undo; ViewportCamera = new FPSCamera(); - DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType); + DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem); ShowDebugDraw = true; ShowEditorPrimitives = true; Gizmos = new GizmosCollection(this); @@ -701,6 +702,14 @@ namespace FlaxEditor.Viewport { return true; } + + private static bool ValidateDragScriptItem(ScriptItem script) + { + var actors = Editor.Instance.CodeEditing.Actors.Get(); + if (actors.Any(x => x.ContentItem == script)) + return true; + return false; + } /// public override DragDropEffect OnDragMove(ref Float2 location, DragData data) diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs index 8a1b4f183..83c9cfab0 100644 --- a/Source/Editor/Viewport/ViewportDraggingHelper.cs +++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs @@ -39,17 +39,19 @@ namespace FlaxEditor.Viewport private readonly EditorViewport _viewport; private readonly DragAssets _dragAssets; private readonly DragActorType _dragActorType; + private readonly DragScriptItems _dragScriptItem; private StaticModel _previewStaticModel; private int _previewModelEntryIndex; private BrushSurface _previewBrushSurface; - internal ViewportDragHandlers(IGizmoOwner owner, EditorViewport viewport, Func validateAsset, Func validateDragActorType) + internal ViewportDragHandlers(IGizmoOwner owner, EditorViewport viewport, Func validateAsset, Func validateDragActorType, Func validateDragScriptItem) { _owner = owner; _viewport = viewport; Add(_dragAssets = new DragAssets(validateAsset)); Add(_dragActorType = new DragActorType(validateDragActorType)); + Add(_dragScriptItem = new DragScriptItems(validateDragScriptItem)); } internal void ClearDragEffects() @@ -102,7 +104,13 @@ namespace FlaxEditor.Viewport foreach (var actorType in _dragActorType.Objects) Spawn(actorType, hit, ref location, ref hitLocation, ref hitNormal); } - + else if (_dragScriptItem.HasValidDrag) + { + result = _dragScriptItem.Effect; + foreach (var scripItem in _dragScriptItem.Objects) + Spawn(scripItem, hit, ref location, ref hitLocation, ref hitNormal); + } + Debug.Log("Hit"); OnDragDrop(new DragDropEventArgs { Hit = hit, HitLocation = hitLocation }); return result; @@ -193,6 +201,18 @@ namespace FlaxEditor.Viewport _viewport.Focus(); } + private void Spawn(ScriptItem item, SceneGraphNode hit, ref Float2 location, ref Vector3 hitLocation, ref Vector3 hitNormal) + { + // Find actors with the same content item and spawn them. + foreach (var actorType in Editor.Instance.CodeEditing.Actors.Get()) + { + if (actorType.ContentItem != item) + continue; + + Spawn(actorType, hit, ref location, ref hitLocation, ref hitNormal); + } + } + private void Spawn(ScriptType item, SceneGraphNode hit, ref Float2 location, ref Vector3 hitLocation, ref Vector3 hitNormal) { var actor = item.CreateInstance() as Actor; diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index f263d4734..1cf5fb40b 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.Content; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Drag; @@ -64,6 +65,7 @@ namespace FlaxEditor.Windows.Assets private PrefabWindow _window; private DragAssets _dragAssets; private DragActorType _dragActorType; + private DragScriptItems _dragScriptItems; private DragHandlers _dragHandlers; public SceneTreePanel(PrefabWindow window) @@ -83,6 +85,14 @@ namespace FlaxEditor.Windows.Assets { return true; } + + private static bool ValidateDragScriptItem(ScriptItem script) + { + var actors = Editor.Instance.CodeEditing.Actors.Get(); + if (actors.Any(x => x.ContentItem == script)) + return true; + return false; + } /// public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) @@ -106,6 +116,13 @@ namespace FlaxEditor.Windows.Assets } if (_dragActorType.OnDragEnter(data)) return _dragActorType.Effect; + if (_dragScriptItems == null) + { + _dragScriptItems = new DragScriptItems(ValidateDragScriptItem); + _dragHandlers.Add(_dragScriptItems); + } + if (_dragScriptItems.OnDragEnter(data)) + return _dragScriptItems.Effect; } return result; } @@ -162,7 +179,30 @@ namespace FlaxEditor.Windows.Assets } result = DragDropEffect.Move; } + // Drag script item + else if (_dragScriptItems != null && _dragScriptItems.HasValidDrag) + { + for (int i = 0; i < _dragScriptItems.Objects.Count; i++) + { + var item = _dragScriptItems.Objects[i]; + // Find actors with the same content item and spawn them. + foreach (var actorType in Editor.Instance.CodeEditing.Actors.Get()) + { + if (actorType.ContentItem != item) + continue; + var actor = actorType.CreateInstance() as Actor; + if (actor == null) + { + Editor.LogWarning("Failed to spawn actor of type " + actorType.TypeName); + continue; + } + actor.Name = actorType.Name; + _window.Spawn(actor); + } + } + result = DragDropEffect.Move; + } _dragHandlers.OnDragDrop(null); } return result; diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 63ba7b960..c1390a1fe 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.Gizmo; using FlaxEditor.Content; using FlaxEditor.GUI.Tree; @@ -31,6 +32,7 @@ namespace FlaxEditor.Windows private DragAssets _dragAssets; private DragActorType _dragActorType; + private DragScriptItems _dragScriptItems; private DragHandlers _dragHandlers; /// @@ -272,6 +274,14 @@ namespace FlaxEditor.Windows { return true; } + + private static bool ValidateDragScriptItem(ScriptItem script) + { + var actors = Editor.Instance.CodeEditing.Actors.Get(); + if (actors.Any(x => x.ContentItem == script)) + return true; + return false; + } /// public override void Draw() @@ -380,6 +390,13 @@ namespace FlaxEditor.Windows } if (_dragActorType.OnDragEnter(data) && result == DragDropEffect.None) return _dragActorType.Effect; + if (_dragScriptItems == null) + { + _dragScriptItems = new DragScriptItems(ValidateDragScriptItem); + _dragHandlers.Add(_dragScriptItems); + } + if (_dragScriptItems.OnDragEnter(data) && result == DragDropEffect.None) + return _dragScriptItems.Effect; } return result; } @@ -445,6 +462,31 @@ namespace FlaxEditor.Windows } result = DragDropEffect.Move; } + // Drag script item + else if (_dragScriptItems != null && _dragScriptItems.HasValidDrag) + { + for (int i = 0; i < _dragScriptItems.Objects.Count; i++) + { + var item = _dragScriptItems.Objects[i]; + // Find actors with the same content item and spawn them. + foreach (var actorType in Editor.Instance.CodeEditing.Actors.Get()) + { + if (actorType.ContentItem != item) + continue; + + var actor = actorType.CreateInstance() as Actor; + if (actor == null) + { + Editor.LogWarning("Failed to spawn actor of type " + actorType.TypeName); + continue; + } + actor.Name = actorType.Name; + Level.SpawnActor(actor); + Editor.Scene.MarkSceneEdited(actor.Scene); + } + } + result = DragDropEffect.Move; + } _dragHandlers.OnDragDrop(null); } From b85f471fcb0b7cad2b6eb8d399989a1ff460251f Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 23 Jan 2024 08:59:27 -0600 Subject: [PATCH 060/103] Add drag cleanup on destroy. --- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 1 + Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs | 1 + Source/Editor/Windows/SceneTreeWindow.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index d31955acc..64def3eae 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -814,6 +814,7 @@ namespace FlaxEditor.SceneGraph.GUI _dragScripts = null; _dragAssets = null; _dragActorType = null; + _dragScriptItems = null; _dragHandlers?.Clear(); _dragHandlers = null; _highlights = null; diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index 1cf5fb40b..351dd3f62 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -213,6 +213,7 @@ namespace FlaxEditor.Windows.Assets _window = null; _dragAssets = null; _dragActorType = null; + _dragScriptItems = null; _dragHandlers?.Clear(); _dragHandlers = null; diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index c1390a1fe..9385fb781 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -498,6 +498,7 @@ namespace FlaxEditor.Windows { _dragAssets = null; _dragActorType = null; + _dragScriptItems = null; _dragHandlers?.Clear(); _dragHandlers = null; _tree = null; From 39e7be6322a21353bf26f9799e083e1c12604f36 Mon Sep 17 00:00:00 2001 From: whocares77 <97740209+whocares77@users.noreply.github.com> Date: Wed, 24 Jan 2024 19:58:29 +0300 Subject: [PATCH 061/103] Added "Start Time" option for Audio Source Actor Added ability to set the start time of playback if "Play On Start" is enabled. --- Source/Engine/Audio/AudioSource.cpp | 11 ++++++++++- Source/Engine/Audio/AudioSource.h | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp index cb32e0967..a474ec0c7 100644 --- a/Source/Engine/Audio/AudioSource.cpp +++ b/Source/Engine/Audio/AudioSource.cpp @@ -19,6 +19,7 @@ AudioSource::AudioSource(const SpawnParams& params) , _minDistance(1000.0f) , _loop(false) , _playOnStart(false) + , _startTime(0.0f) , _allowSpatialization(true) { Clip.Changed.Bind(this); @@ -71,6 +72,11 @@ void AudioSource::SetPlayOnStart(bool value) _playOnStart = value; } +void AudioSource::SetStartTime(float value) +{ + _startTime = value; +} + void AudioSource::SetMinDistance(float value) { value = Math::Max(0.0f, value); @@ -361,6 +367,7 @@ void AudioSource::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(DopplerFactor, _dopplerFactor); SERIALIZE_MEMBER(Loop, _loop); SERIALIZE_MEMBER(PlayOnStart, _playOnStart); + SERIALIZE_MEMBER(StartTime, _startTime); SERIALIZE_MEMBER(AllowSpatialization, _allowSpatialization); } @@ -377,6 +384,7 @@ void AudioSource::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE_MEMBER(DopplerFactor, _dopplerFactor); DESERIALIZE_MEMBER(Loop, _loop); DESERIALIZE_MEMBER(PlayOnStart, _playOnStart); + DESERIALIZE_MEMBER(StartTime, _startTime); DESERIALIZE_MEMBER(AllowSpatialization, _allowSpatialization); DESERIALIZE(Clip); } @@ -538,7 +546,8 @@ void AudioSource::BeginPlay(SceneBeginData* data) #if USE_EDITOR if (Time::GetGamePaused()) return; -#endif +#endif Play(); + SetTime(GetStartTime()); } } diff --git a/Source/Engine/Audio/AudioSource.h b/Source/Engine/Audio/AudioSource.h index 70cdb4180..5c0c23c03 100644 --- a/Source/Engine/Audio/AudioSource.h +++ b/Source/Engine/Audio/AudioSource.h @@ -52,6 +52,7 @@ private: float _dopplerFactor = 1.0f; bool _loop; bool _playOnStart; + float _startTime; bool _allowSpatialization; bool _clipChanged = false; @@ -148,11 +149,25 @@ public: return _playOnStart; } + /// + /// Determines the time (in seconds) at which the audio clip starts playing if Play On Start is enabled. + /// + API_PROPERTY(Attributes = "EditorOrder(51), DefaultValue(0.0f), Limit(0, float.MaxValue, 0.01f), EditorDisplay(\"Audio Source\", \"Start Time\"), VisibleIf(nameof(PlayOnStart))") + FORCE_INLINE float GetStartTime() const + { + return _startTime; + } + /// /// Determines whether the audio clip should auto play on game start. /// API_PROPERTY() void SetPlayOnStart(bool value); + /// + /// Determines the time (in seconds) at which the audio clip starts playing if Play On Start is enabled. + /// + API_PROPERTY() void SetStartTime(float value); + /// /// Gets the minimum distance at which audio attenuation starts. When the listener is closer to the source than this value, audio is heard at full volume. Once farther away the audio starts attenuating. /// From ee19bca7e4be0e49ead09990165b6d00ed499b98 Mon Sep 17 00:00:00 2001 From: z1dev Date: Fri, 26 Jan 2024 09:07:02 +0100 Subject: [PATCH 062/103] Fixing skipped static and other fields generating "else if" without "if" first. --- .../Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 5e63f489d..1e8bb749f 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -2423,28 +2423,29 @@ namespace Flax.Build.Bindings // Getter for structure field contents.AppendLine(" static void GetField(void* ptr, const String& name, Variant& value)"); contents.AppendLine(" {"); - for (var i = 0; i < structureInfo.Fields.Count; i++) + for (int i = 0, count = 0; i < structureInfo.Fields.Count; i++) { var fieldInfo = structureInfo.Fields[i]; if (fieldInfo.IsReadOnly || fieldInfo.IsStatic || fieldInfo.IsConstexpr || fieldInfo.Access == AccessLevel.Private) continue; - if (i == 0) + if (count == 0) contents.AppendLine($" if (name == TEXT(\"{fieldInfo.Name}\"))"); else contents.AppendLine($" else if (name == TEXT(\"{fieldInfo.Name}\"))"); contents.AppendLine($" value = {GenerateCppWrapperNativeToVariant(buildData, fieldInfo.Type, structureInfo, $"(({structureTypeNameNative}*)ptr)->{fieldInfo.Name}")};"); + count++; } contents.AppendLine(" }").AppendLine(); // Setter for structure field contents.AppendLine(" static void SetField(void* ptr, const String& name, const Variant& value)"); contents.AppendLine(" {"); - for (var i = 0; i < structureInfo.Fields.Count; i++) + for (int i = 0, count = 0; i < structureInfo.Fields.Count; i++) { var fieldInfo = structureInfo.Fields[i]; if (fieldInfo.IsReadOnly || fieldInfo.IsStatic || fieldInfo.IsConstexpr || fieldInfo.Access == AccessLevel.Private) continue; - if (i == 0) + if (count == 0) contents.AppendLine($" if (name == TEXT(\"{fieldInfo.Name}\"))"); else contents.AppendLine($" else if (name == TEXT(\"{fieldInfo.Name}\"))"); @@ -2460,6 +2461,7 @@ namespace Flax.Build.Bindings } else contents.AppendLine($" (({structureTypeNameNative}*)ptr)->{fieldInfo.Name} = {GenerateCppWrapperVariantToNative(buildData, fieldInfo.Type, structureInfo, "value")};"); + count++; } contents.AppendLine(" }"); From 18fc6ebd3971e2f74c87f32c73a23710604a74e0 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 26 Jan 2024 10:38:36 -0600 Subject: [PATCH 063/103] Half windowed size and center on screen if going to windowed mode. --- Source/Engine/Platform/Windows/WindowsWindow.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Platform/Windows/WindowsWindow.cpp b/Source/Engine/Platform/Windows/WindowsWindow.cpp index 181077125..fec322bc3 100644 --- a/Source/Engine/Platform/Windows/WindowsWindow.cpp +++ b/Source/Engine/Platform/Windows/WindowsWindow.cpp @@ -324,7 +324,19 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool maximized) lStyle |= WS_OVERLAPPED | WS_SYSMENU | WS_BORDER | WS_CAPTION; SetWindowLong(_handle, GWL_STYLE, lStyle); - SetWindowPos(_handle, nullptr, 0, 0, (int)_settings.Size.X, (int)_settings.Size.Y, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + const Float2 clientSize = GetClientSize(); + const Float2 desktopSize = Platform::GetDesktopSize(); + // Move window and half size if it is larger than desktop size + if (clientSize.X >= desktopSize.X && clientSize.Y >= desktopSize.Y) + { + const Float2 halfSize = desktopSize * 0.5f; + const Float2 middlePos = halfSize * 0.5f; + SetWindowPos(_handle, nullptr, (int)middlePos.X, (int)middlePos.Y, (int)halfSize.X, (int)halfSize.Y, SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE); + } + else + { + SetWindowPos(_handle, nullptr, 0, 0, (int)clientSize.X, (int)clientSize.Y, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + } if (maximized) { From 74d348706c41774d4b6cb73a3d81a7eae969425e Mon Sep 17 00:00:00 2001 From: ruan Date: Sat, 27 Jan 2024 14:23:41 -0400 Subject: [PATCH 064/103] Add skeleton mask asset parameter to Blend With Mask anim node. --- Source/Editor/Surface/Archetypes/Animation.cs | 50 ++++++++++++++++++- .../Animations/Graph/AnimGroup.Animation.cpp | 11 ++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index c659b475a..b2494081a 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -28,6 +28,52 @@ namespace FlaxEditor.Surface.Archetypes } } + /// + /// Customized for Blend with Mask node. + /// + public class SkeletonMaskSample : SurfaceNode + { + private AssetSelect _assetSelect; + private Box _assetBox; + + /// + public SkeletonMaskSample(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(id, context, nodeArch, groupArch) + { + } + + /// + public override void OnSurfaceLoaded(SurfaceNodeActions action) + { + base.OnSurfaceLoaded(action); + + if (Surface != null) + { + _assetSelect = GetChild(); + + // 4 is the id of skeleton mask parameter node. + if (TryGetBox(4, out var box)) + { + _assetBox = box; + _assetSelect.Visible = !_assetBox.HasAnyConnection; + } + } + } + + /// + public override void ConnectionTick(Box box) + { + base.ConnectionTick(box); + + if (_assetBox == null) + return; + if (box.ID != _assetBox.ID) + return; + + _assetSelect.Visible = !box.HasAnyConnection; + } + } + /// /// Customized for the animation sampling nodes /// @@ -552,6 +598,7 @@ namespace FlaxEditor.Surface.Archetypes TypeID = 11, Title = "Blend with Mask", Description = "Blend animation poses using skeleton mask", + Create = (id, context, arch, groupArch) => new SkeletonMaskSample(id, context, arch, groupArch), Flags = NodeFlags.AnimGraph, Size = new Float2(180, 140), DefaultValues = new object[] @@ -565,7 +612,8 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(0, "Pose A", true, typeof(void), 1), NodeElementArchetype.Factory.Input(1, "Pose B", true, typeof(void), 2), NodeElementArchetype.Factory.Input(2, "Alpha", true, typeof(float), 3, 0), - NodeElementArchetype.Factory.Asset(0, 70, 1, typeof(SkeletonMask)), + NodeElementArchetype.Factory.Input(3, "Skeleton Mask Asset", true, typeof(SkeletonMask), 4), + NodeElementArchetype.Factory.Asset(0, Surface.Constants.LayoutOffsetY * 4, 1, typeof(SkeletonMask)), } }, new NodeArchetype diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index cdf1ebd84..ab3bb0250 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -1131,6 +1131,17 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu { const float alpha = Math::Saturate((float)tryGetValue(node->GetBox(3), node->Values[0])); auto mask = node->Assets[0].As(); + auto maskAssetBox = node->GetBox(4); // 4 is the id of skeleton mask parameter node. + + // Check if have some mask asset conected with the mask node + if (maskAssetBox->HasConnection()) + { + const Value assetBoxValue = tryGetValue(maskAssetBox, Value::Null); + + // Use the mask conected with this node instead of default mask asset + if (assetBoxValue != Value::Null) + mask = (SkeletonMask *)assetBoxValue.AsAsset; + } // Only A or missing/invalid mask if (Math::NearEqual(alpha, 0.0f, ANIM_GRAPH_BLEND_THRESHOLD) || mask == nullptr || mask->WaitForLoaded()) From af8e1e527f0755401013d7d67fef559671c0d4ef Mon Sep 17 00:00:00 2001 From: NoriteSC <53096989+NoriteSC@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:31:12 +0100 Subject: [PATCH 065/103] Update BindingsGenerator.Cpp.cs --- .../Flax.Build/Bindings/BindingsGenerator.Cpp.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 5e63f489d..fc053eda7 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1937,7 +1937,7 @@ namespace Flax.Build.Bindings var useCSharp = EngineConfiguration.WithCSharp(buildData.TargetOptions); var hasInterface = classInfo.Interfaces != null && classInfo.Interfaces.Any(x => x.Access == AccessLevel.Public); CppInternalCalls.Clear(); - + if (classInfo.IsAutoSerialization) GenerateCppAutoSerialization(buildData, contents, moduleInfo, classInfo, classTypeNameNative); GenerateCppTypeInternalsStatics?.Invoke(buildData, classInfo, contents); @@ -1955,7 +1955,7 @@ namespace Flax.Build.Bindings var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0; CppIncludeFiles.Add("Engine/Profiler/ProfilerCPU.h"); var bindPrefix = eventInfo.IsStatic ? classTypeNameNative + "::" : "__obj->"; - + //eventInfo. if (useCSharp) { // C# event invoking wrapper (calls C# event from C++ delegate) @@ -1965,12 +1965,12 @@ namespace Flax.Build.Bindings contents.Append(" "); if (eventInfo.IsStatic) contents.Append("static "); - contents.AppendFormat("void {0}_ManagedWrapper(", eventInfo.Name); + contents.AppendFormat("void {0}_ManagedWrapper(", eventInfo.Name); for (var i = 0; i < paramsCount; i++) { if (i != 0) contents.Append(", "); - contents.Append(eventInfo.Type.GenericArgs[i]).Append(" arg" + i); + contents.Append(eventInfo.Type.GenericArgs[i].GetFullNameNative(buildData,classInfo)).Append(" arg" + i); } contents.Append(')').AppendLine(); contents.Append(" {").AppendLine(); @@ -2058,7 +2058,7 @@ namespace Flax.Build.Bindings { if (i != 0) contents.Append(", "); - contents.Append(eventInfo.Type.GenericArgs[i]); + contents.Append(eventInfo.Type.GenericArgs[i].GetFullNameNative(buildData, classInfo)); } contents.Append(")> f;").AppendLine(); if (eventInfo.IsStatic) @@ -2084,7 +2084,7 @@ namespace Flax.Build.Bindings { if (i != 0) contents.Append(", "); - contents.Append(eventInfo.Type.GenericArgs[i]).Append(" arg" + i); + contents.Append(eventInfo.Type.GenericArgs[i].GetFullNameNative(buildData, classInfo)).Append(" arg" + i); } contents.Append(')').AppendLine(); contents.Append(" {").AppendLine(); @@ -2111,7 +2111,7 @@ namespace Flax.Build.Bindings { if (i != 0) contents.Append(", "); - contents.Append(eventInfo.Type.GenericArgs[i]); + contents.Append(eventInfo.Type.GenericArgs[i].GetFullNameNative(buildData, classInfo)); } contents.Append(")> f;").AppendLine(); if (eventInfo.IsStatic) From dec9fbd74b7418778c36232fbc2207c1949f528d Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 2 Feb 2024 09:18:48 -0600 Subject: [PATCH 066/103] Add hold arrow key in tree to continuously scroll actors --- Source/Editor/GUI/Tree/Tree.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index 3a9780ed9..ab0d4adc4 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -19,7 +19,7 @@ namespace FlaxEditor.GUI.Tree /// /// The key updates timeout in seconds. /// - public static float KeyUpdateTimeout = 0.12f; + public static float KeyUpdateTimeout = 0.25f; /// /// Delegate for selected tree nodes collection change. @@ -349,8 +349,8 @@ namespace FlaxEditor.GUI.Tree var window = Root; if (_keyUpdateTime >= KeyUpdateTimeout && window is WindowRootControl windowRoot && windowRoot.Window.IsFocused) { - bool keyUpArrow = window.GetKeyDown(KeyboardKeys.ArrowUp); - bool keyDownArrow = window.GetKeyDown(KeyboardKeys.ArrowDown); + bool keyUpArrow = window.GetKey(KeyboardKeys.ArrowUp); + bool keyDownArrow = window.GetKey(KeyboardKeys.ArrowDown); // Check if arrow flags are different if (keyDownArrow != keyUpArrow) From 0793ebc132734b1d8703a1d37d28523b25cabafa Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 10:58:14 +0100 Subject: [PATCH 067/103] Improve #2221 to be more dynamic when clicking arrow --- Source/Editor/GUI/Tree/Tree.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Editor/GUI/Tree/Tree.cs b/Source/Editor/GUI/Tree/Tree.cs index ab0d4adc4..e86e1382a 100644 --- a/Source/Editor/GUI/Tree/Tree.cs +++ b/Source/Editor/GUI/Tree/Tree.cs @@ -113,7 +113,7 @@ namespace FlaxEditor.GUI.Tree AutoFocus = false; _supportMultiSelect = supportMultiSelect; - _keyUpdateTime = KeyUpdateTimeout * 10; + _keyUpdateTime = KeyUpdateTimeout; } internal void OnRightClickInternal(TreeNode node, ref Float2 location) @@ -347,6 +347,8 @@ namespace FlaxEditor.GUI.Tree if (ContainsFocus && node != null && node.AutoFocus) { var window = Root; + if (window.GetKeyDown(KeyboardKeys.ArrowUp) || window.GetKeyDown(KeyboardKeys.ArrowDown)) + _keyUpdateTime = KeyUpdateTimeout; if (_keyUpdateTime >= KeyUpdateTimeout && window is WindowRootControl windowRoot && windowRoot.Window.IsFocused) { bool keyUpArrow = window.GetKey(KeyboardKeys.ArrowUp); From f9448c3b42116cdfec08efa2daf97e85e91d79af Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 10:59:55 +0100 Subject: [PATCH 068/103] Skip calling `SetTime` api if start time is unused #2203 --- Source/Engine/Audio/AudioSource.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Audio/AudioSource.cpp b/Source/Engine/Audio/AudioSource.cpp index a474ec0c7..89d1d0a5b 100644 --- a/Source/Engine/Audio/AudioSource.cpp +++ b/Source/Engine/Audio/AudioSource.cpp @@ -546,8 +546,9 @@ void AudioSource::BeginPlay(SceneBeginData* data) #if USE_EDITOR if (Time::GetGamePaused()) return; -#endif +#endif Play(); - SetTime(GetStartTime()); + if (GetStartTime() > 0) + SetTime(GetStartTime()); } } From b2d0afd4eff03565ed4f2b20535165522762668b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 11:09:08 +0100 Subject: [PATCH 069/103] Simplify code in #2202 for script type searching --- .../SourceCodeEditing/CachedTypesCollection.cs | 15 +++++++++++++++ Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 12 +++--------- Source/Editor/Viewport/MainEditorGizmoViewport.cs | 5 +---- Source/Editor/Viewport/PrefabWindowViewport.cs | 7 ++----- Source/Editor/Viewport/ViewportDraggingHelper.cs | 7 ++----- .../Windows/Assets/PrefabWindow.Hierarchy.cs | 14 ++++---------- Source/Editor/Windows/SceneTreeWindow.cs | 14 ++++---------- 7 files changed, 31 insertions(+), 43 deletions(-) diff --git a/Source/Editor/Modules/SourceCodeEditing/CachedTypesCollection.cs b/Source/Editor/Modules/SourceCodeEditing/CachedTypesCollection.cs index a73c3d2ee..648ec74e3 100644 --- a/Source/Editor/Modules/SourceCodeEditing/CachedTypesCollection.cs +++ b/Source/Editor/Modules/SourceCodeEditing/CachedTypesCollection.cs @@ -45,6 +45,21 @@ namespace FlaxEditor.Modules.SourceCodeEditing _checkAssembly = checkAssembly; } + /// + /// Gets the type matching the certain Script. + /// + /// The content item. + /// The type matching that item, or null if not found. + public ScriptType Get(Content.ScriptItem script) + { + foreach (var type in Get()) + { + if (type.ContentItem == script) + return type; + } + return ScriptType.Null; + } + /// /// Gets all the types from the all loaded assemblies (including project scripts and scripts from the plugins). /// diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 64def3eae..bf39a501b 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -693,12 +693,9 @@ namespace FlaxEditor.SceneGraph.GUI for (int i = 0; i < _dragScriptItems.Objects.Count; i++) { var item = _dragScriptItems.Objects[i]; - // Find actors with the same content item and spawn them. - foreach (var actorType in Editor.Instance.CodeEditing.Actors.Get()) + var actorType = Editor.Instance.CodeEditing.Actors.Get(item); + if (actorType != ScriptType.Null) { - if (actorType.ContentItem != item) - continue; - var actor = actorType.CreateInstance() as Actor; if (actor == null) { @@ -768,10 +765,7 @@ namespace FlaxEditor.SceneGraph.GUI private static bool ValidateDragScriptItem(ScriptItem script) { - var actors = Editor.Instance.CodeEditing.Actors.Get(); - if (actors.Any(x => x.ContentItem == script)) - return true; - return false; + return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null; } /// diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index c501def33..ed6bd4915 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -943,10 +943,7 @@ namespace FlaxEditor.Viewport private static bool ValidateDragScriptItem(ScriptItem script) { - var actors = Editor.Instance.CodeEditing.Actors.Get(); - if (actors.Any(x => x.ContentItem == script)) - return true; - return false; + return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null; } /// diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index a46002028..6c1509de8 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -702,13 +702,10 @@ namespace FlaxEditor.Viewport { return true; } - + private static bool ValidateDragScriptItem(ScriptItem script) { - var actors = Editor.Instance.CodeEditing.Actors.Get(); - if (actors.Any(x => x.ContentItem == script)) - return true; - return false; + return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null; } /// diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs index 83c9cfab0..52e52b37f 100644 --- a/Source/Editor/Viewport/ViewportDraggingHelper.cs +++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs @@ -203,12 +203,9 @@ namespace FlaxEditor.Viewport private void Spawn(ScriptItem item, SceneGraphNode hit, ref Float2 location, ref Vector3 hitLocation, ref Vector3 hitNormal) { - // Find actors with the same content item and spawn them. - foreach (var actorType in Editor.Instance.CodeEditing.Actors.Get()) + var actorType = Editor.Instance.CodeEditing.Actors.Get(item); + if (actorType != ScriptType.Null) { - if (actorType.ContentItem != item) - continue; - Spawn(actorType, hit, ref location, ref hitLocation, ref hitNormal); } } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index 351dd3f62..9418e7b75 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -85,13 +85,10 @@ namespace FlaxEditor.Windows.Assets { return true; } - + private static bool ValidateDragScriptItem(ScriptItem script) { - var actors = Editor.Instance.CodeEditing.Actors.Get(); - if (actors.Any(x => x.ContentItem == script)) - return true; - return false; + return Editor.Instance.CodeEditing.Actors.Get(script); } /// @@ -185,12 +182,9 @@ namespace FlaxEditor.Windows.Assets for (int i = 0; i < _dragScriptItems.Objects.Count; i++) { var item = _dragScriptItems.Objects[i]; - // Find actors with the same content item and spawn them. - foreach (var actorType in Editor.Instance.CodeEditing.Actors.Get()) + var actorType = Editor.Instance.CodeEditing.Actors.Get(item); + if (actorType != ScriptType.Null) { - if (actorType.ContentItem != item) - continue; - var actor = actorType.CreateInstance() as Actor; if (actor == null) { diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 9385fb781..97bf85b35 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -274,13 +274,10 @@ namespace FlaxEditor.Windows { return true; } - + private static bool ValidateDragScriptItem(ScriptItem script) { - var actors = Editor.Instance.CodeEditing.Actors.Get(); - if (actors.Any(x => x.ContentItem == script)) - return true; - return false; + return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null; } /// @@ -468,12 +465,9 @@ namespace FlaxEditor.Windows for (int i = 0; i < _dragScriptItems.Objects.Count; i++) { var item = _dragScriptItems.Objects[i]; - // Find actors with the same content item and spawn them. - foreach (var actorType in Editor.Instance.CodeEditing.Actors.Get()) + var actorType = Editor.Instance.CodeEditing.Actors.Get(item); + if (actorType != ScriptType.Null) { - if (actorType.ContentItem != item) - continue; - var actor = actorType.CreateInstance() as Actor; if (actor == null) { From b2621ff799bae12c6022ea62136a080d44abbd47 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 11:13:00 +0100 Subject: [PATCH 070/103] Codestyle fix for #2214 --- Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 17e426053..4e632014a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -1937,7 +1937,7 @@ namespace Flax.Build.Bindings var useCSharp = EngineConfiguration.WithCSharp(buildData.TargetOptions); var hasInterface = classInfo.Interfaces != null && classInfo.Interfaces.Any(x => x.Access == AccessLevel.Public); CppInternalCalls.Clear(); - + if (classInfo.IsAutoSerialization) GenerateCppAutoSerialization(buildData, contents, moduleInfo, classInfo, classTypeNameNative); GenerateCppTypeInternalsStatics?.Invoke(buildData, classInfo, contents); @@ -1955,7 +1955,7 @@ namespace Flax.Build.Bindings var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0; CppIncludeFiles.Add("Engine/Profiler/ProfilerCPU.h"); var bindPrefix = eventInfo.IsStatic ? classTypeNameNative + "::" : "__obj->"; - //eventInfo. + if (useCSharp) { // C# event invoking wrapper (calls C# event from C++ delegate) @@ -1965,12 +1965,12 @@ namespace Flax.Build.Bindings contents.Append(" "); if (eventInfo.IsStatic) contents.Append("static "); - contents.AppendFormat("void {0}_ManagedWrapper(", eventInfo.Name); + contents.AppendFormat("void {0}_ManagedWrapper(", eventInfo.Name); for (var i = 0; i < paramsCount; i++) { if (i != 0) contents.Append(", "); - contents.Append(eventInfo.Type.GenericArgs[i].GetFullNameNative(buildData,classInfo)).Append(" arg" + i); + contents.Append(eventInfo.Type.GenericArgs[i].GetFullNameNative(buildData, classInfo)).Append(" arg" + i); } contents.Append(')').AppendLine(); contents.Append(" {").AppendLine(); From 479a917c5908095474d8700a0da6f02e492ebe38 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 11:42:55 +0100 Subject: [PATCH 071/103] Tweak #2186 --- Source/Editor/Windows/PluginsWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index fcacf47ad..2c96dc9a9 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -95,7 +95,7 @@ namespace FlaxEditor.Windows Bounds = new Rectangle(nameLabel.X, tmp1, nameLabel.Width, Height - tmp1 - margin), }; - var xOffset = nameLabel.X + nameLabel.Width; + var xOffset = nameLabel.Width; string versionString = string.Empty; if (desc.IsAlpha) versionString = "ALPHA "; From 6d757946c5c887649f50fdcf7f7f60b9abe7cdef Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 11:49:12 +0100 Subject: [PATCH 072/103] Fix crash when adding physics scene with auto simulation #2180 --- Source/Engine/Physics/Physics.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 7087b2e70..5d9b218ec 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -203,8 +203,11 @@ void Physics::Simulate(float dt) void Physics::CollectResults() { - if (DefaultScene) - DefaultScene->CollectResults(); + for (PhysicsScene* scene : Scenes) + { + if (scene->GetAutoSimulation()) + scene->CollectResults(); + } } bool Physics::IsDuringSimulation() From 40632a4a881d948a3f5db2f8840eb8a4223c7626 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 12:10:05 +0100 Subject: [PATCH 073/103] Fix missing default value for new Visject method parameter if method uses Vector param #2204 --- Source/Editor/Surface/Archetypes/Function.cs | 17 ++++++++++++++--- Source/Editor/Surface/SurfaceUtils.cs | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index 53950dad2..e8b92d121 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -763,6 +763,17 @@ namespace FlaxEditor.Surface.Archetypes public string Name; public ScriptType Type; public bool IsOut; + + public override string ToString() + { + var sb = new StringBuilder(); + if (IsOut) + sb.Append("out "); + sb.Append(Type.ToString()); + sb.Append(" "); + sb.Append(Name); + return sb.ToString(); + } } private struct SignatureInfo @@ -892,7 +903,7 @@ namespace FlaxEditor.Surface.Archetypes { ref var param = ref signature.Params[i]; ref var paramMember = ref memberParameters[i]; - if (param.Type != paramMember.Type || param.IsOut != paramMember.IsOut) + if (!SurfaceUtils.AreScriptTypesEqual(param.Type, paramMember.Type) || param.IsOut != paramMember.IsOut) { // Special case: param.Type is serialized as just a type while paramMember.Type might be a reference for output parameters (eg. `out Int32` vs `out Int32&`) var paramMemberTypeName = paramMember.Type.TypeName; @@ -1660,7 +1671,7 @@ namespace FlaxEditor.Surface.Archetypes SaveSignature(); // Check if return type has been changed - if (_signature.ReturnType != prevReturnType) + if (!SurfaceUtils.AreScriptTypesEqual(_signature.ReturnType, prevReturnType)) { // Update all return nodes used by this function to match the new type var usedNodes = DepthFirstTraversal(false); @@ -2158,7 +2169,7 @@ namespace FlaxEditor.Surface.Archetypes return false; for (int i = 0; i < _signature.Length; i++) { - if (_signature[i].Type != sig.Parameters[i].Type) + if (!SurfaceUtils.AreScriptTypesEqual(_signature[i].Type, sig.Parameters[i].Type)) return false; } return true; diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index 5f4c3ef07..2c8448e92 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -532,5 +532,24 @@ namespace FlaxEditor.Surface value = new Double4(i); return value; } + + private static bool AreScriptTypesEqualInner(ScriptType left, ScriptType right) + { + // Special case for Vector types that use typedefs and might overlap + if (left.Type == typeof(Vector2) && (right.Type == typeof(Float2) || right.Type == typeof(Double2))) + return true; + if (left.Type == typeof(Vector3) && (right.Type == typeof(Float3) || right.Type == typeof(Double3))) + return true; + if (left.Type == typeof(Vector4) && (right.Type == typeof(Float4) || right.Type == typeof(Double4))) + return true; + return false; + } + + internal static bool AreScriptTypesEqual(ScriptType left, ScriptType right) + { + if (left == right) + return true; + return AreScriptTypesEqualInner(left, right) || AreScriptTypesEqualInner(right, left); + } } } From d33ff4306f2e0baa84a99ce9ecb387ee67796f1b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 14:49:44 +0100 Subject: [PATCH 074/103] Fix sprite atlas limit on `4096` #2218 --- Source/Editor/Windows/Assets/SpriteAtlasWindow.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs index 7d09620d5..a564144c6 100644 --- a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs +++ b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs @@ -83,14 +83,14 @@ namespace FlaxEditor.Windows.Assets set => Sprite.Name = value; } - [EditorOrder(1), Limit(-4096, 4096)] + [EditorOrder(1)] public Float2 Location { get => Sprite.Location; set => Sprite.Location = value; } - [EditorOrder(3), Limit(0, 4096)] + [EditorOrder(3), Limit(0)] public Float2 Size { get => Sprite.Size; From 465c14c04a2e814a4831a9950d803a084cc1dec8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 15:06:29 +0100 Subject: [PATCH 075/103] Remove leftover log --- Source/Editor/Viewport/ViewportDraggingHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs index 52e52b37f..bf4ec7979 100644 --- a/Source/Editor/Viewport/ViewportDraggingHelper.cs +++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs @@ -110,7 +110,6 @@ namespace FlaxEditor.Viewport foreach (var scripItem in _dragScriptItem.Objects) Spawn(scripItem, hit, ref location, ref hitLocation, ref hitNormal); } - Debug.Log("Hit"); OnDragDrop(new DragDropEventArgs { Hit = hit, HitLocation = hitLocation }); return result; From 873be6ac178d9ae019a4dbab728f166e154c52f3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 15:41:35 +0100 Subject: [PATCH 076/103] Convert `Matrix3x3` into normal type --- Source/Engine/Core/Math/Matrix3x3.cs | 12 ++---------- Source/Engine/Core/Math/Matrix3x3.h | 3 ++- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Source/Engine/Core/Math/Matrix3x3.cs b/Source/Engine/Core/Math/Matrix3x3.cs index f4487bb1e..44af4542b 100644 --- a/Source/Engine/Core/Math/Matrix3x3.cs +++ b/Source/Engine/Core/Math/Matrix3x3.cs @@ -56,13 +56,7 @@ using System.Runtime.InteropServices; namespace FlaxEngine { - /// - /// Represents a 3x3 Matrix ( contains only Scale and Rotation ). - /// - [Serializable] - [StructLayout(LayoutKind.Sequential, Pack = 4)] - // ReSharper disable once InconsistentNaming - public struct Matrix3x3 : IEquatable, IFormattable + partial struct Matrix3x3 : IEquatable, IFormattable { /// /// The size of the type, in bytes. @@ -135,9 +129,7 @@ namespace FlaxEngine /// The value that will be assigned to all components. public Matrix3x3(float value) { - M11 = M12 = M13 = - M21 = M22 = M23 = - M31 = M32 = M33 = value; + M11 = M12 = M13 = M21 = M22 = M23 = M31 = M32 = M33 = value; } /// diff --git a/Source/Engine/Core/Math/Matrix3x3.h b/Source/Engine/Core/Math/Matrix3x3.h index 344f68455..0680e7735 100644 --- a/Source/Engine/Core/Math/Matrix3x3.h +++ b/Source/Engine/Core/Math/Matrix3x3.h @@ -9,8 +9,9 @@ /// /// Represents a 3x3 mathematical matrix. /// -API_STRUCT(InBuild) struct FLAXENGINE_API Matrix3x3 +API_STRUCT() struct FLAXENGINE_API Matrix3x3 { + DECLARE_SCRIPTING_TYPE_MINIMAL(Matrix3x3); public: union { From 5a50ec592f3705deeeec4c5e2a71c4022b10c48f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 16:01:49 +0100 Subject: [PATCH 077/103] Add clickable parsing errors in build tool --- .../Bindings/BindingsGenerator.Parsing.cs | 69 +++++++++++-------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index 843bb60fa..0b71af086 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -47,6 +47,20 @@ namespace Flax.Build.Bindings } } + private class ParseException : Exception + { + public ParseException(ref ParsingContext context, string msg) + : base(GetParseErrorLocation(ref context, msg)) + { + } + } + + private static string GetParseErrorLocation(ref ParsingContext context, string msg) + { + // Make it a link clickable in Visual Studio build output + return $"{context.File.Name}({context.Tokenizer.CurrentLine}): {msg}"; + } + private static string[] ParseComment(ref ParsingContext context) { if (context.StringCache == null) @@ -180,7 +194,7 @@ namespace Flax.Build.Bindings case TokenType.RightParent: parameters.Add(tag); return parameters; - default: throw new Exception($"Expected right parent or next argument, but got {token.Type}."); + default: throw new ParseException(ref context, $"Expected right parent or next argument, but got {token.Type}."); } } } @@ -302,7 +316,7 @@ namespace Flax.Build.Bindings if (context.PreprocessorDefines.TryGetValue(length, out var define)) length = define; if (!int.TryParse(length, out type.ArraySize)) - throw new Exception($"Failed to parse the field {entry} array size '{length}'"); + throw new ParseException(ref context, $"Failed to parse the field {entry} array size '{length}'"); } private static List ParseFunctionParameters(ref ParsingContext context) @@ -354,7 +368,7 @@ namespace Flax.Build.Bindings if (valid) break; var location = "function parameter"; - Log.Warning($"Unknown or not supported tag parameter {tag} used on {location} at line {context.Tokenizer.CurrentLine}"); + Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{location}'")); break; } } @@ -483,8 +497,7 @@ namespace Flax.Build.Bindings desc.Inheritance = new List(); desc.Inheritance.Add(inheritType); token = context.Tokenizer.NextToken(); - while (token.Type == TokenType.CommentSingleLine - || token.Type == TokenType.CommentMultiLine) + while (token.Type == TokenType.CommentSingleLine || token.Type == TokenType.CommentMultiLine) { token = context.Tokenizer.NextToken(); } @@ -563,7 +576,7 @@ namespace Flax.Build.Bindings // Read 'class' keyword var token = context.Tokenizer.NextToken(); if (token.Value != "class") - throw new Exception($"Invalid {ApiTokens.Class} usage (expected 'class' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); + throw new ParseException(ref context, $"Invalid {ApiTokens.Class} usage (expected 'class' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); // Read specifiers while (true) @@ -644,7 +657,7 @@ namespace Flax.Build.Bindings ParseTypeTag?.Invoke(ref valid, tag, desc); if (valid) break; - Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}"); + Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'")); break; } } @@ -672,7 +685,7 @@ namespace Flax.Build.Bindings // Read 'class' keyword var token = context.Tokenizer.NextToken(); if (token.Value != "class") - throw new Exception($"Invalid {ApiTokens.Interface} usage (expected 'class' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); + throw new ParseException(ref context, $"Invalid {ApiTokens.Interface} usage (expected 'class' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); // Read specifiers while (true) @@ -735,7 +748,7 @@ namespace Flax.Build.Bindings ParseTypeTag?.Invoke(ref valid, tag, desc); if (valid) break; - Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}"); + Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'")); break; } } @@ -815,13 +828,13 @@ namespace Flax.Build.Bindings { case "const": if (desc.IsConst) - throw new Exception($"Invalid double 'const' specifier in function {desc.Name} at line {context.Tokenizer.CurrentLine}."); + throw new ParseException(ref context, $"Invalid double 'const' specifier in function {desc.Name}"); desc.IsConst = true; break; case "override": desc.IsVirtual = true; break; - default: throw new Exception($"Unknown identifier '{token.Value}' in function {desc.Name} at line {context.Tokenizer.CurrentLine}."); + default: throw new ParseException(ref context, $"Unknown identifier '{token.Value}' in function {desc.Name}"); } } else if (token.Type == TokenType.LeftCurlyBrace) @@ -875,7 +888,7 @@ namespace Flax.Build.Bindings ParseMemberTag?.Invoke(ref valid, tag, desc); if (valid) break; - Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}"); + Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'")); break; } } @@ -892,11 +905,11 @@ namespace Flax.Build.Bindings var classInfo = context.ScopeInfo as ClassInfo; if (classInfo == null) - throw new Exception($"Found property {propertyName} outside the class at line {context.Tokenizer.CurrentLine}."); + throw new ParseException(ref context, $"Found property {propertyName} outside the class"); var isGetter = !functionInfo.ReturnType.IsVoid; if (!isGetter && functionInfo.Parameters.Count == 0) - throw new Exception($"Property {propertyName} setter method in class {classInfo.Name} has to have value parameter to set (line {context.Tokenizer.CurrentLine})."); + throw new ParseException(ref context, $"Property {propertyName} setter method in class {classInfo.Name} has to have value parameter to set (line {context.Tokenizer.CurrentLine})."); var propertyType = isGetter ? functionInfo.ReturnType : functionInfo.Parameters[0].Type; var propertyInfo = classInfo.Properties.FirstOrDefault(x => x.Name == propertyName); @@ -917,7 +930,7 @@ namespace Flax.Build.Bindings else { if (propertyInfo.IsStatic != functionInfo.IsStatic) - throw new Exception($"Property {propertyName} in class {classInfo.Name} has to have both getter and setter methods static or non-static (line {context.Tokenizer.CurrentLine})."); + throw new ParseException(ref context, $"Property {propertyName} in class {classInfo.Name} has to have both getter and setter methods static or non-static (line {context.Tokenizer.CurrentLine})."); } if (functionInfo.Tags != null) { @@ -934,9 +947,9 @@ namespace Flax.Build.Bindings } if (isGetter && propertyInfo.Getter != null) - throw new Exception($"Property {propertyName} in class {classInfo.Name} cannot have multiple getter method (line {context.Tokenizer.CurrentLine}). Getter methods of properties must return value, while setters take this as a parameter."); + throw new ParseException(ref context, $"Property {propertyName} in class {classInfo.Name} cannot have multiple getter method (line {context.Tokenizer.CurrentLine}). Getter methods of properties must return value, while setters take this as a parameter."); if (!isGetter && propertyInfo.Setter != null) - throw new Exception($"Property {propertyName} in class {classInfo.Name} cannot have multiple setter method (line {context.Tokenizer.CurrentLine}). Getter methods of properties must return value, while setters take this as a parameter."); + throw new ParseException(ref context, $"Property {propertyName} in class {classInfo.Name} cannot have multiple setter method (line {context.Tokenizer.CurrentLine}). Getter methods of properties must return value, while setters take this as a parameter."); if (isGetter) propertyInfo.Getter = functionInfo; @@ -963,7 +976,7 @@ namespace Flax.Build.Bindings return propertyInfo; if (getterType.Type == "Array" && setterType.Type == "Span" && getterType.GenericArgs?.Count == 1 && setterType.GenericArgs?.Count == 1 && getterType.GenericArgs[0].Equals(setterType.GenericArgs[0])) return propertyInfo; - throw new Exception($"Property {propertyName} in class {classInfo.Name} (line {context.Tokenizer.CurrentLine}) has mismatching getter return type ({getterType}) and setter parameter type ({setterType}). Both getter and setter methods must use the same value type used for property."); + throw new ParseException(ref context, $"Property {propertyName} in class {classInfo.Name} (line {context.Tokenizer.CurrentLine}) has mismatching getter return type ({getterType}) and setter parameter type ({setterType}). Both getter and setter methods must use the same value type used for property."); } if (propertyInfo.Comment != null) @@ -996,7 +1009,7 @@ namespace Flax.Build.Bindings // Read 'enum' or `enum class` keywords var token = context.Tokenizer.NextToken(); if (token.Value != "enum") - throw new Exception($"Invalid {ApiTokens.Enum} usage at line {context.Tokenizer.CurrentLine} (expected 'enum' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); + throw new ParseException(ref context, $"Invalid {ApiTokens.Enum} usage (expected 'enum' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); token = context.Tokenizer.NextToken(); if (token.Value != "class") context.Tokenizer.PreviousToken(); @@ -1079,7 +1092,7 @@ namespace Flax.Build.Bindings entry.Attributes = tag.Value; break; default: - Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name + " enum entry"} at line {context.Tokenizer.CurrentLine}"); + Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'")); break; } } @@ -1153,7 +1166,7 @@ namespace Flax.Build.Bindings ParseTypeTag?.Invoke(ref valid, tag, desc); if (valid) break; - Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}"); + Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'")); break; } } @@ -1176,7 +1189,7 @@ namespace Flax.Build.Bindings // Read 'struct' keyword var token = context.Tokenizer.NextToken(); if (token.Value != "struct") - throw new Exception($"Invalid {ApiTokens.Struct} usage (expected 'struct' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); + throw new ParseException(ref context, $"Invalid {ApiTokens.Struct} usage (expected 'struct' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); // Read name desc.Name = desc.NativeName = ParseName(ref context); @@ -1233,7 +1246,7 @@ namespace Flax.Build.Bindings ParseTypeTag?.Invoke(ref valid, tag, desc); if (valid) break; - Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}"); + Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'")); break; } } @@ -1366,7 +1379,7 @@ namespace Flax.Build.Bindings ParseMemberTag?.Invoke(ref valid, tag, desc); if (valid) break; - Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}"); + Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'")); break; } } @@ -1396,7 +1409,7 @@ namespace Flax.Build.Bindings if (desc.Type.Type == "Action") desc.Type = new TypeInfo { Type = "Delegate", GenericArgs = new List() }; else if (desc.Type.Type != "Delegate") - throw new Exception($"Invalid {ApiTokens.Event} type. Only Action and Delegate<> types are supported. '{desc.Type}' used on event at line {context.Tokenizer.CurrentLine}."); + throw new ParseException(ref context, $"Invalid {ApiTokens.Event} type. Only Action and Delegate<> types are supported. '{desc.Type}' used on event."); // Read name desc.Name = ParseName(ref context); @@ -1438,7 +1451,7 @@ namespace Flax.Build.Bindings ParseMemberTag?.Invoke(ref valid, tag, desc); if (valid) break; - Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}"); + Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'")); break; } } @@ -1474,7 +1487,7 @@ namespace Flax.Build.Bindings // Read 'typedef' keyword var token = context.Tokenizer.NextToken(); if (token.Value != "typedef") - throw new Exception($"Invalid {ApiTokens.Typedef} usage (expected 'typedef' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); + throw new ParseException(ref context, $"Invalid {ApiTokens.Typedef} usage (expected 'typedef' keyword but got '{token.Value} {context.Tokenizer.NextToken().Value}')."); // Read type definition desc.Type = ParseType(ref context); @@ -1513,7 +1526,7 @@ namespace Flax.Build.Bindings ParseTypeTag?.Invoke(ref valid, tag, desc); if (valid) break; - Log.Warning($"Unknown or not supported tag parameter {tag} used on {desc.Name} at line {context.Tokenizer.CurrentLine}"); + Log.Warning(GetParseErrorLocation(ref context, $"Unknown or not supported tag parameter '{tag}' used on '{desc.Name}'")); break; } } From c0e3b2788029483093893825073a5918ab759f70 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 16:17:43 +0100 Subject: [PATCH 078/103] Codestyle fix #2211 --- Source/Editor/Surface/Archetypes/Animation.cs | 7 +++---- Source/Engine/Animations/Graph/AnimGroup.Animation.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index b2494081a..cbe9c3d85 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -69,7 +69,6 @@ namespace FlaxEditor.Surface.Archetypes return; if (box.ID != _assetBox.ID) return; - _assetSelect.Visible = !box.HasAnyConnection; } } @@ -122,7 +121,7 @@ namespace FlaxEditor.Surface.Archetypes Title = _assetBox.HasAnyConnection || asset == null ? "Animation" : asset.ShortName; else Title = asset?.ShortName ?? "Animation"; - + var style = Style.Current; Resize(Mathf.Max(230, style.FontLarge.MeasureText(Title).X + 30), 160); } @@ -570,7 +569,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 10, Title = "Blend Additive", - Description = + Description = "Blend animation poses (with additive mode)" + "\n" + "\nNote: " + @@ -598,7 +597,7 @@ namespace FlaxEditor.Surface.Archetypes TypeID = 11, Title = "Blend with Mask", Description = "Blend animation poses using skeleton mask", - Create = (id, context, arch, groupArch) => new SkeletonMaskSample(id, context, arch, groupArch), + Create = (id, context, arch, groupArch) => new SkeletonMaskSample(id, context, arch, groupArch), Flags = NodeFlags.AnimGraph, Size = new Float2(180, 140), DefaultValues = new object[] diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index ab3bb0250..d3b61c951 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -228,7 +228,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* trace.Value = animPos; trace.NodeId = node->ID; } - + // Evaluate nested animations bool hasNested = false; if (anim->NestedAnims.Count() != 0) @@ -1133,14 +1133,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu auto mask = node->Assets[0].As(); auto maskAssetBox = node->GetBox(4); // 4 is the id of skeleton mask parameter node. - // Check if have some mask asset conected with the mask node + // Check if have some mask asset connected with the mask node if (maskAssetBox->HasConnection()) { const Value assetBoxValue = tryGetValue(maskAssetBox, Value::Null); - // Use the mask conected with this node instead of default mask asset + // Use the mask connected with this node instead of default mask asset if (assetBoxValue != Value::Null) - mask = (SkeletonMask *)assetBoxValue.AsAsset; + mask = (SkeletonMask*)assetBoxValue.AsAsset; } // Only A or missing/invalid mask From 6954a488eaa35bcb568194a8007462ae6bd57de4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 16:38:44 +0100 Subject: [PATCH 079/103] Codestyle fixes --- .../CustomEditors/Editors/CollectionEditor.cs | 7 +-- Source/Editor/Tools/Terrain/Sculpt/Mode.cs | 14 +++--- .../Editor/Tools/Terrain/Sculpt/SmoothMode.cs | 45 ++++--------------- 3 files changed, 20 insertions(+), 46 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 526c91d20..00322fc81 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -55,11 +55,11 @@ namespace FlaxEditor.CustomEditors.Editors private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor) { menu.ItemsContainer.RemoveChildren(); - + menu.AddButton("Copy", linkedEditor.Copy); var paste = menu.AddButton("Paste", linkedEditor.Paste); paste.Enabled = linkedEditor.CanPaste; - + menu.AddSeparator(); var moveUpButton = menu.AddButton("Move up", OnMoveUpClicked); moveUpButton.Enabled = Index > 0; @@ -118,7 +118,7 @@ namespace FlaxEditor.CustomEditors.Editors Editor = editor; Index = index; Offsets = new Margin(7, 7, 0, 0); - + MouseButtonRightClicked += OnMouseButtonRightClicked; if (_canReorder) { @@ -173,6 +173,7 @@ namespace FlaxEditor.CustomEditors.Editors /// Determines if value of collection can be null. /// protected bool NotNullItems; + private IntValueBox _sizeBox; private Color _background; private int _elementsCount; diff --git a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs index a92b899e1..cce2f982f 100644 --- a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs +++ b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs @@ -53,11 +53,11 @@ namespace FlaxEditor.Tools.Terrain.Sculpt /// /// Gets all patches that will be affected by the brush /// - /// - /// - /// - /// - public unsafe virtual List GetAffectedPatches(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain) + /// The brush. + /// The options. + /// The gizmo. + /// The terrain. + public virtual unsafe List GetAffectedPatches(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain) { List affectedPatches = new(); @@ -155,7 +155,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt /// The options. /// The gizmo. /// The terrain. - public unsafe void Apply(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain) + public void Apply(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain) { var affectedPatches = GetAffectedPatches(brush, ref options, gizmo, terrain); @@ -176,7 +176,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt /// /// /// - public unsafe virtual void ApplyBrush(SculptTerrainGizmoMode gizmo, List affectedPatches) + public virtual void ApplyBrush(SculptTerrainGizmoMode gizmo, List affectedPatches) { for (int i = 0; i < affectedPatches.Count; i++) { diff --git a/Source/Editor/Tools/Terrain/Sculpt/SmoothMode.cs b/Source/Editor/Tools/Terrain/Sculpt/SmoothMode.cs index 49ccfe0de..387bb555e 100644 --- a/Source/Editor/Tools/Terrain/Sculpt/SmoothMode.cs +++ b/Source/Editor/Tools/Terrain/Sculpt/SmoothMode.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using FlaxEngine; -using FlaxEditor.Tools.Terrain.Brushes; using System; namespace FlaxEditor.Tools.Terrain.Sculpt @@ -29,15 +28,9 @@ namespace FlaxEditor.Tools.Terrain.Sculpt var heightmapSize = affectedPatches[0].HeightmapSize; var radius = Mathf.Max(Mathf.CeilToInt(FilterRadius * 0.01f * affectedPatches[0].Brush.Size), 2); - - ///// - /// Calculate bounding coordinates of the total affected area - /// - - + // Calculate bounding coordinates of the total affected area Int2 modifieedAreaMinCoord = Int2.Maximum; Int2 modifiedAreaMaxCoord = Int2.Minimum; - for (int i = 0; i < affectedPatches.Count; i++) { var patch = affectedPatches[i]; @@ -46,27 +39,14 @@ namespace FlaxEditor.Tools.Terrain.Sculpt var br = tl + patch.ModifiedSize; if (tl.X <= modifieedAreaMinCoord.X && tl.Y <= modifieedAreaMinCoord.Y) - { modifieedAreaMinCoord = tl; - } - if (br.X >= modifiedAreaMaxCoord.X && br.Y >= modifiedAreaMaxCoord.Y) - { modifiedAreaMaxCoord = br; - } } - - var totalModifiedSize = modifiedAreaMaxCoord - modifieedAreaMinCoord; - - ///// - /// Build map of heights in affected area - /// - - + // Build map of heights in affected area var modifiedHeights = new float[totalModifiedSize.X * totalModifiedSize.Y]; - for (int i = 0; i < affectedPatches.Count; i++) { var patch = affectedPatches[i]; @@ -75,32 +55,26 @@ namespace FlaxEditor.Tools.Terrain.Sculpt { for (int x = 0; x < patch.ModifiedSize.X; x++) { - // read height from current patch + // Read height from current patch var localCoordX = (x + patch.ModifiedOffset.X); var localCoordY = (z + patch.ModifiedOffset.Y); var coordHeight = patch.SourceHeightMap[(localCoordY * heightmapSize) + localCoordX]; - // calculate the absolute coordinate of the terrain point + // Calculate the absolute coordinate of the terrain point var absoluteCurrentPointCoord = patch.PatchCoord * (heightmapSize - 1) + patch.ModifiedOffset + new Int2(x, z); var currentPointCoordRelativeToModifiedArea = absoluteCurrentPointCoord - modifieedAreaMinCoord; - // store height + // Store height var index = (currentPointCoordRelativeToModifiedArea.Y * totalModifiedSize.X) + currentPointCoordRelativeToModifiedArea.X; modifiedHeights[index] = coordHeight; } } } - - ///// - /// Iterate through modified points and smooth now that we have height information for all necessary points - /// - - + // Iterate through modified points and smooth now that we have height information for all necessary points for (int i = 0; i < affectedPatches.Count; i++) { var patch = affectedPatches[i]; - var brushPosition = patch.Gizmo.CursorPosition; var strength = Mathf.Saturate(patch.Strength); @@ -108,16 +82,16 @@ namespace FlaxEditor.Tools.Terrain.Sculpt { for (int x = 0; x < patch.ModifiedSize.X; x++) { - // read height from current patch + // Read height from current patch var localCoordX = (x + patch.ModifiedOffset.X); var localCoordY = (z + patch.ModifiedOffset.Y); var coordHeight = patch.SourceHeightMap[(localCoordY * heightmapSize) + localCoordX]; - // calculate the absolute coordinate of the terrain point + // Calculate the absolute coordinate of the terrain point var absoluteCurrentPointCoord = patch.PatchCoord * (heightmapSize - 1) + patch.ModifiedOffset + new Int2(x, z); var currentPointCoordRelativeToModifiedArea = absoluteCurrentPointCoord - modifieedAreaMinCoord; - // calculate brush influence at the current position + // Calculate brush influence at the current position var samplePositionLocal = patch.PatchPositionLocal + new Vector3(localCoordX * FlaxEngine.Terrain.UnitsPerVertex, coordHeight, localCoordY * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref patch.TerrainWorld, out Vector3 samplePositionWorld); var paintAmount = patch.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength; @@ -149,7 +123,6 @@ namespace FlaxEditor.Tools.Terrain.Sculpt { var coordIndex = (dz * totalModifiedSize.X) + dx; var height = modifiedHeights[coordIndex]; - smoothValue += height; smoothValueSamples++; } From 9da9a122b8d63f35212ebc76c695d3951370638d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 16:59:17 +0100 Subject: [PATCH 080/103] Fix error in Editor --- .../Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs | 2 +- Source/Editor/GUI/AssetPicker.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs index 568a57794..f9ccf548a 100644 --- a/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ModelInstanceEntryEditor.cs @@ -76,7 +76,7 @@ namespace FlaxEditor.CustomEditors.Editors private void OnSelectedMaterialChanged() { - if (_isRefreshing) + if (_isRefreshing || _modelInstance == null) return; _isRefreshing = true; var slots = _modelInstance.MaterialSlots; diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs index 84f58daf1..9e8e0485e 100644 --- a/Source/Editor/GUI/AssetPicker.cs +++ b/Source/Editor/GUI/AssetPicker.cs @@ -75,6 +75,9 @@ namespace FlaxEditor.GUI /// protected virtual void OnSelectedItemChanged() { + if (IsDisposing) + return; + // Update tooltip string tooltip; if (Validator.SelectedItem is AssetItem assetItem) From 5fc768bbbde5ba0c958f4acdd3e33a7f35f0aadf Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 17:10:55 +0100 Subject: [PATCH 081/103] Fix spatial audio playback in OpenAL with Large Worlds enabled #2015 --- Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp index ace8b6591..3bd709259 100644 --- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp +++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp @@ -116,7 +116,7 @@ namespace ALC { AudioBackend::Listener::TransformChanged(listener); - const Vector3 velocity = listener->GetVelocity(); + const Float3 velocity = listener->GetVelocity(); alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity)); alListenerf(AL_GAIN, Audio::GetVolume()); } @@ -319,8 +319,6 @@ void AudioBackendOAL::Listener_OnAdd(AudioListener* listener) ALC::RebuildContexts(false); #else AudioBackend::Listener::TransformChanged(listener); - const Vector3 velocity = listener->GetVelocity(); - alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity)); alListenerf(AL_GAIN, Audio::GetVolume()); #endif } @@ -336,7 +334,7 @@ void AudioBackendOAL::Listener_VelocityChanged(AudioListener* listener) { ALC_GET_LISTENER_CONTEXT(listener) - const Vector3 velocity = listener->GetVelocity(); + const Float3 velocity = listener->GetVelocity(); alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity)); } @@ -344,15 +342,15 @@ void AudioBackendOAL::Listener_TransformChanged(AudioListener* listener) { ALC_GET_LISTENER_CONTEXT(listener) - const Vector3 position = listener->GetPosition(); + const Float3 position = listener->GetPosition(); const Quaternion orientation = listener->GetOrientation(); - const Vector3 flipX(-1, 1, 1); - const Vector3 alOrientation[2] = + const Float3 flipX(-1, 1, 1); + const Float3 alOrientation[2] = { // Forward - orientation * Vector3::Forward * flipX, + orientation * Float3::Forward * flipX, // Up - orientation * Vector3::Up * flipX + orientation * Float3::Up * flipX }; alListenerfv(AL_ORIENTATION, (float*)alOrientation); From 7c9218840d4509ae4f83aeb47ce0810082a0bf1b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 17:27:27 +0100 Subject: [PATCH 082/103] Format code #2149 --- .../Editor/CustomEditors/Editors/BindableButtonEditor.cs | 2 +- .../Editor/CustomEditors/Editors/GamepadButtonEditor.cs | 8 ++++---- Source/Editor/CustomEditors/Editors/KeyboardKeysEditor.cs | 8 ++++---- Source/Editor/CustomEditors/Editors/MouseButtonEditor.cs | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs b/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs index 2d4bf0df3..f8323bb49 100644 --- a/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs +++ b/Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs @@ -1,4 +1,4 @@ -using FlaxEditor.GUI; +using FlaxEditor.GUI; using FlaxEngine; using FlaxEngine.GUI; diff --git a/Source/Editor/CustomEditors/Editors/GamepadButtonEditor.cs b/Source/Editor/CustomEditors/Editors/GamepadButtonEditor.cs index 7bae24922..6b0d190c0 100644 --- a/Source/Editor/CustomEditors/Editors/GamepadButtonEditor.cs +++ b/Source/Editor/CustomEditors/Editors/GamepadButtonEditor.cs @@ -1,4 +1,4 @@ -using System; +using System; using FlaxEngine; namespace FlaxEditor.CustomEditors.Editors @@ -15,17 +15,17 @@ namespace FlaxEditor.CustomEditors.Editors public override void Initialize(LayoutElementsContainer layout) { base.Initialize(layout); - FlaxEngine.Scripting.Update += ScriptingOnUpdate; + FlaxEngine.Scripting.Update += OnUpdate; } /// protected override void Deinitialize() { - FlaxEngine.Scripting.Update -= ScriptingOnUpdate; + FlaxEngine.Scripting.Update -= OnUpdate; base.Deinitialize(); } - private void ScriptingOnUpdate() + private void OnUpdate() { if (!IsListeningForInput) return; diff --git a/Source/Editor/CustomEditors/Editors/KeyboardKeysEditor.cs b/Source/Editor/CustomEditors/Editors/KeyboardKeysEditor.cs index 765527b7e..375ea1d96 100644 --- a/Source/Editor/CustomEditors/Editors/KeyboardKeysEditor.cs +++ b/Source/Editor/CustomEditors/Editors/KeyboardKeysEditor.cs @@ -1,4 +1,4 @@ -using FlaxEngine; +using FlaxEngine; namespace FlaxEditor.CustomEditors.Editors { @@ -14,17 +14,17 @@ namespace FlaxEditor.CustomEditors.Editors public override void Initialize(LayoutElementsContainer layout) { base.Initialize(layout); - Window.KeyUp += WindowOnKeyUp; + Window.KeyUp += OnKeyUp; } /// protected override void Deinitialize() { - Window.KeyUp -= WindowOnKeyUp; + Window.KeyUp -= OnKeyUp; base.Deinitialize(); } - private void WindowOnKeyUp(KeyboardKeys key) + private void OnKeyUp(KeyboardKeys key) { if (!IsListeningForInput) return; diff --git a/Source/Editor/CustomEditors/Editors/MouseButtonEditor.cs b/Source/Editor/CustomEditors/Editors/MouseButtonEditor.cs index 7964d3a4d..4e46f6717 100644 --- a/Source/Editor/CustomEditors/Editors/MouseButtonEditor.cs +++ b/Source/Editor/CustomEditors/Editors/MouseButtonEditor.cs @@ -1,4 +1,4 @@ -using FlaxEngine; +using FlaxEngine; namespace FlaxEditor.CustomEditors.Editors { @@ -14,17 +14,17 @@ namespace FlaxEditor.CustomEditors.Editors public override void Initialize(LayoutElementsContainer layout) { base.Initialize(layout); - Window.MouseUp += WindowOnMouseUp; + Window.MouseUp += OnMouseUp; } /// protected override void Deinitialize() { - Window.MouseUp -= WindowOnMouseUp; + Window.MouseUp -= OnMouseUp; base.Deinitialize(); } - private void WindowOnMouseUp(ref Float2 mouse, MouseButton button, ref bool handled) + private void OnMouseUp(ref Float2 mouse, MouseButton button, ref bool handled) { if (!IsListeningForInput) return; From 169024ae478d3b7e6151163bf004d4fa5829dec2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 6 Feb 2024 17:39:43 +0100 Subject: [PATCH 083/103] Fix new asset naming to always validate filename #2212 --- Source/Editor/Windows/ContentWindow.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index d43174cf3..68aa03678 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -799,7 +799,10 @@ namespace FlaxEditor.Windows if (proxy == null) throw new ArgumentNullException(nameof(proxy)); + // Setup name string name = initialName ?? proxy.NewItemName; + if (!proxy.IsFileNameValid(name) || Utilities.Utils.HasInvalidPathChar(name)) + name = proxy.NewItemName; // If the proxy can not be created in the current folder, then navigate to the content folder if (!proxy.CanCreate(CurrentViewFolder)) From a9259b20a47bd43c7b35dc7abb9faf65e5ee1d69 Mon Sep 17 00:00:00 2001 From: nothingTVatYT <34131388+nothingTVatYT@users.noreply.github.com> Date: Wed, 7 Feb 2024 02:01:39 +0100 Subject: [PATCH 084/103] fix off-by-one in collection range check The check failed randomly after script reloading an dissued the following error: ``` 00:19:37.789 ]: [Error] Exception has been thrown during Window.OnDraw. Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index') [ 00:19:37.805 ]: [Warning] Exception has been thrown during Window.OnUpdate. Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index') Stack strace: at FlaxEditor.GUI.Tabs.Tabs.get_SelectedTab() in /home/me/Flax/FlaxEngine/Source/Editor/GUI/Tabs/Tabs.cs:line 242 ``` --- Source/Editor/GUI/Tabs/Tabs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs index 3c70363e7..c9b1e1eff 100644 --- a/Source/Editor/GUI/Tabs/Tabs.cs +++ b/Source/Editor/GUI/Tabs/Tabs.cs @@ -239,7 +239,7 @@ namespace FlaxEditor.GUI.Tabs /// public Tab SelectedTab { - get => _selectedIndex < 0 || Children.Count <= _selectedIndex ? null : Children[_selectedIndex + 1] as Tab; + get => _selectedIndex < 0 || Children.Count <= (_selectedIndex+1) ? null : Children[_selectedIndex + 1] as Tab; set => SelectedTabIndex = value != null ? Children.IndexOf(value) - 1 : -1; } From c67d352065c6b0b1e431816ed3b353c18a069ea5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 12:51:52 +0100 Subject: [PATCH 085/103] Refactor win32 exe icon updating #928 --- .../Platform/Windows/WindowsPlatformTools.cpp | 454 +++++++++++++++- Source/Editor/Utilities/EditorUtilities.cpp | 492 ------------------ Source/Editor/Utilities/EditorUtilities.h | 8 - 3 files changed, 453 insertions(+), 501 deletions(-) diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp index feca5c0a0..447331ac9 100644 --- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp @@ -5,11 +5,463 @@ #include "WindowsPlatformTools.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/Windows/WindowsPlatformSettings.h" +#include "Engine/Core/Math/Color32.h" #include "Engine/Core/Config/GameSettings.h" #include "Editor/Utilities/EditorUtilities.h" #include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Content/Content.h" #include "Engine/Content/JsonAsset.h" +#include + +#define MSDOS_SIGNATURE 0x5A4D +#define PE_SIGNATURE 0x00004550 +#define PE_32BIT_SIGNATURE 0x10B +#define PE_64BIT_SIGNATURE 0x20B +#define PE_SECTION_UNINITIALIZED_DATA 0x00000080 +#define PE_IMAGE_DIRECTORY_ENTRY_RESOURCE 2 +#define PE_IMAGE_RT_ICON 3 + +/// +/// MS-DOS header found at the beginning in a PE format file. +/// +struct MSDOSHeader +{ + uint16 signature; + uint16 lastSize; + uint16 numBlocks; + uint16 numReloc; + uint16 hdrSize; + uint16 minAlloc; + uint16 maxAlloc; + uint16 ss; + uint16 sp; + uint16 checksum; + uint16 ip; + uint16 cs; + uint16 relocPos; + uint16 numOverlay; + uint16 reserved1[4]; + uint16 oemId; + uint16 oemInfo; + uint16 reserved2[10]; + uint32 lfanew; +}; + +/// +/// COFF header found in a PE format file. +/// +struct COFFHeader +{ + uint16 machine; + uint16 numSections; + uint32 timeDateStamp; + uint32 ptrSymbolTable; + uint32 numSymbols; + uint16 sizeOptHeader; + uint16 characteristics; +}; + +/// +/// Contains address and size of data areas in a PE image. +/// +struct PEDataDirectory +{ + uint32 virtualAddress; + uint32 size; +}; + +/// +/// Optional header in a 32-bit PE format file. +/// +struct PEOptionalHeader32 +{ + uint16 signature; + uint8 majorLinkerVersion; + uint8 minorLinkerVersion; + uint32 sizeCode; + uint32 sizeInitializedData; + uint32 sizeUninitializedData; + uint32 addressEntryPoint; + uint32 baseCode; + uint32 baseData; + uint32 baseImage; + uint32 alignmentSection; + uint32 alignmentFile; + uint16 majorOSVersion; + uint16 minorOSVersion; + uint16 majorImageVersion; + uint16 minorImageVersion; + uint16 majorSubsystemVersion; + uint16 minorSubsystemVersion; + uint32 reserved; + uint32 sizeImage; + uint32 sizeHeaders; + uint32 checksum; + uint16 subsystem; + uint16 characteristics; + uint32 sizeStackReserve; + uint32 sizeStackCommit; + uint32 sizeHeapReserve; + uint32 sizeHeapCommit; + uint32 loaderFlags; + uint32 NumRvaAndSizes; + PEDataDirectory dataDirectory[16]; +}; + +/// +/// Optional header in a 64-bit PE format file. +/// +struct PEOptionalHeader64 +{ + uint16 signature; + uint8 majorLinkerVersion; + uint8 minorLinkerVersion; + uint32 sizeCode; + uint32 sizeInitializedData; + uint32 sizeUninitializedData; + uint32 addressEntryPoint; + uint32 baseCode; + uint64 baseImage; + uint32 alignmentSection; + uint32 alignmentFile; + uint16 majorOSVersion; + uint16 minorOSVersion; + uint16 majorImageVersion; + uint16 minorImageVersion; + uint16 majorSubsystemVersion; + uint16 minorSubsystemVersion; + uint32 reserved; + uint32 sizeImage; + uint32 sizeHeaders; + uint32 checksum; + uint16 subsystem; + uint16 characteristics; + uint64 sizeStackReserve; + uint64 sizeStackCommit; + uint64 sizeHeapReserve; + uint64 sizeHeapCommit; + uint32 loaderFlags; + uint32 NumRvaAndSizes; + PEDataDirectory dataDirectory[16]; +}; + +/// +/// A section header in a PE format file. +/// +struct PESectionHeader +{ + char name[8]; + uint32 virtualSize; + uint32 relativeVirtualAddress; + uint32 physicalSize; + uint32 physicalAddress; + uint8 deprecated[12]; + uint32 flags; +}; + +/// +/// A resource table header within a .rsrc section in a PE format file. +/// +struct PEImageResourceDirectory +{ + uint32 flags; + uint32 timeDateStamp; + uint16 majorVersion; + uint16 minorVersion; + uint16 numNamedEntries; + uint16 numIdEntries; +}; + +/// +/// A single entry in a resource table within a .rsrc section in a PE format file. +/// +struct PEImageResourceEntry +{ + uint32 type; + uint32 offsetDirectory : 31; + uint32 isDirectory : 1; +}; + +/// +/// An entry in a resource table referencing resource data. Found within a .rsrc section in a PE format file. +/// +struct PEImageResourceEntryData +{ + uint32 offsetData; + uint32 size; + uint32 codePage; + uint32 resourceHandle; +}; + +/// +/// Header used in icon file format. +/// +struct IconHeader +{ + uint32 size; + int32 width; + int32 height; + uint16 planes; + uint16 bitCount; + uint32 compression; + uint32 sizeImage; + int32 xPelsPerMeter; + int32 yPelsPerMeter; + uint32 clrUsed; + uint32 clrImportant; +}; + +void UpdateIconData(uint8* iconData, const TextureData* icon) +{ + IconHeader* iconHeader = (IconHeader*)iconData; + if (iconHeader->size != sizeof(IconHeader) || iconHeader->compression != 0 || iconHeader->planes != 1 || iconHeader->bitCount != 32) + { + // Unsupported format + return; + } + uint8* iconPixels = iconData + sizeof(IconHeader); + const uint32 width = iconHeader->width; + const uint32 height = iconHeader->height / 2; + + // Try to pick a proper mip (require the same size) + int32 srcPixelsMip = 0; + const int32 mipLevels = icon->GetMipLevels(); + for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) + { + const uint32 iconWidth = Math::Max(1, icon->Width >> mipIndex); + const uint32 iconHeight = Math::Max(1, icon->Height >> mipIndex); + if (width == iconWidth && height == iconHeight) + { + srcPixelsMip = mipIndex; + break; + } + } + const TextureMipData* srcPixels = icon->GetData(0, srcPixelsMip); + const Color32* srcPixelsData = (Color32*)srcPixels->Data.Get(); + const Int2 srcPixelsSize(Math::Max(1, icon->Width >> srcPixelsMip), Math::Max(1, icon->Height >> srcPixelsMip)); + const auto sampler = TextureTool::GetSampler(icon->Format); + ASSERT_LOW_LAYER(sampler); + + // Write colors + uint32* colorData = (uint32*)iconPixels; + uint32 idx = 0; + for (int32 y = (int32)height - 1; y >= 0; y--) + { + float v = (float)y / height; + for (uint32 x = 0; x < width; x++) + { + float u = (float)x / width; + const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch); + colorData[idx++] = Color32(c).GetAsBGRA(); + } + } + + // Write AND mask + uint32 colorDataSize = width * height * sizeof(uint32); + uint8* maskData = iconPixels + colorDataSize; + uint32 numPackedPixels = width / 8; // One per bit in byte + for (int32 y = (int32)height - 1; y >= 0; y--) + { + uint8 mask = 0; + float v = (float)y / height; + for (uint32 packedX = 0; packedX < numPackedPixels; packedX++) + { + for (uint32 pixelIdx = 0; pixelIdx < 8; pixelIdx++) + { + uint32 x = packedX * 8 + pixelIdx; + float u = (float)x / width; + const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch); + if (c.A < 0.25f) + mask |= 1 << (7 - pixelIdx); + } + *maskData = mask; + maskData++; + } + } +} + +void SetIconData(PEImageResourceDirectory* base, PEImageResourceDirectory* current, uint8* imageData, uint32 sectionAddress, const TextureData* iconRGBA8) +{ + uint32 numEntries = current->numIdEntries; // Not supporting name entries + PEImageResourceEntry* entries = (PEImageResourceEntry*)(current + 1); + for (uint32 i = 0; i < numEntries; i++) + { + // Only at root does the type identify resource type + if (base == current && entries[i].type != PE_IMAGE_RT_ICON) + continue; + + if (entries[i].isDirectory) + { + PEImageResourceDirectory* child = (PEImageResourceDirectory*)(((uint8*)base) + entries[i].offsetDirectory); + SetIconData(base, child, imageData, sectionAddress, iconRGBA8); + } + else + { + PEImageResourceEntryData* data = (PEImageResourceEntryData*)(((uint8*)base) + entries[i].offsetDirectory); + uint8* iconData = imageData + (data->offsetData - sectionAddress); + UpdateIconData(iconData, iconRGBA8); + } + } +} + +bool UpdateExeIcon(const String& path, const TextureData& icon) +{ + if (!FileSystem::FileExists(path)) + { + LOG(Warning, "Missing file"); + return true; + } + if (icon.Width < 1 || icon.Height < 1 || icon.GetMipLevels() <= 0) + { + LOG(Warning, "Inalid icon data"); + return true; + } + + // Ensure that image format can be sampled + const TextureData* iconRGBA8 = &icon; + TextureData tmpData1; + //if (icon.Format != PixelFormat::R8G8B8A8_UNorm) + if (TextureTool::GetSampler(icon.Format) == nullptr) + { + if (TextureTool::Convert(tmpData1, *iconRGBA8, PixelFormat::R8G8B8A8_UNorm)) + { + LOG(Warning, "Failed convert icon data."); + return true; + } + iconRGBA8 = &tmpData1; + } + + // Use fixed-size input icon image + TextureData tmpData2; + if (iconRGBA8->Width != 256 || iconRGBA8->Height != 256) + { + if (TextureTool::Resize(tmpData2, *iconRGBA8, 256, 256)) + { + LOG(Warning, "Failed resize icon data."); + return true; + } + iconRGBA8 = &tmpData2; + } + + // A PE file is structured as such: + // - MSDOS Header + // - PE Signature + // - COFF Header + // - PE Optional Header + // - One or multiple sections + // - .code + // - .data + // - ... + // - .rsrc + // - icon/cursor/etc data + + std::fstream stream; +#if PLATFORM_WINDOWS + stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary); +#else + StringAsANSI<> pathAnsi(path.Get()); + stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary); +#endif + if (!stream.is_open()) + { + LOG(Warning, "Cannot open file"); + return true; + } + + // First check magic number to ensure file is even an executable + uint16 magicNum; + stream.read((char*)&magicNum, sizeof(magicNum)); + if (magicNum != MSDOS_SIGNATURE) + { + LOG(Warning, "Provided file is not a valid executable."); + return true; + } + + // Read the MSDOS header and skip over it + stream.seekg(0); + MSDOSHeader msdosHeader; + stream.read((char*)&msdosHeader, sizeof(MSDOSHeader)); + + // Read PE signature + stream.seekg(msdosHeader.lfanew); + uint32 peSignature; + stream.read((char*)&peSignature, sizeof(peSignature)); + if (peSignature != PE_SIGNATURE) + { + LOG(Warning, "Provided file is not in PE format."); + return true; + } + + // Read COFF header + COFFHeader coffHeader; + stream.read((char*)&coffHeader, sizeof(COFFHeader)); + if (coffHeader.sizeOptHeader == 0) + { + LOG(Warning, "Provided file is not a valid executable."); + return true; + } + uint32 sectionHeadersCount = coffHeader.numSections; + + // Read optional header + auto optionalHeaderPos = stream.tellg(); + uint16 optionalHeaderSignature; + stream.read((char*)&optionalHeaderSignature, sizeof(optionalHeaderSignature)); + PEDataDirectory* dataDirectory = nullptr; + stream.seekg(optionalHeaderPos); + if (optionalHeaderSignature == PE_32BIT_SIGNATURE) + { + PEOptionalHeader32 optionalHeader; + stream.read((char*)&optionalHeader, sizeof(optionalHeader)); + dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; + } + else if (optionalHeaderSignature == PE_64BIT_SIGNATURE) + { + PEOptionalHeader64 optionalHeader; + stream.read((char*)&optionalHeader, sizeof(optionalHeader)); + dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; + } + else + { + LOG(Warning, "Unrecognized PE format."); + return true; + } + + // Read section headers + auto sectionHeaderPos = optionalHeaderPos + (std::ifstream::pos_type)coffHeader.sizeOptHeader; + stream.seekg(sectionHeaderPos); + Array sectionHeaders; + sectionHeaders.Resize(sectionHeadersCount); + stream.read((char*)sectionHeaders.Get(), sizeof(PESectionHeader) * sectionHeadersCount); + + // Look for .rsrc section header + for (uint32 i = 0; i < sectionHeadersCount; i++) + { + PESectionHeader& sectionHeader = sectionHeaders[i]; + if (sectionHeader.flags & PE_SECTION_UNINITIALIZED_DATA) + continue; + if (strcmp(sectionHeader.name, ".rsrc") == 0) + { + uint32 imageSize = sectionHeader.physicalSize; + Array imageData; + imageData.Resize(imageSize); + + stream.seekg(sectionHeader.physicalAddress); + stream.read((char*)imageData.Get(), imageSize); + + uint32 resourceDirOffset = dataDirectory->virtualAddress - sectionHeader.relativeVirtualAddress; + PEImageResourceDirectory* resourceDirectory = (PEImageResourceDirectory*)&imageData.Get()[resourceDirOffset]; + + SetIconData(resourceDirectory, resourceDirectory, imageData.Get(), sectionHeader.relativeVirtualAddress, iconRGBA8); + stream.seekp(sectionHeader.physicalAddress); + stream.write((char*)imageData.Get(), imageSize); + } + } + + stream.close(); + + return false; +} IMPLEMENT_ENGINE_SETTINGS_GETTER(WindowsPlatformSettings, WindowsPlatform); @@ -50,7 +502,7 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data) TextureData iconData; if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData)) { - if (EditorUtilities::UpdateExeIcon(files[0], iconData)) + if (UpdateExeIcon(files[0], iconData)) { data.Error(TEXT("Failed to change output executable file icon.")); return true; diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp index a2418ceb1..a4000a324 100644 --- a/Source/Editor/Utilities/EditorUtilities.cpp +++ b/Source/Editor/Utilities/EditorUtilities.cpp @@ -9,7 +9,6 @@ #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Tools/TextureTool/TextureTool.h" -#include "Engine/Core/Math/Color32.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Content/Content.h" #include "Engine/Content/AssetReference.h" @@ -20,497 +19,6 @@ #endif #include -#define MSDOS_SIGNATURE 0x5A4D -#define PE_SIGNATURE 0x00004550 -#define PE_32BIT_SIGNATURE 0x10B -#define PE_64BIT_SIGNATURE 0x20B -#define PE_NUM_DIRECTORY_ENTRIES 16 -#define PE_SECTION_UNINITIALIZED_DATA 0x00000080 -#define PE_IMAGE_DIRECTORY_ENTRY_RESOURCE 2 -#define PE_IMAGE_RT_ICON 3 - -/// -/// MS-DOS header found at the beginning in a PE format file. -/// -struct MSDOSHeader -{ - uint16 signature; - uint16 lastSize; - uint16 numBlocks; - uint16 numReloc; - uint16 hdrSize; - uint16 minAlloc; - uint16 maxAlloc; - uint16 ss; - uint16 sp; - uint16 checksum; - uint16 ip; - uint16 cs; - uint16 relocPos; - uint16 numOverlay; - uint16 reserved1[4]; - uint16 oemId; - uint16 oemInfo; - uint16 reserved2[10]; - uint32 lfanew; -}; - -/// -/// COFF header found in a PE format file. -/// -struct COFFHeader -{ - uint16 machine; - uint16 numSections; - uint32 timeDateStamp; - uint32 ptrSymbolTable; - uint32 numSymbols; - uint16 sizeOptHeader; - uint16 characteristics; -}; - -/// -/// Contains address and size of data areas in a PE image. -/// -struct PEDataDirectory -{ - uint32 virtualAddress; - uint32 size; -}; - -/// -/// Optional header in a 32-bit PE format file. -/// -struct PEOptionalHeader32 -{ - uint16 signature; - uint8 majorLinkerVersion; - uint8 minorLinkerVersion; - uint32 sizeCode; - uint32 sizeInitializedData; - uint32 sizeUninitializedData; - uint32 addressEntryPoint; - uint32 baseCode; - uint32 baseData; - uint32 baseImage; - uint32 alignmentSection; - uint32 alignmentFile; - uint16 majorOSVersion; - uint16 minorOSVersion; - uint16 majorImageVersion; - uint16 minorImageVersion; - uint16 majorSubsystemVersion; - uint16 minorSubsystemVersion; - uint32 reserved; - uint32 sizeImage; - uint32 sizeHeaders; - uint32 checksum; - uint16 subsystem; - uint16 characteristics; - uint32 sizeStackReserve; - uint32 sizeStackCommit; - uint32 sizeHeapReserve; - uint32 sizeHeapCommit; - uint32 loaderFlags; - uint32 NumRvaAndSizes; - PEDataDirectory dataDirectory[16]; -}; - -/// -/// Optional header in a 64-bit PE format file. -/// -struct PEOptionalHeader64 -{ - uint16 signature; - uint8 majorLinkerVersion; - uint8 minorLinkerVersion; - uint32 sizeCode; - uint32 sizeInitializedData; - uint32 sizeUninitializedData; - uint32 addressEntryPoint; - uint32 baseCode; - uint64 baseImage; - uint32 alignmentSection; - uint32 alignmentFile; - uint16 majorOSVersion; - uint16 minorOSVersion; - uint16 majorImageVersion; - uint16 minorImageVersion; - uint16 majorSubsystemVersion; - uint16 minorSubsystemVersion; - uint32 reserved; - uint32 sizeImage; - uint32 sizeHeaders; - uint32 checksum; - uint16 subsystem; - uint16 characteristics; - uint64 sizeStackReserve; - uint64 sizeStackCommit; - uint64 sizeHeapReserve; - uint64 sizeHeapCommit; - uint32 loaderFlags; - uint32 NumRvaAndSizes; - PEDataDirectory dataDirectory[16]; -}; - -/// -/// A section header in a PE format file. -/// -struct PESectionHeader -{ - char name[8]; - uint32 virtualSize; - uint32 relativeVirtualAddress; - uint32 physicalSize; - uint32 physicalAddress; - uint8 deprecated[12]; - uint32 flags; -}; - -/// -/// A resource table header within a .rsrc section in a PE format file. -/// -struct PEImageResourceDirectory -{ - uint32 flags; - uint32 timeDateStamp; - uint16 majorVersion; - uint16 minorVersion; - uint16 numNamedEntries; - uint16 numIdEntries; -}; - -/// -/// A single entry in a resource table within a .rsrc section in a PE format file. -/// -struct PEImageResourceEntry -{ - uint32 type; - uint32 offsetDirectory : 31; - uint32 isDirectory : 1; -}; - -/// -/// An entry in a resource table referencing resource data. Found within a .rsrc section in a PE format file. -/// -struct PEImageResourceEntryData -{ - uint32 offsetData; - uint32 size; - uint32 codePage; - uint32 resourceHandle; -}; - -/// -/// Header used in icon file format. -/// -struct IconHeader -{ - uint32 size; - int32 width; - int32 height; - uint16 planes; - uint16 bitCount; - uint32 compression; - uint32 sizeImage; - int32 xPelsPerMeter; - int32 yPelsPerMeter; - uint32 clrUsed; - uint32 clrImportant; -}; - -void UpdateIconData(uint8* iconData, const TextureData* icon) -{ - IconHeader* iconHeader = (IconHeader*)iconData; - - if (iconHeader->size != sizeof(IconHeader) || iconHeader->compression != 0 || iconHeader->planes != 1 || iconHeader->bitCount != 32) - { - // Unsupported format - return; - } - - uint8* iconPixels = iconData + sizeof(IconHeader); - uint32 width = iconHeader->width; - uint32 height = iconHeader->height / 2; - - // Check if can use mip from texture data or sample different mip - uint32 iconTexSize; - if (width != height) - { - // Only square icons are supported - return; - } - if (Math::IsPowerOfTwo(width)) - { - // Use mip - iconTexSize = width; - } - else - { - // Use resized mip - iconTexSize = Math::RoundUpToPowerOf2(width); - } - - // Try to pick a proper mip (require the same size) - const TextureMipData* srcPixels = nullptr; - int32 mipLevels = icon->GetMipLevels(); - for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) - { - uint32 iconWidth = Math::Max(1, icon->Width >> mipIndex); - uint32 iconHeight = Math::Max(1, icon->Height >> mipIndex); - - if (iconTexSize == iconWidth && iconTexSize == iconHeight) - { - srcPixels = icon->GetData(0, mipIndex); - break; - } - } - if (srcPixels == nullptr) - { - // No icon of this size provided - return; - } - - // Write colors - uint32* colorData = (uint32*)iconPixels; - - uint32 idx = 0; - for (int32 y = (int32)height - 1; y >= 0; y--) - { - float v = (float)y / height; - for (uint32 x = 0; x < width; x++) - { - float u = (float)x / width; - - int32 i = (int32)(v * iconTexSize) * srcPixels->RowPitch + (int32)(u * iconTexSize) * sizeof(Color32); - colorData[idx++] = ((Color32*)&srcPixels->Data.Get()[i])->GetAsBGRA(); - } - } - - // Write AND mask - uint32 colorDataSize = width * height * sizeof(uint32); - uint8* maskData = iconPixels + colorDataSize; - - // One per bit in byte - uint32 numPackedPixels = width / 8; - - for (int32 y = (int32)height - 1; y >= 0; y--) - { - uint8 mask = 0; - float v = (float)y / height; - for (uint32 packedX = 0; packedX < numPackedPixels; packedX++) - { - for (uint32 pixelIdx = 0; pixelIdx < 8; pixelIdx++) - { - uint32 x = packedX * 8 + pixelIdx; - float u = (float)x / width; - int32 i = (int32)(v * iconTexSize) * srcPixels->RowPitch + (int32)(u * iconTexSize) * sizeof(Color32); - Color32 color = *((Color32*)&srcPixels->Data.Get()[i]); - if (color.A < 64) - mask |= 1 << (7 - pixelIdx); - } - - *maskData = mask; - maskData++; - } - } -} - -void SetIconData(PEImageResourceDirectory* base, PEImageResourceDirectory* current, uint8* imageData, uint32 sectionAddress, const TextureData* iconRGBA8) -{ - uint32 numEntries = current->numIdEntries; // Not supporting name entries - PEImageResourceEntry* entries = (PEImageResourceEntry*)(current + 1); - - for (uint32 i = 0; i < numEntries; i++) - { - // Only at root does the type identify resource type - if (base == current && entries[i].type != PE_IMAGE_RT_ICON) - continue; - - if (entries[i].isDirectory) - { - PEImageResourceDirectory* child = (PEImageResourceDirectory*)(((uint8*)base) + entries[i].offsetDirectory); - SetIconData(base, child, imageData, sectionAddress, iconRGBA8); - } - else - { - PEImageResourceEntryData* data = (PEImageResourceEntryData*)(((uint8*)base) + entries[i].offsetDirectory); - - uint8* iconData = imageData + (data->offsetData - sectionAddress); - UpdateIconData(iconData, iconRGBA8); - } - } -} - -bool EditorUtilities::UpdateExeIcon(const String& path, const TextureData& icon) -{ - // Validate input - if (!FileSystem::FileExists(path)) - { - LOG(Warning, "Missing file"); - return true; - } - if (icon.Width < 1 || icon.Height < 1 || icon.GetMipLevels() <= 0) - { - LOG(Warning, "Inalid icon data"); - return true; - } - - // Convert to RGBA8 format if need to - const TextureData* iconRGBA8 = &icon; - TextureData tmpData1; - if (icon.Format != PixelFormat::R8G8B8A8_UNorm) - { - if (TextureTool::Convert(tmpData1, *iconRGBA8, PixelFormat::R8G8B8A8_UNorm)) - { - LOG(Warning, "Failed convert icon data."); - return true; - } - iconRGBA8 = &tmpData1; - } - - // Resize if need to - TextureData tmpData2; - if (iconRGBA8->Width > 256 || iconRGBA8->Height > 256) - { - if (TextureTool::Resize(tmpData2, *iconRGBA8, 256, 256)) - { - LOG(Warning, "Failed resize icon data."); - return true; - } - iconRGBA8 = &tmpData2; - } - - // A PE file is structured as such: - // - MSDOS Header - // - PE Signature - // - COFF Header - // - PE Optional Header - // - One or multiple sections - // - .code - // - .data - // - ... - // - .rsrc - // - icon/cursor/etc data - - std::fstream stream; -#if PLATFORM_WINDOWS - stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary); -#else - StringAsANSI<> pathAnsi(path.Get()); - stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary); -#endif - if (!stream.is_open()) - { - LOG(Warning, "Cannot open file"); - return true; - } - - // First check magic number to ensure file is even an executable - uint16 magicNum; - stream.read((char*)&magicNum, sizeof(magicNum)); - if (magicNum != MSDOS_SIGNATURE) - { - LOG(Warning, "Provided file is not a valid executable."); - return true; - } - - // Read the MSDOS header and skip over it - stream.seekg(0); - - MSDOSHeader msdosHeader; - stream.read((char*)&msdosHeader, sizeof(MSDOSHeader)); - - // Read PE signature - stream.seekg(msdosHeader.lfanew); - - uint32 peSignature; - stream.read((char*)&peSignature, sizeof(peSignature)); - - if (peSignature != PE_SIGNATURE) - { - LOG(Warning, "Provided file is not in PE format."); - return true; - } - - // Read COFF header - COFFHeader coffHeader; - stream.read((char*)&coffHeader, sizeof(COFFHeader)); - - // .exe files always have an optional header - if (coffHeader.sizeOptHeader == 0) - { - LOG(Warning, "Provided file is not a valid executable."); - return true; - } - - uint32 numSectionHeaders = coffHeader.numSections; - - // Read optional header - auto optionalHeaderPos = stream.tellg(); - - uint16 optionalHeaderSignature; - stream.read((char*)&optionalHeaderSignature, sizeof(optionalHeaderSignature)); - - PEDataDirectory* dataDirectory = nullptr; - stream.seekg(optionalHeaderPos); - if (optionalHeaderSignature == PE_32BIT_SIGNATURE) - { - PEOptionalHeader32 optionalHeader; - stream.read((char*)&optionalHeader, sizeof(optionalHeader)); - - dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; - } - else if (optionalHeaderSignature == PE_64BIT_SIGNATURE) - { - PEOptionalHeader64 optionalHeader; - stream.read((char*)&optionalHeader, sizeof(optionalHeader)); - - dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; - } - else - { - LOG(Warning, "Unrecognized PE format."); - return true; - } - - // Read section headers - auto sectionHeaderPos = optionalHeaderPos + (std::ifstream::pos_type)coffHeader.sizeOptHeader; - stream.seekg(sectionHeaderPos); - - Array sectionHeaders; - sectionHeaders.Resize(numSectionHeaders); - stream.read((char*)sectionHeaders.Get(), sizeof(PESectionHeader) * numSectionHeaders); - - // Look for .rsrc section header - for (uint32 i = 0; i < numSectionHeaders; i++) - { - if (sectionHeaders[i].flags & PE_SECTION_UNINITIALIZED_DATA) - continue; - - if (strcmp(sectionHeaders[i].name, ".rsrc") == 0) - { - uint32 imageSize = sectionHeaders[i].physicalSize; - Array imageData; - imageData.Resize(imageSize); - - stream.seekg(sectionHeaders[i].physicalAddress); - stream.read((char*)imageData.Get(), imageSize); - - uint32 resourceDirOffset = dataDirectory->virtualAddress - sectionHeaders[i].relativeVirtualAddress; - PEImageResourceDirectory* resourceDirectory = (PEImageResourceDirectory*)&imageData.Get()[resourceDirOffset]; - - SetIconData(resourceDirectory, resourceDirectory, imageData.Get(), sectionHeaders[i].relativeVirtualAddress, iconRGBA8); - stream.seekp(sectionHeaders[i].physicalAddress); - stream.write((char*)imageData.Get(), imageSize); - } - } - - stream.close(); - - return false; -} - bool EditorUtilities::FormatAppPackageName(String& packageName) { const auto gameSettings = GameSettings::Get(); diff --git a/Source/Editor/Utilities/EditorUtilities.h b/Source/Editor/Utilities/EditorUtilities.h index 881bfe3b8..10e09cee1 100644 --- a/Source/Editor/Utilities/EditorUtilities.h +++ b/Source/Editor/Utilities/EditorUtilities.h @@ -22,14 +22,6 @@ public: SplashScreen, }; - /// - /// Updates the Win32 executable file icon. - /// - /// The exe path. - /// The icon image data. - /// True if fails, otherwise false. - static bool UpdateExeIcon(const String& path, const TextureData& icon); - static bool FormatAppPackageName(String& packageName); static bool GetApplicationImage(const Guid& imageId, TextureData& imageData, ApplicationImageType type = ApplicationImageType::Icon); static bool GetTexture(const Guid& textureId, TextureData& textureData); From 8441726da774f3f6793c65fcc6a346b8d9ef13ba Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 18:43:48 +0100 Subject: [PATCH 086/103] Improve support for fixed-arrays in api fields --- Source/Editor/Windows/Profiler/CPU.cs | 4 - Source/Engine/Physics/Collisions.h | 6 +- .../Bindings/BindingsGenerator.CSharp.cs | 160 ++++++++++-------- 3 files changed, 88 insertions(+), 82 deletions(-) diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs index ba569f04f..7638485e4 100644 --- a/Source/Editor/Windows/Profiler/CPU.cs +++ b/Source/Editor/Windows/Profiler/CPU.cs @@ -20,9 +20,7 @@ namespace FlaxEngine get { fixed (short* name = Name0) - { return new string((char*)name); - } } } @@ -31,9 +29,7 @@ namespace FlaxEngine fixed (short* name = Name0) { fixed (char* p = prefix) - { return Utils.MemoryCompare(new IntPtr(name), new IntPtr(p), (ulong)(prefix.Length * 2)) == 0; - } } } } diff --git a/Source/Engine/Physics/Collisions.h b/Source/Engine/Physics/Collisions.h index 7336e8d5c..230337bc9 100644 --- a/Source/Engine/Physics/Collisions.h +++ b/Source/Engine/Physics/Collisions.h @@ -9,7 +9,7 @@ class PhysicsColliderActor; /// /// Contains a contact point data for the collision location. /// -API_STRUCT() struct FLAXENGINE_API ContactPoint +API_STRUCT(NoDefault) struct FLAXENGINE_API ContactPoint { DECLARE_SCRIPTING_TYPE_MINIMAL(ContactPoint); @@ -41,7 +41,7 @@ struct TIsPODType /// /// Contains a collision information passed to the OnCollisionEnter/OnCollisionExit events. /// -API_STRUCT() struct FLAXENGINE_API Collision +API_STRUCT(NoDefault) struct FLAXENGINE_API Collision { DECLARE_SCRIPTING_TYPE_MINIMAL(Collision); @@ -81,7 +81,7 @@ API_STRUCT() struct FLAXENGINE_API Collision /// /// The contacts locations. /// - API_FIELD(Private, NoArray) ContactPoint Contacts[COLLISION_NAX_CONTACT_POINTS]; + API_FIELD(Internal, NoArray) ContactPoint Contacts[COLLISION_NAX_CONTACT_POINTS]; public: /// diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 51ce5b647..7c5eb270f 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -90,6 +90,15 @@ namespace Flax.Build.Bindings "Int4", }; + private static bool GenerateCSharpUseFixedBuffer(string managedType) + { + return managedType == "byte" || managedType == "char" || + managedType == "short" || managedType == "ushort" || + managedType == "int" || managedType == "uint" || + managedType == "long" || managedType == "ulong" || + managedType == "float" || managedType == "double"; + } + private static string GenerateCSharpDefaultValueNativeToManaged(BuildData buildData, string value, ApiTypeInfo caller, TypeInfo valueType = null, bool attribute = false, string managedType = null) { if (string.IsNullOrEmpty(value)) @@ -1453,10 +1462,9 @@ namespace Flax.Build.Bindings contents.Append(indent + "{"); indent += " "; - toNativeContent.Append($"return new {structureInfo.Name}Internal() {{ "); - toManagedContent.Append($"return new {structureInfo.Name}() {{ "); + toNativeContent.Append($"var unmanaged = new {structureInfo.Name}Internal();").AppendLine(); + toManagedContent.Append($"var managed = new {structureInfo.Name}();").AppendLine(); - bool useSeparator = false; contents.AppendLine(); foreach (var fieldInfo in structureInfo.Fields) { @@ -1474,11 +1482,7 @@ namespace Flax.Build.Bindings else originalType = type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); - contents.Append(indent).Append(GenerateCSharpAccessLevel(fieldInfo.Access)); - if (fieldInfo.IsConstexpr) - contents.Append("const "); - else if (fieldInfo.IsStatic) - contents.Append("static "); + contents.Append(indent).Append("public "); var apiType = FindApiTypeInfo(buildData, fieldInfo.Type, structureInfo); bool internalType = apiType is StructureInfo fieldStructureInfo && UseCustomMarshalling(buildData, fieldStructureInfo, structureInfo); @@ -1486,16 +1490,43 @@ namespace Flax.Build.Bindings if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) { - contents.Append(type).Append(' ').Append(fieldInfo.Name + "0;").AppendLine(); - for (int i = 1; i < fieldInfo.Type.ArraySize; i++) +#if USE_NETCORE + if (GenerateCSharpUseFixedBuffer(originalType)) { - contents.AppendLine(); - GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); - contents.Append(indent).Append(GenerateCSharpAccessLevel(fieldInfo.Access)); - if (fieldInfo.IsStatic) - contents.Append("static "); - contents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine(); + // Use fixed statement with primitive types of buffers + contents.Append($"fixed {originalType} {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}];").AppendLine(); + + // Copy fixed-size array + toManagedContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(managed.{fieldInfo.Name}0), new IntPtr(unmanaged.{fieldInfo.Name}0), sizeof({originalType}) * {fieldInfo.Type.ArraySize}ul);"); + toNativeContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(unmanaged.{fieldInfo.Name}0), new IntPtr(managed.{fieldInfo.Name}0), sizeof({originalType}) * {fieldInfo.Type.ArraySize}ul);"); } + else +#endif + { + // Padding in structs for fixed-size array + contents.Append(type).Append(' ').Append(fieldInfo.Name).Append("0;").AppendLine(); + for (int i = 1; i < fieldInfo.Type.ArraySize; i++) + { + GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); + contents.Append(indent).Append("public "); + contents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine(); + } + + // Copy fixed-size array item one-by-one + if (fieldInfo.Access == AccessLevel.Public || fieldInfo.Access == AccessLevel.Internal) + { + for (int i = 0; i < fieldInfo.Type.ArraySize; i++) + { + toManagedContent.AppendLine($"managed.{fieldInfo.Name}{i} = unmanaged.{fieldInfo.Name}{i};"); + toNativeContent.AppendLine($"unmanaged.{fieldInfo.Name}{i} = managed.{fieldInfo.Name}{i};"); + } + } + else + { + throw new NotImplementedException("TODO: generate utility method to copy private/protected array data items"); + } + } + continue; } else { @@ -1525,34 +1556,17 @@ namespace Flax.Build.Bindings //else if (type == "Guid") // type = "GuidNative"; - contents.Append(type).Append(' ').Append(fieldInfo.Name); - contents.Append(';').AppendLine(); + contents.Append(type).Append(' ').Append(fieldInfo.Name).Append(';').AppendLine(); } // Generate struct constructor/getter and deconstructor/setter function - if (fieldInfo.NoArray && fieldInfo.Type.IsArray) - continue; - - if (useSeparator) - { - toManagedContent.Append(", "); - toNativeContent.Append(", "); - freeContents2.Append(""); - freeContents.Append(""); - } - useSeparator = true; - - toManagedContent.Append(fieldInfo.Name); - toManagedContent.Append(" = "); - - toNativeContent.Append(fieldInfo.Name); - toNativeContent.Append(" = "); - + toManagedContent.Append("managed.").Append(fieldInfo.Name).Append(" = "); + toNativeContent.Append("unmanaged.").Append(fieldInfo.Name).Append(" = "); if (fieldInfo.Type.IsObjectRef) { var managedType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type.GenericArgs[0], structureInfo); - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1560,8 +1574,8 @@ namespace Flax.Build.Bindings } else if (fieldInfo.Type.Type == "ScriptingObject") { - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1569,8 +1583,8 @@ namespace Flax.Build.Bindings } else if (fieldInfo.Type.IsPtr && originalType != "IntPtr" && !originalType.EndsWith("*")) { - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1578,8 +1592,8 @@ namespace Flax.Build.Bindings } else if (fieldInfo.Type.Type == "Dictionary") { - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak)"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak);"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } @@ -1591,16 +1605,16 @@ namespace Flax.Build.Bindings // Marshal blittable array elements back to original non-blittable elements string originalElementTypeMarshaller = originalElementType + "Marshaller"; string internalElementType = $"{originalElementTypeMarshaller}.{originalElementType}Internal"; - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.ConvertArray((Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)).ToSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(NativeInterop.ConvertArray(managed.{fieldInfo.Name}, {originalElementTypeMarshaller}.ToNative)), GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.ConvertArray((Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)).ToSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(NativeInterop.ConvertArray(managed.{fieldInfo.Name}, {originalElementTypeMarshaller}.ToNative)), GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); } else if (fieldInfo.Type.GenericArgs[0].IsObjectRef) { // Array elements passed as GCHandles - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span ptrs = (Unsafe.As(handle.Target)).ToSpan(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1609,53 +1623,53 @@ namespace Flax.Build.Bindings else { // Blittable array elements - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? (Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)).ToArray<{originalElementType}>() : null"); - toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? (Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)).ToArray<{originalElementType}>() : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); } } else if (fieldInfo.Type.Type == "Version") { - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak)"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak);"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } else if (originalType == "string") { - toManagedContent.Append($"ManagedString.ToManaged(managed.{fieldInfo.Name})"); - toNativeContent.Append($"ManagedString.ToNative(managed.{fieldInfo.Name})"); + toManagedContent.AppendLine($"ManagedString.ToManaged(unmanaged.{fieldInfo.Name});"); + toNativeContent.AppendLine($"ManagedString.ToNative(managed.{fieldInfo.Name});"); freeContents.AppendLine($"ManagedString.Free(unmanaged.{fieldInfo.Name});"); freeContents2.AppendLine($"ManagedString.Free(unmanaged.{fieldInfo.Name});"); } else if (originalType == "bool") { - toManagedContent.Append($"managed.{fieldInfo.Name} != 0"); - toNativeContent.Append($"managed.{fieldInfo.Name} ? (byte)1 : (byte)0"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != 0;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} ? (byte)1 : (byte)0;"); } else if (fieldInfo.Type.Type == "Variant") { // Variant passed as boxed object handle - toManagedContent.Append($"ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(managed.{fieldInfo.Name})"); - toNativeContent.Append($"ManagedHandleMarshaller.NativeToManaged.ConvertToUnmanaged(managed.{fieldInfo.Name})"); + toManagedContent.AppendLine($"ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged.{fieldInfo.Name});"); + toNativeContent.AppendLine($"ManagedHandleMarshaller.NativeToManaged.ConvertToUnmanaged(managed.{fieldInfo.Name});"); } else if (internalType) { - toManagedContent.Append($"{internalTypeMarshaller}.ToManaged(managed.{fieldInfo.Name})"); - toNativeContent.Append($"{internalTypeMarshaller}.ToNative(managed.{fieldInfo.Name})"); + toManagedContent.AppendLine($"{internalTypeMarshaller}.ToManaged(unmanaged.{fieldInfo.Name});"); + toNativeContent.AppendLine($"{internalTypeMarshaller}.ToNative(managed.{fieldInfo.Name});"); freeContents.AppendLine($"{internalTypeMarshaller}.Free(unmanaged.{fieldInfo.Name});"); freeContents2.AppendLine($"{internalTypeMarshaller}.Free(unmanaged.{fieldInfo.Name});"); } /*else if (originalType == "Guid") { - toManagedContent.Append("(Guid)managed.").Append(fieldInfo.Name); + toManagedContent.Append("(Guid)unmanaged.").Append(fieldInfo.Name); toNativeContent.Append("(GuidNative)managed.").Append(fieldInfo.Name); }*/ else { - toManagedContent.Append("managed.").Append(fieldInfo.Name); - toNativeContent.Append("managed.").Append(fieldInfo.Name); + toManagedContent.Append("unmanaged.").Append(fieldInfo.Name).AppendLine(";"); + toNativeContent.Append("managed.").Append(fieldInfo.Name).AppendLine(";"); } } @@ -1663,8 +1677,8 @@ namespace Flax.Build.Bindings indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(indent + "}").AppendLine(); - toManagedContent.AppendLine(" };"); - toNativeContent.AppendLine(" };"); + toNativeContent.Append("return unmanaged;"); + toManagedContent.Append("return managed;"); } var indent2 = indent + " "; @@ -1712,7 +1726,7 @@ namespace Flax.Build.Bindings contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(freeContents.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); // Managed/native converters - contents.Append(indent).AppendLine($"internal static {structureInfo.Name} ToManaged({structureInfo.Name}Internal managed)"); + contents.Append(indent).AppendLine($"internal static {structureInfo.Name} ToManaged({structureInfo.Name}Internal unmanaged)"); contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(toManagedContent.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); contents.Append(indent).AppendLine($"internal static {structureInfo.Name}Internal ToNative({structureInfo.Name} managed)"); contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(toNativeContent.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); @@ -1767,15 +1781,12 @@ namespace Flax.Build.Bindings managedType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); fieldInfo.Type.IsArray = true; #if USE_NETCORE - // Use fixed statement with primitive types of buffers - if (managedType == "char") + if (GenerateCSharpUseFixedBuffer(managedType)) { - // char's are not blittable, store as short instead - contents.Append($"fixed short {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine(); - } - else if (managedType == "byte") - { - contents.Append($"fixed byte {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine(); + // Use fixed statement with primitive types of buffers + if (managedType == "char") + managedType = "short"; // char's are not blittable, store as short instead + contents.Append($"fixed {managedType} {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}];").AppendLine(); } else #endif @@ -1785,7 +1796,6 @@ namespace Flax.Build.Bindings for (int i = 1; i < fieldInfo.Type.ArraySize; i++) { contents.AppendLine(); - GenerateCSharpComment(contents, indent, fieldInfo.Comment); GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); contents.Append(indent).Append(GenerateCSharpAccessLevel(fieldInfo.Access)); if (fieldInfo.IsStatic) From 2042525e92938bd886dcd516eee2aa125ca8cb14 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 18:53:09 +0100 Subject: [PATCH 087/103] Add pooling to some `StringBuilder` objects in build tool --- .../Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 7c5eb270f..845e4e62b 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1444,10 +1444,10 @@ namespace Flax.Build.Bindings indent += " "; - StringBuilder toManagedContent = new StringBuilder(); - StringBuilder toNativeContent = new StringBuilder(); - StringBuilder freeContents = new StringBuilder(); - StringBuilder freeContents2 = new StringBuilder(); + var toManagedContent = GetStringBuilder(); + var toNativeContent = GetStringBuilder(); + var freeContents = GetStringBuilder(); + var freeContents2 = GetStringBuilder(); { // Native struct begin @@ -1734,6 +1734,11 @@ namespace Flax.Build.Bindings contents.AppendLine("#pragma warning restore 1591"); indent = indent.Substring(0, indent.Length - 4); contents.Append(indent).AppendLine("}").AppendLine(); + + PutStringBuilder(toManagedContent); + PutStringBuilder(toNativeContent); + PutStringBuilder(freeContents); + PutStringBuilder(freeContents2); } #endif // Struct docs From d1f40d1c47c78a75a0fa82772a5a88ea0b08fde8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 18:53:55 +0100 Subject: [PATCH 088/103] Fix missing visibleIf attr --- Source/Engine/Tools/ModelTool/ModelTool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index df89a1519..90292d72c 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -251,7 +251,7 @@ public: API_FIELD(Attributes="EditorOrder(1140), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry))") bool SloppyOptimization = false; // Only used if Sloppy is false. Target error is an approximate measure of the deviation from the original mesh using distance normalized to [0..1] range (e.g. 1e-2f means that simplifier will try to maintain the error to be below 1% of the mesh extents). - API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(SloppyOptimization), true), Limit(0.01f, 1, 0.001f)") + API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGeometry)), Limit(0.01f, 1, 0.001f)") float LODTargetError = 0.05f; public: // Materials From e75902e9003b83d7253dafc52b761aa5042d562c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 18:56:07 +0100 Subject: [PATCH 089/103] Variosu tweaks --- Source/Editor/Modules/WindowsModule.cs | 2 -- Source/Editor/Surface/Archetypes/Animation.StateMachine.cs | 2 -- Source/Editor/Windows/EditorWindow.cs | 2 ++ Source/Engine/Animations/Graph/AnimGraph.h | 2 +- Source/Engine/Animations/Graph/AnimGroup.Animation.cpp | 2 +- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index a935e73f2..cf2715b48 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -725,9 +725,7 @@ namespace FlaxEditor.Modules for (int i = 0; i < Windows.Count; i++) { if (string.Equals(Windows[i].SerializationTypename, typename, StringComparison.OrdinalIgnoreCase)) - { return Windows[i]; - } } // Check if it's an asset ID diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index 9ec1bab19..abe66d3cc 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -191,9 +191,7 @@ namespace FlaxEditor.Surface.Archetypes var value = title; int count = 1; while (!OnRenameValidate(null, value)) - { value = title + " " + count++; - } Values[0] = value; Title = value; diff --git a/Source/Editor/Windows/EditorWindow.cs b/Source/Editor/Windows/EditorWindow.cs index ff0673c11..df70a0b33 100644 --- a/Source/Editor/Windows/EditorWindow.cs +++ b/Source/Editor/Windows/EditorWindow.cs @@ -226,6 +226,8 @@ namespace FlaxEditor.Windows /// public override void OnDestroy() { + if (IsDisposing) + return; OnExit(); // Unregister diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index c160b671c..a6dc68596 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -205,7 +205,7 @@ struct FLAXENGINE_API AnimGraphSlot /// /// The animation graph state container for a single node playback trace (eg. animation sample info or state transition). Can be used by Anim Graph debugging or custom scripting. /// -API_STRUCT() struct FLAXENGINE_API AnimGraphTraceEvent +API_STRUCT(NoDefault) struct FLAXENGINE_API AnimGraphTraceEvent { DECLARE_SCRIPTING_TYPE_MINIMAL(AnimGraphTraceEvent); diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index d3b61c951..a9d29cda3 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -506,7 +506,7 @@ Variant AnimGraphExecutor::SampleState(AnimGraphNode* state) void AnimGraphExecutor::InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition) { - // Reset transiton + // Reset transition stateMachineBucket.ActiveTransition = transition; stateMachineBucket.TransitionPosition = 0.0f; From a38d1ad7ccb8e58a11cd436788691a355b07b4e6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 19:03:38 +0100 Subject: [PATCH 090/103] Fix anim graph trace events debugging to include nodes path for nested graphs --- Source/Editor/Surface/AnimGraphSurface.cs | 26 ++++++++++++++++--- .../Editor/Surface/VisjectSurface.Context.cs | 2 ++ .../Editor/Surface/VisjectSurfaceContext.cs | 5 ++++ Source/Engine/Animations/Graph/AnimGraph.cpp | 1 + Source/Engine/Animations/Graph/AnimGraph.h | 5 +++- .../Animations/Graph/AnimGroup.Animation.cpp | 23 +++++++++++----- 6 files changed, 51 insertions(+), 11 deletions(-) diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs index ccd78412a..3b704e26e 100644 --- a/Source/Editor/Surface/AnimGraphSurface.cs +++ b/Source/Editor/Surface/AnimGraphSurface.cs @@ -115,16 +115,36 @@ namespace FlaxEditor.Surface internal AnimGraphTraceEvent[] LastTraceEvents; - internal bool TryGetTraceEvent(SurfaceNode node, out AnimGraphTraceEvent traceEvent) + internal unsafe bool TryGetTraceEvent(SurfaceNode node, out AnimGraphTraceEvent traceEvent) { if (LastTraceEvents != null) { foreach (var e in LastTraceEvents) { + // Node IDs must match if (e.NodeId == node.ID) { - traceEvent = e; - return true; + uint* nodePath = e.NodePath0; + + // Get size of the path + int nodePathSize = 0; + while (nodePathSize < 8 && nodePath[nodePathSize] != 0) + nodePathSize++; + + // Follow input node contexts path to verify if it matches with the path in the event + var c = node.Context; + for (int i = nodePathSize - 1; i >= 0 && c != null; i--) + { + if (c.OwnerNodeID != nodePath[i]) + c = null; + else + c = c.Parent; + } + if (c != null) + { + traceEvent = e; + return true; + } } } } diff --git a/Source/Editor/Surface/VisjectSurface.Context.cs b/Source/Editor/Surface/VisjectSurface.Context.cs index 1d8b97729..691ad4e50 100644 --- a/Source/Editor/Surface/VisjectSurface.Context.cs +++ b/Source/Editor/Surface/VisjectSurface.Context.cs @@ -62,6 +62,8 @@ namespace FlaxEditor.Surface surfaceContext = CreateContext(_context, context); _context?.Children.Add(surfaceContext); _contextCache.Add(contextHandle, surfaceContext); + if (context is SurfaceNode asNode) + surfaceContext.OwnerNodeID = asNode.ID; context.OnContextCreated(surfaceContext); diff --git a/Source/Editor/Surface/VisjectSurfaceContext.cs b/Source/Editor/Surface/VisjectSurfaceContext.cs index 0886996b6..9902f224a 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.cs @@ -156,6 +156,11 @@ namespace FlaxEditor.Surface /// public event Action ControlDeleted; + /// + /// Identifier of the node that 'owns' this context (eg. State Machine which created this graph of state nodes). + /// + public uint OwnerNodeID; + /// /// Initializes a new instance of the class. /// diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index f7b883bd7..9ec87c78f 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -215,6 +215,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) // Prepare context data for the evaluation context.GraphStack.Clear(); context.GraphStack.Push((Graph*)&_graph); + context.NodePath.Clear(); context.Data = &data; context.DeltaTime = dt; context.CurrentFrameIndex = ++data.CurrentFrame; diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index a6dc68596..d8acae234 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -215,6 +215,8 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API AnimGraphTraceEvent API_FIELD() float Value = 0; // Identifier of the node in the graph. API_FIELD() uint32 NodeId = 0; + // Ids of graph nodes (call of hierarchy). + API_FIELD(Internal, NoArray) uint32 NodePath[8] = {}; }; /// @@ -796,6 +798,7 @@ struct AnimGraphContext AnimGraphTransitionData TransitionData; Array> CallStack; Array> GraphStack; + Array > NodePath; Dictionary Functions; ChunkedArray PoseCache; int32 PoseCacheSize; @@ -891,7 +894,7 @@ private: Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, float speedA, float speedB, float alpha); Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC); Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode); - Variant SampleState(AnimGraphNode* state); + Variant SampleState(AnimGraphContext& context, const AnimGraphNode* state); void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr); AnimGraphStateTransition* UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState = nullptr); void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData); diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index a9d29cda3..3d729e80e 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -227,6 +227,9 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* trace.Asset = anim; trace.Value = animPos; trace.NodeId = node->ID; + auto* nodePath = context.NodePath.Get(); + for (int32 i = 0; i < context.NodePath.Count(); i++) + trace.NodePath[i] = nodePath[i]; } // Evaluate nested animations @@ -494,14 +497,17 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const return nodes; } -Variant AnimGraphExecutor::SampleState(AnimGraphNode* state) +Variant AnimGraphExecutor::SampleState(AnimGraphContext& context, const AnimGraphNode* state) { auto& data = state->Data.State; if (data.Graph == nullptr || data.Graph->GetRootNode() == nullptr) return Value::Null; ANIM_GRAPH_PROFILE_EVENT("Evaluate State"); + context.NodePath.Add(state->ID); auto rootNode = data.Graph->GetRootNode(); - return eatBox((Node*)rootNode, &rootNode->Boxes[0]); + auto result = eatBox((Node*)rootNode, &rootNode->Boxes[0]); + context.NodePath.Pop(); + return result; } void AnimGraphExecutor::InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition) @@ -537,7 +543,7 @@ AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphCon } // Evaluate source state transition data (position, length, etc.) - const Value sourceStatePtr = SampleState(state); + const Value sourceStatePtr = SampleState(context, state); auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule? if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr)) { @@ -1660,6 +1666,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu bucket.CurrentState = bucket.ActiveTransition->Destination; \ InitStateTransition(context, bucket) + context.NodePath.Push(node->ID); + // Update the active transition if (bucket.ActiveTransition) { @@ -1767,11 +1775,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu if (bucket.BaseTransitionState) { // Sample the other state (eg. when blending from interrupted state to the another state from the old destination) - value = SampleState(bucket.BaseTransitionState); + value = SampleState(context, bucket.BaseTransitionState); if (bucket.BaseTransition) { // Evaluate the base pose from the time when transition was interrupted - const auto destinationState = SampleState(bucket.BaseTransition->Destination); + const auto destinationState = SampleState(context, bucket.BaseTransition->Destination); const float alpha = bucket.BaseTransitionPosition / bucket.BaseTransition->BlendDuration; value = Blend(node, value, destinationState, alpha, bucket.BaseTransition->BlendMode); } @@ -1779,14 +1787,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu else { // Sample the current state - value = SampleState(bucket.CurrentState); + value = SampleState(context, bucket.CurrentState); } // Handle active transition blending if (bucket.ActiveTransition) { // Sample the active transition destination state - const auto destinationState = SampleState(bucket.ActiveTransition->Destination); + const auto destinationState = SampleState(context, bucket.ActiveTransition->Destination); // Perform blending const float alpha = bucket.TransitionPosition / bucket.ActiveTransition->BlendDuration; @@ -1794,6 +1802,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } bucket.LastUpdateFrame = context.CurrentFrameIndex; + context.NodePath.Pop(); #undef END_TRANSITION break; } From cfb8350c65f642dbe8a6fb3c710e8c24b3f0cc96 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 19:22:07 +0100 Subject: [PATCH 091/103] Fix anim graph debugging to handle nested graph connections highlights properly --- Source/Editor/Surface/AnimGraphSurface.cs | 7 +----- .../Editor/Surface/VisjectSurface.Context.cs | 24 +++++++++++++++++++ .../Windows/Assets/AnimationGraphWindow.cs | 19 ++++++++------- Source/Engine/Animations/Animations.cpp | 4 ++-- Source/Engine/Animations/Animations.h | 21 ++++++++++++++-- Source/Engine/Animations/Graph/AnimGraph.cpp | 10 +++++++- .../Animations/Graph/AnimGroup.Animation.cpp | 2 +- 7 files changed, 67 insertions(+), 20 deletions(-) diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs index 3b704e26e..56764bafd 100644 --- a/Source/Editor/Surface/AnimGraphSurface.cs +++ b/Source/Editor/Surface/AnimGraphSurface.cs @@ -134,12 +134,7 @@ namespace FlaxEditor.Surface // Follow input node contexts path to verify if it matches with the path in the event var c = node.Context; for (int i = nodePathSize - 1; i >= 0 && c != null; i--) - { - if (c.OwnerNodeID != nodePath[i]) - c = null; - else - c = c.Parent; - } + c = c.OwnerNodeID == nodePath[i] ? c.Parent : null; if (c != null) { traceEvent = e; diff --git a/Source/Editor/Surface/VisjectSurface.Context.cs b/Source/Editor/Surface/VisjectSurface.Context.cs index 691ad4e50..0cb8e3ead 100644 --- a/Source/Editor/Surface/VisjectSurface.Context.cs +++ b/Source/Editor/Surface/VisjectSurface.Context.cs @@ -33,6 +33,30 @@ namespace FlaxEditor.Surface /// public event Action ContextChanged; + /// + /// Finds the surface context with the given owning nodes IDs path. + /// + /// The node ids path. + /// Found context or null if cannot. + public VisjectSurfaceContext FindContext(Span nodePath) + { + // Get size of the path + int nodePathSize = 0; + while (nodePathSize < nodePath.Length && nodePath[nodePathSize] != 0) + nodePathSize++; + + // Follow each context path to verify if it matches with the path in the input path + foreach (var e in _contextCache) + { + var c = e.Value; + for (int i = nodePathSize - 1; i >= 0 && c != null; i--) + c = c.OwnerNodeID == nodePath[i] ? c.Parent : null; + if (c != null) + return e.Value; + } + return null; + } + /// /// Creates the Visject surface context for the given surface data source context. /// diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs index e0d8c9ded..93eea95e1 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs @@ -154,10 +154,11 @@ namespace FlaxEditor.Windows.Assets } [StructLayout(LayoutKind.Sequential)] - private struct AnimGraphDebugFlowInfo + private unsafe struct AnimGraphDebugFlowInfo { public uint NodeId; public int BoxId; + public fixed uint NodePath[8]; } private FlaxObjectRefPickerControl _debugPicker; @@ -252,25 +253,26 @@ namespace FlaxEditor.Windows.Assets return obj is AnimatedModel player && player.AnimationGraph == OriginalAsset; } - private void OnDebugFlow(Asset asset, Object obj, uint nodeId, uint boxId) + private unsafe void OnDebugFlow(Animations.DebugFlowInfo flowInfo) { // Filter the flow if (_debugPicker.Value != null) { - if (asset != OriginalAsset || _debugPicker.Value != obj) + if (flowInfo.Asset != OriginalAsset || _debugPicker.Value != flowInfo.Instance) return; } else { - if (asset != Asset || _preview.PreviewActor != obj) + if (flowInfo.Asset != Asset || _preview.PreviewActor != flowInfo.Instance) return; } // Register flow to show it in UI on a surface - var flowInfo = new AnimGraphDebugFlowInfo { NodeId = nodeId, BoxId = (int)boxId }; + var flow = new AnimGraphDebugFlowInfo { NodeId = flowInfo.NodeId, BoxId = (int)flowInfo.BoxId }; + Utils.MemoryCopy(new IntPtr(flow.NodePath), new IntPtr(flowInfo.NodePath0), sizeof(uint) * 8ul); lock (_debugFlows) { - _debugFlows.Add(flowInfo); + _debugFlows.Add(flow); } } @@ -394,7 +396,7 @@ namespace FlaxEditor.Windows.Assets } /// - public override void OnUpdate() + public override unsafe void OnUpdate() { // Extract animations playback state from the events tracing var debugActor = _debugPicker.Value as AnimatedModel; @@ -413,7 +415,8 @@ namespace FlaxEditor.Windows.Assets { foreach (var debugFlow in _debugFlows) { - var node = Surface.Context.FindNode(debugFlow.NodeId); + var context = Surface.FindContext(new Span(debugFlow.NodePath, 8)); + var node = context?.FindNode(debugFlow.NodeId); var box = node?.GetBox(debugFlow.BoxId); box?.HighlightConnections(); } diff --git a/Source/Engine/Animations/Animations.cpp b/Source/Engine/Animations/Animations.cpp index e571af162..59acc5860 100644 --- a/Source/Engine/Animations/Animations.cpp +++ b/Source/Engine/Animations/Animations.cpp @@ -52,7 +52,7 @@ namespace AnimationsService AnimationManagerInstance; TaskGraphSystem* Animations::System = nullptr; #if USE_EDITOR -Delegate Animations::DebugFlow; +Delegate Animations::DebugFlow; #endif AnimEvent::AnimEvent(const SpawnParams& params) @@ -127,7 +127,7 @@ void AnimationsSystem::Execute(TaskGraph* graph) #if USE_EDITOR // If debug flow is registered, then warm it up (eg. static cached method inside DebugFlow_ManagedWrapper) so it doesn't crash on highly multi-threaded code if (Animations::DebugFlow.IsBinded()) - Animations::DebugFlow(nullptr, nullptr, 0, 0); + Animations::DebugFlow(Animations::DebugFlowInfo()); #endif // Schedule work to update all animated models in async diff --git a/Source/Engine/Animations/Animations.h b/Source/Engine/Animations/Animations.h index d82b9d05f..038d20d9d 100644 --- a/Source/Engine/Animations/Animations.h +++ b/Source/Engine/Animations/Animations.h @@ -22,8 +22,25 @@ API_CLASS(Static) class FLAXENGINE_API Animations API_FIELD(ReadOnly) static TaskGraphSystem* System; #if USE_EDITOR - // Custom event that is called every time the Anim Graph signal flows over the graph (including the data connections). Can be used to read and visualize the animation blending logic. Args are: anim graph asset, animated object, node id, box id - API_EVENT() static Delegate DebugFlow; + // Data wrapper for the debug flow information. + API_STRUCT(NoDefault) struct DebugFlowInfo + { + DECLARE_SCRIPTING_TYPE_MINIMAL(DebugFlowInfo); + + // Anim Graph asset + API_FIELD() Asset* Asset = nullptr; + // Animated actor + API_FIELD() ScriptingObject* Instance = nullptr; + // Graph node id. + API_FIELD() uint32 NodeId = 0; + // Graph box id. + API_FIELD() uint32 BoxId = 0; + // Ids of graph nodes (call of hierarchy). + API_FIELD(Internal, NoArray) uint32 NodePath[8] = {}; + }; + + // Custom event that is called every time the Anim Graph signal flows over the graph (including the data connections). Can be used to read and visualize the animation blending logic. + API_EVENT() static Delegate DebugFlow; #endif /// diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 9ec87c78f..40c33a3e8 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -425,7 +425,15 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) context.CallStack.Add(caller); #if USE_EDITOR - Animations::DebugFlow(_graph._owner, context.Data->Object, box->GetParent()->ID, box->ID); + Animations::DebugFlowInfo flowInfo; + flowInfo.Asset = _graph._owner; + flowInfo.Instance = context.Data->Object; + flowInfo.NodeId = box->GetParent()->ID; + flowInfo.BoxId = box->ID; + const auto* nodePath = context.NodePath.Get(); + for (int32 i = 0; i < context.NodePath.Count(); i++) + flowInfo.NodePath[i] = nodePath[i]; + Animations::DebugFlow(flowInfo); #endif // Call per group custom processing event diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 3d729e80e..0fab934ca 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -227,7 +227,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* trace.Asset = anim; trace.Value = animPos; trace.NodeId = node->ID; - auto* nodePath = context.NodePath.Get(); + const auto* nodePath = context.NodePath.Get(); for (int32 i = 0; i < context.NodePath.Count(); i++) trace.NodePath[i] = nodePath[i]; } From eed780a0b0ab3ca79eee931483d3eb9c078e315b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 19:31:34 +0100 Subject: [PATCH 092/103] Add highlighting active state in anim graph --- .../Archetypes/Animation.StateMachine.cs | 15 +++++++++++ Source/Engine/Animations/Graph/AnimGraph.h | 2 ++ .../Animations/Graph/AnimGroup.Animation.cpp | 25 +++++++++++++++---- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index abe66d3cc..bb4bc724d 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -653,6 +653,7 @@ namespace FlaxEditor.Surface.Archetypes protected Rectangle _renameButtonRect; private bool _cursorChanged = false; private bool _textRectHovered = false; + private bool _debugActive; /// /// The transitions list from this state to the others. @@ -1090,6 +1091,16 @@ namespace FlaxEditor.Surface.Archetypes // TODO: maybe update only on actual transitions change? UpdateTransitions(); + + // Debug current state + if (((AnimGraphSurface)Surface).TryGetTraceEvent(this, out var traceEvent)) + { + _debugActive = true; + } + else + { + _debugActive = false; + } } /// @@ -1130,6 +1141,10 @@ namespace FlaxEditor.Surface.Archetypes // Close button Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey); + + // Debug outline + if (_debugActive) + Render2D.DrawRectangle(_textRect.MakeExpanded(1.0f), style.ProgressNormal); } /// diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index d8acae234..a774e6b85 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -803,6 +803,8 @@ struct AnimGraphContext ChunkedArray PoseCache; int32 PoseCacheSize; Dictionary ValueCache; + + AnimGraphTraceEvent& AddTraceEvent(const AnimGraphNode* node); }; /// diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 0fab934ca..57829e8fd 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -52,6 +52,17 @@ void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData node = value; } +AnimGraphTraceEvent& AnimGraphContext::AddTraceEvent(const AnimGraphNode* node) +{ + auto& trace = Data->TraceEvents.AddOne(); + trace.Value = 0.0f; + trace.NodeId = node->ID; + const auto* nodePath = NodePath.Get(); + for (int32 i = 0; i < NodePath.Count(); i++) + trace.NodePath[i] = nodePath[i]; + return trace; +} + int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim) { // TODO: cache the root node index (use dictionary with Animation* -> int32 for fast lookups) @@ -223,13 +234,9 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* auto& context = Context.Get(); if (context.Data->EnableTracing) { - auto& trace = context.Data->TraceEvents.AddOne(); + auto& trace = context.AddTraceEvent(node); trace.Asset = anim; trace.Value = animPos; - trace.NodeId = node->ID; - const auto* nodePath = context.NodePath.Get(); - for (int32 i = 0; i < context.NodePath.Count(); i++) - trace.NodePath[i] = nodePath[i]; } // Evaluate nested animations @@ -502,11 +509,19 @@ Variant AnimGraphExecutor::SampleState(AnimGraphContext& context, const AnimGrap auto& data = state->Data.State; if (data.Graph == nullptr || data.Graph->GetRootNode() == nullptr) return Value::Null; + + // Add to trace + if (context.Data->EnableTracing) + { + auto& trace = context.AddTraceEvent(state); + } + ANIM_GRAPH_PROFILE_EVENT("Evaluate State"); context.NodePath.Add(state->ID); auto rootNode = data.Graph->GetRootNode(); auto result = eatBox((Node*)rootNode, &rootNode->Boxes[0]); context.NodePath.Pop(); + return result; } From 082768d08c09a4aa6431083ff8b75ec59b21b5b2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Feb 2024 23:39:02 +0100 Subject: [PATCH 093/103] Refactor `ThreadLocal` when running on hardware with more cores than `PLATFORM_THREADS_LIMIT` --- .../Animations/Graph/AnimGraph.Custom.cpp | 2 +- Source/Engine/Animations/Graph/AnimGraph.cpp | 17 +- Source/Engine/Animations/Graph/AnimGraph.h | 2 +- .../Animations/Graph/AnimGroup.Animation.cpp | 12 +- Source/Engine/Content/Storage/FlaxStorage.cpp | 13 +- Source/Engine/Content/Storage/FlaxStorage.h | 2 +- Source/Engine/Core/Collections/Sorting.cpp | 7 +- Source/Engine/Engine/EngineService.cpp | 16 +- Source/Engine/Level/SceneObjectsFactory.cpp | 2 +- ...rticleEmitterGraph.CPU.ParticleModules.cpp | 4 +- .../ParticleEmitterGraph.CPU.Particles.cpp | 8 +- .../Graph/CPU/ParticleEmitterGraph.CPU.cpp | 16 +- .../Graph/CPU/ParticleEmitterGraph.CPU.h | 2 +- Source/Engine/Platform/Base/PlatformBase.cpp | 4 + Source/Engine/Scripting/Scripting.cpp | 2 +- Source/Engine/Scripting/Scripting.h | 4 +- Source/Engine/Serialization/FileReadStream.h | 5 - Source/Engine/Threading/JobSystem.cpp | 2 +- Source/Engine/Threading/ThreadLocal.h | 164 ++++++++---------- Source/Engine/Threading/ThreadPool.cpp | 2 +- 20 files changed, 147 insertions(+), 139 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp index cdd5b03eb..860ac99d4 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp @@ -89,7 +89,7 @@ void AnimGraphExecutor::initRuntime() void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& value) { #if USE_CSHARP - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.ValueCache.TryGet(boxBase, value)) return; auto box = (AnimGraphBox*)boxBase; diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 40c33a3e8..09c03b71a 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -9,7 +9,7 @@ extern void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 i); -ThreadLocal AnimGraphExecutor::Context; +ThreadLocal AnimGraphExecutor::Context; Transform AnimGraphImpulse::GetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex) const { @@ -104,7 +104,7 @@ AnimGraphInstanceData::OutgoingEvent AnimGraphInstanceData::ActiveEvent::End(Ani AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor) { - auto& context = AnimGraphExecutor::Context.Get(); + auto& context = *AnimGraphExecutor::Context.Get(); const int32 count = executor->_skeletonNodesCount; if (context.PoseCacheSize == context.PoseCache.Count()) context.PoseCache.AddOne(); @@ -204,7 +204,10 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) // Initialize auto& skeleton = _graph.BaseModel->Skeleton; - auto& context = Context.Get(); + auto& contextPtr = Context.Get(); + if (!contextPtr) + contextPtr = New(); + auto& context = *contextPtr; { ANIM_GRAPH_PROFILE_EVENT("Init"); @@ -378,12 +381,12 @@ void AnimGraphExecutor::GetInputValue(Box* box, Value& result) AnimGraphImpulse* AnimGraphExecutor::GetEmptyNodes() { - return &Context.Get().EmptyNodes; + return &Context.Get()->EmptyNodes; } void AnimGraphExecutor::InitNodes(AnimGraphImpulse* nodes) const { - const auto& emptyNodes = Context.Get().EmptyNodes; + const auto& emptyNodes = Context.Get()->EmptyNodes; Platform::MemoryCopy(nodes->Nodes.Get(), emptyNodes.Nodes.Get(), sizeof(Transform) * _skeletonNodesCount); nodes->RootMotion = emptyNodes.RootMotion; nodes->Position = emptyNodes.Position; @@ -405,7 +408,7 @@ void AnimGraphExecutor::ResetBuckets(AnimGraphContext& context, AnimGraphBase* g VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) { - auto& context = Context.Get(); + auto& context = *Context.Get(); // Check if graph is looped or is too deep if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK) @@ -450,6 +453,6 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) VisjectExecutor::Graph* AnimGraphExecutor::GetCurrentGraph() const { - auto& context = Context.Get(); + auto& context = *Context.Get(); return context.GraphStack.Peek(); } diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index a774e6b85..f3c148e0a 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -819,7 +819,7 @@ private: int32 _skeletonNodesCount = 0; // Per-thread context to allow async execution - static ThreadLocal Context; + static ThreadLocal Context; public: /// diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 57829e8fd..536ed7d83 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -87,7 +87,7 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float if (anim->Events.Count() == 0) return; ANIM_GRAPH_PROFILE_EVENT("Events"); - auto& context = Context.Get(); + auto& context = *Context.Get(); float eventTimeMin = animPrevPos; float eventTimeMax = animPos; if (loop && context.DeltaTime * speed < 0) @@ -231,7 +231,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed); // Add to trace - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.Data->EnableTracing) { auto& trace = context.AddTraceEvent(node); @@ -655,7 +655,7 @@ void ComputeMultiBlendLength(float& length, AnimGraphNode* node) void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); switch (node->TypeID) { // Get @@ -766,7 +766,7 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); auto node = (AnimGraphNode*)nodeBase; switch (node->TypeID) { @@ -790,7 +790,7 @@ void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.ValueCache.TryGet(boxBase, value)) return; auto box = (AnimGraphBox*)boxBase; @@ -2272,7 +2272,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.ValueCache.TryGet(boxBase, value)) return; switch (node->TypeID) diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index df99418bb..ca39ebaf9 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -211,7 +211,10 @@ FlaxStorage::~FlaxStorage() #if USE_EDITOR // Ensure to close any outstanding file handles to prevent file locking in case it failed to load - _file.DeleteAll(); + Array streams; + _file.GetValues(streams); + for (FileReadStream* stream : streams) + Delete(stream); #endif } @@ -1264,7 +1267,6 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data) } #if ASSETS_LOADING_EXTRA_VERIFICATION - // Validate loaded header (asset ID and type ID must be the same) if (e.ID != data.Header.ID) { @@ -1274,7 +1276,6 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data) { LOG(Error, "Loading asset header data mismatch! Expected Type Name: {0}, loaded header: {1}.\nSource: {2}", e.TypeName, data.Header.ToString(), ToString()); } - #endif return false; @@ -1337,7 +1338,11 @@ bool FlaxStorage::CloseFileHandles() return true; // Failed, someone is still accessing the file // Close file handles (from all threads) - _file.DeleteAll(); + Array streams; + _file.GetValues(streams); + for (FileReadStream* stream : streams) + Delete(stream); + _file.Clear(); return false; } diff --git a/Source/Engine/Content/Storage/FlaxStorage.h b/Source/Engine/Content/Storage/FlaxStorage.h index 77c912c5a..842511430 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.h +++ b/Source/Engine/Content/Storage/FlaxStorage.h @@ -94,7 +94,7 @@ protected: CriticalSection _loadLocker; // Storage - ThreadLocalObject _file; + ThreadLocal _file; Array _chunks; // Metadata diff --git a/Source/Engine/Core/Collections/Sorting.cpp b/Source/Engine/Core/Collections/Sorting.cpp index 49ce0f3d4..85c0fc9f1 100644 --- a/Source/Engine/Core/Collections/Sorting.cpp +++ b/Source/Engine/Core/Collections/Sorting.cpp @@ -5,11 +5,14 @@ #include "Engine/Threading/ThreadLocal.h" // Use a cached storage for the sorting (one per thread to reduce locking) -ThreadLocal SortingStacks; +ThreadLocal SortingStacks; Sorting::SortingStack& Sorting::SortingStack::Get() { - return SortingStacks.Get(); + SortingStack*& stack = SortingStacks.Get(); + if (!stack) + stack = New(); + return *stack; } Sorting::SortingStack::SortingStack() diff --git a/Source/Engine/Engine/EngineService.cpp b/Source/Engine/Engine/EngineService.cpp index 7eea66853..c400ef943 100644 --- a/Source/Engine/Engine/EngineService.cpp +++ b/Source/Engine/Engine/EngineService.cpp @@ -72,9 +72,6 @@ void EngineService::OnInit() // Init services from front to back auto& services = GetServices(); -#if TRACY_ENABLE - Char nameBuffer[100]; -#endif for (int32 i = 0; i < services.Count(); i++) { const auto service = services[i]; @@ -82,6 +79,7 @@ void EngineService::OnInit() #if TRACY_ENABLE ZoneScoped; int32 nameBufferLength = 0; + Char nameBuffer[100]; for (int32 j = 0; j < name.Length(); j++) if (name[j] != ' ') nameBuffer[nameBufferLength++] = name[j]; @@ -114,6 +112,18 @@ void EngineService::OnDispose() const auto service = services[i]; if (service->IsInitialized) { +#if TRACY_ENABLE + ZoneScoped; + const StringView name(service->Name); + int32 nameBufferLength = 0; + Char nameBuffer[100]; + for (int32 j = 0; j < name.Length(); j++) + if (name[j] != ' ') + nameBuffer[nameBufferLength++] = name[j]; + Platform::MemoryCopy(nameBuffer + nameBufferLength, TEXT("::Dispose"), 10 * sizeof(Char)); + nameBufferLength += 10; + ZoneName(nameBuffer, nameBufferLength); +#endif service->IsInitialized = false; service->Dispose(); } diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index d9c86d250..006e54aa6 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -63,7 +63,7 @@ SceneObjectsFactory::Context::~Context() { if (Async) { - Array> modifiers; + Array> modifiers; Modifiers.GetValues(modifiers); for (ISerializeModifier* e : modifiers) { diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp index 6ea4b8b4e..f198fdcd7 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp @@ -52,7 +52,7 @@ namespace int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) { const auto node = _graph.SpawnModules[index]; - auto& context = Context.Get(); + auto& context = *Context.Get(); auto& data = context.Data->SpawnModulesData[index]; // Accumulate the previous frame fraction @@ -120,7 +120,7 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* node, int32 particlesStart, int32 particlesEnd) { - auto& context = Context.Get(); + auto& context = *Context.Get(); auto stride = context.Data->Buffer->Stride; auto start = context.Data->Buffer->GetParticleCPU(particlesStart); diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index cb2d7004e..4c30746c3 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -12,7 +12,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); switch (node->TypeID) { // Get @@ -168,7 +168,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTextures(Box* box, Node* node, void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); switch (node->TypeID) { // Linearize Depth @@ -202,7 +202,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Va void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* nodeBase, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); auto node = (ParticleEmitterGraphCPUNode*)nodeBase; switch (node->TypeID) { @@ -468,7 +468,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node void ParticleEmitterGraphCPUExecutor::ProcessGroupFunction(Box* box, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); switch (node->TypeID) { // Function Input diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp index a8f7898e1..12ecd054b 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp @@ -8,7 +8,7 @@ #include "Engine/Engine/Time.h" #include "Engine/Profiler/ProfilerCPU.h" -ThreadLocal ParticleEmitterGraphCPUExecutor::Context; +ThreadLocal ParticleEmitterGraphCPUExecutor::Context; namespace { @@ -122,7 +122,10 @@ ParticleEmitterGraphCPUExecutor::ParticleEmitterGraphCPUExecutor(ParticleEmitter void ParticleEmitterGraphCPUExecutor::Init(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, float dt) { - auto& context = Context.Get(); + auto& contextPtr = Context.Get(); + if (!contextPtr) + contextPtr = New(); + auto& context = *contextPtr; context.GraphStack.Clear(); context.GraphStack.Push(&_graph); context.Data = &data; @@ -252,8 +255,8 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa case 401: { // Prepare graph data - auto& context = Context.Get(); Init(emitter, effect, data); + auto& context = *Context.Get(); // Find the maximum radius of the particle light float maxRadius = 0.0f; @@ -377,7 +380,7 @@ void ParticleEmitterGraphCPUExecutor::Draw(ParticleEmitter* emitter, ParticleEff // Prepare graph data Init(emitter, effect, data); - auto& context = Context.Get(); + auto& context = *Context.Get(); // Draw lights for (int32 moduleIndex = 0; moduleIndex < emitter->Graph.LightModules.Count(); moduleIndex++) @@ -571,7 +574,6 @@ int32 ParticleEmitterGraphCPUExecutor::UpdateSpawn(ParticleEmitter* emitter, Par PROFILE_CPU_NAMED("Spawn"); // Prepare data - auto& context = Context.Get(); Init(emitter, effect, data, dt); // Spawn particles @@ -587,7 +589,7 @@ int32 ParticleEmitterGraphCPUExecutor::UpdateSpawn(ParticleEmitter* emitter, Par VisjectExecutor::Value ParticleEmitterGraphCPUExecutor::eatBox(Node* caller, Box* box) { // Check if graph is looped or is too deep - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.CallStackSize >= PARTICLE_EMITTER_MAX_CALL_STACK) { OnError(caller, box, TEXT("Graph is looped or too deep!")); @@ -618,6 +620,6 @@ VisjectExecutor::Value ParticleEmitterGraphCPUExecutor::eatBox(Node* caller, Box VisjectExecutor::Graph* ParticleEmitterGraphCPUExecutor::GetCurrentGraph() const { - auto& context = Context.Get(); + auto& context = *Context.Get(); return (Graph*)context.GraphStack.Peek(); } diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h index 4f55da6e2..34a65d721 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h @@ -133,7 +133,7 @@ private: ParticleEmitterGraphCPU& _graph; // Per-thread context to allow async execution - static ThreadLocal Context; + static ThreadLocal Context; public: /// diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 9a6b1b9dc..5c1b2ca28 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -41,6 +41,10 @@ static_assert(sizeof(bool) == 1, "Invalid bool type size."); static_assert(sizeof(float) == 4, "Invalid float type size."); static_assert(sizeof(double) == 8, "Invalid double type size."); +// Check configuration +static_assert((PLATFORM_THREADS_LIMIT & (PLATFORM_THREADS_LIMIT - 1)) == 0, "Threads limit must be power of two."); +static_assert(PLATFORM_THREADS_LIMIT % 4 == 0, "Threads limit must be multiple of 4."); + float PlatformBase::CustomDpiScale = 1.0f; Array> PlatformBase::Users; Delegate PlatformBase::UserAdded; diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 6cabbc0dd..e3a0991e3 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -115,7 +115,7 @@ Action Scripting::ScriptsLoaded; Action Scripting::ScriptsUnload; Action Scripting::ScriptsReloading; Action Scripting::ScriptsReloaded; -ThreadLocal Scripting::ObjectsLookupIdMapping; +ThreadLocal Scripting::ObjectsLookupIdMapping; ScriptingService ScriptingServiceInstance; bool initFlaxEngine(); diff --git a/Source/Engine/Scripting/Scripting.h b/Source/Engine/Scripting/Scripting.h index a22349928..6ed2beca4 100644 --- a/Source/Engine/Scripting/Scripting.h +++ b/Source/Engine/Scripting/Scripting.h @@ -6,7 +6,7 @@ #include "Engine/Scripting/ScriptingType.h" #include "Types.h" -template +template class ThreadLocal; /// @@ -114,7 +114,7 @@ public: /// /// The objects lookup identifier mapping used to override the object ids on FindObject call (used by the object references deserialization). /// - static ThreadLocal ObjectsLookupIdMapping; + static ThreadLocal ObjectsLookupIdMapping; /// /// Finds the object by the given identifier. Searches registered scene objects and optionally assets. Logs warning if fails. diff --git a/Source/Engine/Serialization/FileReadStream.h b/Source/Engine/Serialization/FileReadStream.h index 40a14185b..57287abcf 100644 --- a/Source/Engine/Serialization/FileReadStream.h +++ b/Source/Engine/Serialization/FileReadStream.h @@ -12,7 +12,6 @@ class FLAXENGINE_API FileReadStream : public ReadStream { private: - File* _file; uint32 _virtualPosInBuffer; // Current position in the buffer (index) uint32 _bufferSize; // Amount of loaded bytes from the file to the buffer @@ -33,11 +32,9 @@ public: ~FileReadStream(); public: - /// /// Gets the file handle. /// - /// File FORCE_INLINE const File* GetFile() const { return _file; @@ -49,7 +46,6 @@ public: void Unlink(); public: - /// /// Open file to write data to it /// @@ -58,7 +54,6 @@ public: static FileReadStream* Open(const StringView& path); public: - // [ReadStream] void Flush() final override; void Close() final override; diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index c89e6aca5..dea8298ce 100644 --- a/Source/Engine/Threading/JobSystem.cpp +++ b/Source/Engine/Threading/JobSystem.cpp @@ -93,7 +93,7 @@ struct TIsPODType namespace { JobSystemService JobSystemInstance; - Thread* Threads[PLATFORM_THREADS_LIMIT] = {}; + Thread* Threads[PLATFORM_THREADS_LIMIT / 2] = {}; int32 ThreadsCount = 0; bool JobStartingOnDispatch = true; volatile int64 ExitFlag = 0; diff --git a/Source/Engine/Threading/ThreadLocal.h b/Source/Engine/Threading/ThreadLocal.h index 4de8ced57..aba1f1170 100644 --- a/Source/Engine/Threading/ThreadLocal.h +++ b/Source/Engine/Threading/ThreadLocal.h @@ -6,14 +6,14 @@ #include "Engine/Platform/Platform.h" /// -/// Per-thread local variable storage. -/// Implemented using atomic with per-thread storage indexed via thread id hashing. -/// ForConsider using 'THREADLOCAL' define before the variable instead. +/// Per-thread local variable storage for basic types (POD). Implemented using atomic with per-thread storage indexed via thread id hashing. Consider using 'THREADLOCAL' define before the variable instead. /// -template +template class ThreadLocal { protected: + constexpr static int32 DynamicMaxThreads = 1024; + static_assert(TIsPODType::Value, "Only POD types are supported"); struct Bucket { @@ -21,34 +21,29 @@ protected: T Value; }; - Bucket _buckets[MaxThreads]; + Bucket _staticBuckets[MaxThreads]; + Bucket* _dynamicBuckets = nullptr; public: - ThreadLocal() { - // Clear buckets - if (ClearMemory) - { - Platform::MemoryClear(_buckets, sizeof(_buckets)); - } - else - { - for (int32 i = 0; i < MaxThreads; i++) - _buckets[i].ThreadID = 0; - } + Platform::MemoryClear(_staticBuckets, sizeof(_staticBuckets)); + } + + ~ThreadLocal() + { + Platform::Free(_dynamicBuckets); } public: - - T& Get() + FORCE_INLINE T& Get() { - return _buckets[GetIndex()].Value; + return GetBucket().Value; } - void Set(const T& value) + FORCE_INLINE void Set(const T& value) { - _buckets[GetIndex()].Value = value; + GetBucket().Value = value; } int32 Count() const @@ -56,9 +51,17 @@ public: int32 result = 0; for (int32 i = 0; i < MaxThreads; i++) { - if (Platform::AtomicRead((int64 volatile*)&_buckets[i].ThreadID) != 0) + if (Platform::AtomicRead((int64 volatile*)&_staticBuckets[i].ThreadID) != 0) result++; } + if (auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets)) + { + for (int32 i = 0; i < MaxThreads; i++) + { + if (Platform::AtomicRead((int64 volatile*)&dynamicBuckets[i].ThreadID) != 0) + result++; + } + } return result; } @@ -67,89 +70,72 @@ public: { for (int32 i = 0; i < MaxThreads; i++) { - if (Platform::AtomicRead((int64 volatile*)&_buckets[i].ThreadID) != 0) - result.Add(_buckets[i].Value); + if (Platform::AtomicRead((int64 volatile*)&_staticBuckets[i].ThreadID) != 0) + result.Add(_staticBuckets[i].Value); } + if (auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets)) + { + for (int32 i = 0; i < MaxThreads; i++) + { + if (Platform::AtomicRead((int64 volatile*)&dynamicBuckets[i].ThreadID) != 0) + result.Add(dynamicBuckets[i].Value); + } + } + } + + void Clear() + { + Platform::MemoryClear(_staticBuckets, sizeof(_staticBuckets)); + Platform::Free(_dynamicBuckets); + _dynamicBuckets = nullptr; } protected: - - FORCE_INLINE static int32 Hash(const int64 value) + Bucket& GetBucket() { - return value & (MaxThreads - 1); - } + const int64 key = (int64)Platform::GetCurrentThreadID(); - FORCE_INLINE int32 GetIndex() - { - int64 key = (int64)Platform::GetCurrentThreadID(); - auto index = Hash(key); - while (true) + // Search statically allocated buckets + int32 index = (int32)(key & (MaxThreads - 1)); + int32 spaceLeft = MaxThreads; + while (spaceLeft) { - const int64 value = Platform::AtomicRead(&_buckets[index].ThreadID); + const int64 value = Platform::AtomicRead(&_staticBuckets[index].ThreadID); if (value == key) - break; - if (value == 0 && Platform::InterlockedCompareExchange(&_buckets[index].ThreadID, key, 0) == 0) - break; - index = Hash(index + 1); + return _staticBuckets[index]; + if (value == 0 && Platform::InterlockedCompareExchange(&_staticBuckets[index].ThreadID, key, 0) == 0) + return _staticBuckets[index]; + index = (index + 1) & (MaxThreads - 1); + spaceLeft--; } - return index; - } -}; -/// -/// Per thread local object -/// -template -class ThreadLocalObject : public ThreadLocal -{ -public: - - typedef ThreadLocal Base; - -public: - - void Delete() - { - auto value = Base::Get(); - Base::SetAll(nullptr); - ::Delete(value); - } - - void DeleteAll() - { - for (int32 i = 0; i < MaxThreads; i++) + // Allocate dynamic buckets if missing + DYNAMIC: + auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets); + if (!dynamicBuckets) { - auto& bucket = Base::_buckets[i]; - if (bucket.Value != nullptr) + dynamicBuckets = (Bucket*)Platform::Allocate(DynamicMaxThreads * sizeof(Bucket), 16); + Platform::MemoryClear(dynamicBuckets, DynamicMaxThreads * sizeof(Bucket)); + if (Platform::InterlockedCompareExchange((intptr volatile*)&_dynamicBuckets, (intptr)dynamicBuckets, 0) != 0) { - ::Delete(bucket.Value); - bucket.ThreadID = 0; - bucket.Value = nullptr; + Platform::Free(dynamicBuckets); + goto DYNAMIC; } } - } - template - void GetNotNullValues(Array& result) const - { - result.EnsureCapacity(MaxThreads); - for (int32 i = 0; i < MaxThreads; i++) + // Search dynamically allocated buckets + index = (int32)(key & (DynamicMaxThreads - 1)); + spaceLeft = DynamicMaxThreads; + while (spaceLeft) { - if (Base::_buckets[i].Value != nullptr) - { - result.Add(Base::_buckets[i].Value); - } + const int64 value = Platform::AtomicRead(&dynamicBuckets[index].ThreadID); + if (value == key) + return dynamicBuckets[index]; + if (value == 0 && Platform::InterlockedCompareExchange(&dynamicBuckets[index].ThreadID, key, 0) == 0) + return dynamicBuckets[index]; + index = (index + 1) & (DynamicMaxThreads - 1); + spaceLeft--; } - } - - int32 CountNotNullValues() const - { - int32 result = 0; - for (int32 i = 0; i < MaxThreads; i++) - { - if (Base::_buckets[i].Value != nullptr) - result++; - } - return result; + return *(Bucket*)nullptr; } }; diff --git a/Source/Engine/Threading/ThreadPool.cpp b/Source/Engine/Threading/ThreadPool.cpp index b7db81ffa..2b6ed5e26 100644 --- a/Source/Engine/Threading/ThreadPool.cpp +++ b/Source/Engine/Threading/ThreadPool.cpp @@ -58,7 +58,7 @@ ThreadPoolService ThreadPoolServiceInstance; bool ThreadPoolService::Init() { // Spawn threads - const int32 numThreads = Math::Clamp(Platform::GetCPUInfo().ProcessorCoreCount - 1, 2, PLATFORM_THREADS_LIMIT); + const int32 numThreads = Math::Clamp(Platform::GetCPUInfo().ProcessorCoreCount - 1, 2, PLATFORM_THREADS_LIMIT / 2); LOG(Info, "Spawning {0} Thread Pool workers", numThreads); for (int32 i = ThreadPoolImpl::Threads.Count(); i < numThreads; i++) { From f9ca69d8a9c15dc5a9562ce971f0a4108df38945 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 8 Feb 2024 10:47:55 +0100 Subject: [PATCH 094/103] Disable dynamic buckets in `ThreadLocal` on non-Desktop platforms that use fixed set of thread count --- Source/Engine/Threading/ThreadLocal.h | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Threading/ThreadLocal.h b/Source/Engine/Threading/ThreadLocal.h index aba1f1170..b766d0430 100644 --- a/Source/Engine/Threading/ThreadLocal.h +++ b/Source/Engine/Threading/ThreadLocal.h @@ -5,6 +5,8 @@ #include "Engine/Core/Types/BaseTypes.h" #include "Engine/Platform/Platform.h" +#define THREAD_LOCAL_USE_DYNAMIC_BUCKETS (PLATFORM_DESKTOP) + /// /// Per-thread local variable storage for basic types (POD). Implemented using atomic with per-thread storage indexed via thread id hashing. Consider using 'THREADLOCAL' define before the variable instead. /// @@ -12,7 +14,6 @@ template class ThreadLocal { protected: - constexpr static int32 DynamicMaxThreads = 1024; static_assert(TIsPODType::Value, "Only POD types are supported"); struct Bucket @@ -22,7 +23,10 @@ protected: }; Bucket _staticBuckets[MaxThreads]; +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS Bucket* _dynamicBuckets = nullptr; + constexpr static int32 DynamicMaxThreads = 1024; +#endif public: ThreadLocal() @@ -30,10 +34,12 @@ public: Platform::MemoryClear(_staticBuckets, sizeof(_staticBuckets)); } +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS ~ThreadLocal() { Platform::Free(_dynamicBuckets); } +#endif public: FORCE_INLINE T& Get() @@ -54,6 +60,7 @@ public: if (Platform::AtomicRead((int64 volatile*)&_staticBuckets[i].ThreadID) != 0) result++; } +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS if (auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets)) { for (int32 i = 0; i < MaxThreads; i++) @@ -62,6 +69,7 @@ public: result++; } } +#endif return result; } @@ -73,6 +81,7 @@ public: if (Platform::AtomicRead((int64 volatile*)&_staticBuckets[i].ThreadID) != 0) result.Add(_staticBuckets[i].Value); } +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS if (auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets)) { for (int32 i = 0; i < MaxThreads; i++) @@ -81,13 +90,16 @@ public: result.Add(dynamicBuckets[i].Value); } } +#endif } void Clear() { Platform::MemoryClear(_staticBuckets, sizeof(_staticBuckets)); +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS Platform::Free(_dynamicBuckets); _dynamicBuckets = nullptr; +#endif } protected: @@ -109,6 +121,7 @@ protected: spaceLeft--; } +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS // Allocate dynamic buckets if missing DYNAMIC: auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets); @@ -136,6 +149,8 @@ protected: index = (index + 1) & (DynamicMaxThreads - 1); spaceLeft--; } +#endif + return *(Bucket*)nullptr; } }; From d08843900e11e41e89707fb46c6bf0d806903a8d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 9 Feb 2024 15:37:29 +0100 Subject: [PATCH 095/103] Add `AnimationRootMotionFlags` to configure root motion component extraction Add `RootMotionMode` to support extracting root motion from animated skeleton pose center of mass #1429 #2152 --- .../Editor/Content/Import/ModelImportEntry.cs | 1 + Source/Engine/Animations/AnimationData.cpp | 84 ++++++++++ Source/Engine/Animations/AnimationData.h | 96 ++++------- Source/Engine/Animations/Curve.h | 2 +- .../Animations/Graph/AnimGraph.Base.cpp | 2 +- Source/Engine/Animations/Graph/AnimGraph.cpp | 2 +- Source/Engine/Animations/Graph/AnimGraph.h | 6 +- .../Animations/Graph/AnimGroup.Animation.cpp | 55 +++++-- Source/Engine/Content/Assets/Animation.cpp | 21 ++- .../ContentImporters/CreateAnimationGraph.cpp | 2 +- Source/Engine/Graphics/Models/ModelData.cpp | 10 +- Source/Engine/Graphics/Models/SkeletonData.h | 46 ++---- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 63 ++++++++ Source/Engine/Tools/ModelTool/ModelTool.cpp | 150 +++++++++++++++++- Source/Engine/Tools/ModelTool/ModelTool.h | 20 ++- 15 files changed, 416 insertions(+), 144 deletions(-) create mode 100644 Source/Engine/Animations/AnimationData.cpp diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs index 484efb1d9..fac782cbd 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -16,6 +16,7 @@ namespace FlaxEngine.Tools private bool ShowModel => Type == ModelType.Model || Type == ModelType.Prefab; private bool ShowSkinnedModel => Type == ModelType.SkinnedModel || Type == ModelType.Prefab; private bool ShowAnimation => Type == ModelType.Animation || Type == ModelType.Prefab; + private bool ShowRootMotion => ShowAnimation && RootMotion != RootMotionMode.None; private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals; private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents; private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom; diff --git a/Source/Engine/Animations/AnimationData.cpp b/Source/Engine/Animations/AnimationData.cpp new file mode 100644 index 000000000..8c3611d7a --- /dev/null +++ b/Source/Engine/Animations/AnimationData.cpp @@ -0,0 +1,84 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#include "AnimationData.h" + +void NodeAnimationData::Evaluate(float time, Transform* result, bool loop) const +{ + if (Position.GetKeyframes().HasItems()) +#if USE_LARGE_WORLDS + { + Float3 position; + Position.Evaluate(position, time, loop); + result->Translation = position; + } +#else + Position.Evaluate(result->Translation, time, loop); +#endif + if (Rotation.GetKeyframes().HasItems()) + Rotation.Evaluate(result->Orientation, time, loop); + if (Scale.GetKeyframes().HasItems()) + Scale.Evaluate(result->Scale, time, loop); +} + +void NodeAnimationData::EvaluateAll(float time, Transform* result, bool loop) const +{ + Float3 position; + Position.Evaluate(position, time, loop); + result->Translation = position; + Rotation.Evaluate(result->Orientation, time, loop); + Scale.Evaluate(result->Scale, time, loop); +} + +int32 NodeAnimationData::GetKeyframesCount() const +{ + return Position.GetKeyframes().Count() + Rotation.GetKeyframes().Count() + Scale.GetKeyframes().Count(); +} + +uint64 NodeAnimationData::GetMemoryUsage() const +{ + return NodeName.Length() * sizeof(Char) + Position.GetMemoryUsage() + Rotation.GetMemoryUsage() + Scale.GetMemoryUsage(); +} + +uint64 AnimationData::GetMemoryUsage() const +{ + uint64 result = (Name.Length() + RootNodeName.Length()) * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData); + for (const auto& e : Channels) + result += e.GetMemoryUsage(); + return result; +} + +int32 AnimationData::GetKeyframesCount() const +{ + int32 result = 0; + for (int32 i = 0; i < Channels.Count(); i++) + result += Channels[i].GetKeyframesCount(); + return result; +} + +NodeAnimationData* AnimationData::GetChannel(const StringView& name) +{ + for (auto& e : Channels) + if (e.NodeName == name) + return &e; + return nullptr; +} + +void AnimationData::Swap(AnimationData& other) +{ + ::Swap(Duration, other.Duration); + ::Swap(FramesPerSecond, other.FramesPerSecond); + ::Swap(RootMotionFlags, other.RootMotionFlags); + ::Swap(Name, other.Name); + ::Swap(RootNodeName, other.RootNodeName); + Channels.Swap(other.Channels); +} + +void AnimationData::Dispose() +{ + Name.Clear(); + Duration = 0.0; + FramesPerSecond = 0.0; + RootNodeName.Clear(); + RootMotionFlags = AnimationRootMotionFlags::None; + Channels.Resize(0); +} diff --git a/Source/Engine/Animations/AnimationData.h b/Source/Engine/Animations/AnimationData.h index c69de8afa..00717b05f 100644 --- a/Source/Engine/Animations/AnimationData.h +++ b/Source/Engine/Animations/AnimationData.h @@ -3,8 +3,8 @@ #pragma once #include "Engine/Core/Types/String.h" -#include "Engine/Animations/Curve.h" #include "Engine/Core/Math/Transform.h" +#include "Engine/Animations/Curve.h" /// /// Single node animation data container. @@ -50,19 +50,7 @@ public: /// The time to evaluate the curves at. /// The interpolated value from the curve at provided time. /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. - void Evaluate(float time, Transform* result, bool loop = true) const - { - if (Position.GetKeyframes().HasItems()) - { - Float3 position; - Position.Evaluate(position, time, loop); - result->Translation = position; - } - if (Rotation.GetKeyframes().HasItems()) - Rotation.Evaluate(result->Orientation, time, loop); - if (Scale.GetKeyframes().HasItems()) - Scale.Evaluate(result->Scale, time, loop); - } + void Evaluate(float time, Transform* result, bool loop = true) const; /// /// Evaluates the animation transformation at the specified time. @@ -70,29 +58,37 @@ public: /// The time to evaluate the curves at. /// The interpolated value from the curve at provided time. /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. - void EvaluateAll(float time, Transform* result, bool loop = true) const - { - Float3 position; - Position.Evaluate(position, time, loop); - result->Translation = position; - Rotation.Evaluate(result->Orientation, time, loop); - Scale.Evaluate(result->Scale, time, loop); - } + void EvaluateAll(float time, Transform* result, bool loop = true) const; /// /// Gets the total amount of keyframes in the animation curves. /// - int32 GetKeyframesCount() const - { - return Position.GetKeyframes().Count() + Rotation.GetKeyframes().Count() + Scale.GetKeyframes().Count(); - } + int32 GetKeyframesCount() const; - uint64 GetMemoryUsage() const - { - return NodeName.Length() * sizeof(Char) + Position.GetMemoryUsage() + Rotation.GetMemoryUsage() + Scale.GetMemoryUsage(); - } + uint64 GetMemoryUsage() const; }; +/// +/// Root Motion modes that can be applied by the animation. Used as flags for selective behavior. +/// +API_ENUM(Attributes="Flags") enum class AnimationRootMotionFlags : byte +{ + // No root motion. + None = 0, + // Root node position along XZ plane. Applies horizontal movement. Good for stationary animations (eg. idle). + RootPositionXZ = 1 << 0, + // Root node position along Y axis (up). Applies vertical movement. Good for all 'grounded' animations unless jumping is handled from code. + RootPositionY = 1 << 1, + // Root node rotation. Applies orientation changes. Good for animations that have baked-in root rotation (eg. turn animations). + RootRotation = 1 << 2, + // Root node position. + RootPosition = RootPositionXZ | RootPositionY, + // Root node position and rotation. + RootTransform = RootPosition | RootRotation, +}; + +DECLARE_ENUM_OPERATORS(AnimationRootMotionFlags); + /// /// Skeleton nodes animation data container. Includes metadata about animation sampling, duration and node animations curves. /// @@ -111,7 +107,7 @@ struct AnimationData /// /// Enables root motion extraction support from this animation. /// - bool EnableRootMotion = false; + AnimationRootMotionFlags RootMotionFlags = AnimationRootMotionFlags::None; /// /// The animation name. @@ -140,49 +136,23 @@ public: return static_cast(Duration / FramesPerSecond); } - uint64 GetMemoryUsage() const - { - uint64 result = (Name.Length() + RootNodeName.Length()) * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData); - for (const auto& e : Channels) - result += e.GetMemoryUsage(); - return result; - } + uint64 GetMemoryUsage() const; /// /// Gets the total amount of keyframes in the all animation channels. /// - int32 GetKeyframesCount() const - { - int32 result = 0; - for (int32 i = 0; i < Channels.Count(); i++) - result += Channels[i].GetKeyframesCount(); - return result; - } + int32 GetKeyframesCount() const; + + NodeAnimationData* GetChannel(const StringView& name); /// /// Swaps the contents of object with the other object without copy operation. Performs fast internal data exchange. /// /// The other object. - void Swap(AnimationData& other) - { - ::Swap(Duration, other.Duration); - ::Swap(FramesPerSecond, other.FramesPerSecond); - ::Swap(EnableRootMotion, other.EnableRootMotion); - ::Swap(Name, other.Name); - ::Swap(RootNodeName, other.RootNodeName); - Channels.Swap(other.Channels); - } + void Swap(AnimationData& other); /// /// Releases data. /// - void Dispose() - { - Name.Clear(); - Duration = 0.0; - FramesPerSecond = 0.0; - RootNodeName.Clear(); - EnableRootMotion = false; - Channels.Resize(0); - } + void Dispose(); }; diff --git a/Source/Engine/Animations/Curve.h b/Source/Engine/Animations/Curve.h index 74ad4168d..e0e7bd6b5 100644 --- a/Source/Engine/Animations/Curve.h +++ b/Source/Engine/Animations/Curve.h @@ -730,7 +730,7 @@ public: void TransformTime(float timeScale, float timeOffset) { for (int32 i = 0; i < _keyframes.Count(); i++) - _keyframes[i].Time = _keyframes[i].Time * timeScale + timeOffset;; + _keyframes[i].Time = _keyframes[i].Time * timeScale + timeOffset; } uint64 GetMemoryUsage() const diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index c05b82b70..af5508705 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -157,7 +157,7 @@ bool AnimGraphBase::onNodeLoaded(Node* n) if (_rootNode->Values.Count() < 1) { _rootNode->Values.Resize(1); - _rootNode->Values[0] = (int32)RootMotionMode::NoExtraction; + _rootNode->Values[0] = (int32)RootMotionExtraction::NoExtraction; } break; // Animation diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 09c03b71a..93bfc2609 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -213,7 +213,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) // Init data from base model _skeletonNodesCount = skeleton.Nodes.Count(); - _rootMotionMode = (RootMotionMode)(int32)_graph._rootNode->Values[0]; + _rootMotionMode = (RootMotionExtraction)(int32)_graph._rootNode->Values[0]; // Prepare context data for the evaluation context.GraphStack.Clear(); diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index f3c148e0a..b570cf9fe 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -92,9 +92,9 @@ enum class BoneTransformMode }; /// -/// The animated model root motion mode. +/// The animated model root motion extraction modes. /// -enum class RootMotionMode +enum class RootMotionExtraction { /// /// Don't extract nor apply the root motion. @@ -815,7 +815,7 @@ class AnimGraphExecutor : public VisjectExecutor friend AnimGraphNode; private: AnimGraph& _graph; - RootMotionMode _rootMotionMode = RootMotionMode::NoExtraction; + RootMotionExtraction _rootMotionMode = RootMotionExtraction::NoExtraction; int32 _skeletonNodesCount = 0; // Per-thread context to allow async execution diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 536ed7d83..30b1bb022 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -21,13 +21,13 @@ namespace base += additive; } - FORCE_INLINE void NormalizeRotations(AnimGraphImpulse* nodes, RootMotionMode rootMotionMode) + FORCE_INLINE void NormalizeRotations(AnimGraphImpulse* nodes, RootMotionExtraction rootMotionMode) { for (int32 i = 0; i < nodes->Nodes.Count(); i++) { nodes->Nodes[i].Orientation.Normalize(); } - if (rootMotionMode != RootMotionMode::NoExtraction) + if (rootMotionMode != RootMotionExtraction::NoExtraction) { nodes->RootMotion.Orientation.Normalize(); } @@ -323,16 +323,21 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* } // Handle root motion - if (_rootMotionMode != RootMotionMode::NoExtraction && anim->Data.EnableRootMotion) + if (_rootMotionMode != RootMotionExtraction::NoExtraction && anim->Data.RootMotionFlags != AnimationRootMotionFlags::None) { // Calculate the root motion node transformation + const bool motionPositionXZ = EnumHasAnyFlags(anim->Data.RootMotionFlags, AnimationRootMotionFlags::RootPositionXZ); + const bool motionPositionY = EnumHasAnyFlags(anim->Data.RootMotionFlags, AnimationRootMotionFlags::RootPositionY); + const bool motionRotation = EnumHasAnyFlags(anim->Data.RootMotionFlags, AnimationRootMotionFlags::RootRotation); + const Vector3 motionPositionMask(motionPositionXZ ? 1.0f : 0.0f, motionPositionY ? 1.0f : 0.0f, motionPositionXZ ? 1.0f : 0.0f); + const bool motionPosition = motionPositionXZ | motionPositionY; const int32 rootNodeIndex = GetRootNodeIndex(anim); const Transform& refPose = emptyNodes->Nodes[rootNodeIndex]; Transform& rootNode = nodes->Nodes[rootNodeIndex]; Transform& dstNode = nodes->RootMotion; Transform srcNode = Transform::Identity; const int32 nodeToChannel = mapping.NodesMapping[rootNodeIndex]; - if (_rootMotionMode == RootMotionMode::Enable && nodeToChannel != -1) + if (_rootMotionMode == RootMotionExtraction::Enable && nodeToChannel != -1) { // Get the root bone transformation Transform rootBefore = refPose; @@ -356,16 +361,20 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* // Complex motion calculation to preserve the looped movement // (end - before + now - begin) // It sums the motion since the last update to anim end and since the start to now - srcNode.Translation = rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation; - srcNode.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated()); + if (motionPosition) + srcNode.Translation = (rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation) * motionPositionMask; + if (motionRotation) + srcNode.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated()); //srcNode.Orientation = Quaternion::Identity; } else { // Simple motion delta // (now - before) - srcNode.Translation = rootNode.Translation - rootBefore.Translation; - srcNode.Orientation = rootBefore.Orientation.Conjugated() * rootNode.Orientation; + if (motionPosition) + srcNode.Translation = (rootNode.Translation - rootBefore.Translation) * motionPositionMask; + if (motionRotation) + srcNode.Orientation = rootBefore.Orientation.Conjugated() * rootNode.Orientation; } // Convert root motion from local-space to the actor-space (eg. if root node is not actually a root and its parents have rotation/scale) @@ -379,28 +388,40 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* } } - // Remove root node motion after extraction - rootNode = refPose; + // Remove root node motion after extraction (only extracted components) + if (motionPosition) + rootNode.Translation = refPose.Translation * motionPositionMask + rootNode.Translation * (Vector3::One - motionPositionMask); + if (motionRotation) + rootNode.Orientation = refPose.Orientation; // Blend root motion if (mode == ProcessAnimationMode::BlendAdditive) { - dstNode.Translation += srcNode.Translation * weight; - BlendAdditiveWeightedRotation(dstNode.Orientation, srcNode.Orientation, weight); + if (motionPosition) + dstNode.Translation += srcNode.Translation * weight * motionPositionMask; + if (motionRotation) + BlendAdditiveWeightedRotation(dstNode.Orientation, srcNode.Orientation, weight); } else if (mode == ProcessAnimationMode::Add) { - dstNode.Translation += srcNode.Translation * weight; - dstNode.Orientation += srcNode.Orientation * weight; + if (motionPosition) + dstNode.Translation += srcNode.Translation * weight * motionPositionMask; + if (motionRotation) + dstNode.Orientation += srcNode.Orientation * weight; } else if (weighted) { - dstNode.Translation = srcNode.Translation * weight; - dstNode.Orientation = srcNode.Orientation * weight; + if (motionPosition) + dstNode.Translation = srcNode.Translation * weight * motionPositionMask; + if (motionRotation) + dstNode.Orientation = srcNode.Orientation * weight; } else { - dstNode = srcNode; + if (motionPosition) + dstNode.Translation = srcNode.Translation * motionPositionMask; + if (motionRotation) + dstNode.Orientation = srcNode.Orientation; } } diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index 8aa635244..ec418c5fb 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -413,10 +413,10 @@ bool Animation::Save(const StringView& path) MemoryWriteStream stream(4096); // Info - stream.WriteInt32(102); + stream.WriteInt32(103); stream.WriteDouble(Data.Duration); stream.WriteDouble(Data.FramesPerSecond); - stream.WriteBool(Data.EnableRootMotion); + stream.WriteByte((byte)Data.RootMotionFlags); stream.WriteString(Data.RootNodeName, 13); // Animation channels @@ -532,17 +532,22 @@ Asset::LoadResult Animation::load() int32 headerVersion = *(int32*)stream.GetPositionHandle(); switch (headerVersion) { - case 100: - case 101: - case 102: - { + case 103: stream.ReadInt32(&headerVersion); stream.ReadDouble(&Data.Duration); stream.ReadDouble(&Data.FramesPerSecond); - Data.EnableRootMotion = stream.ReadBool(); + stream.ReadByte((byte*)&Data.RootMotionFlags); + stream.ReadString(&Data.RootNodeName, 13); + break; + case 100: + case 101: + case 102: + stream.ReadInt32(&headerVersion); + stream.ReadDouble(&Data.Duration); + stream.ReadDouble(&Data.FramesPerSecond); + Data.RootMotionFlags = stream.ReadBool() ? AnimationRootMotionFlags::RootPositionXZ : AnimationRootMotionFlags::None; stream.ReadString(&Data.RootNodeName, 13); break; - } default: stream.ReadDouble(&Data.Duration); stream.ReadDouble(&Data.FramesPerSecond); diff --git a/Source/Engine/ContentImporters/CreateAnimationGraph.cpp b/Source/Engine/ContentImporters/CreateAnimationGraph.cpp index 695e5c2a7..e9c88b68c 100644 --- a/Source/Engine/ContentImporters/CreateAnimationGraph.cpp +++ b/Source/Engine/ContentImporters/CreateAnimationGraph.cpp @@ -20,7 +20,7 @@ CreateAssetResult CreateAnimationGraph::Create(CreateAssetContext& context) rootNode.Type = GRAPH_NODE_MAKE_TYPE(9, 1); rootNode.ID = 1; rootNode.Values.Resize(1); - rootNode.Values[0] = (int32)RootMotionMode::NoExtraction; + rootNode.Values[0] = (int32)RootMotionExtraction::NoExtraction; rootNode.Boxes.Resize(1); rootNode.Boxes[0] = AnimGraphBox(&rootNode, 0, VariantType::Void); diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index 6e6ae04b5..cc95ed6ba 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -911,10 +911,10 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const } // Info - stream->WriteInt32(100); // Header version (for fast version upgrades without serialization format change) + stream->WriteInt32(103); // Header version (for fast version upgrades without serialization format change) stream->WriteDouble(anim.Duration); stream->WriteDouble(anim.FramesPerSecond); - stream->WriteBool(anim.EnableRootMotion); + stream->WriteByte((byte)anim.RootMotionFlags); stream->WriteString(anim.RootNodeName, 13); // Animation channels @@ -928,6 +928,12 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const Serialization::Serialize(*stream, channel.Scale); } + // Animation events + stream->WriteInt32(0); + + // Nested animations + stream->WriteInt32(0); + return false; } diff --git a/Source/Engine/Graphics/Models/SkeletonData.h b/Source/Engine/Graphics/Models/SkeletonData.h index 816f69c85..83908d18a 100644 --- a/Source/Engine/Graphics/Models/SkeletonData.h +++ b/Source/Engine/Graphics/Models/SkeletonData.h @@ -91,7 +91,7 @@ public: FORCE_INLINE SkeletonNode& RootNode() { ASSERT(Nodes.HasItems()); - return Nodes[0]; + return Nodes.Get()[0]; } /// @@ -100,52 +100,24 @@ public: FORCE_INLINE const SkeletonNode& RootNode() const { ASSERT(Nodes.HasItems()); - return Nodes[0]; + return Nodes.Get()[0]; } /// /// Swaps the contents of object with the other object without copy operation. Performs fast internal data exchange. /// - void Swap(SkeletonData& other) - { - Nodes.Swap(other.Nodes); - Bones.Swap(other.Bones); - } + void Swap(SkeletonData& other); - int32 FindNode(const StringView& name) const - { - for (int32 i = 0; i < Nodes.Count(); i++) - { - if (Nodes[i].Name == name) - return i; - } - return -1; - } + Transform GetNodeTransform(int32 nodeIndex) const; + void SetNodeTransform(int32 nodeIndex, const Transform& value); - int32 FindBone(int32 nodeIndex) const - { - for (int32 i = 0; i < Bones.Count(); i++) - { - if (Bones[i].NodeIndex == nodeIndex) - return i; - } - return -1; - } + int32 FindNode(const StringView& name) const; + int32 FindBone(int32 nodeIndex) const; - uint64 GetMemoryUsage() const - { - uint64 result = Nodes.Capacity() * sizeof(SkeletonNode) + Bones.Capacity() * sizeof(SkeletonBone); - for (const auto& e : Nodes) - result += (e.Name.Length() + 1) * sizeof(Char); - return result; - } + uint64 GetMemoryUsage() const; /// /// Releases data. /// - void Dispose() - { - Nodes.Resize(0); - Bones.Resize(0); - } + void Dispose(); }; diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 48ff53549..fc0dccf8a 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -18,6 +18,69 @@ #include "Engine/Threading/Task.h" #include "Engine/Threading/Threading.h" +void SkeletonData::Swap(SkeletonData& other) +{ + Nodes.Swap(other.Nodes); + Bones.Swap(other.Bones); +} + +Transform SkeletonData::GetNodeTransform(int32 nodeIndex) const +{ + const int32 parentIndex = Nodes[nodeIndex].ParentIndex; + if (parentIndex == -1) + { + return Nodes[nodeIndex].LocalTransform; + } + const Transform parentTransform = GetNodeTransform(parentIndex); + return parentTransform.LocalToWorld(Nodes[nodeIndex].LocalTransform); +} + +void SkeletonData::SetNodeTransform(int32 nodeIndex, const Transform& value) +{ + const int32 parentIndex = Nodes[nodeIndex].ParentIndex; + if (parentIndex == -1) + { + Nodes[nodeIndex].LocalTransform = value; + return; + } + const Transform parentTransform = GetNodeTransform(parentIndex); + parentTransform.WorldToLocal(value, Nodes[nodeIndex].LocalTransform); +} + +int32 SkeletonData::FindNode(const StringView& name) const +{ + for (int32 i = 0; i < Nodes.Count(); i++) + { + if (Nodes[i].Name == name) + return i; + } + return -1; +} + +int32 SkeletonData::FindBone(int32 nodeIndex) const +{ + for (int32 i = 0; i < Bones.Count(); i++) + { + if (Bones[i].NodeIndex == nodeIndex) + return i; + } + return -1; +} + +uint64 SkeletonData::GetMemoryUsage() const +{ + uint64 result = Nodes.Capacity() * sizeof(SkeletonNode) + Bones.Capacity() * sizeof(SkeletonBone); + for (const auto& e : Nodes) + result += (e.Name.Length() + 1) * sizeof(Char); + return result; +} + +void SkeletonData::Dispose() +{ + Nodes.Resize(0); + Bones.Resize(0); +} + void SkinnedMesh::Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere) { _model = model; diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index d0aa537f6..447301f46 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -19,15 +19,15 @@ #include "Engine/Content/Content.h" #include "Engine/Serialization/MemoryWriteStream.h" #if USE_EDITOR +#include "Engine/Core/Utilities.h" +#include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Types/Pair.h" #include "Engine/Core/Types/Variant.h" +#include "Engine/Platform/FileSystem.h" #include "Engine/Graphics/Models/SkeletonUpdater.h" #include "Engine/Graphics/Models/SkeletonMapping.h" -#include "Engine/Core/Utilities.h" -#include "Engine/Core/Types/StringView.h" -#include "Engine/Platform/FileSystem.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/ContentImporters/CreateMaterial.h" @@ -35,6 +35,7 @@ #include "Engine/ContentImporters/CreateCollisionData.h" #include "Engine/Serialization/Serialization.h" #include "Editor/Utilities/EditorUtilities.h" +#include "Engine/Animations/Graph/AnimGraph.h" #include #endif @@ -361,7 +362,8 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(SkipEmptyCurves); SERIALIZE(OptimizeKeyframes); SERIALIZE(ImportScaleTracks); - SERIALIZE(EnableRootMotion); + SERIALIZE(RootMotion); + SERIALIZE(RootMotionFlags); SERIALIZE(RootNodeName); SERIALIZE(GenerateLODs); SERIALIZE(BaseLOD); @@ -410,7 +412,8 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(SkipEmptyCurves); DESERIALIZE(OptimizeKeyframes); DESERIALIZE(ImportScaleTracks); - DESERIALIZE(EnableRootMotion); + DESERIALIZE(RootMotion); + DESERIALIZE(RootMotionFlags); DESERIALIZE(RootNodeName); DESERIALIZE(GenerateLODs); DESERIALIZE(BaseLOD); @@ -435,6 +438,15 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(AnimationIndex); if (AnimationIndex != -1) ObjectIndex = AnimationIndex; + + // [Deprecated on 08.02.2024, expires on 08.02.2026] + bool EnableRootMotion = false; + DESERIALIZE(EnableRootMotion); + if (EnableRootMotion) + { + RootMotion = RootMotionMode::ExtractNode; + RootMotionFlags = AnimationRootMotionFlags::RootPositionXZ; + } } void RemoveNamespace(String& name) @@ -809,6 +821,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option break; case ModelType::Animation: options.ImportTypes = ImportDataTypes::Animations; + if (options.RootMotion == RootMotionMode::ExtractCenterOfMass) + options.ImportTypes |= ImportDataTypes::Skeleton; break; case ModelType::Prefab: options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Animations; @@ -1373,6 +1387,129 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } } + // Process root motion setup + animation.RootMotionFlags = options.RootMotion != RootMotionMode::None ? options.RootMotionFlags : AnimationRootMotionFlags::None; + animation.RootNodeName = options.RootNodeName.TrimTrailing(); + if (animation.RootMotionFlags != AnimationRootMotionFlags::None && animation.Channels.HasItems()) + { + if (options.RootMotion == RootMotionMode::ExtractNode) + { + if (animation.RootNodeName.HasChars() && animation.GetChannel(animation.RootNodeName) == nullptr) + { + LOG(Warning, "Missing Root Motion node '{}'", animation.RootNodeName); + } + } + else if (options.RootMotion == RootMotionMode::ExtractCenterOfMass && data.Skeleton.Nodes.HasItems()) // TODO: finish implementing this + { + // Setup root node animation track + const auto& rootName = data.Skeleton.Nodes.First().Name; + auto rootChannelPtr = animation.GetChannel(rootName); + if (!rootChannelPtr) + { + animation.Channels.Insert(0, NodeAnimationData()); + rootChannelPtr = &animation.Channels[0]; + rootChannelPtr->NodeName = rootName; + } + animation.RootNodeName = rootName; + auto& rootChannel = *rootChannelPtr; + rootChannel.Position.Clear(); + + // Calculate skeleton center of mass position over the animation frames + const int32 frames = (int32)animation.Duration; + const int32 nodes = data.Skeleton.Nodes.Count(); + Array centerOfMass; + centerOfMass.Resize(frames); + for (int32 frame = 0; frame < frames; frame++) + { + auto& key = centerOfMass[frame]; + + // Evaluate skeleton pose at the animation frame + AnimGraphImpulse pose; + pose.Nodes.Resize(nodes); + for (int32 nodeIndex = 0; nodeIndex < nodes; nodeIndex++) + { + Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform; + auto& node = data.Skeleton.Nodes[nodeIndex]; + if (auto* channel = animation.GetChannel(node.Name)) + channel->Evaluate(frame, &srcNode, false); + pose.Nodes[nodeIndex] = srcNode; + } + + // Calculate average location of the pose (center of mass) + key = Float3::Zero; + for (int32 nodeIndex = 0; nodeIndex < nodes; nodeIndex++) + key += pose.GetNodeModelTransformation(data.Skeleton, nodeIndex).Translation; + key /= nodes; + } + + // Calculate skeleton center of mass movement over the animation frames + rootChannel.Position.Resize(frames); + const Float3 centerOfMassRefPose = centerOfMass[0]; + for (int32 frame = 0; frame < frames; frame++) + { + auto& key = rootChannel.Position[frame]; + key.Time = frame; + key.Value = centerOfMass[frame] - centerOfMassRefPose; + } + + // Remove root motion from the children (eg. if Root moves, then Hips should skip that movement delta) + Float3 maxMotion = Float3::Zero; + for (int32 i = 0; i < animation.Channels.Count(); i++) + { + auto& anim = animation.Channels[i]; + const int32 animNodeIndex = data.Skeleton.FindNode(anim.NodeName); + + // Skip channels that have one of their parents already animated + { + int32 nodeIndex = animNodeIndex; + nodeIndex = data.Skeleton.Nodes[nodeIndex].ParentIndex; + while (nodeIndex > 0) + { + const String& nodeName = data.Skeleton.Nodes[nodeIndex].Name; + if (animation.GetChannel(nodeName) != nullptr) + break; + nodeIndex = data.Skeleton.Nodes[nodeIndex].ParentIndex; + } + if (nodeIndex > 0 || &anim == rootChannelPtr) + continue; + } + + // Remove motion + auto& animPos = anim.Position.GetKeyframes(); + for (int32 frame = 0; frame < animPos.Count(); frame++) + { + auto& key = animPos[frame]; + + // Evaluate root motion at the keyframe location + Float3 rootMotion; + rootChannel.Position.Evaluate(rootMotion, key.Time, false); + Float3::Max(maxMotion, rootMotion, maxMotion); + + // Evaluate skeleton pose at the animation frame + AnimGraphImpulse pose; + pose.Nodes.Resize(nodes); + pose.Nodes[0] = data.Skeleton.Nodes[0].LocalTransform; // Use ref pose of root + for (int32 nodeIndex = 1; nodeIndex < nodes; nodeIndex++) // Skip new root + { + Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform; + auto& node = data.Skeleton.Nodes[nodeIndex]; + if (auto* channel = animation.GetChannel(node.Name)) + channel->Evaluate(frame, &srcNode, false); + pose.Nodes[nodeIndex] = srcNode; + } + + // Convert root motion to the local space of this node so the node stays at the same location after adding new root channel + Transform parentNodeTransform = pose.GetNodeModelTransformation(data.Skeleton, data.Skeleton.Nodes[animNodeIndex].ParentIndex); + rootMotion = parentNodeTransform.WorldToLocal(rootMotion); + + // Remove motion + key.Value -= rootMotion; + } + } + LOG(Info, "Calculated root motion: {}", maxMotion); + } + } + // Optimize the keyframes if (options.OptimizeKeyframes) { @@ -1395,9 +1532,6 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option const int32 after = animation.GetKeyframesCount(); LOG(Info, "Optimized {0} animation keyframe(s). Before: {1}, after: {2}, Ratio: {3}%", before - after, before, after, Utilities::RoundTo2DecimalPlaces((float)after / before)); } - - animation.EnableRootMotion = options.EnableRootMotion; - animation.RootNodeName = options.RootNodeName; } } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index 90292d72c..6468c1a43 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -129,6 +129,19 @@ public: Custom = 1, }; + /// + /// Declares the imported animation Root Motion modes. + /// + API_ENUM(Attributes="HideInEditor") enum class RootMotionMode + { + // Root Motion feature is disabled. + None = 0, + // Motion is extracted from the root node (or node specified by name). + ExtractNode = 1, + // Motion is extracted from the center of mass movement (estimated based on the skeleton pose animation). + ExtractCenterOfMass = 2, + }; + /// /// Model import options. /// @@ -228,9 +241,12 @@ public: bool ImportScaleTracks = false; // Enables root motion extraction support from this animation. API_FIELD(Attributes="EditorOrder(1060), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))") - bool EnableRootMotion = false; + RootMotionMode RootMotion = RootMotionMode::None; + // Adjusts root motion applying flags. Can customize how root node animation can affect target actor movement (eg. apply both position and rotation changes). + API_FIELD(Attributes="EditorOrder(1060), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowRootMotion))") + AnimationRootMotionFlags RootMotionFlags = AnimationRootMotionFlags::RootPositionXZ; // The custom node name to be used as a root motion source. If not specified the actual root node will be used. - API_FIELD(Attributes="EditorOrder(1070), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))") + API_FIELD(Attributes="EditorOrder(1070), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowRootMotion))") String RootNodeName = TEXT(""); public: // Level Of Detail From da1b2f0c07caf20034bb84253f3cbd66a965a691 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 9 Feb 2024 15:37:42 +0100 Subject: [PATCH 096/103] Fix crash when file handle was null --- Source/Engine/Content/Storage/FlaxStorage.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index ca39ebaf9..17cf2d193 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -214,7 +214,10 @@ FlaxStorage::~FlaxStorage() Array streams; _file.GetValues(streams); for (FileReadStream* stream : streams) - Delete(stream); + { + if (stream) + Delete(stream); + } #endif } @@ -1341,7 +1344,10 @@ bool FlaxStorage::CloseFileHandles() Array streams; _file.GetValues(streams); for (FileReadStream* stream : streams) - Delete(stream); + { + if (stream) + Delete(stream); + } _file.Clear(); return false; } From 87c66b01682c80df6a285cb363449615cefd6aae Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 9 Feb 2024 15:38:11 +0100 Subject: [PATCH 097/103] Fix root motion preview in animation window --- Source/Editor/Windows/Assets/AnimationWindow.cs | 2 +- Source/Engine/Content/Assets/AnimationGraph.cpp | 4 ++-- Source/Engine/Content/Assets/AnimationGraph.h | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index 8f88d93f6..c512d287f 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -48,7 +48,7 @@ namespace FlaxEditor.Windows.Assets // Use virtual animation graph to playback the animation _animGraph = FlaxEngine.Content.CreateVirtualAsset(); - _animGraph.InitAsAnimation(model, _window.Asset); + _animGraph.InitAsAnimation(model, _window.Asset, true, true); PreviewActor.AnimationGraph = _animGraph; } diff --git a/Source/Engine/Content/Assets/AnimationGraph.cpp b/Source/Engine/Content/Assets/AnimationGraph.cpp index fe87abfe4..9b431aefa 100644 --- a/Source/Engine/Content/Assets/AnimationGraph.cpp +++ b/Source/Engine/Content/Assets/AnimationGraph.cpp @@ -67,7 +67,7 @@ void AnimationGraph::OnDependencyModified(BinaryAsset* asset) #endif -bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop) +bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop, bool rootMotion) { if (!IsVirtual()) { @@ -89,7 +89,7 @@ bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, b rootNode.Type = GRAPH_NODE_MAKE_TYPE(9, 1); rootNode.ID = 1; rootNode.Values.Resize(1); - rootNode.Values[0] = (int32)RootMotionMode::NoExtraction; + rootNode.Values[0] = (int32)(rootMotion ? RootMotionExtraction::Enable : RootMotionExtraction::Ignore); rootNode.Boxes.Resize(1); rootNode.Boxes[0] = AnimGraphBox(&rootNode, 0, VariantType::Void); auto& animNode = graph.Nodes[1]; diff --git a/Source/Engine/Content/Assets/AnimationGraph.h b/Source/Engine/Content/Assets/AnimationGraph.h index f378b9bbe..716329347 100644 --- a/Source/Engine/Content/Assets/AnimationGraph.h +++ b/Source/Engine/Content/Assets/AnimationGraph.h @@ -37,8 +37,9 @@ public: /// The base model asset. /// The animation to play. /// True if play animation in a loop. + /// True if apply root motion. Otherwise it will be ignored. /// True if failed, otherwise false. - API_FUNCTION() bool InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop = true); + API_FUNCTION() bool InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop = true, bool rootMotion = false); /// /// Tries to load surface graph from the asset. From 080202cf959f8828095100156868c31322965479 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 9 Feb 2024 15:52:53 +0100 Subject: [PATCH 098/103] Fix crash when storage file gets deleted due to missing ref from async thread which failed to load it --- .../Content/Storage/ContentStorageManager.cpp | 29 ++++++++++--------- .../Content/Storage/FlaxStorageReference.h | 6 ++-- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Source/Engine/Content/Storage/ContentStorageManager.cpp b/Source/Engine/Content/Storage/ContentStorageManager.cpp index 61e73a3f2..47bdcd3b2 100644 --- a/Source/Engine/Content/Storage/ContentStorageManager.cpp +++ b/Source/Engine/Content/Storage/ContentStorageManager.cpp @@ -58,8 +58,8 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b Locker.Lock(); // Try fast lookup - FlaxStorage* result; - if (!StorageMap.TryGet(path, result)) + FlaxStorage* storage; + if (!StorageMap.TryGet(path, storage)) { // Detect storage type and create object const bool isPackage = path.EndsWith(StringView(PACKAGE_FILES_EXTENSION)); @@ -67,39 +67,42 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b { auto package = New(path); Packages.Add(package); - result = package; + storage = package; } else { auto file = New(path); Files.Add(file); - result = file; + storage = file; } // Register storage container - StorageMap.Add(path, result); + StorageMap.Add(path, storage); } + // Build reference (before releasing the lock so ContentStorageSystem::Job won't delete it when running from async thread) + FlaxStorageReference result(storage); + Locker.Unlock(); if (loadIt) { // Initialize storage container - result->LockChunks(); - const bool loadFailed = result->Load(); - result->UnlockChunks(); + storage->LockChunks(); + const bool loadFailed = storage->Load(); + storage->UnlockChunks(); if (loadFailed) { LOG(Error, "Failed to load {0}.", path); Locker.Lock(); StorageMap.Remove(path); - if (result->IsPackage()) - Packages.Remove((FlaxPackage*)result); + if (storage->IsPackage()) + Packages.Remove((FlaxPackage*)storage); else - Files.Remove((FlaxFile*)result); + Files.Remove((FlaxFile*)storage); Locker.Unlock(); - Delete(result); - return nullptr; + result = nullptr; + Delete(storage); } } diff --git a/Source/Engine/Content/Storage/FlaxStorageReference.h b/Source/Engine/Content/Storage/FlaxStorageReference.h index 03f4d0c8d..e4a5df079 100644 --- a/Source/Engine/Content/Storage/FlaxStorageReference.h +++ b/Source/Engine/Content/Storage/FlaxStorageReference.h @@ -58,17 +58,17 @@ public: return _storage != nullptr; } - FORCE_INLINE bool operator ==(const FlaxStorageReference& other) const + FORCE_INLINE bool operator==(const FlaxStorageReference& other) const { return _storage == other._storage; } - FORCE_INLINE bool operator !=(const FlaxStorageReference& other) const + FORCE_INLINE bool operator!=(const FlaxStorageReference& other) const { return _storage != other._storage; } - FORCE_INLINE FlaxStorage* operator ->() const + FORCE_INLINE FlaxStorage* operator->() const { return _storage; } From c6460078829c45ab942b28c8f6e7ed2a7725f473 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 9 Feb 2024 23:29:43 +0100 Subject: [PATCH 099/103] Fix color grading issue with LUT texture used --- Content/Shaders/ColorGrading.flax | 4 ++-- Source/Shaders/ColorGrading.shader | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Content/Shaders/ColorGrading.flax b/Content/Shaders/ColorGrading.flax index dae561361..ee620d1a2 100644 --- a/Content/Shaders/ColorGrading.flax +++ b/Content/Shaders/ColorGrading.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce60152b7076175eb50e07ad9895eacbfb4a9ed1365c1507266b21fcd3339958 -size 10925 +oid sha256:58bf83f2b334cd28a2db8a2c60a17c56f813edc9050a2d006456f8479cd05d13 +size 10629 diff --git a/Source/Shaders/ColorGrading.shader b/Source/Shaders/ColorGrading.shader index ae639fc4d..9d9175614 100644 --- a/Source/Shaders/ColorGrading.shader +++ b/Source/Shaders/ColorGrading.shader @@ -243,7 +243,7 @@ float4 CombineLUTs(float2 uv, uint layerIndex) // Apply LDR LUT color grading { - float3 uvw = color * (15.0 / 16.0) + (0.5f / 16.0); + float3 uvw = saturate(color) * (15.0 / 16.0) + (0.5f / 16.0); float3 lutColor = SampleUnwrappedTexture3D(LutTexture, SamplerLinearClamp, uvw, 16).rgb; color = lerp(color, lutColor, LutWeight); } From 4a19657e7524d5ca6524013b0f1c06ef72f9fdde Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 10 Feb 2024 17:26:10 +0100 Subject: [PATCH 100/103] Fix crash when replicating C# object with `NetworkReplicated` attribute on derived generic class #1988 --- Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index d646a81cd..7e4f26bbf 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -802,7 +802,7 @@ namespace Flax.Build.Plugins // Serialize base type if (type.BaseType != null && type.BaseType.FullName != "System.ValueType" && type.BaseType.FullName != "FlaxEngine.Object" && type.BaseType.CanBeResolved()) { - GenerateSerializeCallback(module, il, type.BaseType.Resolve(), serialize); + GenerateSerializeCallback(module, il, type.BaseType, serialize); } var ildContext = new DotnetIlContext(il); @@ -874,12 +874,13 @@ namespace Flax.Build.Plugins return m; } - private static void GenerateSerializeCallback(ModuleDefinition module, ILProcessor il, TypeDefinition type, bool serialize) + private static void GenerateSerializeCallback(ModuleDefinition module, ILProcessor il, TypeReference type, bool serialize) { if (type.IsScriptingObject()) { // NetworkReplicator.InvokeSerializer(typeof(), instance, stream, ) - il.Emit(OpCodes.Ldtoken, module.ImportReference(type)); + module.ImportReference(type); + il.Emit(OpCodes.Ldtoken, type); module.GetType("System.Type", out var typeType); var getTypeFromHandle = typeType.Resolve().GetMethod("GetTypeFromHandle"); il.Emit(OpCodes.Call, module.ImportReference(getTypeFromHandle)); From 196a4ffe4927956559e6d0b9c13d511167b5ff5b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 10 Feb 2024 18:17:29 +0100 Subject: [PATCH 101/103] Add warning on not implemented generic type network serializer (need better codegen) #1988 --- Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 7e4f26bbf..085c56680 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -805,6 +805,9 @@ namespace Flax.Build.Plugins GenerateSerializeCallback(module, il, type.BaseType, serialize); } + if (type.HasGenericParameters) // TODO: implement network replication for generic classes + MonoCecil.CompilationError($"Not supported generic type '{type.FullName}' for network replication."); + var ildContext = new DotnetIlContext(il); // Serialize all type fields marked with NetworkReplicated attribute From 46441f6a030ac7d71df625b6d515e702b56e2d62 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 10 Feb 2024 18:43:15 +0100 Subject: [PATCH 102/103] Fix crash when using multi-threaded objects spawn and caching scripting VTables --- Source/Engine/Scripting/BinaryModule.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 44ef32503..0cded22b4 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -533,7 +533,12 @@ void ScriptingType::HackObjectVTable(void* object, ScriptingTypeHandle baseTypeH if (!Script.VTable) { // Ensure to have valid Script VTable hacked - SetupScriptObjectVTable(object, baseTypeHandle, wrapperIndex); + BinaryModule::Locker.Lock(); + if (!Script.VTable) + { + SetupScriptObjectVTable(object, baseTypeHandle, wrapperIndex); + } + BinaryModule::Locker.Unlock(); } // Override object vtable with hacked one that has calls to overriden scripting functions From 04f1c9a59b59a79b1ecd4005c8bfea25afe40a5f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 10 Feb 2024 21:06:48 +0100 Subject: [PATCH 103/103] Fix missing networked object ID resolving #1607 --- Source/Engine/Networking/NetworkReplicator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index f9efba4a5..407bd8c58 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -714,6 +714,7 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b stream->SenderId = senderClientId; // Deserialize object + Scripting::ObjectsLookupIdMapping.Set(&IdsRemappingTable); const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, false); if (failed) {