diff --git a/Content/Editor/Fonts/Inconsolata-Regular.flax b/Content/Editor/Fonts/Inconsolata-Regular.flax
index a2fdf0626..d85ef610a 100644
--- a/Content/Editor/Fonts/Inconsolata-Regular.flax
+++ b/Content/Editor/Fonts/Inconsolata-Regular.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:36995fa880bf47331618b1e96f613c6925e72484fe76e98d6e44c1985ecab017
-size 93015
+oid sha256:139e2f54a268f4febc753aaa9807a33a6eb2dd84e788c5b8b4f50ca8683d2b3b
+size 93116
diff --git a/Content/Editor/Fonts/NotoSansSC-Regular.flax b/Content/Editor/Fonts/NotoSansSC-Regular.flax
index 39964e6c5..8f8f00157 100644
--- a/Content/Editor/Fonts/NotoSansSC-Regular.flax
+++ b/Content/Editor/Fonts/NotoSansSC-Regular.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:19fa43eb7b31ee3936348b1f271c464c79d7020a21d33e3cdbe54f98c3b14304
-size 10560899
+oid sha256:2ddc7af5fab1dd4ad858ad2abf11c6c243d908345720892cd56242fbc9c33b02
+size 10560900
diff --git a/Content/Editor/Fonts/Roboto-Regular.flax b/Content/Editor/Fonts/Roboto-Regular.flax
index a1e416f77..01251cac6 100644
--- a/Content/Editor/Fonts/Roboto-Regular.flax
+++ b/Content/Editor/Fonts/Roboto-Regular.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:574b86d8e7439e88c3d7b4265cbc5ad7089c49368c47661b2c6f4c997cb53d86
-size 172093
+oid sha256:c0584b3a3c186364464de155edece5cb6a8c46000676ac82a45f951f3a67fac5
+size 172194
diff --git a/Content/Editor/Fonts/SegMDL2.flax b/Content/Editor/Fonts/SegMDL2.flax
index d06bac811..b627849d9 100644
--- a/Content/Editor/Fonts/SegMDL2.flax
+++ b/Content/Editor/Fonts/SegMDL2.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0c4a17c412fce35275af5be883c784b57d192eb188fb3568990b0ae8e0e6d34e
-size 204049
+oid sha256:f7a9d723d6342b8a484dfb2ee784cef506b3311a6092be0e9de35f941d698ff5
+size 204150
diff --git a/Content/Editor/Fonts/Segoe Media Center Regular.flax b/Content/Editor/Fonts/Segoe Media Center Regular.flax
index e9038cd65..6c17d96bf 100644
--- a/Content/Editor/Fonts/Segoe Media Center Regular.flax
+++ b/Content/Editor/Fonts/Segoe Media Center Regular.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5d277b4abbdc1c91f056dd0d838631135242643abb41ce5751b1be3aaca72401
-size 72697
+oid sha256:c21443a111daede658cb634f56b4ae0838dbb2eaa75d4ba8ba74a4c77df44de8
+size 72798
diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax
index 257953bf9..98cfbb80a 100644
--- a/Content/Shaders/GI/DDGI.flax
+++ b/Content/Shaders/GI/DDGI.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5b017cf857f443553020e4bc7c8c8c5da3a826a2514322664a023ffa6005f7a5
-size 38217
+oid sha256:50753dbd37cb2a6fd6f48a597e90c8b8b689c7e433e66a5f21bc4ffa94244895
+size 38260
diff --git a/Content/Shaders/GUI.flax b/Content/Shaders/GUI.flax
index d5e3f59fa..79948533e 100644
--- a/Content/Shaders/GUI.flax
+++ b/Content/Shaders/GUI.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4fed6a05104322f61a77f6cf69b64478518b829165a2a3ab7fc151098dcd48be
-size 4526
+oid sha256:324f564e3b0324dbdf5712f3f1e11f54f276d11bc203aecabeef93953b2ab904
+size 4719
diff --git a/Content/Shaders/GlobalSignDistanceField.flax b/Content/Shaders/GlobalSignDistanceField.flax
index 5afcb4bf4..01dae76b1 100644
--- a/Content/Shaders/GlobalSignDistanceField.flax
+++ b/Content/Shaders/GlobalSignDistanceField.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1f07ebb16820897e8598ae7a0627cb75b3d28e9dceea3ad4bd9ff543d5cdd01c
-size 13979
+oid sha256:0506a5485a0107f67714ceb8c1714f18cb5718bacfde36fad91d53ea3cb60de9
+size 14044
diff --git a/Flax.flaxproj b/Flax.flaxproj
index c9c27281b..78c03fce6 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -4,7 +4,7 @@
"Major": 1,
"Minor": 11,
"Revision": 0,
- "Build": 6807
+ "Build": 6808
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.",
diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
index 9844f3fda..efcd1bec3 100644
--- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
@@ -103,11 +103,12 @@ namespace FlaxEditor.CustomEditors.Dedicated
var actors = ScriptsEditor.ParentEditor.Values;
foreach (var a in actors)
{
- if (a.GetType() != requireActor.RequiredType)
- {
- item.Enabled = false;
- break;
- }
+ if (a.GetType() == requireActor.RequiredType)
+ continue;
+ if (requireActor.IncludeInheritedTypes && a.GetType().IsSubclassOf(requireActor.RequiredType))
+ continue;
+ item.Enabled = false;
+ break;
}
}
cm.AddItem(item);
@@ -739,6 +740,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 +803,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,19 +821,11 @@ 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;
- if (actor.GetType() != attribute.RequiredType && !actor.GetType().IsSubclassOf(attribute.RequiredType))
+ if (actor.GetType() != attribute.RequiredType && (attribute.IncludeInheritedTypes && !actor.GetType().IsSubclassOf(attribute.RequiredType)))
{
Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` on `{script.Actor}` is missing a required Actor of type `{attribute.RequiredType}`.");
hasAllRequirements = false;
@@ -850,7 +838,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 +851,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 +878,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 +897,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/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
index 3e0332b09..a8bfe4f5f 100644
--- a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs
@@ -18,7 +18,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
public class SplineEditor : ActorEditor
{
///
- /// Storage undo spline data
+ /// Stores undo spline data.
///
private struct UndoData
{
@@ -83,7 +83,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
///
- /// Edit curve options manipulate the curve as free mode
+ /// Edit curve options manipulate the curve as free mode.
///
private sealed class FreeTangentMode : EditTangentOptionBase
{
@@ -98,7 +98,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
///
- /// Edit curve options to set tangents to linear
+ /// Edit curve options to set tangents to linear.
///
private sealed class LinearTangentMode : EditTangentOptionBase
{
@@ -107,13 +107,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
SetKeyframeLinear(spline, index);
- // change the selection to tangent parent (a spline point / keyframe)
+ // Change the selection to tangent parent (a spline point / keyframe)
Editor.SetSelectSplinePointNode(spline, index);
}
}
///
- /// Edit curve options to align tangents of selected spline
+ /// Edit curve options to align tangents of selected spline.
///
private sealed class AlignedTangentMode : EditTangentOptionBase
{
@@ -168,8 +168,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
///
- /// Edit curve options manipulate the curve setting selected point
- /// tangent in as smoothed but tangent out as linear
+ /// Edit curve options manipulate the curve setting selected point tangent in as smoothed but tangent out as linear.
///
private sealed class SmoothInTangentMode : EditTangentOptionBase
{
@@ -182,8 +181,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
///
- /// Edit curve options manipulate the curve setting selected point
- /// tangent in as linear but tangent out as smoothed
+ /// Edit curve options manipulate the curve setting selected point tangent in as linear but tangent out as smoothed.
///
private sealed class SmoothOutTangentMode : EditTangentOptionBase
{
@@ -219,7 +217,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
var enabled = EnabledInHierarchy && tab.EnabledInHierarchy;
var style = FlaxEngine.GUI.Style.Current;
var size = Size;
- var textHeight = 16.0f;
+ var textHeight = 30.0f;
+ // Make icons smaller when tabs get thinner
var iconSize = size.Y - textHeight;
var iconRect = new Rectangle((Width - iconSize) / 2, 0, iconSize, iconSize);
if (tab._mirrorIcon)
@@ -230,8 +229,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
var color = style.Foreground;
if (!enabled)
color *= 0.6f;
+ var textRect = new Rectangle(0, size.Y - textHeight, size.X, textHeight);
+ Render2D.PushClip(new Rectangle(Float2.Zero, Size));
Render2D.DrawSprite(tab._customIcon, iconRect, color);
- Render2D.DrawText(style.FontMedium, tab._customText, new Rectangle(0, iconSize, size.X, textHeight), color, TextAlignment.Center, TextAlignment.Center);
+ Render2D.DrawText(style.FontMedium, tab._customText, textRect, color, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords, 0.65f);
+ Render2D.PopClip();
}
}
@@ -291,18 +293,21 @@ namespace FlaxEditor.CustomEditors.Dedicated
return;
_selectedSpline = spline;
- layout.Space(10);
- var tabSize = 46;
+ //var tabSize = 46;
+ var tabSize = 60;
var icons = Editor.Instance.Icons;
- layout.Header("Selected spline point");
+ var group = layout.Group("Selected Point");
_selectedPointsTabs = new Tabs
{
Height = tabSize,
TabsSize = new Float2(tabSize),
AutoTabsSize = true,
- Parent = layout.ContainerControl,
+ Parent = group.ContainerControl,
};
+ // Move the group above the group containing spline points
+ group.Control.IndexInParent = 3;
+
_linearTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedLinear, "Linear", icons.SplineLinear64));
_freeTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedFree, "Free", icons.SplineFree64));
_alignedTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedAligned, "Aligned", icons.SplineAligned64));
@@ -310,13 +315,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
_smoothOutTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedSmoothOut, "Smooth Out", icons.SplineSmoothIn64, true));
_selectedPointsTabs.SelectedTabIndex = -1;
- layout.Header("All spline points");
+ group = layout.Group("All Points");
_allPointsTabs = new Tabs
{
Height = tabSize,
TabsSize = new Float2(tabSize),
AutoTabsSize = true,
- Parent = layout.ContainerControl,
+ Parent = group.ContainerControl,
};
_setLinearAllTangentsTab = _allPointsTabs.AddTab(new IconTab(OnSetTangentsLinear, "Set Linear Tangents", icons.SplineLinear64));
_setSmoothAllTangentsTab = _allPointsTabs.AddTab(new IconTab(OnSetTangentsSmooth, "Set Smooth Tangents", icons.SplineAligned64));
diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
index 614d2c160..1628f69ec 100644
--- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
@@ -690,7 +690,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
return grid;
}
- private CustomElementsContainer UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color borderColor, out FloatValueBox valueBox)
+ private CustomElementsContainer UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color highlightColor, out FloatValueBox valueBox)
{
valueBox = null;
var grid = UniformGridTwoByOne(el);
@@ -701,8 +701,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
valueBox = floatEditorElement.ValueBox;
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
- valueBox.BorderColor = Color.Lerp(borderColor, back, ActorTransformEditor.AxisGreyOutFactor);
- valueBox.BorderSelectedColor = borderColor;
+ valueBox.HighlightColor = highlightColor;
+ valueBox.BorderSelectedColor = highlightColor;
}
return grid;
}
diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
index 02f5ca7ec..131a9b10a 100644
--- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs
@@ -14,22 +14,17 @@ namespace FlaxEditor.CustomEditors.Editors
///
/// The X axis color.
///
- public static Color AxisColorX = new Color(1.0f, 0.0f, 0.02745f, 1.0f);
+ public static Color AxisColorX = new Color(0.8f, 0.0f, 0.027f, 1.0f);
///
/// The Y axis color.
///
- public static Color AxisColorY = new Color(0.239215f, 1.0f, 0.047058f, 1.0f);
+ public static Color AxisColorY = new Color(0.239215f, 0.65f, 0.047058f, 1.0f);
///
/// The Z axis color.
///
- public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 1.0f, 1.0f);
-
- ///
- /// The axes colors grey out scale when input field is not focused.
- ///
- public static float AxisGreyOutFactor = 0.6f;
+ public static Color AxisColorZ = new Color(0.0f, 0.42352f, 0.8f, 1.0f);
///
/// Custom editor for actor position property.
@@ -43,18 +38,20 @@ namespace FlaxEditor.CustomEditors.Editors
base.Initialize(layout);
if (XElement.ValueBox.Parent is UniformGridPanel ug)
+ {
+ ug.SlotPadding = new Margin(3.0f, 0.0f, 0.0f, 0.0f);
CheckLayout(ug);
+ }
// Override colors
- var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
- XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX;
- XElement.ValueBox.Category = Utils.ValueCategory.Distance;
- YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY;
- YElement.ValueBox.Category = Utils.ValueCategory.Distance;
- ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
+ XElement.ValueBox.HighlightColor = AxisColorX;
+ XElement.ValueBox.Category = Utils.ValueCategory.Distance;
+ YElement.ValueBox.HighlightColor = AxisColorY;
+ YElement.ValueBox.Category = Utils.ValueCategory.Distance;
+ ZElement.ValueBox.HighlightColor = AxisColorZ;
ZElement.ValueBox.Category = Utils.ValueCategory.Distance;
}
}
@@ -71,18 +68,20 @@ namespace FlaxEditor.CustomEditors.Editors
base.Initialize(layout);
if (XElement.ValueBox.Parent is UniformGridPanel ug)
+ {
+ ug.SlotPadding = new Margin(3.0f, 0.0f, 0.0f, 0.0f);
CheckLayout(ug);
+ }
// Override colors
- var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
- XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX;
- XElement.ValueBox.Category = Utils.ValueCategory.Angle;
- YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY;
- YElement.ValueBox.Category = Utils.ValueCategory.Angle;
- ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
+ XElement.ValueBox.HighlightColor = AxisColorX;
+ XElement.ValueBox.Category = Utils.ValueCategory.Angle;
+ YElement.ValueBox.HighlightColor = AxisColorY;
+ YElement.ValueBox.Category = Utils.ValueCategory.Angle;
+ ZElement.ValueBox.HighlightColor = AxisColorZ;
ZElement.ValueBox.Category = Utils.ValueCategory.Angle;
}
}
@@ -129,17 +128,19 @@ namespace FlaxEditor.CustomEditors.Editors
}
if (XElement.ValueBox.Parent is UniformGridPanel ug)
+ {
+ ug.SlotPadding = new Margin(3.0f, 0.0f, 0.0f, 0.0f);
CheckLayout(ug);
+ }
// Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
- var grayOutFactor = 0.6f;
- XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX;
- YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY;
- ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
+ XElement.ValueBox.HighlightColor = AxisColorX;
+ YElement.ValueBox.HighlightColor = AxisColorY;
+ ZElement.ValueBox.HighlightColor = AxisColorZ;
}
///
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/CustomEditors/GUI/PropertiesList.cs b/Source/Editor/CustomEditors/GUI/PropertiesList.cs
index 3847a8f36..f430bae06 100644
--- a/Source/Editor/CustomEditors/GUI/PropertiesList.cs
+++ b/Source/Editor/CustomEditors/GUI/PropertiesList.cs
@@ -70,9 +70,9 @@ namespace FlaxEditor.CustomEditors.GUI
UpdateSplitRect();
}
- private void AutoSizeSplitter()
+ private void AutoSizeSplitter(bool ignoreCustomSplitterValue = false)
{
- if (_hasCustomSplitterValue || !Editor.Instance.Options.Options.Interface.AutoSizePropertiesPanelSplitter)
+ if (_hasCustomSplitterValue && !ignoreCustomSplitterValue)
return;
Font font = Style.Current.FontMedium;
@@ -178,6 +178,21 @@ namespace FlaxEditor.CustomEditors.GUI
return base.OnMouseDown(location, button);
}
+ ///
+ public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
+ {
+ if (button == MouseButton.Left && _splitterRect.Contains(location))
+ {
+ if (_splitterClicked)
+ EndTracking();
+
+ AutoSizeSplitter(true);
+ return true;
+ }
+
+ return base.OnMouseDoubleClick(location, button);
+ }
+
///
public override bool OnMouseUp(Float2 location, MouseButton button)
{
@@ -220,7 +235,8 @@ namespace FlaxEditor.CustomEditors.GUI
// Refresh
UpdateSplitRect();
PerformLayout(true);
- AutoSizeSplitter();
+ if (Editor.Instance.Options.Options.Interface.AutoSizePropertiesPanelSplitter)
+ AutoSizeSplitter();
}
///
diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp
index 58739917e..c08648505 100644
--- a/Source/Editor/Editor.cpp
+++ b/Source/Editor/Editor.cpp
@@ -526,6 +526,23 @@ int32 Editor::LoadProduct()
return 12;
}
+ // Get the last opened project path
+ String localCachePath;
+ FileSystem::GetSpecialFolderPath(SpecialFolder::AppData, localCachePath);
+ String editorConfigPath = localCachePath / TEXT("Flax");
+ String lastProjectSettingPath = editorConfigPath / TEXT("LastProject.txt");
+ if (!FileSystem::DirectoryExists(editorConfigPath))
+ FileSystem::CreateDirectory(editorConfigPath);
+ String lastProjectPath;
+ if (FileSystem::FileExists(lastProjectSettingPath))
+ File::ReadAllText(lastProjectSettingPath, lastProjectPath);
+ if (!FileSystem::DirectoryExists(lastProjectPath))
+ lastProjectPath = String::Empty;
+
+ // Try to open the last project when requested
+ if (projectPath.IsEmpty() && CommandLine::Options.LastProject.IsTrue() && !lastProjectPath.IsEmpty())
+ projectPath = lastProjectPath;
+
// Missing project case
if (projectPath.IsEmpty())
{
@@ -541,7 +558,7 @@ int32 Editor::LoadProduct()
Array files;
if (FileSystem::ShowOpenFileDialog(
nullptr,
- StringView::Empty,
+ lastProjectPath,
TEXT("Project files (*.flaxproj)\0*.flaxproj\0All files (*.*)\0*.*\0"),
false,
TEXT("Select project to open in Editor"),
@@ -625,6 +642,10 @@ int32 Editor::LoadProduct()
}
}
+ // Update the last opened project path
+ if (lastProjectPath.Compare(Project->ProjectFolderPath) != 0)
+ File::WriteAllText(lastProjectSettingPath, Project->ProjectFolderPath, Encoding::UTF8);
+
return 0;
}
diff --git a/Source/Editor/GUI/Input/ValueBox.cs b/Source/Editor/GUI/Input/ValueBox.cs
index 88ec9a4ee..c26761683 100644
--- a/Source/Editor/GUI/Input/ValueBox.cs
+++ b/Source/Editor/GUI/Input/ValueBox.cs
@@ -89,6 +89,11 @@ namespace FlaxEditor.GUI.Input
///
public bool IsSliding => _isSliding;
+ ///
+ /// The color of the highlight to the left of the value box.
+ ///
+ public Color HighlightColor;
+
///
/// Occurs when sliding starts.
///
@@ -211,6 +216,12 @@ namespace FlaxEditor.GUI.Input
Render2D.DrawRectangle(bounds, style.SelectionBorder);
}
}
+
+ if (HighlightColor != Color.Transparent)
+ {
+ var highlightRect = new Rectangle(-3.0f, 0.0f, 3.0f, Height);
+ Render2D.FillRectangle(highlightRect, HighlightColor);
+ }
}
///
diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs
index 765893bed..61109556c 100644
--- a/Source/Editor/Gizmo/UIEditorGizmo.cs
+++ b/Source/Editor/Gizmo/UIEditorGizmo.cs
@@ -180,6 +180,11 @@ namespace FlaxEditor
///
public bool EnableCamera => _view != null && EnableBackground;
+ ///
+ /// True if enable grid drawing.
+ ///
+ public bool ShowGrid { get; set; } = true;
+
///
/// Transform gizmo to use sync with (selection, snapping, transformation settings).
///
@@ -387,19 +392,53 @@ namespace FlaxEditor
if (_mouseMovesWidget && _activeWidget.UIControl)
{
// Calculate transform delta
- var resizeAxisAbs = _activeWidget.ResizeAxis.Absolute;
- var resizeAxisPos = Float2.Clamp(_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
- var resizeAxisNeg = Float2.Clamp(-_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
var delta = location - _mouseMovesPos;
// TODO: scale/size snapping?
- delta *= resizeAxisAbs;
// Resize control via widget
var moveLocation = _mouseMovesPos + delta;
var control = _activeWidget.UIControl.Control;
var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation);
- control.LocalLocation += uiControlDelta * resizeAxisNeg;
- control.Size += uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg;
+
+ // Transform delta to control local space
+ var rotation = GetTotalRotation(control) * Mathf.DegreesToRadians;
+ var cos = Mathf.Cos(rotation);
+ var sin = Mathf.Sin(rotation);
+ var localDeltaX = uiControlDelta.X * cos + uiControlDelta.Y * sin;
+ var localDeltaY = uiControlDelta.Y * cos - uiControlDelta.X * sin;
+ var localDelta = new Float2(localDeltaX, localDeltaY);
+ localDelta *= _activeWidget.ResizeAxis.Absolute;
+
+ // Calculate size change
+ var resizeAxisPos = Float2.Clamp(_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
+ var resizeAxisNeg = Float2.Clamp(-_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
+ var dSizeScaled = localDelta * resizeAxisPos - localDelta * resizeAxisNeg;
+ var scale = control.Scale;
+ var dSize = new Float2(
+ Mathf.Abs(scale.X) > Mathf.Epsilon ? dSizeScaled.X / scale.X : 0,
+ Mathf.Abs(scale.Y) > Mathf.Epsilon ? dSizeScaled.Y / scale.Y : 0);
+
+ // Apply size change
+ control.Size += dSize;
+
+ // Calculate location offset to keep the opposite edge stationary
+ // When PivotRelative is false, resizing keeps Top-Left (Location) constant,
+ // so we only need to slide back if we are resizing Left or Top edges.
+ if (!control.PivotRelative)
+ {
+ var pivotOffset = Float2.Zero;
+ if (_activeWidget.ResizeAxis.X < 0 && Mathf.Abs(dSize.X) > Mathf.Epsilon)
+ pivotOffset.X = -dSize.X * scale.X;
+ if (_activeWidget.ResizeAxis.Y < 0 && Mathf.Abs(dSize.Y) > Mathf.Epsilon)
+ pivotOffset.Y = -dSize.Y * scale.Y;
+
+ // Transform offset back to parent space and apply
+ var dLocationX = pivotOffset.X * cos - pivotOffset.Y * sin;
+ var dLocationY = pivotOffset.X * sin + pivotOffset.Y * cos;
+ var dLocation = new Float2(dLocationX, dLocationY);
+
+ control.LocalLocation += dLocation;
+ }
// Don't move if layout doesn't allow it
if (control.Parent != null)
@@ -492,17 +531,20 @@ namespace FlaxEditor
// Draw background
Surface.VisjectSurface.DrawBackgroundDefault(Editor.Instance.UI.VisjectSurfaceBackground, Width, Height);
- // Draw grid
- var viewRect = GetClientArea();
- var upperLeft = _view.PointFromParent(viewRect.Location);
- var bottomRight = _view.PointFromParent(viewRect.Size);
- var min = Float2.Min(upperLeft, bottomRight);
- var max = Float2.Max(upperLeft, bottomRight);
- var pixelRange = (max - min) * ViewScale;
- Render2D.PushClip(ref viewRect);
- DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
- DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
- Render2D.PopClip();
+ if (ShowGrid)
+ {
+ // Draw grid
+ var viewRect = GetClientArea();
+ var upperLeft = _view.PointFromParent(viewRect.Location);
+ var bottomRight = _view.PointFromParent(viewRect.Size);
+ var min = Float2.Min(upperLeft, bottomRight);
+ var max = Float2.Max(upperLeft, bottomRight);
+ var pixelRange = (max - min) * ViewScale;
+ Render2D.PushClip(ref viewRect);
+ DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
+ DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
+ Render2D.PopClip();
+ }
}
base.Draw();
@@ -634,7 +676,7 @@ namespace FlaxEditor
// Draw sizing widgets
if (_widgets == null)
_widgets = new List();
- var widgetSize = 10.0f;
+ var widgetSize = 8.0f;
var viewScale = ViewScale;
if (viewScale < 0.7f)
widgetSize *= viewScale;
@@ -685,7 +727,7 @@ namespace FlaxEditor
anchorRectSize *= viewScale;
// Make anchor rects and rotate if parent is rotated.
- var parentRotation = controlParent.Rotation * Mathf.DegreesToRadians;
+ var parentRotation = GetTotalRotation(controlParent) * Mathf.DegreesToRadians;
var rect1Axis = new Float2(-1, -1);
var rect1 = new Rectangle(anchorUpperLeft +
@@ -717,17 +759,25 @@ namespace FlaxEditor
}
}
+ private float GetTotalRotation(Control control)
+ {
+ if (control.Parent != null)
+ return control.Rotation + GetTotalRotation(control.Parent);
+ return control.Rotation;
+ }
+
private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size, float scale, Float2 resizeAxis, CursorType cursor)
{
var style = Style.Current;
var control = uiControl.Control;
- var rotation = control.Rotation;
+ var rotation = GetTotalRotation(control);
var rotationInRadians = rotation * Mathf.DegreesToRadians;
- var rect = new Rectangle((pos +
- new Float2(resizeAxis.X * Mathf.Cos(rotationInRadians) - resizeAxis.Y * Mathf.Sin(rotationInRadians),
- resizeAxis.Y * Mathf.Cos(rotationInRadians) + resizeAxis.X * Mathf.Sin(rotationInRadians)) * 10 * scale) - size * 0.5f,
- size);
-
+ var position = (pos + new Float2(resizeAxis.X * Mathf.Cos(rotationInRadians) - resizeAxis.Y * Mathf.Sin(rotationInRadians),
+ resizeAxis.Y * Mathf.Cos(rotationInRadians) + resizeAxis.X * Mathf.Sin(rotationInRadians)) * 4 * (scale < 0.7f ? scale : 1));
+ var halfSize = size * 0.5f;
+ // Keep at 0, 0 rect position until later to correctly render rotation.
+ var rect = new Rectangle(0, 0, size);
+
// Find more correct cursor at different angles
var unwindRotation = Mathf.UnwindDegrees(rotation);
if (unwindRotation is (>= 45 and < 135) or (> -135 and <= -45) )
@@ -749,6 +799,10 @@ namespace FlaxEditor
default: break;
}
}
+
+ Render2D.PushTransform(Matrix3x3.Translation2D(position));
+ Render2D.PushTransform(Matrix3x3.RotationZ(rotationInRadians));
+ Render2D.PushTransform(Matrix3x3.Translation2D(-1 * halfSize));
if (rect.Contains(ref mousePos))
{
Render2D.FillRectangle(rect, style.Foreground);
@@ -759,9 +813,14 @@ namespace FlaxEditor
Render2D.FillRectangle(rect, style.ForegroundGrey);
Render2D.DrawRectangle(rect, style.Foreground);
}
+ Render2D.PopTransform();
+ Render2D.PopTransform();
+ Render2D.PopTransform();
+
if (!_mouseMovesWidget && uiControl != null)
{
// Collect widget
+ rect.Location = position - halfSize;
_widgets.Add(new Widget
{
UIControl = uiControl,
diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
index e379e911b..af0187248 100644
--- a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
@@ -325,7 +325,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
var codeEditor = options.SourceCode.SourceCodeEditor;
if (codeEditor != "None")
{
- foreach (var e in Editor.Instance.CodeEditing.Editors)
+ foreach (var e in _editors)
{
if (string.Equals(codeEditor, e.Name, StringComparison.OrdinalIgnoreCase))
{
@@ -334,7 +334,10 @@ namespace FlaxEditor.Modules.SourceCodeEditing
}
}
}
- Editor.Instance.CodeEditing.SelectedEditor = editor;
+ if (editor == null && _editors.Count != 0)
+ editor = _editors[0];
+
+ SelectedEditor = editor;
}
///
diff --git a/Source/Editor/Modules/SourceCodeEditing/DefaultSourceCodeEditor.cs b/Source/Editor/Modules/SourceCodeEditing/DefaultSourceCodeEditor.cs
index 074dade6a..00057e9a7 100644
--- a/Source/Editor/Modules/SourceCodeEditing/DefaultSourceCodeEditor.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/DefaultSourceCodeEditor.cs
@@ -41,9 +41,9 @@ namespace FlaxEditor.Modules.SourceCodeEditing
var vsCode = codeEditing.GetInBuildEditor(CodeEditorTypes.VSCode);
var rider = codeEditing.GetInBuildEditor(CodeEditorTypes.Rider);
-#if PLATFORM_WINDOW
+#if PLATFORM_WINDOWS
// Favor the newest Visual Studio
- for (int i = (int)CodeEditorTypes.VS2019; i >= (int)CodeEditorTypes.VS2008; i--)
+ for (int i = (int)CodeEditorTypes.VS2026; i >= (int)CodeEditorTypes.VS2008; i--)
{
var visualStudio = codeEditing.GetInBuildEditor((CodeEditorTypes)i);
if (visualStudio != null)
@@ -74,7 +74,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
public string Name => "Default";
///
- public string GenerateProjectCustomArgs => null;
+ public string GenerateProjectCustomArgs => _currentEditor?.GenerateProjectCustomArgs;
///
public void OpenSolution()
diff --git a/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs b/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs
index a2a333805..222a7a0bc 100644
--- a/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs
@@ -22,71 +22,14 @@ namespace FlaxEditor.Modules.SourceCodeEditing
public InBuildSourceCodeEditor(CodeEditorTypes type)
{
Type = type;
- switch (type)
- {
- case CodeEditorTypes.Custom:
- Name = "Custom";
- break;
- case CodeEditorTypes.SystemDefault:
- Name = "System Default";
- break;
- case CodeEditorTypes.VS2008:
- Name = "Visual Studio 2008";
- break;
- case CodeEditorTypes.VS2010:
- Name = "Visual Studio 2010";
- break;
- case CodeEditorTypes.VS2012:
- Name = "Visual Studio 2012";
- break;
- case CodeEditorTypes.VS2013:
- Name = "Visual Studio 2013";
- break;
- case CodeEditorTypes.VS2015:
- Name = "Visual Studio 2015";
- break;
- case CodeEditorTypes.VS2017:
- Name = "Visual Studio 2017";
- break;
- case CodeEditorTypes.VS2019:
- Name = "Visual Studio 2019";
- break;
- case CodeEditorTypes.VS2022:
- Name = "Visual Studio 2022";
- break;
- case CodeEditorTypes.VS2026:
- Name = "Visual Studio 2026";
- break;
- case CodeEditorTypes.VSCode:
- Name = "Visual Studio Code";
- break;
- case CodeEditorTypes.VSCodeInsiders:
- Name = "Visual Studio Code - Insiders";
- break;
- case CodeEditorTypes.Rider:
- Name = "Rider";
- break;
- default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
- }
+ Name = CodeEditingManager.GetName(type);
}
///
public string Name { get; set; }
///
- public string GenerateProjectCustomArgs
- {
- get
- {
- switch (Type)
- {
- case CodeEditorTypes.VSCodeInsiders:
- case CodeEditorTypes.VSCode: return "-vscode -vs2022";
- case CodeEditorTypes.Rider: return "-vs2022";
- default: return null;
- }
- }
- }
+ public string GenerateProjectCustomArgs => CodeEditingManager.GetGenerateProjectCustomArgs(Type);
///
public void OpenSolution()
diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs
index f26506c74..f031ab383 100644
--- a/Source/Editor/Options/InterfaceOptions.cs
+++ b/Source/Editor/Options/InterfaceOptions.cs
@@ -159,7 +159,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
{
@@ -179,6 +179,22 @@ namespace FlaxEditor.Options
GameWindowThenRestore,
}
+ ///
+ /// 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.
///
@@ -525,6 +541,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/Scripting/CodeEditor.cpp b/Source/Editor/Scripting/CodeEditor.cpp
index b372da189..fcc8eef7c 100644
--- a/Source/Editor/Scripting/CodeEditor.cpp
+++ b/Source/Editor/Scripting/CodeEditor.cpp
@@ -139,6 +139,34 @@ CodeEditor* CodeEditingManager::GetCodeEditor(CodeEditorTypes editorType)
return nullptr;
}
+String CodeEditingManager::GetName(CodeEditorTypes editorType)
+{
+ const auto editor = GetCodeEditor(editorType);
+ if (editor)
+ {
+ return editor->GetName();
+ }
+ else
+ {
+ LOG(Warning, "Missing code editor type {0}", (int32)editorType);
+ return String::Empty;
+ }
+}
+
+String CodeEditingManager::GetGenerateProjectCustomArgs(CodeEditorTypes editorType)
+{
+ const auto editor = GetCodeEditor(editorType);
+ if (editor)
+ {
+ return editor->GetGenerateProjectCustomArgs();
+ }
+ else
+ {
+ LOG(Warning, "Missing code editor type {0}", (int32)editorType);
+ return String::Empty;
+ }
+}
+
void CodeEditingManager::OpenFile(CodeEditorTypes editorType, const String& path, int32 line)
{
const auto editor = GetCodeEditor(editorType);
diff --git a/Source/Editor/Scripting/CodeEditor.h b/Source/Editor/Scripting/CodeEditor.h
index 9cc71977b..0baae21b0 100644
--- a/Source/Editor/Scripting/CodeEditor.h
+++ b/Source/Editor/Scripting/CodeEditor.h
@@ -109,9 +109,18 @@ public:
///
/// Gets the name of the editor.
///
- /// The name
+ /// The name.
virtual String GetName() const = 0;
+ ///
+ /// Gets the custom arguments for the Flax.Build tool to add when generating project files for this code editor.
+ ///
+ /// The custom arguments to generate project files.
+ virtual String GetGenerateProjectCustomArgs() const
+ {
+ return String::Empty;
+ }
+
///
/// Opens the file.
///
@@ -169,6 +178,20 @@ public:
/// The editor object or null if not found.
static CodeEditor* GetCodeEditor(CodeEditorTypes editorType);
+ ///
+ /// Gets the name of the editor.
+ ///
+ /// The code editor type.
+ /// The name.
+ API_FUNCTION() static String GetName(CodeEditorTypes editorType);
+
+ ///
+ /// Gets the custom arguments for the Flax.Build tool to add when generating project files for this code editor.
+ ///
+ /// The code editor type.
+ /// The custom arguments to generate project files.
+ API_FUNCTION() static String GetGenerateProjectCustomArgs(CodeEditorTypes editorType);
+
///
/// Opens the file. Handles async opening.
///
diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp
index 8884ca322..b63815dce 100644
--- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp
+++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp
@@ -257,12 +257,17 @@ String RiderCodeEditor::GetName() const
return TEXT("Rider");
}
+String RiderCodeEditor::GetGenerateProjectCustomArgs() const
+{
+ return TEXT("-vs2022");
+}
+
void RiderCodeEditor::OpenFile(const String& path, int32 line)
{
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
- ScriptsBuilder::GenerateProject(TEXT("-vs2022"));
+ ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}
// Open file
@@ -290,7 +295,7 @@ void RiderCodeEditor::OpenSolution()
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
- ScriptsBuilder::GenerateProject(TEXT("-vs2022"));
+ ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}
// Open solution
@@ -312,5 +317,5 @@ void RiderCodeEditor::OpenSolution()
void RiderCodeEditor::OnFileAdded(const String& path)
{
- ScriptsBuilder::GenerateProject();
+ ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}
diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.h b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.h
index d9bf00947..f3c43b5b2 100644
--- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.h
+++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.h
@@ -35,6 +35,7 @@ public:
// [CodeEditor]
CodeEditorTypes GetType() const override;
String GetName() const override;
+ String GetGenerateProjectCustomArgs() const override;
void OpenFile(const String& path, int32 line) override;
void OpenSolution() override;
void OnFileAdded(const String& path) override;
diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp
index 5c06eec9c..2d79cc02b 100644
--- a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp
+++ b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.cpp
@@ -145,7 +145,46 @@ CodeEditorTypes VisualStudioEditor::GetType() const
String VisualStudioEditor::GetName() const
{
- return String(ToString(_version));
+ const Char* name;
+ switch (_version)
+ {
+ case VisualStudioVersion::VS2008:
+ name = TEXT("Visual Studio 2008");
+ break;
+ case VisualStudioVersion::VS2010:
+ name = TEXT("Visual Studio 2010");
+ break;
+ case VisualStudioVersion::VS2012:
+ name = TEXT("Visual Studio 2012");
+ break;
+ case VisualStudioVersion::VS2013:
+ name = TEXT("Visual Studio 2013");
+ break;
+ case VisualStudioVersion::VS2015:
+ name = TEXT("Visual Studio 2015");
+ break;
+ case VisualStudioVersion::VS2017:
+ name = TEXT("Visual Studio 2017");
+ break;
+ case VisualStudioVersion::VS2019:
+ name = TEXT("Visual Studio 2019");
+ break;
+ case VisualStudioVersion::VS2022:
+ name = TEXT("Visual Studio 2022");
+ break;
+ case VisualStudioVersion::VS2026:
+ name = TEXT("Visual Studio 2026");
+ break;
+ default:
+ name = ToString(_version);
+ break;
+ }
+ return String(name);
+}
+
+String VisualStudioEditor::GetGenerateProjectCustomArgs() const
+{
+ return String::Format(TEXT("-{0}"), String(ToString(_version)).ToLower());
}
void VisualStudioEditor::OpenFile(const String& path, int32 line)
@@ -153,7 +192,7 @@ void VisualStudioEditor::OpenFile(const String& path, int32 line)
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
- ScriptsBuilder::GenerateProject();
+ ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}
// Open file
@@ -172,7 +211,7 @@ void VisualStudioEditor::OpenSolution()
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
- ScriptsBuilder::GenerateProject();
+ ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}
// Open solution
@@ -187,7 +226,7 @@ void VisualStudioEditor::OpenSolution()
void VisualStudioEditor::OnFileAdded(const String& path)
{
// TODO: finish dynamic files adding to the project - for now just regenerate it
- ScriptsBuilder::GenerateProject();
+ ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
return;
if (!FileSystem::FileExists(_solutionPath))
{
diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.h b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.h
index 1bf1f1433..5c32a1171 100644
--- a/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.h
+++ b/Source/Editor/Scripting/CodeEditors/VisualStudio/VisualStudioEditor.h
@@ -56,6 +56,7 @@ public:
// [CodeEditor]
CodeEditorTypes GetType() const override;
String GetName() const override;
+ String GetGenerateProjectCustomArgs() const override;
void OpenFile(const String& path, int32 line) override;
void OpenSolution() override;
void OnFileAdded(const String& path) override;
diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
index 5eba7f20c..bf2ef6bb6 100644
--- a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
+++ b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
@@ -128,6 +128,11 @@ String VisualStudioCodeEditor::GetName() const
return _isInsiders ? TEXT("Visual Studio Code - Insiders") : TEXT("Visual Studio Code");
}
+String VisualStudioCodeEditor::GetGenerateProjectCustomArgs() const
+{
+ return TEXT("-vs2022 -vscode");
+}
+
void VisualStudioCodeEditor::OpenFile(const String& path, int32 line)
{
// Generate VS solution files for intellisense
diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.h b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.h
index 0212f207e..5091e1134 100644
--- a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.h
+++ b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.h
@@ -37,6 +37,7 @@ public:
// [CodeEditor]
CodeEditorTypes GetType() const override;
String GetName() const override;
+ String GetGenerateProjectCustomArgs() const override;
void OpenFile(const String& path, int32 line) override;
void OpenSolution() override;
bool UseAsyncForOpen() const override;
diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs
index 241f857b5..a5c7b1da7 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 d5319b875..5346972e9 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/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index 1e7d2295a..2182c2f55 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -247,8 +247,8 @@ namespace FlaxEditor.Viewport
{
_movementSpeed = value;
- if (_cameraButton != null)
- _cameraButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed);
+ if (_orthographicModeButton != null)
+ _orthographicModeButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed);
}
}
@@ -581,7 +581,7 @@ namespace FlaxEditor.Viewport
// Camera Settings Menu
var cameraCM = new ContextMenu();
- _cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), _editor.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth)
+ _cameraButton = new ViewportWidgetButton("", _editor.Icons.Camera64, cameraCM)
{
Tag = this,
TooltipText = "Camera Settings.",
@@ -590,7 +590,7 @@ namespace FlaxEditor.Viewport
_cameraWidget.Parent = this;
// Orthographic/Perspective Mode Widget
- _orthographicModeButton = new ViewportWidgetButton(string.Empty, _editor.Icons.CamSpeed32, null, true)
+ _orthographicModeButton = new OrthoCamToggleViewportWidgetButton(cameraSpeedTextWidth)
{
Checked = !_isOrtho,
TooltipText = "Toggle Orthographic/Perspective Mode.",
diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs
index 7f2b1a471..a8bdc8f20 100644
--- a/Source/Editor/Viewport/PrefabWindowViewport.cs
+++ b/Source/Editor/Viewport/PrefabWindowViewport.cs
@@ -6,7 +6,6 @@ using System.Linq;
using FlaxEditor.Content;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
-using FlaxEditor.GUI.Input;
using FlaxEditor.Modules;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
@@ -15,7 +14,6 @@ using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
-using FlaxEngine.Json;
using Utils = FlaxEditor.Utilities.Utils;
namespace FlaxEditor.Viewport
@@ -185,6 +183,7 @@ namespace FlaxEditor.Viewport
showGridButton.Clicked += () =>
{
_gridGizmo.Enabled = !_gridGizmo.Enabled;
+ _uiRoot.ShowGrid = _gridGizmo.Enabled;
showGridButton.Checked = _gridGizmo.Enabled;
};
showGridButton.Checked = true;
diff --git a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
index 55c3e9c71..dd09dc24c 100644
--- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
+++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
@@ -7,6 +7,100 @@ using FlaxEngine.GUI;
namespace FlaxEditor.Viewport.Widgets
{
+ ///
+ /// Otrhographic view toggle viewport Widget Button class.
+ /// Will draw a custom camera frustum to represent an orthographic or perspective camera.
+ ///
+ ///
+ [HideInEditor]
+ public class OrthoCamToggleViewportWidgetButton : ViewportWidgetButton
+ {
+ private const int iconPointCount = 4;
+ private const float iconRenderScale = 4.0f;
+
+ private readonly Float2 iconDrawOffset = new Float2(3.0f, 3.0f);
+
+ private readonly Float2[] iconPointsPerspective = new[]
+ {
+ new Float2(0.0f, 1.0f), // Top left
+ new Float2(4.0f, 0.0f), // Top right
+ new Float2(4.0f, 3.0f), // Bottom right
+ new Float2(0.0f, 2.0f), // Bottom left
+ };
+
+ private readonly Float2[] iconPointsOrtho = new[]
+ {
+ new Float2(0.0f, 0.0f), // Top left
+ new Float2(4.0f, 0.0f), // Top right
+ new Float2(4.0f, 3.0f), // Bottom right
+ new Float2(0.0f, 3.0f), // Bottom left
+ };
+
+ private bool wasChecked;
+ private float lerpWeight;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public OrthoCamToggleViewportWidgetButton(float speedTextWidth)
+ : base("00.0", SpriteHandle.Invalid, null, true, 20.0f + speedTextWidth)
+ {
+ wasChecked = Checked;
+ }
+
+ ///
+ public override void Update(float deltaTime)
+ {
+ if (wasChecked != Checked)
+ {
+ lerpWeight = 0.0f;
+ wasChecked = Checked;
+ }
+
+ if (lerpWeight <= 1.0f)
+ lerpWeight += deltaTime * 4.2f;
+
+ base.Update(deltaTime);
+ }
+
+ ///
+ public override void Draw()
+ {
+ // Cache data
+ var style = Style.Current;
+ var textRect = new Rectangle(0.0f, 0.0f, Width - 2.0f, Height);
+ var backgroundRect = textRect with { Width = Width - 1.0f };
+
+ // Check if is checked or mouse is over
+ if (Checked)
+ Render2D.FillRectangle(backgroundRect, style.BackgroundSelected * (IsMouseOver ? 0.9f : 0.6f));
+ else if (IsMouseOver)
+ Render2D.FillRectangle(backgroundRect, style.BackgroundHighlighted);
+
+ // Draw text
+ Render2D.DrawText(style.FontMedium, Text, textRect, style.ForegroundViewport * (IsMouseOver ? 1.0f : 0.9f), TextAlignment.Far, TextAlignment.Center);
+
+ // Draw camera frustum icon
+ Float2[] currentStart = Checked ? iconPointsOrtho : iconPointsPerspective;
+ Float2[] currentTarget = Checked ? iconPointsPerspective : iconPointsOrtho;
+
+ var features = Render2D.Features;
+ Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
+ for (int i = 1; i < iconPointCount + 1; i++)
+ {
+ int endPointIndex = Mathf.Wrap(i, 0, iconPointCount - 1);
+ Float2 lineStart = Float2.Lerp(currentStart[i - 1], currentTarget[i - 1], lerpWeight);
+ Float2 lineEnd = Float2.Lerp(currentStart[endPointIndex], currentTarget[endPointIndex], lerpWeight);
+
+ lineStart = lineStart * iconRenderScale + iconDrawOffset;
+ lineEnd = lineEnd * iconRenderScale + iconDrawOffset;
+
+ Render2D.DrawLine(lineStart, lineEnd, Color.White);
+ }
+ Render2D.Features = features;
+ }
+ }
+
///
/// Viewport Widget Button class.
///
diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs
index 1d9afbdfe..bd34802ce 100644
--- a/Source/Editor/Windows/AboutDialog.cs
+++ b/Source/Editor/Windows/AboutDialog.cs
@@ -2,6 +2,7 @@
//#define USE_AUTODESK_FBX_SDK
using System.Collections.Generic;
+using System.Reflection;
using FlaxEditor.GUI.Dialogs;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -45,9 +46,16 @@ namespace FlaxEditor.Windows
VerticalAlignment = TextAlignment.Center,
Parent = this
};
+ var assembly = typeof(Editor).Assembly;
+ var assemblyCopyright = assembly.GetCustomAttribute();
+ var assemblyInformationalVersion = assembly.GetCustomAttribute();
+ var versionParts = assemblyInformationalVersion.InformationalVersion.Split('+');
+ string versionInfo = string.Empty;
+ if (versionParts.Length == 3)
+ versionInfo = $"\nBranch: {versionParts[1]}+{(versionParts[2].Length == 40 ? versionParts[2].Substring(0, 8) : versionParts[2])}";
new Label(nameLabel.Left, nameLabel.Bottom + 4, nameLabel.Width, 50)
{
- Text = string.Format("Version: {0}\nCopyright (c) 2012-2025 Wojciech Figat.\nAll rights reserved.", Globals.EngineVersion),
+ Text = $"Version: {Globals.EngineVersion}{versionInfo}\n{assemblyCopyright.Copyright.Replace(". ", ".\n")}",
HorizontalAlignment = TextAlignment.Near,
VerticalAlignment = TextAlignment.Near,
Parent = this
@@ -98,6 +106,7 @@ namespace FlaxEditor.Windows
"Chandler Cox",
"Ari Vuollet",
"Vincent Saarmann",
+ "Michael Salvini",
});
authors.Sort();
var authorsLabel = new Label(4, topParentControl.Bottom + 20, Width - 8, 70)
diff --git a/Source/Editor/Windows/Assets/FontWindow.cs b/Source/Editor/Windows/Assets/FontWindow.cs
index 3f4a7a681..e08a87b05 100644
--- a/Source/Editor/Windows/Assets/FontWindow.cs
+++ b/Source/Editor/Windows/Assets/FontWindow.cs
@@ -21,6 +21,10 @@ namespace FlaxEditor.Windows.Assets
///
private sealed class PropertiesProxy
{
+ [DefaultValue(FontRasterMode.Bitmap)]
+ [EditorOrder(5), EditorDisplay("Properties"), Tooltip("The rasterization mode used when generating font atlases.")]
+ public FontRasterMode RasterMode;
+
[DefaultValue(FontHinting.Default)]
[EditorOrder(10), EditorDisplay("Properties"), Tooltip("The font hinting used when rendering characters.")]
public FontHinting Hinting;
@@ -41,7 +45,8 @@ namespace FlaxEditor.Windows.Assets
{
options = new FontOptions
{
- Hinting = Hinting
+ Hinting = Hinting,
+ RasterMode = RasterMode,
};
if (AntiAliasing)
options.Flags |= FontFlags.AntiAliasing;
@@ -57,6 +62,7 @@ namespace FlaxEditor.Windows.Assets
AntiAliasing = (options.Flags & FontFlags.AntiAliasing) == FontFlags.AntiAliasing;
Bold = (options.Flags & FontFlags.Bold) == FontFlags.Bold;
Italic = (options.Flags & FontFlags.Italic) == FontFlags.Italic;
+ RasterMode = options.RasterMode;
}
}
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/Editor/Windows/EditGameWindow.cs b/Source/Editor/Windows/EditGameWindow.cs
index dbeab28b1..5f91aebe6 100644
--- a/Source/Editor/Windows/EditGameWindow.cs
+++ b/Source/Editor/Windows/EditGameWindow.cs
@@ -257,7 +257,7 @@ namespace FlaxEditor.Windows
private void UpdateCameraPreview()
{
// Disable rendering preview during GI baking
- if (Editor.StateMachine.CurrentState.IsPerformanceHeavy)
+ if (Editor == null || Editor.StateMachine.CurrentState.IsPerformanceHeavy)
{
HideAllCameraPreviews();
return;
@@ -406,6 +406,14 @@ namespace FlaxEditor.Windows
}
}
+ ///
+ protected override void OnSizeChanged()
+ {
+ base.OnSizeChanged();
+
+ UpdateCameraPreview();
+ }
+
///
public override void OnDestroy()
{
diff --git a/Source/Engine/AI/Behavior.cpp b/Source/Engine/AI/Behavior.cpp
index 442051d73..5eee07dbf 100644
--- a/Source/Engine/AI/Behavior.cpp
+++ b/Source/Engine/AI/Behavior.cpp
@@ -109,7 +109,14 @@ void Behavior::UpdateAsync()
const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context);
if (result != BehaviorUpdateResult::Running)
_result = result;
- if (_result != BehaviorUpdateResult::Running)
+ if (_result != BehaviorUpdateResult::Running && tree->Graph.Root->Loop)
+ {
+ // Reset State
+ _result = BehaviorUpdateResult::Running;
+ _accumulatedTime = 0.0f;
+ _totalTime = 0;
+ }
+ else if (_result != BehaviorUpdateResult::Running)
{
Finished();
}
diff --git a/Source/Engine/AI/BehaviorTreeNodes.h b/Source/Engine/AI/BehaviorTreeNodes.h
index 7aaab5f3c..05a472da7 100644
--- a/Source/Engine/AI/BehaviorTreeNodes.h
+++ b/Source/Engine/AI/BehaviorTreeNodes.h
@@ -96,6 +96,10 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeRootNode : public BehaviorTre
// The target amount of the behavior logic updates per second.
API_FIELD(Attributes="EditorOrder(100)")
float UpdateFPS = 10.0f;
+
+ // Whether to loop the root node.
+ API_FIELD(Attributes="EditorOrder(200)")
+ bool Loop = true;
};
///
diff --git a/Source/Engine/Audio/Audio.cpp b/Source/Engine/Audio/Audio.cpp
index 5574919c4..12cde01e8 100644
--- a/Source/Engine/Audio/Audio.cpp
+++ b/Source/Engine/Audio/Audio.cpp
@@ -3,7 +3,6 @@
#include "Audio.h"
#include "AudioBackend.h"
#include "AudioSettings.h"
-#include "FlaxEngine.Gen.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Level/Level.h"
diff --git a/Source/Engine/Content/Upgraders/FontAssetUpgrader.h b/Source/Engine/Content/Upgraders/FontAssetUpgrader.h
index 80c2e6c39..4ca6e6d73 100644
--- a/Source/Engine/Content/Upgraders/FontAssetUpgrader.h
+++ b/Source/Engine/Content/Upgraders/FontAssetUpgrader.h
@@ -5,6 +5,7 @@
#if USE_EDITOR
#include "BinaryAssetUpgrader.h"
+#include "Engine/Render2D/FontAsset.h"
///
/// Font Asset Upgrader
@@ -17,10 +18,33 @@ public:
{
const Upgrader upgraders[] =
{
- {},
+ { 3, 4, &Upgrade_3_To_4 },
};
setup(upgraders, ARRAY_COUNT(upgraders));
}
+
+private:
+ struct FontOptionsOld
+ {
+ FontHinting Hinting;
+ FontFlags Flags;
+ };
+
+ static bool Upgrade_3_To_4(AssetMigrationContext& context)
+ {
+ ASSERT(context.Input.SerializedVersion == 3 && context.Output.SerializedVersion == 4);
+
+ FontOptionsOld optionsOld;
+ Platform::MemoryCopy(&optionsOld, context.Input.CustomData.Get(), sizeof(FontOptionsOld));
+
+ FontOptions options;
+ options.Hinting = optionsOld.Hinting;
+ options.Flags = optionsOld.Flags;
+ options.RasterMode = FontRasterMode::Bitmap;
+ context.Output.CustomData.Copy(&options);
+
+ return CopyChunk(context, 0);
+ }
};
#endif
diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp
index 9cad287dc..696f43a9e 100644
--- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp
+++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp
@@ -36,6 +36,30 @@
#include "CreateAnimation.h"
#include "CreateBehaviorTree.h"
#include "CreateJson.h"
+#include "Engine/Content/Assets/Model.h"
+
+namespace
+{
+ bool IsAssetTypeNameTextureFile(const String& typeName)
+ {
+ return typeName == Texture::TypeName || typeName == SpriteAtlas::TypeName;
+ }
+
+ bool IsAssetTypeNameModelFile(const String& typeName)
+ {
+ return typeName == Model::TypeName || typeName == SkinnedModel::TypeName || typeName == Animation::TypeName;
+ }
+
+ bool IsAssetTypeNameMatch(const String& a, const String& b)
+ {
+ // Special case when reimporting model/texture but different type
+ if (IsAssetTypeNameTextureFile(a) && IsAssetTypeNameTextureFile(b))
+ return true;
+ if (IsAssetTypeNameModelFile(a) && IsAssetTypeNameModelFile(b))
+ return true;
+ return a == b;
+ }
+}
// Tags used to detect asset creation mode
const String AssetsImportingManager::CreateTextureTag(TEXT("Texture"));
@@ -84,8 +108,6 @@ CreateAssetContext::CreateAssetContext(const StringView& inputPath, const String
CustomArg = arg;
Data.Header.ID = id;
SkipMetadata = false;
-
- // TODO: we should use ASNI only chars path (Assimp can use only that kind)
OutputPath = Content::CreateTemporaryAssetPath();
}
@@ -122,6 +144,24 @@ CreateAssetResult CreateAssetContext::Run(const CreateAssetFunction& callback)
Data.Metadata.Copy((const byte*)buffer.GetString(), (uint32)buffer.GetSize());
}
+ // Check if target asset already exists but has different type
+ AssetInfo targetAssetInfo;
+ if (Content::GetAssetInfo(TargetAssetPath, targetAssetInfo) && !IsAssetTypeNameMatch(targetAssetInfo.TypeName, Data.Header.TypeName))
+ {
+ // Change path
+ int32 index = 0;
+ String newTargetAssetPath;
+ do
+ {
+ newTargetAssetPath = StringUtils::GetDirectoryName(TargetAssetPath);
+ newTargetAssetPath /= StringUtils::GetFileNameWithoutExtension(TargetAssetPath) + String::Format(TEXT(" ({})."), index++) + FileSystem::GetExtension(TargetAssetPath);
+ } while (index < 100 && FileSystem::FileExists(newTargetAssetPath));
+ TargetAssetPath = newTargetAssetPath;
+
+ // Change id
+ Data.Header.ID = Guid::New();
+ }
+
// Save file
result = FlaxStorage::Create(OutputPath, Data) ? CreateAssetResult::CannotSaveFile : CreateAssetResult::Ok;
if (result == CreateAssetResult::Ok)
diff --git a/Source/Engine/ContentImporters/ImportFont.cpp b/Source/Engine/ContentImporters/ImportFont.cpp
index c7dc01fe6..964f3907e 100644
--- a/Source/Engine/ContentImporters/ImportFont.cpp
+++ b/Source/Engine/ContentImporters/ImportFont.cpp
@@ -12,12 +12,13 @@
CreateAssetResult ImportFont::Import(CreateAssetContext& context)
{
// Base
- IMPORT_SETUP(FontAsset, 3);
+ IMPORT_SETUP(FontAsset, 4);
// Setup header
FontOptions options;
options.Hinting = FontHinting::Default;
options.Flags = FontFlags::AntiAliasing;
+ options.RasterMode = FontRasterMode::Bitmap;
context.Data.CustomData.Copy(&options);
// Open the file
diff --git a/Source/Engine/Core/Math/Color.cs b/Source/Engine/Core/Math/Color.cs
index be4a12789..dbfd70c7e 100644
--- a/Source/Engine/Core/Math/Color.cs
+++ b/Source/Engine/Core/Math/Color.cs
@@ -82,6 +82,11 @@ namespace FlaxEngine
}
}
+ ///
+ /// Gets the brightness of the color
+ ///
+ public float Brightness => R * 0.299f + G * 0.587f + B * 0.114f;
+
///
/// Returns the minimum color component value: Min(r,g,b).
///
diff --git a/Source/Engine/Engine/CommandLine.cpp b/Source/Engine/Engine/CommandLine.cpp
index e0f9441eb..ad2c9c903 100644
--- a/Source/Engine/Engine/CommandLine.cpp
+++ b/Source/Engine/Engine/CommandLine.cpp
@@ -149,6 +149,7 @@ bool CommandLine::Parse(const Char* cmdLine)
PARSE_BOOL_SWITCH("-clearcache ", ClearCache);
PARSE_BOOL_SWITCH("-clearcooker ", ClearCookerCache);
PARSE_ARG_SWITCH("-project ", Project);
+ PARSE_BOOL_SWITCH("-lastproject ", LastProject);
PARSE_BOOL_SWITCH("-new ", NewProject);
PARSE_BOOL_SWITCH("-genprojectfiles ", GenProjectFiles);
PARSE_ARG_SWITCH("-build ", Build);
diff --git a/Source/Engine/Engine/CommandLine.h b/Source/Engine/Engine/CommandLine.h
index ad49bee04..0464a478f 100644
--- a/Source/Engine/Engine/CommandLine.h
+++ b/Source/Engine/Engine/CommandLine.h
@@ -133,6 +133,11 @@ public:
///
String Project;
+ ///
+ /// -lastproject (Opens the last project)
+ ///
+ Nullable LastProject;
+
///
/// -new (generates the project files inside the specified project folder or uses current workspace folder)
///
diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp
index 5e8224b4e..b53b69741 100644
--- a/Source/Engine/Engine/Engine.cpp
+++ b/Source/Engine/Engine/Engine.cpp
@@ -595,7 +595,11 @@ void EngineImpl::InitLog()
#if COMPILE_WITH_DEV_ENV
LOG(Info, "Compiled for Dev Environment");
#endif
+#if defined(FLAXENGINE_BRANCH) && defined(FLAXENGINE_COMMIT)
+ LOG(Info, "Version " FLAXENGINE_VERSION_TEXT ", {}, {}", StringAsUTF16<>(FLAXENGINE_BRANCH).Get(), StringAsUTF16<>(FLAXENGINE_COMMIT).Get());
+#else
LOG(Info, "Version " FLAXENGINE_VERSION_TEXT);
+#endif
const Char* cpp = TEXT("?");
if (__cplusplus == 202101L) cpp = TEXT("C++23");
else if (__cplusplus == 202002L) cpp = TEXT("C++20");
diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp
index efe89bf5c..459f1b662 100644
--- a/Source/Engine/Foliage/Foliage.cpp
+++ b/Source/Engine/Foliage/Foliage.cpp
@@ -22,7 +22,7 @@
#include "Engine/Serialization/Serialization.h"
#include "Engine/Utilities/Encryption.h"
-#define FOLIAGE_GET_DRAW_MODES(renderContext, type) (type.DrawModes & renderContext.View.Pass & renderContext.View.GetShadowsDrawPassMask(type.ShadowsMode))
+#define FOLIAGE_GET_DRAW_MODES(renderContext, type) (type._drawModes & renderContext.View.Pass & renderContext.View.GetShadowsDrawPassMask(type.ShadowsMode))
#define FOLIAGE_CAN_DRAW(renderContext, type) (type.IsReady() && FOLIAGE_GET_DRAW_MODES(renderContext, type) != DrawPass::None && type.Model->CanBeRendered())
Foliage::Foliage(const SpawnParams& params)
@@ -360,7 +360,7 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::D
draw.DrawState = &instance.DrawState;
draw.Bounds = sphere;
draw.PerInstanceRandom = instance.Random;
- draw.DrawModes = type.DrawModes;
+ draw.DrawModes = type._drawModes;
draw.SetStencilValue(_layer);
type.Model->Draw(context.RenderContext, draw);
@@ -597,14 +597,22 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Me
void Foliage::InitType(const RenderView& view, FoliageType& type)
{
- const DrawPass drawModes = type.DrawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode);
+ const DrawPass drawModes = type._drawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode);
type._canDraw = type.IsReady() && drawModes != DrawPass::None && type.Model && type.Model->CanBeRendered();
+ bool drawModesDirty = false;
for (int32 j = 0; j < type.Entries.Count(); j++)
{
auto& e = type.Entries[j];
e.ReceiveDecals = type.ReceiveDecals != 0;
e.ShadowsMode = type.ShadowsMode;
+ if (type._drawModesDirty)
+ {
+ type._drawModesDirty = 0;
+ drawModesDirty = true;
+ }
}
+ if (drawModesDirty)
+ GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::DrawModes);
}
int32 Foliage::GetInstancesCount() const
@@ -1250,7 +1258,7 @@ void Foliage::Draw(RenderContext& renderContext)
draw.Deformation = nullptr;
draw.Bounds = instance.Bounds;
draw.PerInstanceRandom = instance.Random;
- draw.DrawModes = type.DrawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode);
+ draw.DrawModes = type._drawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode);
draw.SetStencilValue(_layer);
type.Model->Draw(renderContext, draw);
return;
diff --git a/Source/Engine/Foliage/FoliageType.cpp b/Source/Engine/Foliage/FoliageType.cpp
index 8b8c84420..e9eb63b2d 100644
--- a/Source/Engine/Foliage/FoliageType.cpp
+++ b/Source/Engine/Foliage/FoliageType.cpp
@@ -13,6 +13,7 @@ FoliageType::FoliageType()
, Index(-1)
{
_isReady = 0;
+ _drawModesDirty = 0;
ReceiveDecals = true;
UseDensityScaling = false;
@@ -32,7 +33,7 @@ FoliageType& FoliageType::operator=(const FoliageType& other)
CullDistance = other.CullDistance;
CullDistanceRandomRange = other.CullDistanceRandomRange;
ScaleInLightmap = other.ScaleInLightmap;
- DrawModes = other.DrawModes;
+ SetDrawModes(other._drawModes);
ShadowsMode = other.ShadowsMode;
PaintDensity = other.PaintDensity;
PaintRadius = other.PaintRadius;
@@ -69,6 +70,19 @@ void FoliageType::SetMaterials(const Array& value)
Entries[i].Material = value[i];
}
+DrawPass FoliageType::GetDrawModes() const
+{
+ return _drawModes;
+}
+
+void FoliageType::SetDrawModes(DrawPass value)
+{
+ if (_drawModes == value)
+ return;
+ _drawModes = value;
+ _drawModesDirty = 1;
+}
+
Float3 FoliageType::GetRandomScale() const
{
Float3 result;
@@ -150,7 +164,7 @@ void FoliageType::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE(CullDistance);
SERIALIZE(CullDistanceRandomRange);
SERIALIZE(ScaleInLightmap);
- SERIALIZE(DrawModes);
+ SERIALIZE_MEMBER(DrawModes, _drawModes);
SERIALIZE(ShadowsMode);
SERIALIZE_BIT(ReceiveDecals);
SERIALIZE_BIT(UseDensityScaling);
@@ -191,7 +205,7 @@ void FoliageType::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
DESERIALIZE(CullDistance);
DESERIALIZE(CullDistanceRandomRange);
DESERIALIZE(ScaleInLightmap);
- DESERIALIZE(DrawModes);
+ DESERIALIZE_MEMBER(DrawModes, _drawModes);
DESERIALIZE(ShadowsMode);
DESERIALIZE_BIT(ReceiveDecals);
DESERIALIZE_BIT(UseDensityScaling);
diff --git a/Source/Engine/Foliage/FoliageType.h b/Source/Engine/Foliage/FoliageType.h
index 224ed0bd8..8b36f618b 100644
--- a/Source/Engine/Foliage/FoliageType.h
+++ b/Source/Engine/Foliage/FoliageType.h
@@ -48,6 +48,8 @@ API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FoliageType : public ScriptingOb
private:
uint8 _isReady : 1;
uint8 _canDraw : 1;
+ uint8 _drawModesDirty : 1;
+ DrawPass _drawModes = DrawPass::Depth | DrawPass::GBuffer | DrawPass::Forward;
public:
///
@@ -124,9 +126,15 @@ public:
API_FIELD() float ScaleInLightmap = 1.0f;
///
- /// The draw passes to use for rendering this foliage type.
+ /// Gets the draw passes to use for rendering this foliage type.
///
- API_FIELD() DrawPass DrawModes = DrawPass::Depth | DrawPass::GBuffer | DrawPass::Forward;
+ API_PROPERTY(Attributes="DefaultValue(DrawPass.Depth | DrawPass.GBuffer | DrawPass.Forward)")
+ DrawPass GetDrawModes() const;
+
+ ///
+ /// Sets the draw passes to use for rendering this foliage type.
+ ///
+ API_PROPERTY() void SetDrawModes(DrawPass value);
///
/// The shadows casting mode.
@@ -184,7 +192,7 @@ public:
API_FIELD() float PlacementRandomRollAngle = 0.0f;
///
- /// The density scaling scale applied to the global scale for the foliage instances of this type. Can be used to boost or reduce density scaling effect on this foliage type. Default is 1.
+ /// The density scale factor applied to the global scale for the foliage instances of this type. Can be used to boost or reduce density scaling effect on this foliage type. Default is 1. Lower to reduce density scaling effect when downscaling foliage via global quality/scalability.
///
API_FIELD() float DensityScalingScale = 1.0f;
diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp
index eadbfcba9..bbec523a2 100644
--- a/Source/Engine/Graphics/Models/MeshBase.cpp
+++ b/Source/Engine/Graphics/Models/MeshBase.cpp
@@ -158,6 +158,14 @@ void MeshAccessor::Stream::CopyTo(Span dst) const
{
Platform::MemoryCopy(dst.Get(), _data.Get(), _data.Length());
}
+ else if (IsLinear(PixelFormat::R16G16B16A16_Float))
+ {
+ for (int32 i = 0; i < count; i++)
+ {
+ auto v = *(Half4*)(_data.Get() + i * _stride);
+ dst.Get()[i] = Float3(Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y), Float16Compressor::Decompress(v.Z));
+ }
+ }
else
{
for (int32 i = 0; i < count; i++)
diff --git a/Source/Engine/Input/Gamepad.cpp b/Source/Engine/Input/Gamepad.cpp
index f4a2ef4ab..32856839b 100644
--- a/Source/Engine/Input/Gamepad.cpp
+++ b/Source/Engine/Input/Gamepad.cpp
@@ -2,6 +2,71 @@
#include "Gamepad.h"
+namespace
+{
+ GamepadAxis GetButtonAxis(GamepadButton button, bool& positive)
+ {
+ positive = true;
+ switch (button)
+ {
+ case GamepadButton::LeftTrigger:
+ return GamepadAxis::LeftTrigger;
+ case GamepadButton::RightTrigger:
+ return GamepadAxis::RightTrigger;
+ case GamepadButton::LeftStickUp:
+ return GamepadAxis::LeftStickY;
+ case GamepadButton::LeftStickDown:
+ positive = false;
+ return GamepadAxis::LeftStickY;
+ case GamepadButton::LeftStickLeft:
+ positive = false;
+ return GamepadAxis::LeftStickX;
+ case GamepadButton::LeftStickRight:
+ return GamepadAxis::LeftStickX;
+ case GamepadButton::RightStickUp:
+ return GamepadAxis::RightStickY;
+ case GamepadButton::RightStickDown:
+ positive = false;
+ return GamepadAxis::RightStickY;
+ case GamepadButton::RightStickLeft:
+ positive = false;
+ return GamepadAxis::RightStickX;
+ case GamepadButton::RightStickRight:
+ return GamepadAxis::RightStickX;
+ default:
+ return GamepadAxis::None;
+ }
+ }
+
+ bool GetButtonState(const Gamepad::State& state, GamepadButton button, float deadZone)
+ {
+ if (deadZone > 0.01f)
+ {
+ switch (button)
+ {
+ case GamepadButton::LeftTrigger:
+ case GamepadButton::RightTrigger:
+ case GamepadButton::LeftStickUp:
+ case GamepadButton::LeftStickDown:
+ case GamepadButton::LeftStickLeft:
+ case GamepadButton::LeftStickRight:
+ case GamepadButton::RightStickUp:
+ case GamepadButton::RightStickDown:
+ case GamepadButton::RightStickLeft:
+ case GamepadButton::RightStickRight:
+ {
+ bool positive;
+ float axis = state.Axis[(int32)GetButtonAxis(button, positive)];
+ return positive ? axis >= deadZone : axis <= -deadZone;
+ }
+ default:
+ break;
+ }
+ }
+ return state.Buttons[(int32)button];
+ }
+}
+
void GamepadLayout::Init()
{
for (int32 i = 0; i < (int32)GamepadButton::MAX; i++)
@@ -31,6 +96,21 @@ void Gamepad::ResetState()
_mappedPrevState.Clear();
}
+bool Gamepad::GetButton(GamepadButton button, float deadZone) const
+{
+ return GetButtonState(_mappedState, button, deadZone);
+}
+
+bool Gamepad::GetButtonDown(GamepadButton button, float deadZone) const
+{
+ return GetButtonState(_mappedState, button, deadZone) && !GetButtonState(_mappedPrevState, button, deadZone);
+}
+
+bool Gamepad::GetButtonUp(GamepadButton button, float deadZone) const
+{
+ return !GetButtonState(_mappedState, button, deadZone) && GetButtonState(_mappedPrevState, button, deadZone);
+}
+
bool Gamepad::IsAnyButtonDown() const
{
// TODO: optimize with SIMD
diff --git a/Source/Engine/Input/Gamepad.h b/Source/Engine/Input/Gamepad.h
index 20994d85a..dc95c67ae 100644
--- a/Source/Engine/Input/Gamepad.h
+++ b/Source/Engine/Input/Gamepad.h
@@ -148,36 +148,30 @@ public:
/// Gets the gamepad button state (true if being pressed during the current frame).
///
/// Gamepad button to check
+ /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered.
/// True if user holds down the button, otherwise false.
- API_FUNCTION() FORCE_INLINE bool GetButton(const GamepadButton button) const
- {
- return _mappedState.Buttons[static_cast(button)];
- }
+ API_FUNCTION() bool GetButton(GamepadButton button, float deadZone = 0.0f) const;
///
/// Gets the gamepad button down state (true if was pressed during the current frame).
///
/// Gamepad button to check
+ /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered.
/// True if user starts pressing down the button, otherwise false.
- API_FUNCTION() FORCE_INLINE bool GetButtonDown(const GamepadButton button) const
- {
- return _mappedState.Buttons[static_cast(button)] && !_mappedPrevState.Buttons[static_cast(button)];
- }
-
- ///
- /// Checks if any gamepad button is currently pressed.
- ///
- API_PROPERTY() bool IsAnyButtonDown() const;
+ API_FUNCTION() bool GetButtonDown(GamepadButton button, float deadZone = 0.0f) const;
///
/// Gets the gamepad button up state (true if was released during the current frame).
///
/// Gamepad button to check
+ /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered.
/// True if user releases the button, otherwise false.
- API_FUNCTION() FORCE_INLINE bool GetButtonUp(const GamepadButton button) const
- {
- return !_mappedState.Buttons[static_cast(button)] && _mappedPrevState.Buttons[static_cast(button)];
- }
+ API_FUNCTION() bool GetButtonUp(GamepadButton button, float deadZone = 0.0f) const;
+
+ ///
+ /// Checks if any gamepad button is currently pressed.
+ ///
+ API_PROPERTY() bool IsAnyButtonDown() const;
public:
///
diff --git a/Source/Engine/Input/Input.cpp b/Source/Engine/Input/Input.cpp
index 7048140ef..c95d7b03e 100644
--- a/Source/Engine/Input/Input.cpp
+++ b/Source/Engine/Input/Input.cpp
@@ -120,6 +120,7 @@ void InputSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* m
config.MouseButton = JsonTools::GetEnum(v, "MouseButton", MouseButton::None);
config.GamepadButton = JsonTools::GetEnum(v, "GamepadButton", GamepadButton::None);
config.Gamepad = JsonTools::GetEnum(v, "Gamepad", InputGamepadIndex::All);
+ config.DeadZone = JsonTools::GetFloat(v, "DeadZone", 0.5f);
}
}
else
@@ -499,24 +500,24 @@ float Input::GetGamepadAxis(int32 gamepadIndex, GamepadAxis axis)
return 0.0f;
}
-bool Input::GetGamepadButton(int32 gamepadIndex, GamepadButton button)
+bool Input::GetGamepadButton(int32 gamepadIndex, GamepadButton button, float deadZone)
{
if (gamepadIndex >= 0 && gamepadIndex < Gamepads.Count())
- return Gamepads[gamepadIndex]->GetButton(button);
+ return Gamepads[gamepadIndex]->GetButton(button, deadZone);
return false;
}
-bool Input::GetGamepadButtonDown(int32 gamepadIndex, GamepadButton button)
+bool Input::GetGamepadButtonDown(int32 gamepadIndex, GamepadButton button, float deadZone)
{
if (gamepadIndex >= 0 && gamepadIndex < Gamepads.Count())
- return Gamepads[gamepadIndex]->GetButtonDown(button);
+ return Gamepads[gamepadIndex]->GetButtonDown(button, deadZone);
return false;
}
-bool Input::GetGamepadButtonUp(int32 gamepadIndex, GamepadButton button)
+bool Input::GetGamepadButtonUp(int32 gamepadIndex, GamepadButton button, float deadZone)
{
if (gamepadIndex >= 0 && gamepadIndex < Gamepads.Count())
- return Gamepads[gamepadIndex]->GetButtonUp(button);
+ return Gamepads[gamepadIndex]->GetButtonUp(button, deadZone);
return false;
}
@@ -542,13 +543,13 @@ float Input::GetGamepadAxis(InputGamepadIndex gamepad, GamepadAxis axis)
return false;
}
-bool Input::GetGamepadButton(InputGamepadIndex gamepad, GamepadButton button)
+bool Input::GetGamepadButton(InputGamepadIndex gamepad, GamepadButton button, float deadZone)
{
if (gamepad == InputGamepadIndex::All)
{
for (auto g : Gamepads)
{
- if (g->GetButton(button))
+ if (g->GetButton(button, deadZone))
return true;
}
}
@@ -556,18 +557,18 @@ bool Input::GetGamepadButton(InputGamepadIndex gamepad, GamepadButton button)
{
const auto index = static_cast(gamepad);
if (index < Gamepads.Count())
- return Gamepads[index]->GetButton(button);
+ return Gamepads[index]->GetButton(button, deadZone);
}
return false;
}
-bool Input::GetGamepadButtonDown(InputGamepadIndex gamepad, GamepadButton button)
+bool Input::GetGamepadButtonDown(InputGamepadIndex gamepad, GamepadButton button, float deadZone)
{
if (gamepad == InputGamepadIndex::All)
{
for (auto g : Gamepads)
{
- if (g->GetButtonDown(button))
+ if (g->GetButtonDown(button, deadZone))
return true;
}
}
@@ -575,18 +576,18 @@ bool Input::GetGamepadButtonDown(InputGamepadIndex gamepad, GamepadButton button
{
const auto index = static_cast(gamepad);
if (index < Gamepads.Count())
- return Gamepads[index]->GetButtonDown(button);
+ return Gamepads[index]->GetButtonDown(button, deadZone);
}
return false;
}
-bool Input::GetGamepadButtonUp(InputGamepadIndex gamepad, GamepadButton button)
+bool Input::GetGamepadButtonUp(InputGamepadIndex gamepad, GamepadButton button, float deadZone)
{
if (gamepad == InputGamepadIndex::All)
{
for (auto g : Gamepads)
{
- if (g->GetButtonUp(button))
+ if (g->GetButtonUp(button, deadZone))
return true;
}
}
@@ -594,7 +595,7 @@ bool Input::GetGamepadButtonUp(InputGamepadIndex gamepad, GamepadButton button)
{
const auto index = static_cast(gamepad);
if (index < Gamepads.Count())
- return Gamepads[index]->GetButtonUp(button);
+ return Gamepads[index]->GetButtonUp(button, deadZone);
}
return false;
}
@@ -1065,26 +1066,26 @@ void InputService::Update()
bool isActive;
if (config.Mode == InputActionMode::Pressing)
{
- isActive = Input::GetKey(config.Key) || Input::GetMouseButton(config.MouseButton) || Input::GetGamepadButton(config.Gamepad, config.GamepadButton);
+ isActive = Input::GetKey(config.Key) || Input::GetMouseButton(config.MouseButton) || Input::GetGamepadButton(config.Gamepad, config.GamepadButton, config.DeadZone);
}
else if (config.Mode == InputActionMode::Press)
{
- isActive = Input::GetKeyDown(config.Key) || Input::GetMouseButtonDown(config.MouseButton) || Input::GetGamepadButtonDown(config.Gamepad, config.GamepadButton);
+ isActive = Input::GetKeyDown(config.Key) || Input::GetMouseButtonDown(config.MouseButton) || Input::GetGamepadButtonDown(config.Gamepad, config.GamepadButton, config.DeadZone);
}
else
{
- isActive = Input::GetKeyUp(config.Key) || Input::GetMouseButtonUp(config.MouseButton) || Input::GetGamepadButtonUp(config.Gamepad, config.GamepadButton);
+ isActive = Input::GetKeyUp(config.Key) || Input::GetMouseButtonUp(config.MouseButton) || Input::GetGamepadButtonUp(config.Gamepad, config.GamepadButton, config.DeadZone);
}
- if (Input::GetKeyDown(config.Key) || Input::GetMouseButtonDown(config.MouseButton) || Input::GetGamepadButtonDown(config.Gamepad, config.GamepadButton))
+ if (Input::GetKeyDown(config.Key) || Input::GetMouseButtonDown(config.MouseButton) || Input::GetGamepadButtonDown(config.Gamepad, config.GamepadButton, config.DeadZone))
{
data.State = InputActionState::Press;
}
- else if (Input::GetKey(config.Key) || Input::GetMouseButton(config.MouseButton) || Input::GetGamepadButton(config.Gamepad, config.GamepadButton))
+ else if (Input::GetKey(config.Key) || Input::GetMouseButton(config.MouseButton) || Input::GetGamepadButton(config.Gamepad, config.GamepadButton, config.DeadZone))
{
data.State = InputActionState::Pressing;
}
- else if (Input::GetKeyUp(config.Key) || Input::GetMouseButtonUp(config.MouseButton) || Input::GetGamepadButtonUp(config.Gamepad, config.GamepadButton))
+ else if (Input::GetKeyUp(config.Key) || Input::GetMouseButtonUp(config.MouseButton) || Input::GetGamepadButtonUp(config.Gamepad, config.GamepadButton, config.DeadZone))
{
data.State = InputActionState::Release;
}
diff --git a/Source/Engine/Input/Input.h b/Source/Engine/Input/Input.h
index 73e87f5f0..dca26a5f4 100644
--- a/Source/Engine/Input/Input.h
+++ b/Source/Engine/Input/Input.h
@@ -238,24 +238,27 @@ public:
///
/// The gamepad index
/// Gamepad button to check
+ /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered.
/// True if user holds down the button, otherwise false.
- API_FUNCTION() static bool GetGamepadButton(int32 gamepadIndex, GamepadButton button);
+ API_FUNCTION() static bool GetGamepadButton(int32 gamepadIndex, GamepadButton button, float deadZone = 0.0f);
///
/// Gets the gamepad button down state (true if was pressed during the current frame).
///
/// The gamepad index
/// Gamepad button to check
+ /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered.
/// True if user starts pressing down the button, otherwise false.
- API_FUNCTION() static bool GetGamepadButtonDown(int32 gamepadIndex, GamepadButton button);
+ API_FUNCTION() static bool GetGamepadButtonDown(int32 gamepadIndex, GamepadButton button, float deadZone = 0.0f);
///
/// Gets the gamepad button up state (true if was released during the current frame).
///
/// The gamepad index
/// Gamepad button to check
+ /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered.
/// True if user releases the button, otherwise false.
- API_FUNCTION() static bool GetGamepadButtonUp(int32 gamepadIndex, GamepadButton button);
+ API_FUNCTION() static bool GetGamepadButtonUp(int32 gamepadIndex, GamepadButton button, float deadZone = 0.0f);
///
/// Gets the gamepad axis value.
@@ -270,24 +273,27 @@ public:
///
/// The gamepad
/// Gamepad button to check
+ /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered.
/// True if user holds down the button, otherwise false.
- API_FUNCTION() static bool GetGamepadButton(InputGamepadIndex gamepad, GamepadButton button);
+ API_FUNCTION() static bool GetGamepadButton(InputGamepadIndex gamepad, GamepadButton button, float deadZone = 0.0f);
///
/// Gets the gamepad button down state (true if was pressed during the current frame).
///
/// The gamepad
/// Gamepad button to check
+ /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered.
/// True if user starts pressing down the button, otherwise false.
- API_FUNCTION() static bool GetGamepadButtonDown(InputGamepadIndex gamepad, GamepadButton button);
+ API_FUNCTION() static bool GetGamepadButtonDown(InputGamepadIndex gamepad, GamepadButton button, float deadZone = 0.0f);
///
/// Gets the gamepad button up state (true if was released during the current frame).
///
/// The gamepad
/// Gamepad button to check
+ /// Custom dead-zone value to detect gamepad button usage for non-binary buttons such as left/right thumbs that can move freely. By default, any movement is registered.
/// True if user releases the button, otherwise false.
- API_FUNCTION() static bool GetGamepadButtonUp(InputGamepadIndex gamepad, GamepadButton button);
+ API_FUNCTION() static bool GetGamepadButtonUp(InputGamepadIndex gamepad, GamepadButton button, float deadZone = 0.0f);
public:
///
diff --git a/Source/Engine/Input/VirtualInput.h b/Source/Engine/Input/VirtualInput.h
index 817d7014c..a4975aaef 100644
--- a/Source/Engine/Input/VirtualInput.h
+++ b/Source/Engine/Input/VirtualInput.h
@@ -48,6 +48,12 @@ API_STRUCT() struct ActionConfig
///
API_FIELD(Attributes="EditorOrder(40)")
InputGamepadIndex Gamepad;
+
+ ///
+ /// Threshold for non-binary value inputs such as gamepad stick position to decide if action was triggered. Can be sued to activate action only if input value is higher than specified number.
+ ///
+ API_FIELD(Attributes = "EditorOrder(50)")
+ float DeadZone = 0.5f;
};
///
diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp
index f52fab600..8123142aa 100644
--- a/Source/Engine/Level/Actor.cpp
+++ b/Source/Engine/Level/Actor.cpp
@@ -5,6 +5,7 @@
#include "Level.h"
#include "SceneQuery.h"
#include "SceneObjectsFactory.h"
+#include "FlaxEngine.Gen.h"
#include "Scene/Scene.h"
#include "Prefabs/Prefab.h"
#include "Prefabs/PrefabManager.h"
@@ -178,21 +179,20 @@ void Actor::OnDeleteObject()
_scene = nullptr;
}
}
- else if (_parent)
+ else
{
- // Unlink from the parent
- _parent->Children.RemoveKeepOrder(this);
- _parent->_isHierarchyDirty = true;
- _parent = nullptr;
- _scene = nullptr;
+ if (_isEnabled)
+ OnDisable();
+ if (_parent)
+ {
+ // Unlink from the parent
+ _parent->Children.RemoveKeepOrder(this);
+ _parent->_isHierarchyDirty = true;
+ _parent = nullptr;
+ _scene = nullptr;
+ }
}
- // Ensure to exit gameplay in a valid way
- ASSERT(!IsDuringPlay());
-#if BUILD_DEBUG || BUILD_DEVELOPMENT
- ASSERT(!_isEnabled);
-#endif
-
// Fire event
Deleted(this);
diff --git a/Source/Engine/Level/Actors/PointLight.cpp b/Source/Engine/Level/Actors/PointLight.cpp
index 607bf1bc4..b5801166e 100644
--- a/Source/Engine/Level/Actors/PointLight.cpp
+++ b/Source/Engine/Level/Actors/PointLight.cpp
@@ -1,6 +1,7 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#include "PointLight.h"
+#include "Engine/Content/Deprecated.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/RenderView.h"
@@ -196,6 +197,14 @@ void PointLight::Deserialize(DeserializeStream& stream, ISerializeModifier* modi
DESERIALIZE(UseInverseSquaredFalloff);
DESERIALIZE(UseIESBrightness);
DESERIALIZE(IESBrightnessScale);
+
+ // [Deprecated on 12.03.2026, expires on 12.03.2028]
+ if (modifier->EngineBuild <= 6807 && SERIALIZE_FIND_MEMBER(stream, "UseInverseSquaredFalloff") != stream.MemberEnd() && UseInverseSquaredFalloff)
+ {
+ // Convert old non-physical brightness value that was used for Inverse Squared Falloff which wasn't based on proper cm/m units calculations
+ MARK_CONTENT_DEPRECATED();
+ Brightness = Math::Sqrt(Brightness * 0.01f);
+ }
}
bool PointLight::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)
diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp
index 85b77647a..38502cf1b 100644
--- a/Source/Engine/Level/Actors/SpotLight.cpp
+++ b/Source/Engine/Level/Actors/SpotLight.cpp
@@ -1,6 +1,8 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#include "SpotLight.h"
+
+#include "Engine/Content/Deprecated.h"
#include "Engine/Graphics/RenderView.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Content/Assets/IESProfile.h"
@@ -282,6 +284,14 @@ void SpotLight::Deserialize(DeserializeStream& stream, ISerializeModifier* modif
DESERIALIZE(UseInverseSquaredFalloff);
DESERIALIZE(UseIESBrightness);
DESERIALIZE(IESBrightnessScale);
+
+ // [Deprecated on 12.03.2026, expires on 12.03.2028]
+ if (modifier->EngineBuild <= 6807 && SERIALIZE_FIND_MEMBER(stream, "UseInverseSquaredFalloff") != stream.MemberEnd() && UseInverseSquaredFalloff)
+ {
+ // Convert old non-physical brightness value that was used for Inverse Squared Falloff which wasn't based on proper cm/m units calculations
+ MARK_CONTENT_DEPRECATED();
+ Brightness = Math::Sqrt(Brightness * 0.01f);
+ }
}
bool SpotLight::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)
diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp
index f41e4a805..19e4e3f34 100644
--- a/Source/Engine/Level/Actors/StaticModel.cpp
+++ b/Source/Engine/Level/Actors/StaticModel.cpp
@@ -66,6 +66,20 @@ void StaticModel::SetBoundsScale(float value)
UpdateBounds();
}
+DrawPass StaticModel::GetDrawModes() const
+{
+ return _drawModes;
+}
+
+void StaticModel::SetDrawModes(DrawPass value)
+{
+ if (_drawModes == value)
+ return;
+ _drawModes = value;
+ if (_sceneRenderingKey != -1)
+ GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::DrawModes);
+}
+
int32 StaticModel::GetLODBias() const
{
return _lodBias;
@@ -330,13 +344,13 @@ void StaticModel::Draw(RenderContext& renderContext)
return;
if (renderContext.View.Pass == DrawPass::GlobalSDF)
{
- if (EnumHasAnyFlags(DrawModes, DrawPass::GlobalSDF) && Model->SDF.Texture)
+ if (EnumHasAnyFlags(_drawModes, DrawPass::GlobalSDF) && Model->SDF.Texture)
GlobalSignDistanceFieldPass::Instance()->RasterizeModelSDF(this, Model->SDF, _transform, _box);
return;
}
if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
{
- if (EnumHasAnyFlags(DrawModes, DrawPass::GlobalSurfaceAtlas) && Model->SDF.Texture)
+ if (EnumHasAnyFlags(_drawModes, DrawPass::GlobalSurfaceAtlas) && Model->SDF.Texture)
GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, this, _sphere, _transform, Model->LODs.Last().GetBox());
return;
}
@@ -353,7 +367,7 @@ void StaticModel::Draw(RenderContext& renderContext)
draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex) : nullptr;
draw.LightmapUVs = &Lightmap.UVsArea;
draw.Flags = _staticFlags;
- draw.DrawModes = DrawModes;
+ draw.DrawModes = _drawModes;
draw.Bounds = _sphere;
draw.Bounds.Center -= renderContext.View.Origin;
draw.PerInstanceRandom = GetPerInstanceRandom();
@@ -390,7 +404,7 @@ void StaticModel::Draw(RenderContextBatch& renderContextBatch)
draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex) : nullptr;
draw.LightmapUVs = &Lightmap.UVsArea;
draw.Flags = _staticFlags;
- draw.DrawModes = DrawModes;
+ draw.DrawModes = _drawModes;
draw.Bounds = _sphere;
draw.Bounds.Center -= renderContext.View.Origin;
draw.PerInstanceRandom = GetPerInstanceRandom();
@@ -435,7 +449,7 @@ void StaticModel::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE_MEMBER(LODBias, _lodBias);
SERIALIZE_MEMBER(ForcedLOD, _forcedLod);
SERIALIZE_MEMBER(SortOrder, _sortOrder);
- SERIALIZE(DrawModes);
+ SERIALIZE_MEMBER(DrawModes, _drawModes);
if (HasLightmap()
#if USE_EDITOR
@@ -487,7 +501,7 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
DESERIALIZE_MEMBER(LODBias, _lodBias);
DESERIALIZE_MEMBER(ForcedLOD, _forcedLod);
DESERIALIZE_MEMBER(SortOrder, _sortOrder);
- DESERIALIZE(DrawModes);
+ DESERIALIZE_MEMBER(DrawModes, _drawModes);
DESERIALIZE_MEMBER(LightmapIndex, Lightmap.TextureIndex);
DESERIALIZE_MEMBER(LightmapArea, Lightmap.UVsArea);
@@ -537,27 +551,27 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
if (member != stream.MemberEnd() && member->value.IsBool() && member->value.GetBool())
{
MARK_CONTENT_DEPRECATED();
- DrawModes = DrawPass::Depth;
+ _drawModes = DrawPass::Depth;
}
}
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
{
MARK_CONTENT_DEPRECATED();
- DrawModes |= DrawPass::GlobalSDF;
+ _drawModes |= DrawPass::GlobalSDF;
}
// [Deprecated on 27.04.2022, expires on 27.04.2024]
if (modifier->EngineBuild <= 6331)
{
MARK_CONTENT_DEPRECATED();
- DrawModes |= DrawPass::GlobalSurfaceAtlas;
+ _drawModes |= DrawPass::GlobalSurfaceAtlas;
}
{
const auto member = stream.FindMember("RenderPasses");
if (member != stream.MemberEnd() && member->value.IsInt())
{
- DrawModes = (DrawPass)member->value.GetInt();
+ _drawModes = (DrawPass)member->value.GetInt();
}
}
}
diff --git a/Source/Engine/Level/Actors/StaticModel.h b/Source/Engine/Level/Actors/StaticModel.h
index e6ed701cc..4b575a0ed 100644
--- a/Source/Engine/Level/Actors/StaticModel.h
+++ b/Source/Engine/Level/Actors/StaticModel.h
@@ -23,6 +23,7 @@ private:
bool _vertexColorsDirty;
byte _vertexColorsCount;
int8 _sortOrder;
+ DrawPass _drawModes = DrawPass::Default;
Array _vertexColorsData[MODEL_MAX_LODS];
GPUBuffer* _vertexColorsBuffer[MODEL_MAX_LODS];
Model* _residencyChangedModel = nullptr;
@@ -40,12 +41,6 @@ public:
API_FIELD(Attributes="EditorOrder(20), DefaultValue(null), EditorDisplay(\"Model\")")
AssetReference Model;
- ///
- /// The draw passes to use for rendering this object.
- ///
- API_FIELD(Attributes="EditorOrder(15), DefaultValue(DrawPass.Default), EditorDisplay(\"Model\")")
- DrawPass DrawModes = DrawPass::Default;
-
///
/// The baked lightmap entry.
///
@@ -74,6 +69,17 @@ public:
///
API_PROPERTY() void SetBoundsScale(float value);
+ ///
+ /// Gets the draw passes to use for rendering this object.
+ ///
+ API_PROPERTY(Attributes="EditorOrder(15), DefaultValue(DrawPass.Default), EditorDisplay(\"Model\")")
+ DrawPass GetDrawModes() const;
+
+ ///
+ /// Sets the draw passes to use for rendering this object.
+ ///
+ API_PROPERTY() void SetDrawModes(DrawPass value);
+
///
/// Gets the model Level Of Detail bias value. Allows to increase or decrease rendered model quality.
///
diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp
index 9cf6794be..adea4575b 100644
--- a/Source/Engine/Level/Level.cpp
+++ b/Source/Engine/Level/Level.cpp
@@ -5,6 +5,7 @@
#include "LargeWorlds.h"
#include "SceneQuery.h"
#include "SceneObjectsFactory.h"
+#include "FlaxEngine.Gen.h"
#include "Scene/Scene.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Deprecated.h"
@@ -958,9 +959,6 @@ bool LevelImpl::unloadScene(Scene* scene)
// Simple enqueue scene root object to be deleted
scene->DeleteObject();
- // Force flush deleted objects so we actually delete unloaded scene objects (prevent from reloading their managed objects, etc.)
- ObjectsRemovalService::Flush();
-
return false;
}
@@ -1123,6 +1121,32 @@ SceneResult SceneLoader::OnBegin(Args& args)
_lastSceneLoadTime = DateTime::Now();
StartFrame = Engine::UpdateCount;
+ // Validate arguments
+ if (!args.Data.IsArray())
+ {
+ LOG(Error, "Invalid Data member.");
+ CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, Guid::Empty);
+ return SceneResult::Failed;
+ }
+
+ // Peek scene node value (it's the first actor serialized)
+ SceneId = JsonTools::GetGuid(args.Data[0], "ID");
+ if (!SceneId.IsValid())
+ {
+ LOG(Error, "Invalid scene id.");
+ CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
+ return SceneResult::Failed;
+ }
+
+ // Peek meta
+ if (args.EngineBuild < 6000)
+ {
+ LOG(Error, "Invalid serialized engine build.");
+ CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
+ return SceneResult::Failed;
+ }
+ Modifier->EngineBuild = args.EngineBuild;
+
// Scripting backend should be loaded for the current project before loading scene
if (!Scripting::HasGameModulesLoaded())
{
@@ -1136,27 +1160,7 @@ SceneResult SceneLoader::OnBegin(Args& args)
MessageBox::Show(TEXT("Failed to load scripts.\n\nCannot load scene without game script modules.\n\nSee logs for more info."), TEXT("Missing game modules"), MessageBoxButtons::OK, MessageBoxIcon::Error);
}
#endif
- return SceneResult::Failed;
- }
-
- // Peek meta
- if (args.EngineBuild < 6000)
- {
- LOG(Error, "Invalid serialized engine build.");
- return SceneResult::Failed;
- }
- if (!args.Data.IsArray())
- {
- LOG(Error, "Invalid Data member.");
- return SceneResult::Failed;
- }
- Modifier->EngineBuild = args.EngineBuild;
-
- // Peek scene node value (it's the first actor serialized)
- SceneId = JsonTools::GetGuid(args.Data[0], "ID");
- if (!SceneId.IsValid())
- {
- LOG(Error, "Invalid scene id.");
+ CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
return SceneResult::Failed;
}
@@ -1164,6 +1168,7 @@ SceneResult SceneLoader::OnBegin(Args& args)
if (Level::FindScene(SceneId) != nullptr)
{
LOG(Info, "Scene {0} is already loaded.", SceneId);
+ CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
return SceneResult::Failed;
}
diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp
index f60aa8d2f..b45424a3d 100644
--- a/Source/Engine/Level/Scene/Scene.cpp
+++ b/Source/Engine/Level/Scene/Scene.cpp
@@ -389,11 +389,14 @@ void Scene::BeginPlay(SceneBeginData* data)
if (model == nullptr)
CreateCsgModel();
}
+
+ Ticking.SetTicking(true);
}
void Scene::EndPlay()
{
// Improve scene cleanup performance by removing all data from scene rendering and ticking containers
+ Ticking.SetTicking(false);
Ticking.Clear();
Rendering.Clear();
Navigation.Clear();
diff --git a/Source/Engine/Level/Scene/SceneRendering.h b/Source/Engine/Level/Scene/SceneRendering.h
index 25dfc63fa..d522454a6 100644
--- a/Source/Engine/Level/Scene/SceneRendering.h
+++ b/Source/Engine/Level/Scene/SceneRendering.h
@@ -56,6 +56,7 @@ public:
Layer = 4,
StaticFlags = 8,
AutoDelayDuringRendering = 16, // Conditionally allow updating data during rendering when writes are locked
+ DrawModes = 32,
Auto = Visual | Bounds | Layer,
};
diff --git a/Source/Engine/Level/Scene/SceneTicking.cpp b/Source/Engine/Level/Scene/SceneTicking.cpp
index 30a551117..2b0ccc506 100644
--- a/Source/Engine/Level/Scene/SceneTicking.cpp
+++ b/Source/Engine/Level/Scene/SceneTicking.cpp
@@ -44,7 +44,7 @@ void SceneTicking::TickData::Tick()
{
TickScripts(Scripts);
- for (int32 i = 0; i < Ticks.Count(); i++)
+ for (int32 i = 0; i < Ticks.Count() && _canTick; i++)
Ticks.Get()[i].Call();
}
@@ -66,7 +66,7 @@ void SceneTicking::TickData::TickExecuteInEditor()
{
TickScripts(ScriptsExecuteInEditor);
- for (int32 i = 0; i < TicksExecuteInEditor.Count(); i++)
+ for (int32 i = 0; i < TicksExecuteInEditor.Count() && _canTick; i++)
TicksExecuteInEditor.Get()[i].Call();
}
@@ -89,10 +89,8 @@ SceneTicking::FixedUpdateTickData::FixedUpdateTickData()
void SceneTicking::FixedUpdateTickData::TickScripts(const Array