diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 6a1e804b8..dede82115 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -248,6 +248,11 @@ namespace FlaxEditor /// public event Action PlayModeEnd; + /// + /// Fired on Editor update + /// + public event Action EditorUpdate; + internal Editor() { Instance = this; @@ -486,6 +491,8 @@ namespace FlaxEditor { StateMachine.CurrentState.UpdateFPS(); } + + EditorUpdate?.Invoke(); // Update modules for (int i = 0; i < _modules.Count; i++) diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs index b630836ab..1edb5ec58 100644 --- a/Source/Editor/GUI/AssetPicker.cs +++ b/Source/Editor/GUI/AssetPicker.cs @@ -103,9 +103,9 @@ namespace FlaxEditor.GUI private Rectangle Button1Rect => new Rectangle(Height + ButtonsOffset, 0, ButtonsSize, ButtonsSize); - private Rectangle Button2Rect => new Rectangle(Height + ButtonsOffset, ButtonsSize, ButtonsSize, ButtonsSize); + private Rectangle Button2Rect => new Rectangle(Height + ButtonsOffset, ButtonsSize + 2, ButtonsSize, ButtonsSize); - private Rectangle Button3Rect => new Rectangle(Height + ButtonsOffset, ButtonsSize * 2, ButtonsSize, ButtonsSize); + private Rectangle Button3Rect => new Rectangle(Height + ButtonsOffset, (ButtonsSize + 2) * 2, ButtonsSize, ButtonsSize); /// public override void Draw() @@ -147,6 +147,13 @@ namespace FlaxEditor.GUI style.Foreground, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText( + style.FontSmall, + $"{TypeUtils.GetTypeDisplayName(Validator.AssetType.Type)}", + new Rectangle(button1Rect.Right + 2, ButtonsSize + 2, sizeForTextLeft, ButtonsSize), + style.ForegroundGrey, + TextAlignment.Near, + TextAlignment.Center); } } // Check if has no item but has an asset (eg. virtual asset) @@ -169,6 +176,13 @@ namespace FlaxEditor.GUI style.Foreground, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText( + style.FontSmall, + $"{TypeUtils.GetTypeDisplayName(Validator.AssetType.Type)}", + new Rectangle(button1Rect.Right + 2, ButtonsSize + 2, sizeForTextLeft, ButtonsSize), + style.ForegroundGrey, + TextAlignment.Near, + TextAlignment.Center); } } else @@ -176,6 +190,24 @@ namespace FlaxEditor.GUI // No element selected Render2D.FillRectangle(iconRect, style.BackgroundNormal); Render2D.DrawText(style.FontMedium, "No asset\nselected", iconRect, Color.Orange, TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, Height / DefaultIconSize); + float sizeForTextLeft = Width - button1Rect.Right; + if (sizeForTextLeft > 30) + { + Render2D.DrawText( + style.FontSmall, + $"None", + new Rectangle(button1Rect.Right + 2, 0, sizeForTextLeft, ButtonsSize), + style.Foreground, + TextAlignment.Near, + TextAlignment.Center); + Render2D.DrawText( + style.FontSmall, + $"{TypeUtils.GetTypeDisplayName(Validator.AssetType.Type)}", + new Rectangle(button1Rect.Right + 2, ButtonsSize + 2, sizeForTextLeft, ButtonsSize), + style.ForegroundGrey, + TextAlignment.Near, + TextAlignment.Center); + } } // Check if drag is over diff --git a/Source/Editor/GUI/Docking/DockPanel.cs b/Source/Editor/GUI/Docking/DockPanel.cs index 3e00f3a65..5f229f25b 100644 --- a/Source/Editor/GUI/Docking/DockPanel.cs +++ b/Source/Editor/GUI/Docking/DockPanel.cs @@ -629,7 +629,7 @@ namespace FlaxEditor.GUI.Docking internal void MoveTabRight(int index) { - if (index < _tabs.Count - 2) + if (index < _tabs.Count - 1) { var tab = _tabs[index]; _tabs.RemoveAt(index); diff --git a/Source/Editor/GUI/Docking/DockPanelProxy.cs b/Source/Editor/GUI/Docking/DockPanelProxy.cs index 1234c7777..f489b0c39 100644 --- a/Source/Editor/GUI/Docking/DockPanelProxy.cs +++ b/Source/Editor/GUI/Docking/DockPanelProxy.cs @@ -51,6 +51,7 @@ namespace FlaxEditor.GUI.Docking public DockWindow StartDragAsyncWindow; private Rectangle HeaderRectangle => new Rectangle(0, 0, Width, DockPanel.DefaultHeaderHeight); + private bool IsSingleFloatingWindow => _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0; /// /// Initializes a new instance of the class. @@ -187,6 +188,10 @@ namespace FlaxEditor.GUI.Docking var headerRect = HeaderRectangle; var tabsCount = _panel.TabsCount; + // Return and don't draw tab if only 1 window and it is floating + if (IsSingleFloatingWindow) + return; + // Check if has only one window docked if (tabsCount == 1) { @@ -321,6 +326,9 @@ namespace FlaxEditor.GUI.Docking /// public override bool OnMouseDoubleClick(Float2 location, MouseButton button) { + if (IsSingleFloatingWindow) + return base.OnMouseDoubleClick(location, button); + // Maximize/restore on double click var tab = GetTabAtPos(location, out _); var rootWindow = tab?.RootWindow; @@ -339,6 +347,8 @@ namespace FlaxEditor.GUI.Docking /// public override bool OnMouseDown(Float2 location, MouseButton button) { + if (IsSingleFloatingWindow) + return base.OnMouseDown(location, button); MouseDownWindow = GetTabAtPos(location, out IsMouseDownOverCross); // Check buttons @@ -368,6 +378,9 @@ namespace FlaxEditor.GUI.Docking /// public override bool OnMouseUp(Float2 location, MouseButton button) { + if (IsSingleFloatingWindow) + return base.OnMouseUp(location, button); + // Check tabs under mouse position at the beginning and at the end var tab = GetTabAtPos(location, out var overCross); @@ -410,7 +423,7 @@ namespace FlaxEditor.GUI.Docking public override void OnMouseMove(Float2 location) { MousePosition = location; - if (IsMouseLeftButtonDown) + if (IsMouseLeftButtonDown && !IsSingleFloatingWindow) { // Check if mouse is outside the header if (!HeaderRectangle.Contains(location)) @@ -501,7 +514,10 @@ namespace FlaxEditor.GUI.Docking /// public override void GetDesireClientArea(out Rectangle rect) { - rect = new Rectangle(0, DockPanel.DefaultHeaderHeight, Width, Height - DockPanel.DefaultHeaderHeight); + if (IsSingleFloatingWindow) + rect = new Rectangle(0, 0, Width, Height); + else + rect = new Rectangle(0, DockPanel.DefaultHeaderHeight, Width, Height - DockPanel.DefaultHeaderHeight); } private DragDropEffect TrySelectTabUnderLocation(ref Float2 location) diff --git a/Source/Editor/GUI/Popups/AssetSearchPopup.cs b/Source/Editor/GUI/Popups/AssetSearchPopup.cs index 7a7d90aec..e28c19b65 100644 --- a/Source/Editor/GUI/Popups/AssetSearchPopup.cs +++ b/Source/Editor/GUI/Popups/AssetSearchPopup.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.GUI ContentItem = item; ContentItem.AddReference(this); - Name = item.ShortName; + OnItemRenamed(item); TooltipText = item.Path; Height = IconSize + 4; @@ -82,7 +82,9 @@ namespace FlaxEditor.GUI /// public void OnItemRenamed(ContentItem item) { - Name = ContentItem.ShortName; + Name = item.ShortName; + if (item is ScriptItem) + Name = item.FileName; // Show extension for scripts (esp. for .h and .cpp files of the same name) } /// diff --git a/Source/Editor/Modules/ContentFindingModule.cs b/Source/Editor/Modules/ContentFindingModule.cs index 73e22d769..e3aa22b0e 100644 --- a/Source/Editor/Modules/ContentFindingModule.cs +++ b/Source/Editor/Modules/ContentFindingModule.cs @@ -342,9 +342,10 @@ namespace FlaxEditor.Modules { foreach (var contentItem in items) { + var name = contentItem.ShortName; if (contentItem.IsAsset) { - if (nameRegex.Match(contentItem.ShortName).Success) + if (nameRegex.Match(name).Success) { var asset = contentItem as AssetItem; if (asset == null || !typeRegex.Match(asset.TypeName).Success) @@ -358,7 +359,7 @@ namespace FlaxEditor.Modules var splits = asset.TypeName.Split('.'); finalName = splits[splits.Length - 1]; } - matches.Add(new SearchResult { Name = asset.ShortName, Type = finalName, Item = asset }); + matches.Add(new SearchResult { Name = name, Type = finalName, Item = asset }); } } else if (contentItem.IsFolder) @@ -370,11 +371,12 @@ namespace FlaxEditor.Modules } else { - if (nameRegex.Match(contentItem.ShortName).Success && typeRegex.Match(contentItem.GetType().Name).Success) + if (nameRegex.Match(name).Success && typeRegex.Match(contentItem.GetType().Name).Success) { string finalName = contentItem.GetType().Name.Replace("Item", ""); - - matches.Add(new SearchResult { Name = contentItem.ShortName, Type = finalName, Item = contentItem }); + if (contentItem is ScriptItem) + name = contentItem.FileName; // Show extension for scripts (esp. for .h and .cpp files of the same name) + matches.Add(new SearchResult { Name = name, Type = finalName, Item = contentItem }); } } } diff --git a/Source/Editor/Windows/Assets/TextureWindow.cs b/Source/Editor/Windows/Assets/TextureWindow.cs index cf913df2b..9d4b94024 100644 --- a/Source/Editor/Windows/Assets/TextureWindow.cs +++ b/Source/Editor/Windows/Assets/TextureWindow.cs @@ -21,54 +21,75 @@ namespace FlaxEditor.Windows.Assets /// public sealed class TextureWindow : AssetEditorWindowBase { - private sealed class ProxyEditor : GenericEditor + /// + /// Properties base class. + /// + public class PropertiesProxyBase { - public override void Initialize(LayoutElementsContainer layout) + internal TextureWindow _window; + + /// + /// Gathers parameters from the specified texture. + /// + /// The asset window. + public virtual void OnLoad(TextureWindow window) { - var window = ((PropertiesProxy)Values[0])._window; - var texture = window?.Asset; - if (texture == null || !texture.IsLoaded) - { - layout.Label("Loading...", TextAlignment.Center); - return; - } - - // Texture info - var general = layout.Group("General"); - general.Label("Format: " + texture.Format); - general.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)).AddCopyContextMenu(); - general.Label("Mip levels: " + texture.MipLevels); - general.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage)).AddCopyContextMenu(); - - // Texture properties - var properties = layout.Group("Properties"); - var textureGroup = new CustomValueContainer(new ScriptType(typeof(int)), texture.TextureGroup, - (instance, index) => texture.TextureGroup, - (instance, index, value) => - { - texture.TextureGroup = (int)value; - window.MarkAsEdited(); - }); - properties.Property("Texture Group", textureGroup, new TextureGroupEditor(), "The texture group used by this texture."); - - // Import settings - base.Initialize(layout); - - // Reimport - layout.Space(10); - var reimportButton = layout.Button("Reimport"); - reimportButton.Button.Clicked += () => ((PropertiesProxy)Values[0]).Reimport(); + // Link + _window = window; + } + + /// + /// Clears temporary data. + /// + public void OnClean() + { + // Unlink + _window = null; } } + [CustomEditor(typeof(ProxyEditor))] + private sealed class TexturePropertiesProxy : PropertiesProxyBase + { + private sealed class ProxyEditor : GenericEditor + { + public override void Initialize(LayoutElementsContainer layout) + { + var window = ((TexturePropertiesProxy)Values[0])._window; + var texture = window?.Asset; + if (texture == null || !texture.IsLoaded) + { + layout.Label("Loading...", TextAlignment.Center); + return; + } + + // Texture info + var general = layout.Group("General"); + general.Label("Format: " + texture.Format); + general.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)).AddCopyContextMenu(); + general.Label("Mip levels: " + texture.MipLevels); + general.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage)).AddCopyContextMenu(); + + // Texture properties + var properties = layout.Group("Properties"); + var textureGroup = new CustomValueContainer(new ScriptType(typeof(int)), texture.TextureGroup, + (instance, index) => texture.TextureGroup, + (instance, index, value) => + { + texture.TextureGroup = (int)value; + window.MarkAsEdited(); + }); + properties.Property("Texture Group", textureGroup, new TextureGroupEditor(), "The texture group used by this texture."); + } + } + } + /// - /// The texture properties proxy object. + /// The texture import properties proxy object. /// [CustomEditor(typeof(ProxyEditor))] - private sealed class PropertiesProxy + private sealed class ImportPropertiesProxy : PropertiesProxyBase { - internal TextureWindow _window; - [EditorOrder(1000), EditorDisplay("Import Settings", EditorDisplayAttribute.InlineStyle)] public FlaxEngine.Tools.TextureTool.Options ImportSettings = new(); @@ -76,10 +97,9 @@ namespace FlaxEditor.Windows.Assets /// Gathers parameters from the specified texture. /// /// The asset window. - public void OnLoad(TextureWindow window) + public override void OnLoad(TextureWindow window) { - // Link - _window = window; + base.OnLoad(window); // Try to restore target asset texture import options (useful for fast reimport) Editor.TryRestoreImportOptions(ref ImportSettings, window.Item.Path); @@ -109,22 +129,85 @@ namespace FlaxEditor.Windows.Assets public void DiscardChanges() { } - - /// - /// Clears temporary data. - /// - public void OnClean() + + private sealed class ProxyEditor : GenericEditor { - // Unlink - _window = null; + public override void Initialize(LayoutElementsContainer layout) + { + // Import settings + base.Initialize(layout); + + // Reimport + layout.Space(10); + var reimportButton = layout.Button("Reimport"); + reimportButton.Button.Clicked += () => ((ImportPropertiesProxy)Values[0]).Reimport(); + } } } + private class Tab : GUI.Tabs.Tab + { + /// + /// The presenter to use in the tab. + /// + public CustomEditorPresenter Presenter; + + /// + /// The proxy to use in the tab. + /// + public PropertiesProxyBase Proxy; + + public Tab(string text, TextureWindow window, bool modifiesAsset = true) + : base(text) + { + var scrollPanel = new Panel(ScrollBars.Vertical) + { + AnchorPreset = AnchorPresets.StretchAll, + Offsets = Margin.Zero, + Parent = this + }; + + Presenter = new CustomEditorPresenter(null); + Presenter.Panel.Parent = scrollPanel; + if (modifiesAsset) + Presenter.Modified += window.MarkAsEdited; + } + + /// + public override void OnDestroy() + { + Presenter.Deselect(); + Presenter = null; + Proxy = null; + + base.OnDestroy(); + } + } + + private class TextureTab : Tab + { + public TextureTab(TextureWindow window) + : base("Texture", window) + { + Proxy = new TexturePropertiesProxy(); + Presenter.Select(Proxy); + } + } + + private class ImportTab : Tab + { + public ImportTab(TextureWindow window) + : base("Import", window) + { + Proxy = new ImportPropertiesProxy(); + Presenter.Select(Proxy); + } + } + + private readonly GUI.Tabs.Tabs _tabs; private readonly SplitPanel _split; private readonly TexturePreview _preview; - private readonly CustomEditorPresenter _propertiesEditor; private readonly ToolStripButton _saveButton; - private readonly PropertiesProxy _properties; private bool _isWaitingForLoad; /// @@ -145,12 +228,20 @@ namespace FlaxEditor.Windows.Assets { Parent = _split.Panel1 }; + + // Properties tabs + _tabs = new() + { + AnchorPreset = AnchorPresets.StretchAll, + Offsets = Margin.Zero, + TabsSize = new Float2(60, 20), + TabsTextHorizontalAlignment = TextAlignment.Center, + UseScroll = true, + Parent = _split.Panel2 + }; - // Texture properties editor - _propertiesEditor = new CustomEditorPresenter(null); - _propertiesEditor.Panel.Parent = _split.Panel2; - _properties = new PropertiesProxy(); - _propertiesEditor.Select(_properties); + _tabs.AddTab(new TextureTab(this)); + _tabs.AddTab(new ImportTab(this)); // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); @@ -164,7 +255,11 @@ namespace FlaxEditor.Windows.Assets /// protected override void UnlinkItem() { - _properties.OnClean(); + foreach (var child in _tabs.Children) + { + if (child is Tab tab && tab.Proxy != null) + tab.Proxy.OnClean(); + } _preview.Asset = null; _isWaitingForLoad = false; @@ -195,15 +290,6 @@ namespace FlaxEditor.Windows.Assets base.UpdateToolstrip(); } - /// - protected override void OnClose() - { - // Discard unsaved changes - _properties.DiscardChanges(); - - base.OnClose(); - } - /// public override void Save() { @@ -231,8 +317,14 @@ namespace FlaxEditor.Windows.Assets _isWaitingForLoad = false; // Init properties and parameters proxy - _properties.OnLoad(this); - _propertiesEditor.BuildLayout(); + foreach (var child in _tabs.Children) + { + if (child is Tab tab && tab.Proxy != null) + { + tab.Proxy.OnLoad(this); + tab.Presenter.BuildLayout(); + } + } // Setup ClearEditedFlag(); diff --git a/Source/Editor/Windows/Search/ContentFinder.cs b/Source/Editor/Windows/Search/ContentFinder.cs index 5b7ec18df..06b5852dd 100644 --- a/Source/Editor/Windows/Search/ContentFinder.cs +++ b/Source/Editor/Windows/Search/ContentFinder.cs @@ -139,8 +139,8 @@ namespace FlaxEditor.Windows.Search { var item = items[i]; SearchItem searchItem; - if (item.Item is AssetItem assetItem) - searchItem = new AssetSearchItem(item.Name, item.Type, assetItem, this, itemsWidth, itemHeight); + if (item.Item is ContentItem contentItem) + searchItem = new ContentSearchItem(item.Name, item.Type, contentItem, this, itemsWidth, itemHeight); else searchItem = new SearchItem(item.Name, item.Type, item.Item, this, itemsWidth, itemHeight); searchItem.Y = i * itemHeight; diff --git a/Source/Editor/Windows/Search/SearchItem.cs b/Source/Editor/Windows/Search/SearchItem.cs index 780b43346..577f75dc0 100644 --- a/Source/Editor/Windows/Search/SearchItem.cs +++ b/Source/Editor/Windows/Search/SearchItem.cs @@ -113,17 +113,17 @@ namespace FlaxEditor.Windows.Search } /// - /// The for assets. Supports using asset thumbnail. + /// The for assets. Supports using content item thumbnail. /// /// /// - internal class AssetSearchItem : SearchItem, IContentItemOwner + internal class ContentSearchItem : SearchItem, IContentItemOwner { - private AssetItem _asset; - private FlaxEditor.GUI.ContextMenu.ContextMenu _cm; + private ContentItem _asset; + private ContextMenu _cm; /// - public AssetSearchItem(string name, string type, AssetItem item, ContentFinder finder, float width, float height) + public ContentSearchItem(string name, string type, ContentItem item, ContentFinder finder, float width, float height) : base(name, type, item, finder, width, height) { _asset = item; @@ -136,31 +136,41 @@ namespace FlaxEditor.Windows.Search /// public override bool OnShowTooltip(out string text, out Float2 location, out Rectangle area) { - if (string.IsNullOrEmpty(TooltipText) && Item is AssetItem assetItem) + if (string.IsNullOrEmpty(TooltipText) && Item is ContentItem contentItem) { - assetItem.UpdateTooltipText(); - TooltipText = assetItem.TooltipText; + contentItem.UpdateTooltipText(); + TooltipText = contentItem.TooltipText; } return base.OnShowTooltip(out text, out location, out area); } + /// + public override bool OnMouseDown(Float2 location, MouseButton button) + { + if (base.OnMouseDown(location, button)) + return true; + if (button == MouseButton.Right && Item is ContentItem) + return true; + return false; + } + /// public override bool OnMouseUp(Float2 location, MouseButton button) { if (base.OnMouseUp(location, button)) return true; - if (button == MouseButton.Right && Item is AssetItem assetItem) + if (button == MouseButton.Right && Item is ContentItem contentItem) { // Show context menu - var proxy = Editor.Instance.ContentDatabase.GetProxy(assetItem); + var proxy = Editor.Instance.ContentDatabase.GetProxy(contentItem); ContextMenuButton b; - var cm = new FlaxEditor.GUI.ContextMenu.ContextMenu { Tag = assetItem }; + var cm = new ContextMenu { Tag = contentItem }; b = cm.AddButton("Open", () => Editor.Instance.ContentFinding.Open(Item)); cm.AddSeparator(); - cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(assetItem.Path))); - cm.AddButton("Show in Content window", () => Editor.Instance.Windows.ContentWin.Select(assetItem, true)); - b.Enabled = proxy != null && proxy.CanReimport(assetItem); - if (assetItem is BinaryAssetItem binaryAsset) + cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(contentItem.Path))); + cm.AddButton("Show in Content window", () => Editor.Instance.Windows.ContentWin.Select(contentItem, true)); + b.Enabled = proxy != null && proxy.CanReimport(contentItem); + if (contentItem is BinaryAssetItem binaryAsset) { if (!binaryAsset.GetImportPath(out string importPath)) { @@ -172,14 +182,17 @@ namespace FlaxEditor.Windows.Search } } cm.AddSeparator(); - cm.AddButton("Copy asset ID", () => Clipboard.Text = FlaxEngine.Json.JsonSerializer.GetStringID(assetItem.ID)); - cm.AddButton("Select actors using this asset", () => Editor.Instance.SceneEditing.SelectActorsUsingAsset(assetItem.ID)); - cm.AddButton("Show asset references graph", () => Editor.Instance.Windows.Open(new AssetReferencesGraphWindow(Editor.Instance, assetItem))); - cm.AddSeparator(); - proxy?.OnContentWindowContextMenu(cm, assetItem); - assetItem.OnContextMenu(cm); - cm.AddButton("Copy name to Clipboard", () => Clipboard.Text = assetItem.NamePath); - cm.AddButton("Copy path to Clipboard", () => Clipboard.Text = assetItem.Path); + if (contentItem is AssetItem assetItem) + { + cm.AddButton("Copy asset ID", () => Clipboard.Text = FlaxEngine.Json.JsonSerializer.GetStringID(assetItem.ID)); + cm.AddButton("Select actors using this asset", () => Editor.Instance.SceneEditing.SelectActorsUsingAsset(assetItem.ID)); + cm.AddButton("Show asset references graph", () => Editor.Instance.Windows.Open(new AssetReferencesGraphWindow(Editor.Instance, assetItem))); + cm.AddButton("Copy name to Clipboard", () => Clipboard.Text = assetItem.NamePath); + cm.AddButton("Copy path to Clipboard", () => Clipboard.Text = assetItem.Path); + cm.AddSeparator(); + } + proxy?.OnContentWindowContextMenu(cm, contentItem); + contentItem.OnContextMenu(cm); cm.Show(this, location); _cm = cm; return true; diff --git a/Source/Engine/Audio/AudioSource.h b/Source/Engine/Audio/AudioSource.h index b90bb4a73..d55761cb7 100644 --- a/Source/Engine/Audio/AudioSource.h +++ b/Source/Engine/Audio/AudioSource.h @@ -271,8 +271,17 @@ public: public: /// /// Determines whether this audio source started playing audio via audio backend. After audio play it may wait for audio clip data to be loaded or streamed. + /// [Deprecated in v1.9] /// - API_PROPERTY() FORCE_INLINE bool IsActuallyPlayingSth() const + API_PROPERTY() DEPRECATED FORCE_INLINE bool IsActuallyPlayingSth() const + { + return _isActuallyPlayingSth; + } + + /// + /// Determines whether this audio source started playing audio via audio backend. After audio play it may wait for audio clip data to be loaded or streamed. + /// + API_PROPERTY() FORCE_INLINE bool IsActuallyPlaying() const { return _isActuallyPlayingSth; } diff --git a/Source/Engine/Content/JsonAsset.cs b/Source/Engine/Content/JsonAsset.cs index 15ff3cd4d..a3f10b646 100644 --- a/Source/Engine/Content/JsonAsset.cs +++ b/Source/Engine/Content/JsonAsset.cs @@ -14,6 +14,17 @@ namespace FlaxEngine /// public object Instance => _instance ?? (_instance = CreateInstance()); + /// + /// Gets the instance of the serialized object from the json data. Cached internally. + /// + /// The asset instance object or null. + public T GetInstance() + { + if (Instance is T instance) + return instance; + return default; + } + /// /// Creates a new instance of the serialized object from the json asset data. /// diff --git a/Source/Engine/Content/JsonAssetReference.cs b/Source/Engine/Content/JsonAssetReference.cs index bec0cedf7..a3a17fe2b 100644 --- a/Source/Engine/Content/JsonAssetReference.cs +++ b/Source/Engine/Content/JsonAssetReference.cs @@ -33,6 +33,15 @@ namespace FlaxEngine Asset = asset; } + /// + /// Gets the deserialized native object instance of the given type. Returns null if asset is not loaded or loaded object has different type. + /// + /// The asset instance object or null. + public U GetInstance() + { + return Asset ? Asset.GetInstance() : default(U); + } + /// /// Implicit cast operator. /// diff --git a/Source/Engine/Core/Config/LayersAndTagsSettings.cs b/Source/Engine/Core/Config/LayersAndTagsSettings.cs index 005906872..9346efee7 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(CanResize = true, Display = CollectionAttribute.DisplayType.Inline)] + [EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(CanResize = false, Display = CollectionAttribute.DisplayType.Inline)] public string[] Layers = new string[32]; /// diff --git a/Source/Engine/Core/Math/Quaternion.cpp b/Source/Engine/Core/Math/Quaternion.cpp index 03989dab5..5927e0665 100644 --- a/Source/Engine/Core/Math/Quaternion.cpp +++ b/Source/Engine/Core/Math/Quaternion.cpp @@ -294,6 +294,10 @@ Quaternion Quaternion::FromDirection(const Float3& direction) { RotationAxis(Float3::Left, PI_OVER_2, orientation); } + else if (Float3::Dot(direction, Float3::Down) >= 0.999f) + { + RotationAxis(Float3::Right, PI_OVER_2, orientation); + } else { Float3 right, up; diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs index 86a6e4e2d..d4d0fe96c 100644 --- a/Source/Engine/Core/Math/Quaternion.cs +++ b/Source/Engine/Core/Math/Quaternion.cs @@ -654,6 +654,10 @@ namespace FlaxEngine { orientation = RotationAxis(Float3.Left, Mathf.PiOverTwo); } + else if (Float3.Dot(direction, Float3.Down) >= 0.999f) + { + orientation = RotationAxis(Float3.Right, Mathf.PiOverTwo); + } else { var right = Float3.Cross(direction, Float3.Up); diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 3561da8f2..4a4452fc2 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -19,6 +19,7 @@ CharacterController::CharacterController(const SpawnParams& params) , _minMoveDistance(0.0f) , _isUpdatingTransform(false) , _upDirection(Vector3::Up) + , _gravityDisplacement(Vector3::Zero) , _nonWalkableMode(NonWalkableModes::PreventClimbing) , _lastFlags(CollisionFlags::None) { @@ -148,10 +149,16 @@ CharacterController::CollisionFlags CharacterController::GetFlags() const CharacterController::CollisionFlags CharacterController::SimpleMove(const Vector3& speed) { const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds(); - Vector3 displacement = speed; - displacement += GetPhysicsScene()->GetGravity() * deltaTime; - displacement *= deltaTime; - return Move(displacement); + Vector3 displacement = speed + _gravityDisplacement; + CollisionFlags result = Move(displacement * deltaTime); + if ((static_cast(result) & static_cast(CollisionFlags::Below)) != 0) + { + // Reset accumulated gravity acceleration when we touch the ground + _gravityDisplacement = Vector3::Zero; + } + else + _gravityDisplacement += GetPhysicsScene()->GetGravity() * deltaTime; + return result; } CharacterController::CollisionFlags CharacterController::Move(const Vector3& displacement) diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 545f4e9d9..540adc5c9 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -66,6 +66,7 @@ private: float _minMoveDistance; bool _isUpdatingTransform; Vector3 _upDirection; + Vector3 _gravityDisplacement; NonWalkableModes _nonWalkableMode; CollisionFlags _lastFlags; diff --git a/Source/Engine/Physics/Colliders/MeshCollider.cpp b/Source/Engine/Physics/Colliders/MeshCollider.cpp index 621432d0e..0027a0c28 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.cpp +++ b/Source/Engine/Physics/Colliders/MeshCollider.cpp @@ -133,7 +133,13 @@ void MeshCollider::GetGeometry(CollisionShape& collision) // Prepare scale Float3 scale = _cachedScale; const float minSize = 0.001f; - scale = Float3::Max(scale.GetAbsolute(), minSize); + Float3 scaleAbs = scale.GetAbsolute(); + if (scaleAbs.X < minSize) + scale.X = Math::Sign(scale.X) * minSize; + if (scaleAbs.Y < minSize) + scale.Y = Math::Sign(scale.Y) * minSize; + if (scaleAbs.Z < minSize) + scale.Z = Math::Sign(scale.Z) * minSize; // Setup shape (based on type) CollisionDataType type = CollisionDataType::None; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 8bdb87122..55b15ab5d 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -321,7 +321,12 @@ class CharacterControllerHitReportPhysX : public PxUserControllerHitReport { void onHit(const PxControllerHit& hit, Collision& c) { - ASSERT_LOW_LAYER(c.ThisActor && c.OtherActor); + if (c.ThisActor == nullptr || c.OtherActor == nullptr) + { + // One of the actors was deleted (eg. via RigidBody destroyed by gameplay) then skip processing this collision + return; + } + c.Impulse = Vector3::Zero; c.ThisVelocity = P2C(hit.dir) * hit.length; c.OtherVelocity = Vector3::Zero; @@ -564,6 +569,7 @@ namespace Array DeleteObjects; bool _queriesHitTriggers = true; + bool _enableCCD = true; PhysicsCombineMode _frictionCombineMode = PhysicsCombineMode::Average; PhysicsCombineMode _restitutionCombineMode = PhysicsCombineMode::Average; @@ -697,6 +703,8 @@ PxFilterFlags FilterShader( pairFlags |= PxPairFlag::eNOTIFY_TOUCH_LOST; pairFlags |= PxPairFlag::ePOST_SOLVER_VELOCITY; pairFlags |= PxPairFlag::eNOTIFY_CONTACT_POINTS; + if (_enableCCD) + pairFlags |= PxPairFlag::eDETECT_CCD_CONTACT; return PxFilterFlag::eDEFAULT; } @@ -1220,6 +1228,7 @@ void PhysicsBackend::Shutdown() void PhysicsBackend::ApplySettings(const PhysicsSettings& settings) { _queriesHitTriggers = settings.QueriesHitTriggers; + _enableCCD = !settings.DisableCCD; _frictionCombineMode = settings.FrictionCombineMode; _restitutionCombineMode = settings.RestitutionCombineMode; @@ -1256,6 +1265,8 @@ void* PhysicsBackend::CreateScene(const PhysicsSettings& settings) sceneDesc.simulationEventCallback = &scenePhysX->EventsCallback; sceneDesc.filterShader = FilterShader; sceneDesc.bounceThresholdVelocity = settings.BounceThresholdVelocity; + if (settings.EnableEnhancedDeterminism) + sceneDesc.flags |= PxSceneFlag::eENABLE_ENHANCED_DETERMINISM; switch (settings.SolverType) { case PhysicsSolverType::ProjectedGaussSeidelIterativeSolver: diff --git a/Source/Engine/Physics/PhysicsSettings.h b/Source/Engine/Physics/PhysicsSettings.h index fca1ea2d4..728a18367 100644 --- a/Source/Engine/Physics/PhysicsSettings.h +++ b/Source/Engine/Physics/PhysicsSettings.h @@ -94,10 +94,16 @@ public: API_FIELD(Attributes="EditorOrder(71), EditorDisplay(\"Simulation\")") PhysicsBroadPhaseType BroadPhaseType = PhysicsBroadPhaseType::ParallelAutomaticBoxPruning; + /// + /// Enables enhanced determinism in the simulation. This has a performance impact. + /// + API_FIELD(Attributes="EditorOrder(71), EditorDisplay(\"Simulation\")") + bool EnableEnhancedDeterminism = false; + /// /// The solver type to use in the simulation. /// - API_FIELD(Attributes="EditorOrder(72), EditorDisplay(\"Simulation\")") + API_FIELD(Attributes="EditorOrder(73), EditorDisplay(\"Simulation\")") PhysicsSolverType SolverType = PhysicsSolverType::ProjectedGaussSeidelIterativeSolver; /// diff --git a/Source/Engine/UI/GUI/Panels/SplitPanel.cs b/Source/Engine/UI/GUI/Panels/SplitPanel.cs index e721ee8a3..9203bcda0 100644 --- a/Source/Engine/UI/GUI/Panels/SplitPanel.cs +++ b/Source/Engine/UI/GUI/Panels/SplitPanel.cs @@ -130,6 +130,7 @@ namespace FlaxEngine.GUI { // Clear flag _splitterClicked = false; + PerformLayout(); // End capturing mouse EndMouseCapture();