Merge remote-tracking branch 'origin/master' into 1.9

This commit is contained in:
Wojtek Figat
2024-07-19 00:32:54 +02:00
21 changed files with 347 additions and 114 deletions

View File

@@ -248,6 +248,11 @@ namespace FlaxEditor
/// </summary> /// </summary>
public event Action PlayModeEnd; public event Action PlayModeEnd;
/// <summary>
/// Fired on Editor update
/// </summary>
public event Action EditorUpdate;
internal Editor() internal Editor()
{ {
Instance = this; Instance = this;
@@ -487,6 +492,8 @@ namespace FlaxEditor
StateMachine.CurrentState.UpdateFPS(); StateMachine.CurrentState.UpdateFPS();
} }
EditorUpdate?.Invoke();
// Update modules // Update modules
for (int i = 0; i < _modules.Count; i++) for (int i = 0; i < _modules.Count; i++)
{ {

View File

@@ -103,9 +103,9 @@ namespace FlaxEditor.GUI
private Rectangle Button1Rect => new Rectangle(Height + ButtonsOffset, 0, ButtonsSize, ButtonsSize); 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);
/// <inheritdoc /> /// <inheritdoc />
public override void Draw() public override void Draw()
@@ -147,6 +147,13 @@ namespace FlaxEditor.GUI
style.Foreground, style.Foreground,
TextAlignment.Near, TextAlignment.Near,
TextAlignment.Center); 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) // Check if has no item but has an asset (eg. virtual asset)
@@ -169,6 +176,13 @@ namespace FlaxEditor.GUI
style.Foreground, style.Foreground,
TextAlignment.Near, TextAlignment.Near,
TextAlignment.Center); 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 else
@@ -176,6 +190,24 @@ namespace FlaxEditor.GUI
// No element selected // No element selected
Render2D.FillRectangle(iconRect, style.BackgroundNormal); 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); 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 // Check if drag is over

View File

