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/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 1579d3bd4..a77fe9485 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -4,7 +4,7 @@
"Major": 1,
"Minor": 12,
"Revision": 0,
- "Build": 6907
+ "Build": 6908
},
"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 1a308e08a..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);
@@ -824,7 +825,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
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;
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..c1179e393 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.0235294f, 0.9f, 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/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 8e3e4f4e7..68f5e8f03 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/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/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index a996fa628..5413a25b0 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -252,8 +252,8 @@ namespace FlaxEditor.Viewport
{
_movementSpeed = value;
- if (_cameraButton != null)
- _cameraButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed);
+ if (_orthographicModeButton != null)
+ _orthographicModeButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed);
}
}
@@ -587,7 +587,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.",
@@ -596,7 +596,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 ec5db8301..6756cabf6 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-2026 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/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 0441e27a0..3fea63ceb 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 4dad47971..6221d17bf 100644
--- a/Source/Engine/Engine/CommandLine.cpp
+++ b/Source/Engine/Engine/CommandLine.cpp
@@ -166,6 +166,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 1733afc1a..e60772bc1 100644
--- a/Source/Engine/Engine/CommandLine.h
+++ b/Source/Engine/Engine/CommandLine.h
@@ -148,6 +148,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 7a9ae0969..4b037db37 100644
--- a/Source/Engine/Engine/Engine.cpp
+++ b/Source/Engine/Engine/Engine.cpp
@@ -581,7 +581,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 6860c3463..8b9bd40bd 100644
--- a/Source/Engine/Input/Input.cpp
+++ b/Source/Engine/Input/Input.cpp
@@ -121,6 +121,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
@@ -513,24 +514,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;
}
@@ -556,13 +557,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;
}
}
@@ -570,18 +571,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;
}
}
@@ -589,18 +590,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;
}
}
@@ -608,7 +609,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;
}
@@ -1083,26 +1084,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 964a247d9..0f619c85f 100644
--- a/Source/Engine/Input/Input.h
+++ b/Source/Engine/Input/Input.h
@@ -243,24 +243,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.
@@ -275,24 +278,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 a873ae734..629c44096 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"
@@ -960,9 +961,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;
}
@@ -1125,6 +1123,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())
{
@@ -1138,27 +1162,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;
}
@@ -1166,6 +1170,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