diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
index 9844f3fda..1a308e08a 100644
--- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
@@ -739,6 +739,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
///
public override void Initialize(LayoutElementsContainer layout)
{
+ var style = FlaxEngine.GUI.Style.Current;
+
// Area for drag&drop scripts
var dragArea = layout.CustomContainer();
dragArea.CustomControl.ScriptsEditor = this;
@@ -800,17 +802,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
bool hasAllRequirements = true;
if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false))
{
- RequireScriptAttribute scriptAttribute = null;
- foreach (var e in scriptType.GetAttributes(false))
+ var attribute = (RequireScriptAttribute)scriptType.GetAttributes(false).FirstOrDefault(x => x is RequireScriptAttribute);
+ if (attribute != null)
{
- if (e is not RequireScriptAttribute requireScriptAttribute)
- continue;
- scriptAttribute = requireScriptAttribute;
- }
-
- if (scriptAttribute != null)
- {
- foreach (var type in scriptAttribute.RequiredTypes)
+ foreach (var type in attribute.RequiredTypes)
{
if (!type.IsSubclassOf(typeof(Script)))
continue;
@@ -825,15 +820,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
if (scriptType.HasAttribute(typeof(RequireActorAttribute), false))
{
- RequireActorAttribute attribute = null;
- foreach (var e in scriptType.GetAttributes(false))
- {
- if (e is not RequireActorAttribute requireActorAttribute)
- continue;
- attribute = requireActorAttribute;
- break;
- }
-
+ var attribute = (RequireActorAttribute)scriptType.GetAttributes(false).FirstOrDefault(x => x is RequireActorAttribute);
if (attribute != null)
{
var actor = script.Actor;
@@ -850,7 +837,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
var title = Utilities.Utils.GetPropertyNameUI(scriptType.Name);
var group = layout.Group(title, editor);
if (!hasAllRequirements)
- group.Panel.HeaderTextColor = FlaxEngine.GUI.Style.Current.Statusbar.Failed;
+ group.Panel.HeaderTextColor = style.Statusbar.Failed;
if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
{
if (Editor.Instance.ProjectCache.IsGroupToggled(title))
@@ -863,9 +850,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
group.Panel.Open();
// Customize
+ float totalHeaderButtonsOffset = 0f;
group.Panel.TooltipText = Editor.Instance.CodeDocs.GetTooltip(scriptType);
if (script.HasPrefabLink)
- group.Panel.HeaderTextColor = FlaxEngine.GUI.Style.Current.ProgressNormal;
+ group.Panel.HeaderTextColor = style.ProgressNormal;
// Add toggle button to the group
var headerHeight = group.Panel.HeaderHeight;
@@ -889,7 +877,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
TooltipText = "Script reference.",
AutoFocus = true,
IsScrollable = false,
- Color = FlaxEngine.GUI.Style.Current.ForegroundGrey,
+ Color = style.ForegroundGrey,
Parent = group.Panel,
Bounds = new Rectangle(scriptToggle.Right, 0.5f, headerHeight, headerHeight),
Margin = new Margin(1),
@@ -908,10 +896,35 @@ namespace FlaxEditor.CustomEditors.Dedicated
var settingsButton = group.AddSettingsButton();
settingsButton.Tag = script;
settingsButton.Clicked += OnSettingsButtonClicked;
+ totalHeaderButtonsOffset += settingsButton.Width + FlaxEditor.Utilities.Constants.UIMargin;
+
+ // Add script obsolete icon to the group
+ if (scriptType.HasAttribute(typeof(ObsoleteAttribute), false))
+ {
+ var attribute = (ObsoleteAttribute)scriptType.GetAttributes(false).First(x => x is ObsoleteAttribute);
+ var tooltip = "Script marked as obsolete." +
+ (string.IsNullOrEmpty(attribute.Message) ? "" : $"\n{attribute.Message}") +
+ (string.IsNullOrEmpty(attribute.DiagnosticId) ? "" : $"\n{attribute.DiagnosticId}");
+ var obsoleteButton = group.AddHeaderButton(tooltip, totalHeaderButtonsOffset, Editor.Instance.Icons.Info32);
+ obsoleteButton.Color = Color.Orange;
+ obsoleteButton.MouseOverColor = Color.DarkOrange;
+ totalHeaderButtonsOffset += obsoleteButton.Width;
+ }
+
+ // Show visual indicator if script only exists in prefab instance and is not part of the prefab
+ bool isPrefabActor = scripts.Any(s => s.Actor.HasPrefabLink);
+ if (isPrefabActor && script.PrefabID == Guid.Empty)
+ {
+ var prefabInstanceButton = group.AddHeaderButton("Script only exists in this prefab instance.", totalHeaderButtonsOffset, Editor.Instance.Icons.Add32);
+ prefabInstanceButton.Color = style.ProgressNormal;
+ prefabInstanceButton.MouseOverColor = style.ProgressNormal * 0.9f;
+ totalHeaderButtonsOffset += prefabInstanceButton.Width;
+ }
// Adjust margin to not overlap with other ui elements in the header
group.Panel.HeaderTextMargin = group.Panel.HeaderTextMargin with { Left = scriptDrag.Right - 12, Right = settingsButton.Width + Utilities.Constants.UIMargin };
group.Object(values, editor);
+
// Remove drop down arrows and containment lines if no objects in the group
if (group.Children.Count == 0)
{
diff --git a/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs b/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs
index 055c6a29d..a3397d10a 100644
--- a/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs
+++ b/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs
@@ -37,25 +37,35 @@ namespace FlaxEditor.CustomEditors.Elements
public override ContainerControl ContainerControl => Panel;
///
- /// Adds utility settings button to the group header.
+ /// Add utility settings button to the group header.
///
/// The created control.
public Image AddSettingsButton()
+ {
+ return AddHeaderButton("Settings", 0, Style.Current.Settings);
+ }
+
+ ///
+ /// Adds a button to the group header.
+ ///
+ /// The created control.
+ public Image AddHeaderButton(string tooltipText, float xOffset, SpriteHandle sprite)
{
var style = Style.Current;
+ const float padding = 2.0f;
var settingsButtonSize = Panel.HeaderHeight;
Panel.HeaderTextMargin = Panel.HeaderTextMargin with { Right = settingsButtonSize + Utilities.Constants.UIMargin };
; return new Image
{
- TooltipText = "Settings",
+ TooltipText = tooltipText,
AutoFocus = true,
AnchorPreset = AnchorPresets.TopRight,
Parent = Panel,
- Bounds = new Rectangle(Panel.Width - settingsButtonSize, 0, settingsButtonSize, settingsButtonSize),
+ Bounds = new Rectangle(Panel.Width - settingsButtonSize - xOffset, padding * 0.5f, settingsButtonSize - padding, settingsButtonSize - padding),
IsScrollable = false,
Color = style.ForegroundGrey,
Margin = new Margin(1),
- Brush = new SpriteBrush(style.Settings),
+ Brush = new SpriteBrush(sprite),
};
}
}
diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs
index dd38ea4ee..ad6b5f26f 100644
--- a/Source/Editor/Options/InterfaceOptions.cs
+++ b/Source/Editor/Options/InterfaceOptions.cs
@@ -161,7 +161,7 @@ namespace FlaxEditor.Options
}
///
- /// Options focus Game Window behaviour when play mode is entered.
+ /// Options for focus Game Window behaviour when play mode is entered.
///
public enum PlayModeFocus
{
@@ -209,6 +209,22 @@ namespace FlaxEditor.Options
ClientSide,
}
+ ///
+ /// Generic options for a disabled or hidden state. Used for example in create content button.
+ ///
+ public enum DisabledHidden
+ {
+ ///
+ /// Disabled state.
+ ///
+ Disabled,
+
+ ///
+ /// Hidden state.
+ ///
+ Hidden,
+ }
+
///
/// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required.
///
@@ -562,6 +578,13 @@ namespace FlaxEditor.Options
[EditorDisplay("Visject", "Warn when deleting used parameter"), EditorOrder(552)]
public bool WarnOnDeletingUsedVisjectParameter { get; set; } = true;
+ ///
+ /// Gets or sets a value indicating what should happen to unavaliable options in the content create menu.
+ ///
+ [DefaultValue(DisabledHidden.Hidden)]
+ [EditorDisplay("Content"), EditorOrder(600)]
+ public DisabledHidden UnavaliableContentCreateOptions { get; set; } = DisabledHidden.Hidden;
+
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont);
private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont);
diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs
index 6500b2955..56f594586 100644
--- a/Source/Editor/Surface/Archetypes/Material.cs
+++ b/Source/Editor/Surface/Archetypes/Material.cs
@@ -586,8 +586,9 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 13,
Title = "Pre-skinned Local Position",
Description = "Per vertex local position (before skinning)",
+ AlternativeTitles = new[] { "Vertex Position", "Pre skinning Local Vertex Position" },
Flags = NodeFlags.MaterialGraph,
- Size = new Float2(230, 40),
+ Size = new Float2(270, 40),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 0),
@@ -598,8 +599,9 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 14,
Title = "Pre-skinned Local Normal",
Description = "Per vertex local normal (before skinning)",
+ AlternativeTitles = new[] { "Vertex Normal", "Pre skinning Local Normal" },
Flags = NodeFlags.MaterialGraph,
- Size = new Float2(230, 40),
+ Size = new Float2(270, 40),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 0),
diff --git a/Source/Editor/Surface/Archetypes/Particles.cs b/Source/Editor/Surface/Archetypes/Particles.cs
index 5b8c05381..59af7af5e 100644
--- a/Source/Editor/Surface/Archetypes/Particles.cs
+++ b/Source/Editor/Surface/Archetypes/Particles.cs
@@ -436,10 +436,11 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype
{
TypeID = 102,
- Title = "Particle Lifetime",
- Description = "Particle lifetime (in seconds).",
+ Title = "Particle Total Lifetime",
+ Description = "Total particle lifetime (in seconds) at the time when the particle was created. Always the same, no matter the particles age.",
+ AlternativeTitles = new[] { "Age" },
Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph,
- Size = new Float2(200, 30),
+ Size = new Float2(250, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0),
@@ -449,9 +450,10 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 103,
Title = "Particle Age",
- Description = "Particle age (in seconds).",
+ Description = "Particle age (in seconds). How long the particle has been alive since it was created.",
+ AlternativeTitles = new[] { "Lifetime" },
Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph,
- Size = new Float2(200, 30),
+ Size = new Float2(170, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0),
@@ -533,9 +535,10 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 110,
Title = "Particle Normalized Age",
- Description = "Particle normalized age to range 0-1 (age divided by lifetime).",
+ Description = "The normalized age of the particle, represented as 0 (max lifetime) to 1 (max age). (Same as age divided by lifetime.)",
+ AlternativeTitles = new[] { "Lifetime" },
Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph,
- Size = new Float2(230, 30),
+ Size = new Float2(250, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0),
diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs
index 7c2cb23ad..3151f9b17 100644
--- a/Source/Editor/Surface/VisjectSurfaceWindow.cs
+++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs
@@ -708,12 +708,30 @@ namespace FlaxEditor.Surface
{
var index = (int)label.Tag;
menu.AddSeparator();
+ menu.AddButton("Copy name", () => Clipboard.Text = ((IVisjectSurfaceWindow)Values[0]).VisjectSurface.Parameters[index].Name);
+ // TODO: move 'Copy all names' to context menu of the Properties category (as it's not item-specific)
+ menu.AddButton("Copy all names", CopyAllParameterNamesAsConstantCSharpCode);
+ menu.AddSeparator();
menu.AddButton("Rename", () => StartParameterRenaming(index, label));
menu.AddButton("Edit attributes...", () => EditAttributesParameter(index, label));
menu.AddButton("Delete", () => DeleteParameter(index));
OnParamContextMenu(index, menu);
}
+ private void CopyAllParameterNamesAsConstantCSharpCode()
+ {
+ string allParamNames = "";
+ foreach (var param in ((IVisjectSurfaceWindow)Values[0]).VisjectSurface.Parameters)
+ {
+ string cleanParamName = param.Name.Replace(" ", "");
+ // Filter out headers and other non-parameter entries that can be present in the parameters list
+ if (string.IsNullOrEmpty(cleanParamName))
+ continue;
+ allParamNames += $"private const string {cleanParamName}ParameterName = \"{param.Name}\";\n";
+ }
+ Clipboard.Text = allParamNames;
+ }
+
private void StartParameterRenaming(int index, Control label)
{
var window = (IVisjectSurfaceWindow)Values[0];
diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
index 1ad225997..40dd9131b 100644
--- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
@@ -39,8 +39,7 @@ namespace FlaxEditor.Windows
folder = CurrentViewFolder;
}
Assert.IsNotNull(folder);
- bool isRootFolder = CurrentViewFolder == _root.Folder;
-
+
// Create context menu
ContextMenuButton b;
ContextMenu cm = new ContextMenu
@@ -78,7 +77,7 @@ namespace FlaxEditor.Windows
cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(item.Path)));
if (!String.IsNullOrEmpty(Editor.Instance.Windows.ContentWin._itemsSearchBox.Text))
- {
+ {
cm.AddButton("Show in Content Panel", () =>
{
Editor.Instance.Windows.ContentWin.ClearItemsSearch();
@@ -178,19 +177,61 @@ namespace FlaxEditor.Windows
cm.AddSeparator();
+ CreateNewModuleMenu(cm, folder);
+ CreateNewFolderMenu(cm, folder, false, item);
+ CreateNewContentItemMenu(cm, folder);
+
+ if (folder.CanHaveAssets)
+ {
+ cm.AddButton("Import file", () =>
+ {
+ _view.ClearSelection();
+ Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder);
+ });
+ }
+
+ // Remove any leftover separator
+ if (cm.ItemsContainer.Children.LastOrDefault() is ContextMenuSeparator)
+ cm.ItemsContainer.Children.Last().Dispose();
+
+ // Show it
+ cm.Show(this, location);
+ }
+
+ private void CreateNewModuleMenu(ContextMenu menu, ContentFolder folder, bool disableUncreatable = false)
+ {
// Check if is source folder to add new module
if (folder?.ParentFolder?.Node is ProjectTreeNode parentFolderNode && folder.Node == parentFolderNode.Source)
{
- var button = cm.AddButton("New module");
+ var button = menu.AddButton("New module");
button.CloseMenuOnClick = false;
button.Clicked += () => NewModule(button, parentFolderNode.Source.Path);
}
-
- if (!isRootFolder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode))
+ else if (disableUncreatable)
{
- cm.AddButton("New folder", NewFolder);
+ var button = menu.AddButton("New module");
+ button.Enabled = false;
}
+ }
+ private bool CanCreateFolder(ContentItem item = null)
+ {
+ bool canCreateFolder = CurrentViewFolder != _root.Folder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode);
+ return canCreateFolder;
+ }
+
+ private void CreateNewFolderMenu(ContextMenu menu, ContentFolder folder, bool disableUncreatable = false, ContentItem item = null)
+ {
+ bool canCreateFolder = CanCreateFolder(item);
+ if (canCreateFolder || disableUncreatable)
+ {
+ var b = menu.AddButton("New folder", NewFolder);
+ b.Enabled = canCreateFolder;
+ }
+ }
+
+ private void CreateNewContentItemMenu(ContextMenu menu, ContentFolder folder, bool showNew = true, bool disableUncreatable = false)
+ {
// Loop through each proxy and user defined json type and add them to the context menu
var actorType = new ScriptType(typeof(Actor));
var scriptType = new ScriptType(typeof(Script));
@@ -230,7 +271,8 @@ namespace FlaxEditor.Windows
if (p == null)
continue;
- if (p.CanCreate(folder))
+ bool canCreate = p.CanCreate(folder);
+ if (canCreate || disableUncreatable)
{
var parts = attribute.Path.Split('/');
ContextMenuChildMenu childCM = null;
@@ -238,16 +280,20 @@ namespace FlaxEditor.Windows
for (int i = 0; i < parts?.Length; i++)
{
var part = parts[i].Trim();
+ if (part == "New" && !showNew)
+ continue;
if (i == parts.Length - 1)
{
if (mainCM)
{
- cm.AddButton(part, () => NewItem(p));
+ var b = menu.AddButton(part, () => NewItem(p));
+ b.Enabled = canCreate;
mainCM = false;
}
else if (childCM != null)
{
- childCM.ContextMenu.AddButton(part, () => NewItem(p));
+ var b = childCM.ContextMenu.AddButton(part, () => NewItem(p));
+ b.Enabled = canCreate;
childCM.ContextMenu.AutoSort = true;
}
}
@@ -255,35 +301,21 @@ namespace FlaxEditor.Windows
{
if (mainCM)
{
- childCM = cm.GetOrAddChildMenu(part);
+ childCM = menu.GetOrAddChildMenu(part);
childCM.ContextMenu.AutoSort = true;
+ childCM.Enabled = canCreate;
mainCM = false;
}
else if (childCM != null)
{
childCM = childCM.ContextMenu.GetOrAddChildMenu(part);
childCM.ContextMenu.AutoSort = true;
+ childCM.Enabled = canCreate;
}
}
}
}
}
-
- if (folder.CanHaveAssets)
- {
- cm.AddButton("Import file", () =>
- {
- _view.ClearSelection();
- Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder);
- });
- }
-
- // Remove any leftover separator
- if (cm.ItemsContainer.Children.LastOrDefault() is ContextMenuSeparator)
- cm.ItemsContainer.Children.Last().Dispose();
-
- // Show it
- cm.Show(this, location);
}
private void OnExpandAllClicked(ContextMenuButton button)
diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs
index 41382e60c..9b92b380f 100644
--- a/Source/Editor/Windows/ContentWindow.cs
+++ b/Source/Editor/Windows/ContentWindow.cs
@@ -37,6 +37,7 @@ namespace FlaxEditor.Windows
private readonly ToolStrip _toolStrip;
private readonly ToolStripButton _importButton;
+ private readonly ToolStripButton _createNewButton;
private readonly ToolStripButton _navigateBackwardButton;
private readonly ToolStripButton _navigateForwardButton;
private readonly ToolStripButton _navigateUpButton;
@@ -154,11 +155,12 @@ namespace FlaxEditor.Windows
{
Parent = this,
};
- _importButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder)).LinkTooltip("Import content");
+ _importButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder)).LinkTooltip("Import content.");
+ _createNewButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Add64, OnCreateNewItemButtonClicked).LinkTooltip("Create a new asset. Shift + left click to create a new folder.");
_toolStrip.AddSeparator();
- _navigateBackwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Left64, NavigateBackward).LinkTooltip("Navigate backward");
- _navigateForwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Right64, NavigateForward).LinkTooltip("Navigate forward");
- _navigateUpButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Up64, NavigateUp).LinkTooltip("Navigate up");
+ _navigateBackwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Left64, NavigateBackward).LinkTooltip("Navigate backward.");
+ _navigateForwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Right64, NavigateForward).LinkTooltip("Navigate forward.");
+ _navigateUpButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Up64, NavigateUp).LinkTooltip("Navigate up.");
_toolStrip.AddSeparator();
// Navigation bar
@@ -271,6 +273,42 @@ namespace FlaxEditor.Windows
InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus());
}
+ private void OnCreateNewItemButtonClicked()
+ {
+ if (Input.GetKey(KeyboardKeys.Shift) && CanCreateFolder())
+ {
+ NewFolder();
+ return;
+ }
+
+ var menu = new ContextMenu();
+
+ InterfaceOptions interfaceOptions = Editor.Instance.Options.Options.Interface;
+ bool disableUnavaliable = interfaceOptions.UnavaliableContentCreateOptions == InterfaceOptions.DisabledHidden.Disabled;
+
+ CreateNewFolderMenu(menu, CurrentViewFolder, disableUnavaliable);
+ CreateNewModuleMenu(menu, CurrentViewFolder, disableUnavaliable);
+ menu.AddSeparator();
+ CreateNewContentItemMenu(menu, CurrentViewFolder, false, disableUnavaliable);
+ // Hack: Show the menu once to get the direction, then show it above or below the button depending on the direction.
+ menu.Show(this, _createNewButton.UpperLeft);
+ var direction = menu.Direction;
+ menu.Hide();
+ bool below = false;
+ switch (direction)
+ {
+ case ContextMenuDirection.RightDown:
+ case ContextMenuDirection.LeftDown:
+ below = true;
+ break;
+ case ContextMenuDirection.RightUp:
+ case ContextMenuDirection.LeftUp:
+ below = false;
+ break;
+ }
+ menu.Show(this, below ? _createNewButton.BottomLeft : _createNewButton.UpperLeft, direction);
+ }
+
private ContextMenu OnViewDropdownPopupCreate(ComboBox comboBox)
{
var menu = new ContextMenu();
diff --git a/Source/Engine/Audio/AudioSource.h b/Source/Engine/Audio/AudioSource.h
index 58903912c..07762be2f 100644
--- a/Source/Engine/Audio/AudioSource.h
+++ b/Source/Engine/Audio/AudioSource.h
@@ -207,16 +207,16 @@ public:
API_PROPERTY() void SetAttenuation(float value);
///
- /// Gets the doppler effect factor. Scale for source velocity. Default is 1.
+ /// Gets the doppler effect factor. Scale for source velocity. Default is 1. Used by spatial sources only. Cannot scale the effect up (only dim it).
///
- API_PROPERTY(Attributes="EditorOrder(75), DefaultValue(1.0f), Limit(0, float.MaxValue, 0.1f), EditorDisplay(\"Audio Source\")")
+ API_PROPERTY(Attributes="EditorOrder(75), DefaultValue(1.0f), Limit(0, 1.0f, 0.1f), EditorDisplay(\"Audio Source\")")
FORCE_INLINE float GetDopplerFactor() const
{
return _dopplerFactor;
}
///
- /// Sets the doppler effect factor. Scale for source velocity. Default is 1.
+ /// Sets the doppler effect factor. Scale for source velocity. Default is 1. Used by spatial sources only. Cannot scale the effect up (only dim it).
///
API_PROPERTY() void SetDopplerFactor(float value);
diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
index 68ad4007f..f32c34d30 100644
--- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
+++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp
@@ -848,6 +848,13 @@ bool AudioBackendOAL::Base_Init()
}
// Init
+ alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model
+ if (Audio::GetActiveDeviceIndex() == Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1))
+ {
+ // Manually create context if SetActiveDeviceIndex won't call it
+ Base_OnActiveDeviceChanged();
+ }
+ Audio::SetActiveDeviceIndex(activeDeviceIndex);
#ifdef AL_SOFT_source_spatialize
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))
ALC::Features = EnumAddFlags(ALC::Features, FeatureFlags::SpatialMultiChannel);
@@ -856,13 +863,6 @@ bool AudioBackendOAL::Base_Init()
ALC::Features = EnumAddFlags(ALC::Features, FeatureFlags::HRTF);
#endif
Base_SetDopplerFactor(AudioSettings::Get()->DopplerFactor);
- alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model
- if (Audio::GetActiveDeviceIndex() == Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1))
- {
- // Manually create context if SetActiveDeviceIndex won't call it
- Base_OnActiveDeviceChanged();
- }
- Audio::SetActiveDeviceIndex(activeDeviceIndex);
ALC::Inited = true;
// Log service info
diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp
index da9e3b39b..285b9ec72 100644
--- a/Source/Engine/Core/Types/Variant.cpp
+++ b/Source/Engine/Core/Types/Variant.cpp
@@ -246,7 +246,7 @@ VariantType::VariantType(VariantType&& other) noexcept
VariantType& VariantType::operator=(const Types& type)
{
Type = type;
- if (StaticName)
+ if (!StaticName)
Allocator::Free(TypeName);
TypeName = nullptr;
StaticName = 0;
@@ -265,7 +265,7 @@ VariantType& VariantType::operator=(const VariantType& other)
{
ASSERT(this != &other);
Type = other.Type;
- if (StaticName)
+ if (!StaticName)
Allocator::Free(TypeName);
StaticName = other.StaticName;
if (StaticName)
@@ -315,7 +315,7 @@ void VariantType::SetTypeName(const StringView& typeName)
{
if (StringUtils::Length(TypeName) != typeName.Length())
{
- if (StaticName)
+ if (!StaticName)
Allocator::Free(TypeName);
StaticName = 0;
TypeName = static_cast(Allocator::Allocate(typeName.Length() + 1));
@@ -328,7 +328,7 @@ void VariantType::SetTypeName(const StringAnsiView& typeName, bool staticName)
{
if (StringUtils::Length(TypeName) != typeName.Length() || StaticName != staticName)
{
- if (StaticName)
+ if (!StaticName)
Allocator::Free(TypeName);
StaticName = staticName;
if (staticName)
diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp
index 116866848..efe89bf5c 100644
--- a/Source/Engine/Foliage/Foliage.cpp
+++ b/Source/Engine/Foliage/Foliage.cpp
@@ -6,14 +6,13 @@
#include "Engine/Core/Log.h"
#include "Engine/Core/Random.h"
#include "Engine/Engine/Engine.h"
+#include "Engine/Graphics/Graphics.h"
#include "Engine/Graphics/RenderTask.h"
+#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Content/Deprecated.h"
#if !FOLIAGE_USE_SINGLE_QUAD_TREE
#include "Engine/Threading/JobSystem.h"
-#if FOLIAGE_USE_DRAW_CALLS_BATCHING
-#include "Engine/Graphics/RenderTools.h"
-#endif
#endif
#include "Engine/Level/SceneQuery.h"
#include "Engine/Profiler/ProfilerCPU.h"
@@ -125,7 +124,7 @@ void Foliage::AddToCluster(ChunkedArrayLODs.Get()[lod].Meshes;
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
@@ -141,7 +140,7 @@ void Foliage::DrawInstance(RenderContext& renderContext, FoliageInstance& instan
auto* e = result.TryGet(key);
if (!e)
{
- e = &result.Add(key, BatchedDrawCall(renderContext.List))->Value;
+ e = &result.Add(key, BatchedDrawCall(context.RenderContext.List))->Value;
ASSERT_LOW_LAYER(key.Mat);
e->DrawCall.Material = key.Mat;
e->DrawCall.Surface.Lightmap = EnumHasAnyFlags(_staticFlags, StaticFlags::Lightmap) && _scene ? _scene->LightmapsData.GetReadyLightmap(key.Lightmap) : nullptr;
@@ -152,21 +151,18 @@ void Foliage::DrawInstance(RenderContext& renderContext, FoliageInstance& instan
auto& instanceData = e->Instances.AddOne();
Matrix world;
const Transform transform = _transform.LocalToWorld(instance.Transform);
- const Float3 translation = transform.Translation - renderContext.View.Origin;
+ const Float3 translation = transform.Translation - context.ViewOrigin;
Matrix::Transformation(transform.Scale, transform.Orientation, translation, world);
constexpr float worldDeterminantSign = 1.0f;
instanceData.Store(world, world, instance.Lightmap.UVsArea, drawCall.Surface.GeometrySize, instance.Random, worldDeterminantSign, lodDitherFactor);
}
}
-void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, const FoliageType& type, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const
+void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const
{
// Skip clusters that around too far from view
- const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View);
- if (Float3::Distance(lodView->Position, cluster->TotalBoundsSphere.Center - lodView->Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance)
+ if (Float3::Distance(context.LodView.Position, cluster->TotalBoundsSphere.Center - context.LodView.Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance)
return;
- const Vector3 viewOrigin = renderContext.View.Origin;
-
//DebugDraw::DrawBox(cluster->Bounds, Color::Red);
// Draw visible children
@@ -178,10 +174,10 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
BoundingBox box;
#define DRAW_CLUSTER(idx) \
box = cluster->Children[idx]->TotalBounds; \
- box.Minimum -= viewOrigin; \
- box.Maximum -= viewOrigin; \
- if (renderContext.View.CullingFrustum.Intersects(box)) \
- DrawCluster(renderContext, cluster->Children[idx], type, drawCallsLists, result)
+ box.Minimum -= context.ViewOrigin; \
+ box.Maximum -= context.ViewOrigin; \
+ if (context.RenderContext.View.CullingFrustum.Intersects(box)) \
+ DrawCluster(context, cluster->Children[idx], drawCallsLists, result)
DRAW_CLUSTER(0);
DRAW_CLUSTER(1);
DRAW_CLUSTER(2);
@@ -192,21 +188,22 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
{
// Draw visible instances
const auto frame = Engine::FrameCount;
- const auto model = type.Model.Get();
- const auto transitionLOD = renderContext.View.Pass != DrawPass::Depth; // Let the main view pass update LOD transitions
+ const auto model = context.FoliageType.Model.Get();
+ const auto transitionLOD = context.RenderContext.View.Pass != DrawPass::Depth; // Let the main view pass update LOD transitions
// TODO: move DrawState to be stored per-view (so shadows can fade objects on their own)
for (int32 i = 0; i < cluster->Instances.Count(); i++)
{
auto& instance = *cluster->Instances.Get()[i];
BoundingSphere sphere = instance.Bounds;
- sphere.Center -= viewOrigin;
- if (Float3::Distance(lodView->Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance &&
- renderContext.View.CullingFrustum.Intersects(sphere))
+ sphere.Center -= context.ViewOrigin;
+ if (Float3::Distance(context.LodView.Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance &&
+ context.RenderContext.View.CullingFrustum.Intersects(sphere) &&
+ RenderTools::ComputeBoundsScreenRadiusSquared(sphere.Center, sphere.Radius, context.RenderContext.View) * context.ViewScreenSizeSq >= context.MinObjectPixelSizeSq)
{
const auto modelFrame = instance.DrawState.PrevFrame + 1;
// Select a proper LOD index (model may be culled)
- int32 lodIndex = RenderTools::ComputeModelLOD(model, sphere.Center, (float)sphere.Radius, renderContext);
+ int32 lodIndex = RenderTools::ComputeModelLOD(model, sphere.Center, (float)sphere.Radius, context.RenderContext);
if (lodIndex == -1)
{
// Handling model fade-out transition
@@ -231,20 +228,20 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
{
const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD);
const float normalizedProgress = static_cast(instance.DrawState.LODTransition) * (1.0f / 255.0f);
- DrawInstance(renderContext, instance, type, model, prevLOD, normalizedProgress, drawCallsLists, result);
+ DrawInstance(context, instance, model, prevLOD, normalizedProgress, drawCallsLists, result);
}
}
else if (instance.DrawState.LODTransition < 255)
{
const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD);
const float normalizedProgress = static_cast(instance.DrawState.LODTransition) * (1.0f / 255.0f);
- DrawInstance(renderContext, instance, type, model, prevLOD, normalizedProgress, drawCallsLists, result);
+ DrawInstance(context, instance, model, prevLOD, normalizedProgress, drawCallsLists, result);
}
}
instance.DrawState.PrevFrame = frame;
continue;
}
- lodIndex += renderContext.View.ModelLODBias;
+ lodIndex += context.RenderContext.View.ModelLODBias;
lodIndex = model->ClampLODIndex(lodIndex);
if (transitionLOD)
@@ -278,19 +275,19 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
// Draw
if (instance.DrawState.PrevLOD == lodIndex)
{
- DrawInstance(renderContext, instance, type, model, lodIndex, 0.0f, drawCallsLists, result);
+ DrawInstance(context, instance, model, lodIndex, 0.0f, drawCallsLists, result);
}
else if (instance.DrawState.PrevLOD == -1)
{
const float normalizedProgress = static_cast(instance.DrawState.LODTransition) * (1.0f / 255.0f);
- DrawInstance(renderContext, instance, type, model, lodIndex, 1.0f - normalizedProgress, drawCallsLists, result);
+ DrawInstance(context, instance, model, lodIndex, 1.0f - normalizedProgress, drawCallsLists, result);
}
else
{
const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD);
const float normalizedProgress = static_cast(instance.DrawState.LODTransition) * (1.0f / 255.0f);
- DrawInstance(renderContext, instance, type, model, prevLOD, normalizedProgress, drawCallsLists, result);
- DrawInstance(renderContext, instance, type, model, lodIndex, normalizedProgress - 1.0f, drawCallsLists, result);
+ DrawInstance(context, instance, model, prevLOD, normalizedProgress, drawCallsLists, result);
+ DrawInstance(context, instance, model, lodIndex, normalizedProgress - 1.0f, drawCallsLists, result);
}
//DebugDraw::DrawSphere(instance.Bounds, Color::YellowGreen);
@@ -304,14 +301,11 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
#else
-void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, Mesh::DrawInfo& draw)
+void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::DrawInfo& draw)
{
// Skip clusters that around too far from view
- const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View);
- if (Float3::Distance(lodView->Position, cluster->TotalBoundsSphere.Center - lodView->Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance)
+ if (Float3::Distance(context.LodView.Position, cluster->TotalBoundsSphere.Center - context.LodView.Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance)
return;
- const Vector3 viewOrigin = renderContext.View.Origin;
-
//DebugDraw::DrawBox(cluster->Bounds, Color::Red);
// Draw visible children
@@ -323,10 +317,10 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
BoundingBox box;
#define DRAW_CLUSTER(idx) \
box = cluster->Children[idx]->TotalBounds; \
- box.Minimum -= viewOrigin; \
- box.Maximum -= viewOrigin; \
- if (renderContext.View.CullingFrustum.Intersects(box)) \
- DrawCluster(renderContext, cluster->Children[idx], draw)
+ box.Minimum -= context.ViewOrigin; \
+ box.Maximum -= context.ViewOrigin; \
+ if (context.RenderContext.View.CullingFrustum.Intersects(box)) \
+ DrawCluster(context, cluster->Children[idx], draw)
DRAW_CLUSTER(0);
DRAW_CLUSTER(1);
DRAW_CLUSTER(2);
@@ -342,16 +336,17 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
auto& instance = *cluster->Instances[i];
auto& type = FoliageTypes[instance.Type];
BoundingSphere sphere = instance.Bounds;
- sphere.Center -= viewOrigin;
+ sphere.Center -= context.ViewOrigin;
// Check if can draw this instance
if (type._canDraw &&
- Float3::Distance(lodView->Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance &&
- renderContext.View.CullingFrustum.Intersects(sphere))
+ Float3::Distance(context.LodView.Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance &&
+ context.RenderContext.View.CullingFrustum.Intersects(sphere) &&
+ RenderTools::ComputeBoundsScreenRadiusSquared(sphere.Center, sphere.Radius, context.RenderContext.View) * context.ViewScreenSizeSq >= context.MinObjectPixelSizeSq)
{
Matrix world;
const Transform transform = _transform.LocalToWorld(instance.Transform);
- const Float3 translation = transform.Translation - renderContext.View.Origin;
+ const Float3 translation = transform.Translation - context.ViewOrigin;
Matrix::Transformation(transform.Scale, transform.Orientation, translation, world);
// Disable motion blur
@@ -367,7 +362,7 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
draw.PerInstanceRandom = instance.Random;
draw.DrawModes = type.DrawModes;
draw.SetStencilValue(_layer);
- type.Model->Draw(renderContext, draw);
+ type.Model->Draw(context.RenderContext, draw);
//DebugDraw::DrawSphere(instance.Bounds, Color::YellowGreen);
@@ -446,22 +441,50 @@ void Foliage::DrawFoliageJob(int32 i)
PROFILE_CPU();
PROFILE_MEM(Graphics);
const FoliageType& type = FoliageTypes[i];
- if (type.IsReady() && type.Model->CanBeRendered())
+ if (type._canDraw)
{
DrawCallsList drawCallsLists[MODEL_MAX_LODS];
for (RenderContext& renderContext : _renderContextBatch->Contexts)
+ {
+#if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING
DrawType(renderContext, type, drawCallsLists);
+#else
+ Mesh::DrawInfo draw;
+ draw.Flags = GetStaticFlags();
+ draw.DrawModes = (DrawPass)(DrawPass::Default & renderContext.View.Pass);
+ draw.LODBias = 0;
+ draw.ForcedLOD = -1;
+ draw.VertexColors = nullptr;
+ draw.Deformation = nullptr;
+ DrawType(renderContext, type, draw);
+#endif
+ }
}
}
#endif
+#if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING
void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, DrawCallsList* drawCallsLists)
+#else
+void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Mesh::DrawInfo& draw)
+#endif
{
if (!type.Root || !FOLIAGE_CAN_DRAW(renderContext, type))
return;
const DrawPass typeDrawModes = FOLIAGE_GET_DRAW_MODES(renderContext, type);
PROFILE_CPU_ASSET(type.Model);
+ DrawContext context
+ {
+ renderContext,
+ renderContext.LodProxyView ? *renderContext.LodProxyView : renderContext.View,
+ type,
+ renderContext.View.Origin,
+ Math::Square(Graphics::Shadows::MinObjectPixelSize),
+ renderContext.View.ScreenSize.X * renderContext.View.ScreenSize.Y,
+ };
+ if (context.RenderContext.View.Pass != DrawPass::Depth)
+ context.MinObjectPixelSizeSq = 0.0f; // Don't use it in main view
#if FOLIAGE_USE_DRAW_CALLS_BATCHING
// Initialize draw calls for foliage type all LODs meshes
for (int32 lod = 0; lod < type.Model->LODs.Count(); lod++)
@@ -506,7 +529,7 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Dr
// Draw instances of the foliage type
BatchedDrawCalls result(&renderContext.List->Memory);
- DrawCluster(renderContext, type.Root, type, drawCallsLists, result);
+ DrawCluster(context, type.Root, drawCallsLists, result);
// Submit draw calls with valid instances added
for (auto& e : result)
@@ -568,10 +591,22 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Dr
}
}
#else
- DrawCluster(renderContext, type.Root, draw);
+ DrawCluster(context, type.Root, draw);
#endif
}
+void Foliage::InitType(const RenderView& view, FoliageType& type)
+{
+ const DrawPass drawModes = type.DrawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode);
+ type._canDraw = type.IsReady() && drawModes != DrawPass::None && type.Model && type.Model->CanBeRendered();
+ for (int32 j = 0; j < type.Entries.Count(); j++)
+ {
+ auto& e = type.Entries[j];
+ e.ReceiveDecals = type.ReceiveDecals != 0;
+ e.ShadowsMode = type.ShadowsMode;
+ }
+}
+
int32 Foliage::GetInstancesCount() const
{
return Instances.Count();
@@ -1131,12 +1166,7 @@ void Foliage::Draw(RenderContext& renderContext)
// Cache data per foliage instance type
for (auto& type : FoliageTypes)
{
- for (int32 j = 0; j < type.Entries.Count(); j++)
- {
- auto& e = type.Entries[j];
- e.ReceiveDecals = type.ReceiveDecals != 0;
- e.ShadowsMode = type.ShadowsMode;
- }
+ InitType(renderContext.View, type);
}
if (renderContext.View.Pass == DrawPass::GlobalSDF)
@@ -1202,12 +1232,7 @@ void Foliage::Draw(RenderContext& renderContext)
// Draw single foliage instance projection into Global Surface Atlas
auto& instance = *(FoliageInstance*)GlobalSurfaceAtlasPass::Instance()->GetCurrentActorObject();
auto& type = FoliageTypes[instance.Type];
- for (int32 i = 0; i < type.Entries.Count(); i++)
- {
- auto& e = type.Entries[i];
- e.ReceiveDecals = type.ReceiveDecals != 0;
- e.ShadowsMode = type.ShadowsMode;
- }
+ InitType(renderContext.View, type);
Matrix world;
const Transform transform = _transform.LocalToWorld(instance.Transform);
renderContext.View.GetWorldMatrix(transform, world);
@@ -1239,8 +1264,9 @@ void Foliage::Draw(RenderContext& renderContext)
draw.LODBias = 0;
draw.ForcedLOD = -1;
draw.VertexColors = nullptr;
+ draw.Deformation = nullptr;
#else
- DrawCallsList drawCallsLists[MODEL_MAX_LODS];
+ DrawCallsList draw[MODEL_MAX_LODS];
#endif
#if FOLIAGE_USE_SINGLE_QUAD_TREE
if (Root)
@@ -1248,7 +1274,7 @@ void Foliage::Draw(RenderContext& renderContext)
#else
for (auto& type : FoliageTypes)
{
- DrawType(renderContext, type, drawCallsLists);
+ DrawType(renderContext, type, draw);
}
#endif
}
@@ -1265,14 +1291,7 @@ void Foliage::Draw(RenderContextBatch& renderContextBatch)
{
// Cache data per foliage instance type
for (FoliageType& type : FoliageTypes)
- {
- for (int32 j = 0; j < type.Entries.Count(); j++)
- {
- auto& e = type.Entries[j];
- e.ReceiveDecals = type.ReceiveDecals != 0;
- e.ShadowsMode = type.ShadowsMode;
- }
- }
+ InitType(view, type);
// Run async job for each foliage type
_renderContextBatch = &renderContextBatch;
diff --git a/Source/Engine/Foliage/Foliage.h b/Source/Engine/Foliage/Foliage.h
index 6f8b36cf4..83ab6206c 100644
--- a/Source/Engine/Foliage/Foliage.h
+++ b/Source/Engine/Foliage/Foliage.h
@@ -158,6 +158,15 @@ public:
private:
void AddToCluster(ChunkedArray& clusters, FoliageCluster* cluster, FoliageInstance& instance);
+ struct DrawContext
+ {
+ RenderContext& RenderContext;
+ const RenderView& LodView;
+ const FoliageType& FoliageType;
+ Vector3 ViewOrigin;
+ float MinObjectPixelSizeSq;
+ float ViewScreenSizeSq;
+ };
#if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING
struct DrawKey
{
@@ -181,10 +190,12 @@ private:
typedef Array> DrawCallsList;
typedef Dictionary BatchedDrawCalls;
- void DrawInstance(RenderContext& renderContext, FoliageInstance& instance, const FoliageType& type, Model* model, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const;
- void DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, const FoliageType& type, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const;
+ void DrawInstance(DrawContext& context, FoliageInstance& instance, Model* model, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const;
+ void DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const;
+ void DrawType(RenderContext& renderContext, const FoliageType& type, DrawCallsList* drawCallsLists);
#else
- void DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, Mesh::DrawInfo& draw);
+ void DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::DrawInfo& draw);
+ void DrawType(RenderContext& renderContext, const FoliageType& type, Mesh::DrawInfo& draw);
#endif
#if !FOLIAGE_USE_SINGLE_QUAD_TREE
void DrawClusterGlobalSDF(class GlobalSignDistanceFieldPass* globalSDF, const BoundingBox& globalSDFBounds, FoliageCluster* cluster, const FoliageType& type);
@@ -192,7 +203,8 @@ private:
void DrawFoliageJob(int32 i);
RenderContextBatch* _renderContextBatch;
#endif
- void DrawType(RenderContext& renderContext, const FoliageType& type, DrawCallsList* drawCallsLists);
+
+ void InitType(const RenderView& view, FoliageType& type);
public:
///
diff --git a/Source/Engine/Foliage/FoliageType.h b/Source/Engine/Foliage/FoliageType.h
index bbe7e7739..224ed0bd8 100644
--- a/Source/Engine/Foliage/FoliageType.h
+++ b/Source/Engine/Foliage/FoliageType.h
@@ -47,6 +47,7 @@ API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FoliageType : public ScriptingOb
friend Foliage;
private:
uint8 _isReady : 1;
+ uint8 _canDraw : 1;
public:
///
diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp
index 4d5dcecd7..f463df2fe 100644
--- a/Source/Engine/Graphics/Graphics.cpp
+++ b/Source/Engine/Graphics/Graphics.cpp
@@ -32,6 +32,7 @@ bool Graphics::SpreadWorkload = true;
#if !BUILD_RELEASE || USE_EDITOR
float Graphics::TestValue = 0.0f;
#endif
+float Graphics::Shadows::MinObjectPixelSize = 2.0f;
bool Graphics::PostProcessing::ColorGradingVolumeLUT = true;
#if GRAPHICS_API_NULL
diff --git a/Source/Engine/Graphics/Graphics.h b/Source/Engine/Graphics/Graphics.h
index 97ba6a724..aed1232f6 100644
--- a/Source/Engine/Graphics/Graphics.h
+++ b/Source/Engine/Graphics/Graphics.h
@@ -101,8 +101,17 @@ public:
#endif
public:
+ // Shadows rendering configuration.
+ API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API Shadows
+ {
+ DECLARE_SCRIPTING_TYPE_MINIMAL(Shadows);
+
+ // The minimum size in pixels of objects to cast shadows. Improves performance by skipping too small objects (eg. sub-pixel) from rendering into shadow maps.
+ API_FIELD() static float MinObjectPixelSize;
+ };
+
// Post Processing effects rendering configuration.
- API_CLASS(Static, Attributes = "DebugCommand") class FLAXENGINE_API PostProcessing
+ API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API PostProcessing
{
DECLARE_SCRIPTING_TYPE_MINIMAL(PostProcessing);
diff --git a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp
index 379f57eb7..48daecc83 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp
@@ -20,15 +20,6 @@ void CmdBufferVulkan::AddWaitSemaphore(VkPipelineStageFlags waitFlags, Semaphore
_waitSemaphores.Add(waitSemaphore);
}
-void CmdBufferVulkan::Wait(float timeInSecondsToWait)
-{
- if (!IsSubmitted())
- return;
- const bool failed = _device->FenceManager.WaitForFence(_fence, (uint64)(timeInSecondsToWait * 1e9));
- ASSERT(!failed);
- RefreshFenceStatus();
-}
-
void CmdBufferVulkan::Begin()
{
PROFILE_CPU();
@@ -145,6 +136,16 @@ void CmdBufferVulkan::EndEvent()
#endif
+void CmdBufferVulkan::Wait(float timeoutSeconds)
+{
+ if (!IsSubmitted())
+ return;
+ PROFILE_CPU();
+ const bool failed = _device->FenceManager.WaitForFence(GetFence(), timeoutSeconds);
+ ASSERT(!failed);
+ RefreshFenceStatus();
+}
+
void CmdBufferVulkan::RefreshFenceStatus()
{
if (_state == State::Submitted)
@@ -306,14 +307,6 @@ void CmdBufferManagerVulkan::SubmitActiveCmdBuffer(SemaphoreVulkan* signalSemaph
_activeCmdBuffer = nullptr;
}
-void CmdBufferManagerVulkan::WaitForCmdBuffer(CmdBufferVulkan* cmdBuffer, float timeInSecondsToWait)
-{
- ASSERT(cmdBuffer->IsSubmitted());
- const bool failed = _device->FenceManager.WaitForFence(cmdBuffer->GetFence(), (uint64)(timeInSecondsToWait * 1e9));
- ASSERT(!failed);
- cmdBuffer->RefreshFenceStatus();
-}
-
void CmdBufferManagerVulkan::GetNewActiveCommandBuffer()
{
PROFILE_CPU();
diff --git a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h
index fef86b0bb..925f7a40f 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h
@@ -119,7 +119,6 @@ public:
public:
void AddWaitSemaphore(VkPipelineStageFlags waitFlags, SemaphoreVulkan* waitSemaphore);
- void Wait(float timeInSecondsToWait = 1.0f);
void Begin();
void End();
@@ -137,6 +136,7 @@ public:
void EndEvent();
#endif
+ void Wait(float timeoutSeconds = VULKAN_WAIT_TIMEOUT);
void RefreshFenceStatus();
};
@@ -210,8 +210,7 @@ public:
public:
void SubmitActiveCmdBuffer(SemaphoreVulkan* signalSemaphore = nullptr);
- void WaitForCmdBuffer(CmdBufferVulkan* cmdBuffer, float timeInSecondsToWait = 1.0f);
- void RefreshFenceStatus(const CmdBufferVulkan* skipCmdBuffer = nullptr)
+ void RefreshFenceStatus(CmdBufferVulkan* skipCmdBuffer = nullptr)
{
_pool.RefreshFenceStatus(skipCmdBuffer);
}
diff --git a/Source/Engine/GraphicsDevice/Vulkan/Config.h b/Source/Engine/GraphicsDevice/Vulkan/Config.h
index 7a4129a5c..fd5880400 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/Config.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/Config.h
@@ -49,6 +49,11 @@
#define VULKAN_USE_TIMER_QUERIES 1
#endif
+// Fence wait operation timeout in seconds
+#ifndef VULKAN_WAIT_TIMEOUT
+#define VULKAN_WAIT_TIMEOUT 5.0f
+#endif
+
// Toggles GPUTimerQueryVulkan to use BeginQuery/EndQuery via GPuContext rather than old custom implementation
#define GPU_VULKAN_QUERY_NEW 1
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
index 5266a704b..5699350b2 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp
@@ -2253,16 +2253,15 @@ FenceVulkan* FenceManagerVulkan::AllocateFence(bool createSignaled)
return fence;
}
-bool FenceManagerVulkan::WaitForFence(FenceVulkan* fence, uint64 timeInNanoseconds) const
+bool FenceManagerVulkan::WaitForFence(FenceVulkan* fence, float timeoutSeconds) const
{
if (fence->IsSignaled)
return false;
PROFILE_CPU();
ZoneColor(TracyWaitZoneColor);
ASSERT(_usedFences.Contains(fence));
- if (timeInNanoseconds)
- timeInNanoseconds = 1000ll * 1000ll * 1000LL; // 1s
- const VkResult result = vkWaitForFences(_device->Device, 1, &fence->Handle, true, timeInNanoseconds);
+ uint64 timeNanoseconds = (uint64)((double)timeoutSeconds * 1000000000.0);
+ const VkResult result = vkWaitForFences(_device->Device, 1, &fence->Handle, true, timeNanoseconds);
LOG_VULKAN_RESULT(result);
if (result == VK_SUCCESS)
{
@@ -2290,11 +2289,11 @@ void FenceManagerVulkan::ReleaseFence(FenceVulkan*& fence)
fence = nullptr;
}
-void FenceManagerVulkan::WaitAndReleaseFence(FenceVulkan*& fence, uint64 timeInNanoseconds)
+void FenceManagerVulkan::WaitAndReleaseFence(FenceVulkan*& fence, float timeoutSeconds)
{
ScopeLock lock(_device->_fenceLock);
if (!fence->IsSignaled)
- WaitForFence(fence, timeInNanoseconds);
+ WaitForFence(fence, timeoutSeconds);
ResetFence(fence);
_usedFences.Remove(fence);
_freeFences.Add(fence);
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h
index 85ad4a647..b5bfe9075 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h
@@ -106,7 +106,7 @@ public:
}
// Returns true if waiting timed out or failed, false otherwise.
- bool WaitForFence(FenceVulkan* fence, uint64 timeInNanoseconds = 0) const;
+ bool WaitForFence(FenceVulkan* fence, float timeoutSeconds = VULKAN_WAIT_TIMEOUT) const;
void ResetFence(FenceVulkan* fence) const;
@@ -114,7 +114,7 @@ public:
void ReleaseFence(FenceVulkan*& fence);
// Sets the fence handle to null
- void WaitAndReleaseFence(FenceVulkan*& fence, uint64 timeInNanoseconds = 0);
+ void WaitAndReleaseFence(FenceVulkan*& fence, float timeoutSeconds = VULKAN_WAIT_TIMEOUT);
private:
// Returns true if fence was signaled, otherwise false.
diff --git a/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.cpp
index 4d4e06967..39b0d2691 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.cpp
@@ -62,10 +62,7 @@ void QueueVulkan::Submit(CmdBufferVulkan* cmdBuffer, uint32 signalSemaphoresCoun
const bool WaitForIdleOnSubmit = false;
if (WaitForIdleOnSubmit)
{
- bool success = _device->FenceManager.WaitForFence(fence);
- ASSERT(success);
- ASSERT(_device->FenceManager.IsFenceSignaled(fence));
- cmdBuffer->GetOwner()->RefreshFenceStatus();
+ cmdBuffer->Wait();
}
#endif
diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp
index ed667ae05..379c54930 100644
--- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp
+++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp
@@ -2485,7 +2485,7 @@ void PhysicsBackend::SetRigidActorPose(void* actor, const Vector3& position, con
if (kinematic)
{
auto actorPhysX = (PxRigidDynamic*)actor;
- if (actorPhysX->getActorFlags() & PxActorFlag::eDISABLE_SIMULATION)
+ if (actorPhysX->getActorFlags() & PxActorFlag::eDISABLE_SIMULATION || !(actorPhysX->getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC))
{
// Ensures the disabled kinematic actor ends up in the correct pose after enabling simulation
actorPhysX->setGlobalPose(trans, wakeUp);
diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp
index b14fc8b6d..4a2867d3b 100644
--- a/Source/Engine/Renderer/RenderList.cpp
+++ b/Source/Engine/Renderer/RenderList.cpp
@@ -731,6 +731,7 @@ void RenderList::AddDrawCall(const RenderContextBatch& renderContextBatch, DrawP
DrawCallsLists[(int32)DrawCallsListType::MotionVectors].Indices.Add(index);
}
}
+ float minObjectPixelSizeSq = Math::Square(Graphics::Shadows::MinObjectPixelSize);
for (int32 i = 1; i < renderContextBatch.Contexts.Count(); i++)
{
const RenderContext& renderContext = renderContextBatch.Contexts.Get()[i];
@@ -738,7 +739,8 @@ void RenderList::AddDrawCall(const RenderContextBatch& renderContextBatch, DrawP
drawModes = modes & renderContext.View.Pass;
if (drawModes != DrawPass::None &&
(staticFlags & renderContext.View.StaticFlagsMask) == renderContext.View.StaticFlagsCompare &&
- renderContext.View.CullingFrustum.Intersects(bounds))
+ renderContext.View.CullingFrustum.Intersects(bounds) &&
+ RenderTools::ComputeBoundsScreenRadiusSquared(bounds.Center, bounds.Radius, renderContext.View) * (renderContext.View.ScreenSize.X * renderContext.View.ScreenSize.Y) >= minObjectPixelSizeSq)
{
renderContext.List->ShadowDepthDrawCallsList.Indices.Add(index);
}
diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs
index 2fc2ef4b3..7722fc94b 100644
--- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs
+++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs
@@ -437,8 +437,8 @@ namespace FlaxEngine.GUI
// Caret
if (IsFocused && CaretPosition > -1)
{
- float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.7f);
- alpha = alpha * alpha * alpha * alpha * alpha * alpha;
+ float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.8f);
+ alpha = alpha * alpha;
Render2D.FillRectangle(CaretBounds, CaretColor * alpha);
}
diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs
index 1dfd9facc..3d7099db6 100644
--- a/Source/Engine/UI/GUI/Common/TextBox.cs
+++ b/Source/Engine/UI/GUI/Common/TextBox.cs
@@ -310,8 +310,8 @@ namespace FlaxEngine.GUI
// Caret
if (IsFocused && CaretPosition > -1)
{
- float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.7f);
- alpha = alpha * alpha * alpha * alpha * alpha * alpha;
+ float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.8f);
+ alpha = alpha * alpha;
Render2D.FillRectangle(CaretBounds, CaretColor * alpha);
}
diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs
index 243e4786e..489e4c93d 100644
--- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs
+++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs
@@ -191,14 +191,18 @@ namespace FlaxEngine.GUI
///
/// Gets or sets the maximum number of characters the user can type into the text box control.
///
- [EditorOrder(50), Tooltip("The maximum number of characters the user can type into the text box control.")]
+ [EditorOrder(50), Limit(-1), Tooltip("The maximum number of characters the user can type into the text box control.")]
public int MaxLength
{
get => _maxLength;
set
{
- if (_maxLength <= 0)
- throw new ArgumentOutOfRangeException(nameof(MaxLength));
+ // Cap at min of -1 for no max length
+ if (value <= 0)
+ {
+ _maxLength = -1;
+ return;
+ }
if (_maxLength != value)
{
@@ -275,7 +279,7 @@ namespace FlaxEngine.GUI
/// Gets or sets the speed of the caret flashing animation.
///
[EditorDisplay("Caret Style"), EditorOrder(2021), Tooltip("The speed of the caret flashing animation.")]
- public float CaretFlashSpeed { get; set; } = 6.0f;
+ public float CaretFlashSpeed { get; set; } = 6.5f;
///
/// Gets or sets the speed of the selection background flashing animation.
@@ -381,7 +385,7 @@ namespace FlaxEngine.GUI
value = value.Replace(DelChar.ToString(), "");
// Clamp length
- if (value.Length > MaxLength)
+ if (value.Length > MaxLength && MaxLength != -1)
value = value.Substring(0, MaxLength);
// Ensure to use only single line
@@ -514,7 +518,7 @@ namespace FlaxEngine.GUI
AutoFocus = true;
_isMultiline = isMultiline;
- _maxLength = 2147483646;
+ _maxLength = -1;
_selectionStart = _selectionEnd = -1;
var style = Style.Current;
@@ -710,7 +714,7 @@ namespace FlaxEngine.GUI
str = str.Replace("\n", "");
int selectionLength = SelectionLength;
- int charactersLeft = MaxLength - _text.Length + selectionLength;
+ int charactersLeft = (MaxLength != -1 ? MaxLength : int.MaxValue) - _text.Length + selectionLength;
Assert.IsTrue(charactersLeft >= 0);
if (charactersLeft == 0)
return;
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
index 91e8d5f26..f0cef1b15 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
@@ -1300,10 +1300,10 @@ namespace Flax.Build.Bindings
else if (parameterInfo.Type.IsRef && !parameterInfo.Type.IsConst)
{
// Non-const lvalue reference parameters needs to be passed via temporary value
- if (parameterInfo.IsOut || parameterInfo.IsRef)
- contents.Append(indent).AppendFormat("{2}& {0}Temp = {1};", parameterInfo.Name, param, parameterInfo.Type.ToString(false)).AppendLine();
- else
+ if (parameterInfo.Type.Type is "String" or "StringView" or "StringAnsi" or "StringAnsiView" && parameterInfo.Type.GenericArgs == null)
contents.Append(indent).AppendFormat("{2} {0}Temp = {1};", parameterInfo.Name, param, parameterInfo.Type.ToString(false)).AppendLine();
+ else
+ contents.Append(indent).AppendFormat("{2}& {0}Temp = {1};", parameterInfo.Name, param, parameterInfo.Type.ToString(false)).AppendLine();
callParams += parameterInfo.Name;
callParams += "Temp";
}