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();