@@ -629,7 +629,7 @@ namespace FlaxEditor.GUI.Docking
internal void MoveTabRight(int index) internal void MoveTabRight(int index)
{ {
if (index < _tabs.Count - 2) if (index < _tabs.Count - 1)
{ {
var tab = _tabs[index]; var tab = _tabs[index];
_tabs.RemoveAt(index); _tabs.RemoveAt(index);

View File

@@ -51,6 +51,7 @@ namespace FlaxEditor.GUI.Docking
public DockWindow StartDragAsyncWindow; public DockWindow StartDragAsyncWindow;
private Rectangle HeaderRectangle => new Rectangle(0, 0, Width, DockPanel.DefaultHeaderHeight); private Rectangle HeaderRectangle => new Rectangle(0, 0, Width, DockPanel.DefaultHeaderHeight);
private bool IsSingleFloatingWindow => _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DockPanelProxy"/> class. /// Initializes a new instance of the <see cref="DockPanelProxy"/> class.
@@ -187,6 +188,10 @@ namespace FlaxEditor.GUI.Docking
var headerRect = HeaderRectangle; var headerRect = HeaderRectangle;
var tabsCount = _panel.TabsCount; 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 // Check if has only one window docked
if (tabsCount == 1) if (tabsCount == 1)
{ {
@@ -321,6 +326,9 @@ namespace FlaxEditor.GUI.Docking
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button) public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{ {
if (IsSingleFloatingWindow)
return base.OnMouseDoubleClick(location, button);
// Maximize/restore on double click // Maximize/restore on double click
var tab = GetTabAtPos(location, out _); var tab = GetTabAtPos(location, out _);
var rootWindow = tab?.RootWindow; var rootWindow = tab?.RootWindow;
@@ -339,6 +347,8 @@ namespace FlaxEditor.GUI.Docking
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button) public override bool OnMouseDown(Float2 location, MouseButton button)
{ {
if (IsSingleFloatingWindow)
return base.OnMouseDown(location, button);
MouseDownWindow = GetTabAtPos(location, out IsMouseDownOverCross); MouseDownWindow = GetTabAtPos(location, out IsMouseDownOverCross);
// Check buttons // Check buttons
@@ -368,6 +378,9 @@ namespace FlaxEditor.GUI.Docking
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button) 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 // Check tabs under mouse position at the beginning and at the end
var tab = GetTabAtPos(location, out var overCross); var tab = GetTabAtPos(location, out var overCross);
@@ -410,7 +423,7 @@ namespace FlaxEditor.GUI.Docking
public override void OnMouseMove(Float2 location) public override void OnMouseMove(Float2 location)
{ {
MousePosition = location; MousePosition = location;
if (IsMouseLeftButtonDown) if (IsMouseLeftButtonDown && !IsSingleFloatingWindow)
{ {
// Check if mouse is outside the header // Check if mouse is outside the header
if (!HeaderRectangle.Contains(location)) if (!HeaderRectangle.Contains(location))
@@ -501,6 +514,9 @@ namespace FlaxEditor.GUI.Docking
/// <inheritdoc /> /// <inheritdoc />
public override void GetDesireClientArea(out Rectangle rect) public override void GetDesireClientArea(out Rectangle rect)
{ {
if (IsSingleFloatingWindow)
rect = new Rectangle(0, 0, Width, Height);
else
rect = new Rectangle(0, DockPanel.DefaultHeaderHeight, Width, Height - DockPanel.DefaultHeaderHeight); rect = new Rectangle(0, DockPanel.DefaultHeaderHeight, Width, Height - DockPanel.DefaultHeaderHeight);
} }

View File

@@ -38,7 +38,7 @@ namespace FlaxEditor.GUI
ContentItem = item; ContentItem = item;
ContentItem.AddReference(this); ContentItem.AddReference(this);
Name = item.ShortName; OnItemRenamed(item);
TooltipText = item.Path; TooltipText = item.Path;
Height = IconSize + 4; Height = IconSize + 4;
@@ -82,7 +82,9 @@ namespace FlaxEditor.GUI
/// <inheritdoc /> /// <inheritdoc />
public void OnItemRenamed(ContentItem item) 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)
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -342,9 +342,10 @@ namespace FlaxEditor.Modules
{ {
foreach (var contentItem in items) foreach (var contentItem in items)
{ {
var name = contentItem.ShortName;
if (contentItem.IsAsset) if (contentItem.IsAsset)
{ {
if (nameRegex.Match(contentItem.ShortName).Success) if (nameRegex.Match(name).Success)
{ {
var asset = contentItem as AssetItem; var asset = contentItem as AssetItem;
if (asset == null || !typeRegex.Match(asset.TypeName).Success) if (asset == null || !typeRegex.Match(asset.TypeName).Success)
@@ -358,7 +359,7 @@ namespace FlaxEditor.Modules
var splits = asset.TypeName.Split('.'); var splits = asset.TypeName.Split('.');
finalName = splits[splits.Length - 1]; 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) else if (contentItem.IsFolder)
@@ -370,11 +371,12 @@ namespace FlaxEditor.Modules
} }
else 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", ""); string finalName = contentItem.GetType().Name.Replace("Item", "");
if (contentItem is ScriptItem)
matches.Add(new SearchResult { Name = contentItem.ShortName, Type = finalName, Item = contentItem }); 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 });
} }
} }
} }

View File

@@ -20,12 +20,42 @@ namespace FlaxEditor.Windows.Assets
/// <seealso cref="Texture" /> /// <seealso cref="Texture" />
/// <seealso cref="FlaxEditor.Windows.Assets.AssetEditorWindow" /> /// <seealso cref="FlaxEditor.Windows.Assets.AssetEditorWindow" />
public sealed class TextureWindow : AssetEditorWindowBase<Texture> public sealed class TextureWindow : AssetEditorWindowBase<Texture>
{
/// <summary>
/// Properties base class.
/// </summary>
public class PropertiesProxyBase
{
internal TextureWindow _window;
/// <summary>
/// Gathers parameters from the specified texture.
/// </summary>
/// <param name="window">The asset window.</param>
public virtual void OnLoad(TextureWindow window)
{
// Link
_window = window;
}
/// <summary>
/// Clears temporary data.
/// </summary>
public void OnClean()
{
// Unlink
_window = null;
}
}
[CustomEditor(typeof(ProxyEditor))]
private sealed class TexturePropertiesProxy : PropertiesProxyBase
{ {
private sealed class ProxyEditor : GenericEditor private sealed class ProxyEditor : GenericEditor
{ {
public override void Initialize(LayoutElementsContainer layout) public override void Initialize(LayoutElementsContainer layout)
{ {
var window = ((PropertiesProxy)Values[0])._window; var window = ((TexturePropertiesProxy)Values[0])._window;
var texture = window?.Asset; var texture = window?.Asset;
if (texture == null || !texture.IsLoaded) if (texture == null || !texture.IsLoaded)
{ {
@@ -50,25 +80,16 @@ namespace FlaxEditor.Windows.Assets
window.MarkAsEdited(); window.MarkAsEdited();
}); });
properties.Property("Texture Group", textureGroup, new TextureGroupEditor(), "The texture group used by this texture."); 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();
} }
} }
/// <summary> /// <summary>
/// The texture properties proxy object. /// The texture import properties proxy object.
/// </summary> /// </summary>
[CustomEditor(typeof(ProxyEditor))] [CustomEditor(typeof(ProxyEditor))]
private sealed class PropertiesProxy private sealed class ImportPropertiesProxy : PropertiesProxyBase
{ {
internal TextureWindow _window;
[EditorOrder(1000), EditorDisplay("Import Settings", EditorDisplayAttribute.InlineStyle)] [EditorOrder(1000), EditorDisplay("Import Settings", EditorDisplayAttribute.InlineStyle)]
public FlaxEngine.Tools.TextureTool.Options ImportSettings = new(); public FlaxEngine.Tools.TextureTool.Options ImportSettings = new();
@@ -76,10 +97,9 @@ namespace FlaxEditor.Windows.Assets
/// Gathers parameters from the specified texture. /// Gathers parameters from the specified texture.
/// </summary> /// </summary>
/// <param name="window">The asset window.</param> /// <param name="window">The asset window.</param>
public void OnLoad(TextureWindow window) public override void OnLoad(TextureWindow window)
{ {
// Link base.OnLoad(window);
_window = window;
// Try to restore target asset texture import options (useful for fast reimport) // Try to restore target asset texture import options (useful for fast reimport)
Editor.TryRestoreImportOptions(ref ImportSettings, window.Item.Path); Editor.TryRestoreImportOptions(ref ImportSettings, window.Item.Path);
@@ -110,21 +130,84 @@ namespace FlaxEditor.Windows.Assets
{ {
} }
/// <summary> private sealed class ProxyEditor : GenericEditor
/// Clears temporary data.
/// </summary>
public void OnClean()
{ {
// Unlink public override void Initialize(LayoutElementsContainer layout)
_window = null; {
// 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
{
/// <summary>
/// The presenter to use in the tab.
/// </summary>
public CustomEditorPresenter Presenter;
/// <summary>
/// The proxy to use in the tab.
/// </summary>
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;
}
/// <inheritdoc />
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 SplitPanel _split;
private readonly TexturePreview _preview; private readonly TexturePreview _preview;
private readonly CustomEditorPresenter _propertiesEditor;
private readonly ToolStripButton _saveButton; private readonly ToolStripButton _saveButton;
private readonly PropertiesProxy _properties;
private bool _isWaitingForLoad; private bool _isWaitingForLoad;
/// <inheritdoc /> /// <inheritdoc />
@@ -146,11 +229,19 @@ namespace FlaxEditor.Windows.Assets
Parent = _split.Panel1 Parent = _split.Panel1
}; };
// Texture properties editor // Properties tabs
_propertiesEditor = new CustomEditorPresenter(null); _tabs = new()
_propertiesEditor.Panel.Parent = _split.Panel2; {
_properties = new PropertiesProxy(); AnchorPreset = AnchorPresets.StretchAll,
_propertiesEditor.Select(_properties); Offsets = Margin.Zero,
TabsSize = new Float2(60, 20),
TabsTextHorizontalAlignment = TextAlignment.Center,
UseScroll = true,
Parent = _split.Panel2
};
_tabs.AddTab(new TextureTab(this));
_tabs.AddTab(new ImportTab(this));
// Toolstrip // Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save");
@@ -164,7 +255,11 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc /> /// <inheritdoc />
protected override void UnlinkItem() 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; _preview.Asset = null;
_isWaitingForLoad = false; _isWaitingForLoad = false;
@@ -195,15 +290,6 @@ namespace FlaxEditor.Windows.Assets
base.UpdateToolstrip(); base.UpdateToolstrip();
} }
/// <inheritdoc />
protected override void OnClose()
{
// Discard unsaved changes
_properties.DiscardChanges();
base.OnClose();
}
/// <inheritdoc /> /// <inheritdoc />
public override void Save() public override void Save()
{ {
@@ -231,8 +317,14 @@ namespace FlaxEditor.Windows.Assets
_isWaitingForLoad = false; _isWaitingForLoad = false;
// Init properties and parameters proxy // Init properties and parameters proxy
_properties.OnLoad(this); foreach (var child in _tabs.Children)
_propertiesEditor.BuildLayout(); {
if (child is Tab tab && tab.Proxy != null)
{
tab.Proxy.OnLoad(this);
tab.Presenter.BuildLayout();
}
}
// Setup // Setup
ClearEditedFlag(); ClearEditedFlag();

View File

@@ -139,8 +139,8 @@ namespace FlaxEditor.Windows.Search
{ {
var item = items[i]; var item = items[i];
SearchItem searchItem; SearchItem searchItem;
if (item.Item is AssetItem assetItem) if (item.Item is ContentItem contentItem)
searchItem = new AssetSearchItem(item.Name, item.Type, assetItem, this, itemsWidth, itemHeight); searchItem = new ContentSearchItem(item.Name, item.Type, contentItem, this, itemsWidth, itemHeight);
else else
searchItem = new SearchItem(item.Name, item.Type, item.Item, this, itemsWidth, itemHeight); searchItem = new SearchItem(item.Name, item.Type, item.Item, this, itemsWidth, itemHeight);
searchItem.Y = i * itemHeight; searchItem.Y = i * itemHeight;

View File

@@ -113,17 +113,17 @@ namespace FlaxEditor.Windows.Search
} }
/// <summary> /// <summary>
/// The <see cref="SearchItem"/> for assets. Supports using asset thumbnail. /// The <see cref="SearchItem"/> for assets. Supports using content item thumbnail.
/// </summary> /// </summary>
/// <seealso cref="FlaxEditor.Windows.Search.SearchItem" /> /// <seealso cref="FlaxEditor.Windows.Search.SearchItem" />
/// <seealso cref="FlaxEditor.Content.IContentItemOwner" /> /// <seealso cref="FlaxEditor.Content.IContentItemOwner" />
internal class AssetSearchItem : SearchItem, IContentItemOwner internal class ContentSearchItem : SearchItem, IContentItemOwner
{ {
private AssetItem _asset; private ContentItem _asset;
private FlaxEditor.GUI.ContextMenu.ContextMenu _cm; private ContextMenu _cm;
/// <inheritdoc /> /// <inheritdoc />
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) : base(name, type, item, finder, width, height)
{ {
_asset = item; _asset = item;
@@ -136,31 +136,41 @@ namespace FlaxEditor.Windows.Search
/// <inheritdoc /> /// <inheritdoc />
public override bool OnShowTooltip(out string text, out Float2 location, out Rectangle area) 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(); contentItem.UpdateTooltipText();
TooltipText = assetItem.TooltipText; TooltipText = contentItem.TooltipText;
} }
return base.OnShowTooltip(out text, out location, out area); return base.OnShowTooltip(out text, out location, out area);
} }
/// <inheritdoc />
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;
}
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button) public override bool OnMouseUp(Float2 location, MouseButton button)
{ {
if (base.OnMouseUp(location, button)) if (base.OnMouseUp(location, button))
return true; return true;
if (button == MouseButton.Right && Item is AssetItem assetItem) if (button == MouseButton.Right && Item is ContentItem contentItem)
{ {
// Show context menu // Show context menu
var proxy = Editor.Instance.ContentDatabase.GetProxy(assetItem); var proxy = Editor.Instance.ContentDatabase.GetProxy(contentItem);
ContextMenuButton b; 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)); b = cm.AddButton("Open", () => Editor.Instance.ContentFinding.Open(Item));
cm.AddSeparator(); cm.AddSeparator();
cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(assetItem.Path))); cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(contentItem.Path)));
cm.AddButton("Show in Content window", () => Editor.Instance.Windows.ContentWin.Select(assetItem, true)); cm.AddButton("Show in Content window", () => Editor.Instance.Windows.ContentWin.Select(contentItem, true));
b.Enabled = proxy != null && proxy.CanReimport(assetItem); b.Enabled = proxy != null && proxy.CanReimport(contentItem);
if (assetItem is BinaryAssetItem binaryAsset) if (contentItem is BinaryAssetItem binaryAsset)
{ {
if (!binaryAsset.GetImportPath(out string importPath)) if (!binaryAsset.GetImportPath(out string importPath))
{ {
@@ -172,14 +182,17 @@ namespace FlaxEditor.Windows.Search
} }
} }
cm.AddSeparator(); cm.AddSeparator();
if (contentItem is AssetItem assetItem)
{
cm.AddButton("Copy asset ID", () => Clipboard.Text = FlaxEngine.Json.JsonSerializer.GetStringID(assetItem.ID)); 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("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("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 name to Clipboard", () => Clipboard.Text = assetItem.NamePath);
cm.AddButton("Copy path to Clipboard", () => Clipboard.Text = assetItem.Path); cm.AddButton("Copy path to Clipboard", () => Clipboard.Text = assetItem.Path);
cm.AddSeparator();
}
proxy?.OnContentWindowContextMenu(cm, contentItem);
contentItem.OnContextMenu(cm);
cm.Show(this, location); cm.Show(this, location);
_cm = cm; _cm = cm;
return true; return true;

View File

@@ -271,8 +271,17 @@ public:
public: public:
/// <summary> /// <summary>
/// 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. /// 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]
/// </summary> /// </summary>
API_PROPERTY() FORCE_INLINE bool IsActuallyPlayingSth() const API_PROPERTY() DEPRECATED FORCE_INLINE bool IsActuallyPlayingSth() const
{
return _isActuallyPlayingSth;
}
/// <summary>
/// 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.
/// </summary>
API_PROPERTY() FORCE_INLINE bool IsActuallyPlaying() const
{ {
return _isActuallyPlayingSth; return _isActuallyPlayingSth;
} }

View File

@@ -14,6 +14,17 @@ namespace FlaxEngine
/// </summary> /// </summary>
public object Instance => _instance ?? (_instance = CreateInstance()); public object Instance => _instance ?? (_instance = CreateInstance());
/// <summary>
/// Gets the instance of the serialized object from the json data. Cached internally.
/// </summary>
/// <returns>The asset instance object or null.</returns>
public T GetInstance<T>()
{
if (Instance is T instance)
return instance;
return default;
}
/// <summary> /// <summary>
/// Creates a new instance of the serialized object from the json asset data. /// Creates a new instance of the serialized object from the json asset data.
/// </summary> /// </summary>

View File

@@ -33,6 +33,15 @@ namespace FlaxEngine
Asset = asset; Asset = asset;
} }
/// <summary>
/// Gets the deserialized native object instance of the given type. Returns null if asset is not loaded or loaded object has different type.
/// </summary>
/// <returns>The asset instance object or null.</returns>
public U GetInstance<U>()
{
return Asset ? Asset.GetInstance<U>() : default(U);
}
/// <summary> /// <summary>
/// Implicit cast operator. /// Implicit cast operator.
/// </summary> /// </summary>

View File

@@ -18,7 +18,7 @@ namespace FlaxEditor.Content.Settings
/// <summary> /// <summary>
/// The layers names. /// The layers names.
/// </summary> /// </summary>
[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]; public string[] Layers = new string[32];
/// <summary> /// <summary>

View File

@@ -294,6 +294,10 @@ Quaternion Quaternion::FromDirection(const Float3& direction)
{ {
RotationAxis(Float3::Left, PI_OVER_2, orientation); RotationAxis(Float3::Left, PI_OVER_2, orientation);
} }
else if (Float3::Dot(direction, Float3::Down) >= 0.999f)
{
RotationAxis(Float3::Right, PI_OVER_2, orientation);
}
else else
{ {
Float3 right, up; Float3 right, up;

View File

@@ -654,6 +654,10 @@ namespace FlaxEngine
{ {
orientation = RotationAxis(Float3.Left, Mathf.PiOverTwo); orientation = RotationAxis(Float3.Left, Mathf.PiOverTwo);
} }
else if (Float3.Dot(direction, Float3.Down) >= 0.999f)
{
orientation = RotationAxis(Float3.Right, Mathf.PiOverTwo);
}
else else
{ {
var right = Float3.Cross(direction, Float3.Up); var right = Float3.Cross(direction, Float3.Up);

View File

@@ -19,6 +19,7 @@ CharacterController::CharacterController(const SpawnParams& params)
, _minMoveDistance(0.0f) , _minMoveDistance(0.0f)
, _isUpdatingTransform(false) , _isUpdatingTransform(false)
, _upDirection(Vector3::Up) , _upDirection(Vector3::Up)
, _gravityDisplacement(Vector3::Zero)
, _nonWalkableMode(NonWalkableModes::PreventClimbing) , _nonWalkableMode(NonWalkableModes::PreventClimbing)
, _lastFlags(CollisionFlags::None) , _lastFlags(CollisionFlags::None)
{ {
@@ -148,10 +149,16 @@ CharacterController::CollisionFlags CharacterController::GetFlags() const
CharacterController::CollisionFlags CharacterController::SimpleMove(const Vector3& speed) CharacterController::CollisionFlags CharacterController::SimpleMove(const Vector3& speed)
{ {
const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds(); const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds();
Vector3 displacement = speed; Vector3 displacement = speed + _gravityDisplacement;
displacement += GetPhysicsScene()->GetGravity() * deltaTime; CollisionFlags result = Move(displacement * deltaTime);
displacement *= deltaTime; if ((static_cast<int>(result) & static_cast<int>(CollisionFlags::Below)) != 0)
return Move(displacement); {
// 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) CharacterController::CollisionFlags CharacterController::Move(const Vector3& displacement)

View File

@@ -66,6 +66,7 @@ private:
float _minMoveDistance; float _minMoveDistance;
bool _isUpdatingTransform; bool _isUpdatingTransform;
Vector3 _upDirection; Vector3 _upDirection;
Vector3 _gravityDisplacement;
NonWalkableModes _nonWalkableMode; NonWalkableModes _nonWalkableMode;
CollisionFlags _lastFlags; CollisionFlags _lastFlags;

View File

@@ -133,7 +133,13 @@ void MeshCollider::GetGeometry(CollisionShape& collision)
// Prepare scale // Prepare scale
Float3 scale = _cachedScale; Float3 scale = _cachedScale;
const float minSize = 0.001f; 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) // Setup shape (based on type)
CollisionDataType type = CollisionDataType::None; CollisionDataType type = CollisionDataType::None;

View File

@@ -321,7 +321,12 @@ class CharacterControllerHitReportPhysX : public PxUserControllerHitReport
{ {
void onHit(const PxControllerHit& hit, Collision& c) 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.Impulse = Vector3::Zero;
c.ThisVelocity = P2C(hit.dir) * hit.length; c.ThisVelocity = P2C(hit.dir) * hit.length;
c.OtherVelocity = Vector3::Zero; c.OtherVelocity = Vector3::Zero;
@@ -564,6 +569,7 @@ namespace
Array<PxBase*> DeleteObjects; Array<PxBase*> DeleteObjects;
bool _queriesHitTriggers = true; bool _queriesHitTriggers = true;
bool _enableCCD = true;
PhysicsCombineMode _frictionCombineMode = PhysicsCombineMode::Average; PhysicsCombineMode _frictionCombineMode = PhysicsCombineMode::Average;
PhysicsCombineMode _restitutionCombineMode = PhysicsCombineMode::Average; PhysicsCombineMode _restitutionCombineMode = PhysicsCombineMode::Average;
@@ -697,6 +703,8 @@ PxFilterFlags FilterShader(
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_LOST; pairFlags |= PxPairFlag::eNOTIFY_TOUCH_LOST;
pairFlags |= PxPairFlag::ePOST_SOLVER_VELOCITY; pairFlags |= PxPairFlag::ePOST_SOLVER_VELOCITY;
pairFlags |= PxPairFlag::eNOTIFY_CONTACT_POINTS; pairFlags |= PxPairFlag::eNOTIFY_CONTACT_POINTS;
if (_enableCCD)
pairFlags |= PxPairFlag::eDETECT_CCD_CONTACT;
return PxFilterFlag::eDEFAULT; return PxFilterFlag::eDEFAULT;
} }
@@ -1220,6 +1228,7 @@ void PhysicsBackend::Shutdown()
void PhysicsBackend::ApplySettings(const PhysicsSettings& settings) void PhysicsBackend::ApplySettings(const PhysicsSettings& settings)
{ {
_queriesHitTriggers = settings.QueriesHitTriggers; _queriesHitTriggers = settings.QueriesHitTriggers;
_enableCCD = !settings.DisableCCD;
_frictionCombineMode = settings.FrictionCombineMode; _frictionCombineMode = settings.FrictionCombineMode;
_restitutionCombineMode = settings.RestitutionCombineMode; _restitutionCombineMode = settings.RestitutionCombineMode;
@@ -1256,6 +1265,8 @@ void* PhysicsBackend::CreateScene(const PhysicsSettings& settings)
sceneDesc.simulationEventCallback = &scenePhysX->EventsCallback; sceneDesc.simulationEventCallback = &scenePhysX->EventsCallback;
sceneDesc.filterShader = FilterShader; sceneDesc.filterShader = FilterShader;
sceneDesc.bounceThresholdVelocity = settings.BounceThresholdVelocity; sceneDesc.bounceThresholdVelocity = settings.BounceThresholdVelocity;
if (settings.EnableEnhancedDeterminism)
sceneDesc.flags |= PxSceneFlag::eENABLE_ENHANCED_DETERMINISM;
switch (settings.SolverType) switch (settings.SolverType)
{ {
case PhysicsSolverType::ProjectedGaussSeidelIterativeSolver: case PhysicsSolverType::ProjectedGaussSeidelIterativeSolver:

View File

@@ -94,10 +94,16 @@ public:
API_FIELD(Attributes="EditorOrder(71), EditorDisplay(\"Simulation\")") API_FIELD(Attributes="EditorOrder(71), EditorDisplay(\"Simulation\")")
PhysicsBroadPhaseType BroadPhaseType = PhysicsBroadPhaseType::ParallelAutomaticBoxPruning; PhysicsBroadPhaseType BroadPhaseType = PhysicsBroadPhaseType::ParallelAutomaticBoxPruning;
/// <summary>
/// Enables enhanced determinism in the simulation. This has a performance impact.
/// </summary>
API_FIELD(Attributes="EditorOrder(71), EditorDisplay(\"Simulation\")")
bool EnableEnhancedDeterminism = false;
/// <summary> /// <summary>
/// The solver type to use in the simulation. /// The solver type to use in the simulation.
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(72), EditorDisplay(\"Simulation\")") API_FIELD(Attributes="EditorOrder(73), EditorDisplay(\"Simulation\")")
PhysicsSolverType SolverType = PhysicsSolverType::ProjectedGaussSeidelIterativeSolver; PhysicsSolverType SolverType = PhysicsSolverType::ProjectedGaussSeidelIterativeSolver;
/// <summary> /// <summary>

View File

@@ -130,6 +130,7 @@ namespace FlaxEngine.GUI
{ {
// Clear flag // Clear flag
_splitterClicked = false; _splitterClicked = false;
PerformLayout();
// End capturing mouse // End capturing mouse
EndMouseCapture(); EndMouseCapture();