diff --git a/.gitignore b/.gitignore
index 54907892f..b7e11e554 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ Source/*.csproj
/Package_*/
!Source/Engine/Debug
/Source/Platforms/Editor/Linux/Mono/etc/mono/registry
+PackageEditor_Cert.command
PackageEditor_Cert.bat
PackagePlatforms_Cert.bat
diff --git a/Content/Editor/Particles/Smoke.flax b/Content/Editor/Particles/Smoke.flax
index 1335a84f4..b42c2f325 100644
--- a/Content/Editor/Particles/Smoke.flax
+++ b/Content/Editor/Particles/Smoke.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c3dc51e7805056006ca6cbb481ba202583a9b2287c152fc04e28e1d07747d6ce
-size 14706
+oid sha256:334ac0d00495fc88b10839061ff0c3f45323d4f75ab6176b19005199a7324a19
+size 14569
diff --git a/Content/Editor/Particles/Sparks.flax b/Content/Editor/Particles/Sparks.flax
index 7977e231b..7ee0ed6e9 100644
--- a/Content/Editor/Particles/Sparks.flax
+++ b/Content/Editor/Particles/Sparks.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:77d902ab5f79426cc66dc5f19a3b8280136a58aa3c6fd317554d1a032357c65a
-size 15275
+oid sha256:87046a9bfe275cac290b4764de8a512c222ccc386d01af9026d57c3e4b7773b6
+size 13625
diff --git a/Content/Editor/Scripting/ScriptTemplate.cs b/Content/Editor/Scripting/ScriptTemplate.cs
index 663acf05f..30fbe9d86 100644
--- a/Content/Editor/Scripting/ScriptTemplate.cs
+++ b/Content/Editor/Scripting/ScriptTemplate.cs
@@ -33,4 +33,3 @@ public class %class% : Script
// Here you can add code that needs to be called every frame
}
}
-
diff --git a/Flax.flaxproj b/Flax.flaxproj
index 9f851a457..ed47674fd 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -3,6 +3,7 @@
"Version": {
"Major": 1,
"Minor": 8,
+ "Revision": 0,
"Build": 6500
},
"Company": "Flax",
diff --git a/GenerateProjectFiles.bat b/GenerateProjectFiles.bat
index 622939c34..28970a203 100644
--- a/GenerateProjectFiles.bat
+++ b/GenerateProjectFiles.bat
@@ -15,7 +15,7 @@ if errorlevel 1 goto BuildToolFailed
:: Build bindings for all editor configurations
echo Building C# bindings...
-Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor,FlaxGame
+Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor
popd
echo Done!
diff --git a/GenerateProjectFiles.command b/GenerateProjectFiles.command
index 5ee5c0783..a42121252 100755
--- a/GenerateProjectFiles.command
+++ b/GenerateProjectFiles.command
@@ -14,4 +14,4 @@ bash ./Development/Scripts/Mac/CallBuildTool.sh --genproject "$@"
# Build bindings for all editor configurations
echo Building C# bindings...
# TODO: Detect the correct architecture here
-Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor,FlaxGame
+Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor
diff --git a/GenerateProjectFiles.sh b/GenerateProjectFiles.sh
index dceb8abe8..76d96c7ef 100755
--- a/GenerateProjectFiles.sh
+++ b/GenerateProjectFiles.sh
@@ -14,4 +14,4 @@ bash ./Development/Scripts/Linux/CallBuildTool.sh --genproject "$@"
# Build bindings for all editor configurations
echo Building C# bindings...
# TODO: Detect the correct architecture here
-Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor,FlaxGame
+Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=x64 -platform=Linux --buildTargets=FlaxEditor
diff --git a/README.md b/README.md
index fac631a6a..d6688bd03 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
Flax Engine is a high quality modern 3D game engine written in C++ and C#.
-From stunning graphics to powerful scripts - Flax can give everything for your games. Designed for fast workflow with many ready to use features waiting for you right now. To learn more see the website ([www.flaxengine.com](https://flaxengine.com)).
+From stunning graphics to powerful scripts, it's designed for fast workflow with many ready-to-use features waiting for you right now. To learn more see the website ([www.flaxengine.com](https://flaxengine.com)).
This repository contains full source code of the Flax Engine (excluding NDA-protected platforms support). Anyone is welcome to contribute or use the modified source in Flax-based games.
diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
index ec7b8e7e1..42ef6fcdf 100644
--- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
@@ -104,4 +104,19 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data)
return false;
}
+void LinuxPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
+{
+ // Pick the first executable file
+ Array files;
+ FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
+ for (auto& file : files)
+ {
+ if (FileSystem::GetExtension(file).IsEmpty())
+ {
+ executableFile = file;
+ break;
+ }
+ }
+}
+
#endif
diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
index 562b38962..432240d00 100644
--- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
@@ -20,6 +20,7 @@ public:
ArchitectureType GetArchitecture() const override;
bool UseSystemDotnet() const override;
bool OnDeployBinaries(CookingData& data) override;
+ void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
};
#endif
diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
index a1db61dbb..aa56a6b95 100644
--- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
@@ -249,4 +249,19 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
return false;
}
+void MacPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir)
+{
+ // Pick the first executable file
+ Array files;
+ FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
+ for (auto& file : files)
+ {
+ if (FileSystem::GetExtension(file).IsEmpty())
+ {
+ executableFile = file;
+ break;
+ }
+ }
+}
+
#endif
diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
index 21d9141e3..efdd0b733 100644
--- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
@@ -27,6 +27,7 @@ public:
bool IsNativeCodeFile(CookingData& data, const String& file) override;
void OnBuildStarted(CookingData& data) override;
bool OnPostProcess(CookingData& data) override;
+ void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
};
#endif
diff --git a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
index 71c5f6daf..4099e5aee 100644
--- a/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/MeshReferenceEditor.cs
@@ -225,8 +225,15 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
_actor = actor;
- var showActorPicker = actor == null || ParentEditor.Values.All(x => x is not Cloth);
- if (showActorPicker)
+ if (ParentEditor.Values.Any(x => x is Cloth))
+ {
+ // Cloth always picks the parent model mesh
+ if (actor == null)
+ {
+ layout.Label("Cloth needs to be added as a child to model actor.");
+ }
+ }
+ else
{
// Actor reference picker
_actorPicker = layout.Custom();
@@ -242,7 +249,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
var model = staticModel.Model;
if (model == null || model.WaitForLoaded())
+ {
+ layout.Label("No model.");
return;
+ }
var materials = model.MaterialSlots;
var lods = model.LODs;
meshNames = new string[lods.Length][];
@@ -267,7 +277,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
var skinnedModel = animatedModel.SkinnedModel;
if (skinnedModel == null || skinnedModel.WaitForLoaded())
+ {
+ layout.Label("No model.");
return;
+ }
var materials = skinnedModel.MaterialSlots;
var lods = skinnedModel.LODs;
meshNames = new string[lods.Length][];
diff --git a/Source/Editor/GUI/Docking/DockHintWindow.cs b/Source/Editor/GUI/Docking/DockHintWindow.cs
index 52c5dcd3c..6e2353441 100644
--- a/Source/Editor/GUI/Docking/DockHintWindow.cs
+++ b/Source/Editor/GUI/Docking/DockHintWindow.cs
@@ -44,11 +44,11 @@ namespace FlaxEditor.GUI.Docking
var mousePos = window.MousePosition;
var previousSize = window.Size;
window.Restore();
- window.Position = FlaxEngine.Input.MouseScreenPosition - mousePos * window.Size / previousSize;
+ window.Position = Platform.MousePosition - mousePos * window.Size / previousSize;
}
// Calculate dragging offset and move window to the destination position
- var mouseScreenPosition = FlaxEngine.Input.MouseScreenPosition;
+ var mouseScreenPosition = Platform.MousePosition;
// If the _toMove window was not focused when initializing this window, the result vector only contains zeros
// and to prevent a failure, we need to perform an update for the drag offset at later time which will be done in the OnMouseMove event handler.
@@ -83,6 +83,7 @@ namespace FlaxEditor.GUI.Docking
// Enable hit window presentation
Proxy.Window.RenderingEnabled = true;
Proxy.Window.Show();
+ Proxy.Window.Focus();
}
///
@@ -113,7 +114,7 @@ namespace FlaxEditor.GUI.Docking
var window = _toMove.Window?.Window;
if (window == null)
return;
- var mouse = FlaxEngine.Input.MouseScreenPosition;
+ var mouse = Platform.MousePosition;
// Move base window
window.Position = mouse - _dragOffset;
@@ -193,7 +194,7 @@ namespace FlaxEditor.GUI.Docking
// Move window to the mouse position (with some offset for caption bar)
var window = (WindowRootControl)toMove.Root;
- var mouse = FlaxEngine.Input.MouseScreenPosition;
+ var mouse = Platform.MousePosition;
window.Window.Position = mouse - new Float2(8, 8);
// Get floating panel
@@ -244,7 +245,7 @@ namespace FlaxEditor.GUI.Docking
private void UpdateRects()
{
// Cache mouse position
- _mouse = FlaxEngine.Input.MouseScreenPosition;
+ _mouse = Platform.MousePosition;
// Check intersection with any dock panel
var uiMouse = _mouse;
@@ -270,15 +271,16 @@ namespace FlaxEditor.GUI.Docking
// Cache dock rectangles
var size = _rectDock.Size;
var offset = _rectDock.Location;
- float BorderMargin = 4.0f;
- float ProxyHintWindowsSize2 = Proxy.HintWindowsSize * 0.5f;
- float centerX = size.X * 0.5f;
- float centerY = size.Y * 0.5f;
- _rUpper = new Rectangle(centerX - ProxyHintWindowsSize2, BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rBottom = new Rectangle(centerX - ProxyHintWindowsSize2, size.Y - Proxy.HintWindowsSize - BorderMargin, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rLeft = new Rectangle(BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rRight = new Rectangle(size.X - Proxy.HintWindowsSize - BorderMargin, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
- _rCenter = new Rectangle(centerX - ProxyHintWindowsSize2, centerY - ProxyHintWindowsSize2, Proxy.HintWindowsSize, Proxy.HintWindowsSize) + offset;
+ var borderMargin = 4.0f;
+ var hintWindowsSize = Proxy.HintWindowsSize * Platform.DpiScale;
+ var hintWindowsSize2 = hintWindowsSize * 0.5f;
+ var centerX = size.X * 0.5f;
+ var centerY = size.Y * 0.5f;
+ _rUpper = new Rectangle(centerX - hintWindowsSize2, borderMargin, hintWindowsSize, hintWindowsSize) + offset;
+ _rBottom = new Rectangle(centerX - hintWindowsSize2, size.Y - hintWindowsSize - borderMargin, hintWindowsSize, hintWindowsSize) + offset;
+ _rLeft = new Rectangle(borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
+ _rRight = new Rectangle(size.X - hintWindowsSize - borderMargin, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
+ _rCenter = new Rectangle(centerX - hintWindowsSize2, centerY - hintWindowsSize2, hintWindowsSize, hintWindowsSize) + offset;
// Hit test
DockState toSet = DockState.Float;
@@ -428,7 +430,6 @@ namespace FlaxEditor.GUI.Docking
{
if (Window == null)
{
- // Create proxy window
var settings = CreateWindowSettings.Default;
settings.Title = "DockHint.Window";
settings.Size = initSize;
@@ -440,12 +441,10 @@ namespace FlaxEditor.GUI.Docking
settings.IsRegularWindow = false;
settings.SupportsTransparency = true;
settings.ShowInTaskbar = false;
- settings.ShowAfterFirstPaint = true;
+ settings.ShowAfterFirstPaint = false;
settings.IsTopmost = true;
Window = Platform.CreateWindow(ref settings);
-
- // Set opacity and background color
Window.Opacity = 0.6f;
Window.GUI.BackgroundColor = Style.Current.DragWindow;
}
@@ -465,7 +464,7 @@ namespace FlaxEditor.GUI.Docking
var settings = CreateWindowSettings.Default;
settings.Title = name;
- settings.Size = new Float2(HintWindowsSize);
+ settings.Size = new Float2(HintWindowsSize * Platform.DpiScale);
settings.AllowInput = false;
settings.AllowMaximize = false;
settings.AllowMinimize = false;
@@ -479,7 +478,6 @@ namespace FlaxEditor.GUI.Docking
settings.ShowAfterFirstPaint = false;
win = Platform.CreateWindow(ref settings);
-
win.Opacity = 0.6f;
win.GUI.BackgroundColor = Style.Current.DragWindow;
}
diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs
index 7e3af05bf..703079469 100644
--- a/Source/Editor/GUI/Tree/TreeNode.cs
+++ b/Source/Editor/GUI/Tree/TreeNode.cs
@@ -776,11 +776,20 @@ namespace FlaxEditor.GUI.Tree
// Check if mouse hits arrow
if (_mouseOverArrow && HasAnyVisibleChild)
{
- // Toggle open state
- if (_opened)
- Collapse();
+ if (ParentTree.Root.GetKey(KeyboardKeys.Alt))
+ {
+ if (_opened)
+ CollapseAll();
+ else
+ ExpandAll();
+ }
else
- Expand();
+ {
+ if (_opened)
+ Collapse();
+ else
+ Expand();
+ }
}
// Check if mouse hits bar
diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp
index e01fd04bb..969608676 100644
--- a/Source/Editor/Managed/ManagedEditor.Internal.cpp
+++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp
@@ -513,7 +513,9 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa
WindowsManager::WindowsLocker.Unlock();
}
WindowsManager::WindowsLocker.Lock();
- for (auto& win : WindowsManager::Windows)
+ Array> windows;
+ windows.Add(WindowsManager::Windows);
+ for (Window* win : windows)
{
if (win->IsVisible())
win->OnUpdate(deltaTime);
diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs
index 1a4f5ce9e..05d72598f 100644
--- a/Source/Editor/Modules/WindowsModule.cs
+++ b/Source/Editor/Modules/WindowsModule.cs
@@ -237,7 +237,11 @@ namespace FlaxEditor.Modules
///
public void LoadDefaultLayout()
{
- LoadLayout(StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/LayoutDefault.xml"));
+ var path = StringUtils.CombinePaths(Globals.EngineContentFolder, "Editor/LayoutDefault.xml");
+ if (File.Exists(path))
+ {
+ LoadLayout(path);
+ }
}
///
diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs
index eb61c0f68..95c3c1d6f 100644
--- a/Source/Editor/Options/InputBinding.cs
+++ b/Source/Editor/Options/InputBinding.cs
@@ -259,10 +259,7 @@ namespace FlaxEditor.Options
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
- {
return true;
- }
-
return base.CanConvertFrom(context, sourceType);
}
@@ -270,9 +267,7 @@ namespace FlaxEditor.Options
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
- {
return false;
- }
return base.CanConvertTo(context, destinationType);
}
@@ -284,7 +279,6 @@ namespace FlaxEditor.Options
InputBinding.TryParse(str, out var result);
return result;
}
-
return base.ConvertFrom(context, culture, value);
}
@@ -295,7 +289,6 @@ namespace FlaxEditor.Options
{
return ((InputBinding)value).ToString();
}
-
return base.ConvertTo(context, culture, value, destinationType);
}
}
diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs
index cee63a562..0fd018cbc 100644
--- a/Source/Editor/Options/ViewportOptions.cs
+++ b/Source/Editor/Options/ViewportOptions.cs
@@ -26,45 +26,108 @@ namespace FlaxEditor.Options
public float MouseWheelSensitivity { get; set; } = 1.0f;
///
- /// Gets or sets the default movement speed for the viewport camera (must match the dropdown menu values in the viewport).
+ /// Gets or sets the total amount of steps the camera needs to go from minimum to maximum speed.
///
- [DefaultValue(1.0f), Limit(0.01f, 100.0f)]
- [EditorDisplay("Defaults"), EditorOrder(110), Tooltip("The default movement speed for the viewport camera (must match the dropdown menu values in the viewport).")]
- public float DefaultMovementSpeed { get; set; } = 1.0f;
+ [DefaultValue(64), Limit(1, 128)]
+ [EditorDisplay("Camera"), EditorOrder(110), Tooltip("The total amount of steps the camera needs to go from minimum to maximum speed.")]
+ public int TotalCameraSpeedSteps { get; set; } = 64;
+
+ ///
+ /// Gets or sets the degree to which the camera will be eased when using camera flight in the editor window.
+ ///
+ [DefaultValue(3.0f), Limit(1.0f, 8.0f)]
+ [EditorDisplay("Camera"), EditorOrder(111), Tooltip("The degree to which the camera will be eased when using camera flight in the editor window (ignored if camera easing degree is enabled).")]
+ public float CameraEasingDegree { get; set; } = 3.0f;
+
+ ///
+ /// Gets or sets the default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values).
+ ///
+ [DefaultValue(1.0f), Limit(0.05f, 32.0f)]
+ [EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default movement speed for the viewport camera (must be in range between minimum and maximum movement speed values).")]
+ public float MovementSpeed { get; set; } = 1.0f;
+
+ ///
+ /// Gets or sets the default minimum camera movement speed.
+ ///
+ [DefaultValue(0.05f), Limit(0.05f, 32.0f)]
+ [EditorDisplay("Defaults"), EditorOrder(121), Tooltip("The default minimum movement speed for the viewport camera.")]
+ public float MinMovementSpeed { get; set; } = 0.05f;
+
+ ///
+ /// Gets or sets the default maximum camera movement speed.
+ ///
+ [DefaultValue(32.0f), Limit(16.0f, 1000.0f)]
+ [EditorDisplay("Defaults"), EditorOrder(122), Tooltip("The default maximum movement speed for the viewport camera.")]
+ public float MaxMovementSpeed { get; set; } = 32f;
+
+ ///
+ /// Gets or sets the default camera easing mode.
+ ///
+ [DefaultValue(true)]
+ [EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default camera easing mode.")]
+ public bool UseCameraEasing { get; set; } = true;
///
/// Gets or sets the default near clipping plane distance for the viewport camera.
///
[DefaultValue(10.0f), Limit(0.001f, 1000.0f)]
- [EditorDisplay("Defaults"), EditorOrder(120), Tooltip("The default near clipping plane distance for the viewport camera.")]
- public float DefaultNearPlane { get; set; } = 10.0f;
+ [EditorDisplay("Defaults"), EditorOrder(140), Tooltip("The default near clipping plane distance for the viewport camera.")]
+ public float NearPlane { get; set; } = 10.0f;
///
/// Gets or sets the default far clipping plane distance for the viewport camera.
///
[DefaultValue(40000.0f), Limit(10.0f)]
- [EditorDisplay("Defaults"), EditorOrder(130), Tooltip("The default far clipping plane distance for the viewport camera.")]
- public float DefaultFarPlane { get; set; } = 40000.0f;
+ [EditorDisplay("Defaults"), EditorOrder(150), Tooltip("The default far clipping plane distance for the viewport camera.")]
+ public float FarPlane { get; set; } = 40000.0f;
///
/// Gets or sets the default field of view angle (in degrees) for the viewport camera.
///
[DefaultValue(60.0f), Limit(35.0f, 160.0f, 0.1f)]
- [EditorDisplay("Defaults", "Default Field Of View"), EditorOrder(140), Tooltip("The default field of view angle (in degrees) for the viewport camera.")]
- public float DefaultFieldOfView { get; set; } = 60.0f;
+ [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("The default field of view angle (in degrees) for the viewport camera.")]
+ public float FieldOfView { get; set; } = 60.0f;
///
- /// Gets or sets if the panning direction is inverted for the viewport camera.
+ /// Gets or sets the default camera orthographic mode.
///
[DefaultValue(false)]
- [EditorDisplay("Defaults"), EditorOrder(150), Tooltip("Invert the panning direction for the viewport camera.")]
- public bool DefaultInvertPanning { get; set; } = false;
+ [EditorDisplay("Defaults"), EditorOrder(170), Tooltip("The default camera orthographic mode.")]
+ public bool UseOrthographicProjection { get; set; } = false;
///
- /// Scales editor viewport grid.
+ /// Gets or sets the default camera orthographic scale (if camera uses orthographic mode).
+ ///
+ [DefaultValue(5.0f), Limit(0.001f, 100000.0f, 0.1f)]
+ [EditorDisplay("Defaults"), EditorOrder(180), Tooltip("The default camera orthographic scale (if camera uses orthographic mode).")]
+ public float OrthographicScale { get; set; } = 5.0f;
+
+ ///
+ /// Gets or sets the default panning direction for the viewport camera.
+ ///
+ [DefaultValue(false)]
+ [EditorDisplay("Defaults"), EditorOrder(190), Tooltip("The default panning direction for the viewport camera.")]
+ public bool InvertPanning { get; set; } = false;
+
+ ///
+ /// Gets or sets the default relative panning mode.
+ ///
+ [DefaultValue(true)]
+ [EditorDisplay("Defaults"), EditorOrder(200), Tooltip("The default relative panning mode. Uses distance between camera and target to determine panning speed.")]
+ public bool UseRelativePanning { get; set; } = true;
+
+ ///
+ /// Gets or sets the default panning speed (ignored if relative panning is speed enabled).
+ ///
+ [DefaultValue(0.8f), Limit(0.01f, 128.0f, 0.1f)]
+ [EditorDisplay("Defaults"), EditorOrder(210), Tooltip("The default camera panning speed (ignored if relative panning is enabled).")]
+ public float PanningSpeed { get; set; } = 0.8f;
+
+ ///
+ /// Gets or sets the default editor viewport grid scale.
///
[DefaultValue(50.0f), Limit(25.0f, 500.0f, 5.0f)]
- [EditorDisplay("Defaults"), EditorOrder(160), Tooltip("Scales editor viewport grid.")]
+ [EditorDisplay("Defaults"), EditorOrder(220), Tooltip("The default editor viewport grid scale.")]
public float ViewportGridScale { get; set; } = 50.0f;
}
}
diff --git a/Source/Editor/ProjectInfo.cpp b/Source/Editor/ProjectInfo.cpp
index 4e7ee4483..30c558b5d 100644
--- a/Source/Editor/ProjectInfo.cpp
+++ b/Source/Editor/ProjectInfo.cpp
@@ -154,7 +154,8 @@ bool ProjectInfo::LoadProject(const String& projectPath)
Version = ::Version(
JsonTools::GetInt(version, "Major", 0),
JsonTools::GetInt(version, "Minor", 0),
- JsonTools::GetInt(version, "Build", 0));
+ JsonTools::GetInt(version, "Build", -1),
+ JsonTools::GetInt(version, "Revision", -1));
}
}
if (Version.Revision() == 0)
diff --git a/Source/Editor/ProjectInfo.cs b/Source/Editor/ProjectInfo.cs
index b00c4e042..083665f0f 100644
--- a/Source/Editor/ProjectInfo.cs
+++ b/Source/Editor/ProjectInfo.cs
@@ -23,17 +23,11 @@ namespace FlaxEditor
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
- {
writer.WriteNull();
- }
else if (value is Version)
- {
writer.WriteValue(value.ToString());
- }
else
- {
throw new JsonSerializationException("Expected Version object value");
- }
}
///
@@ -47,65 +41,60 @@ namespace FlaxEditor
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
- {
return null;
- }
- else
+
+ if (reader.TokenType == JsonToken.StartObject)
{
- if (reader.TokenType == JsonToken.StartObject)
+ try
{
- try
+ reader.Read();
+ var values = new Dictionary();
+ while (reader.TokenType == JsonToken.PropertyName)
{
+ var key = reader.Value as string;
reader.Read();
- Dictionary values = new Dictionary();
- while (reader.TokenType == JsonToken.PropertyName)
- {
- var key = reader.Value as string;
- reader.Read();
- var val = (long)reader.Value;
- reader.Read();
- values.Add(key, (int)val);
- }
+ var val = (long)reader.Value;
+ reader.Read();
+ values.Add(key, (int)val);
+ }
- int major = 0, minor = 0, build = 0;
- values.TryGetValue("Major", out major);
- values.TryGetValue("Minor", out minor);
- values.TryGetValue("Build", out build);
+ values.TryGetValue("Major", out var major);
+ values.TryGetValue("Minor", out var minor);
+ if (!values.TryGetValue("Build", out var build))
+ build = -1;
+ if (!values.TryGetValue("Revision", out var revision))
+ revision = -1;
- Version v = new Version(major, minor, build);
- return v;
- }
- catch (Exception ex)
- {
- throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
- }
+ if (build <= 0)
+ return new Version(major, minor);
+ if (revision <= 0)
+ return new Version(major, minor, build);
+ return new Version(major, minor, build, revision);
}
- else if (reader.TokenType == JsonToken.String)
+ catch (Exception ex)
{
- try
- {
- Version v = new Version((string)reader.Value!);
- return v;
- }
- catch (Exception ex)
- {
- throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
- }
- }
- else
- {
- throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.Value));
+ throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
}
}
+ if (reader.TokenType == JsonToken.String)
+ {
+ try
+ {
+ return new Version((string)reader.Value!);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
+ }
+ }
+ throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.Value));
}
///
/// Determines whether this instance can convert the specified object type.
///
/// Type of the object.
- ///
- /// true if this instance can convert the specified object type; otherwise, false.
- ///
+ /// true if this instance can convert the specified object type; otherwise, false.
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Version);
diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
index d392e8309..163397768 100644
--- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
+++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
@@ -66,7 +66,8 @@ namespace FlaxEditor.SceneGraph.GUI
_orderInParent = actor.OrderInParent;
Visible = (actor.HideFlags & HideFlags.HideInHierarchy) == 0;
- var id = actor.ID;
+ // Pick the correct id when inside a prefab window.
+ var id = actor.HasPrefabLink && actor.Scene == null ? actor.PrefabObjectID : actor.ID;
if (Editor.Instance.ProjectCache.IsExpandedActor(ref id))
{
Expand(true);
@@ -171,7 +172,8 @@ namespace FlaxEditor.SceneGraph.GUI
// Restore cached state on query filter clear
if (noFilter && actor != null)
{
- var id = actor.ID;
+ // Pick the correct id when inside a prefab window.
+ var id = actor.HasPrefabLink && actor.Scene.Scene == null ? actor.PrefabObjectID : actor.ID;
isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id);
}
@@ -301,10 +303,12 @@ namespace FlaxEditor.SceneGraph.GUI
protected override void OnExpandedChanged()
{
base.OnExpandedChanged();
+ var actor = Actor;
- if (!IsLayoutLocked && Actor)
+ if (!IsLayoutLocked && actor)
{
- var id = Actor.ID;
+ // Pick the correct id when inside a prefab window.
+ var id = actor.HasPrefabLink && actor.Scene == null ? actor.PrefabObjectID : actor.ID;
Editor.Instance.ProjectCache.SetExpandedActor(ref id, IsExpanded);
}
}
diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs
index 226c7b49c..f12fca9db 100644
--- a/Source/Editor/Tools/ClothPainting.cs
+++ b/Source/Editor/Tools/ClothPainting.cs
@@ -225,6 +225,7 @@ namespace FlaxEngine.Tools
var cloth = _cloth;
if (cloth == null)
return;
+ var hasPaintInput = Owner.IsLeftMouseButtonDown && !Owner.IsAltKeyDown;
// Perform detailed tracing to find cursor location for the brush
var ray = Owner.MouseRay;
@@ -240,7 +241,7 @@ namespace FlaxEngine.Tools
// Cursor hit other object or nothing
PaintEnd();
- if (Owner.IsLeftMouseButtonDown)
+ if (hasPaintInput)
{
// Select something else
var view = new Ray(Owner.ViewPosition, Owner.ViewDirection);
@@ -253,7 +254,7 @@ namespace FlaxEngine.Tools
}
// Handle painting
- if (Owner.IsLeftMouseButtonDown)
+ if (hasPaintInput)
PaintStart();
else
PaintEnd();
diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs
index e578933cf..bf2e840ea 100644
--- a/Source/Editor/Viewport/Cameras/FPSCamera.cs
+++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs
@@ -259,7 +259,10 @@ namespace FlaxEditor.Viewport.Cameras
// Pan
if (input.IsPanning)
{
- var panningSpeed = 0.8f;
+ var panningSpeed = (Viewport.RelativePanning)
+ ? Mathf.Abs((position - TargetPoint).Length) * 0.005f
+ : Viewport.PanningSpeed;
+
if (Viewport.InvertPanning)
{
position += up * (mouseDelta.Y * panningSpeed);
diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index 9a444f2da..c49392d01 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -128,12 +128,26 @@ namespace FlaxEditor.Viewport
public const int FpsCameraFilteringFrames = 3;
///
- /// The speed widget button.
+ /// The camera settings widget.
///
- protected ViewportWidgetButton _speedWidget;
+ protected ViewportWidgetsContainer _cameraWidget;
+
+ ///
+ /// The camera settings widget button.
+ ///
+ protected ViewportWidgetButton _cameraButton;
+
+ ///
+ /// The orthographic mode widget button.
+ ///
+ protected ViewportWidgetButton _orthographicModeButton;
+
+ private readonly Editor _editor;
private float _mouseSensitivity;
private float _movementSpeed;
+ private float _minMovementSpeed;
+ private float _maxMovementSpeed;
private float _mouseAccelerationScale;
private bool _useMouseFiltering;
private bool _useMouseAcceleration;
@@ -174,11 +188,17 @@ namespace FlaxEditor.Viewport
private float _fieldOfView;
private float _nearPlane;
private float _farPlane;
- private float _orthoSize = 1.0f;
- private bool _isOrtho = false;
- private float _wheelMovementChangeDeltaSum = 0;
+ private float _orthoSize;
+ private bool _isOrtho;
+ private bool _useCameraEasing;
+ private float _cameraEasingDegree;
+ private float _panningSpeed;
+ private bool _relativePanning;
private bool _invertPanning;
+ private int _speedStep;
+ private int _maxSpeedSteps;
+
///
/// Speed of the mouse.
///
@@ -189,6 +209,25 @@ namespace FlaxEditor.Viewport
///
public float MouseWheelZoomSpeedFactor = 1;
+ ///
+ /// Format of the text for the camera move speed.
+ ///
+ private string MovementSpeedTextFormat
+ {
+ get
+ {
+ if (Mathf.Abs(_movementSpeed - _maxMovementSpeed) < Mathf.Epsilon || Mathf.Abs(_movementSpeed - _minMovementSpeed) < Mathf.Epsilon)
+ return "{0:0.##}";
+
+ if (_movementSpeed < 10.0f)
+ return "{0:0.00}";
+ else if (_movementSpeed < 100.0f)
+ return "{0:0.0}";
+ else
+ return "{0:#}";
+ }
+ }
+
///
/// Gets or sets the camera movement speed.
///
@@ -197,19 +236,40 @@ namespace FlaxEditor.Viewport
get => _movementSpeed;
set
{
- for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
- {
- if (Math.Abs(value - EditorViewportCameraSpeedValues[i]) < 0.001f)
- {
- _movementSpeed = EditorViewportCameraSpeedValues[i];
- if (_speedWidget != null)
- _speedWidget.Text = _movementSpeed.ToString();
- break;
- }
- }
+ _movementSpeed = value;
+
+ if (_cameraButton != null)
+ _cameraButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed);
}
}
+ ///
+ /// Gets or sets the minimum camera movement speed.
+ ///
+ public float MinMovementSpeed
+ {
+ get => _minMovementSpeed;
+ set => _minMovementSpeed = value;
+ }
+
+ ///
+ /// Gets or sets the maximum camera movement speed.
+ ///
+ public float MaxMovementSpeed
+ {
+ get => _maxMovementSpeed;
+ set => _maxMovementSpeed = value;
+ }
+
+ ///
+ /// Gets or sets the camera easing mode.
+ ///
+ public bool UseCameraEasing
+ {
+ get => _useCameraEasing;
+ set => _useCameraEasing = value;
+ }
+
///
/// Gets the mouse movement position delta (user press and move).
///
@@ -396,6 +456,15 @@ namespace FlaxEditor.Viewport
set => _isOrtho = value;
}
+ ///
+ /// Gets or sets if the panning speed should be relative to the camera target.
+ ///
+ public bool RelativePanning
+ {
+ get => _relativePanning;
+ set => _relativePanning = value;
+ }
+
///
/// Gets or sets if the panning direction is inverted.
///
@@ -405,6 +474,15 @@ namespace FlaxEditor.Viewport
set => _invertPanning = value;
}
+ ///
+ /// Gets or sets the camera panning speed.
+ ///
+ public float PanningSpeed
+ {
+ get => _panningSpeed;
+ set => _panningSpeed = value;
+ }
+
///
/// The input actions collection to processed during user input.
///
@@ -419,6 +497,8 @@ namespace FlaxEditor.Viewport
public EditorViewport(SceneRenderTask task, ViewportCamera camera, bool useWidgets)
: base(task)
{
+ _editor = Editor.Instance;
+
_mouseAccelerationScale = 0.1f;
_useMouseFiltering = false;
_useMouseAcceleration = false;
@@ -431,43 +511,299 @@ namespace FlaxEditor.Viewport
// Setup options
{
- var options = Editor.Instance.Options.Options;
- _movementSpeed = options.Viewport.DefaultMovementSpeed;
- _nearPlane = options.Viewport.DefaultNearPlane;
- _farPlane = options.Viewport.DefaultFarPlane;
- _fieldOfView = options.Viewport.DefaultFieldOfView;
- _invertPanning = options.Viewport.DefaultInvertPanning;
-
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
- OnEditorOptionsChanged(options);
+ SetupViewportOptions();
}
+ // Initialize camera values from cache
+ if (_editor.ProjectCache.TryGetCustomData("CameraMovementSpeedValue", out var cachedState))
+ MovementSpeed = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraMinMovementSpeedValue", out cachedState))
+ _minMovementSpeed = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraMaxMovementSpeedValue", out cachedState))
+ _maxMovementSpeed = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("UseCameraEasingState", out cachedState))
+ _useCameraEasing = bool.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraPanningSpeedValue", out cachedState))
+ _panningSpeed = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraInvertPanningState", out cachedState))
+ _invertPanning = bool.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraRelativePanningState", out cachedState))
+ _relativePanning = bool.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraOrthographicState", out cachedState))
+ _isOrtho = bool.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraOrthographicSizeValue", out cachedState))
+ _orthoSize = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraFieldOfViewValue", out cachedState))
+ _fieldOfView = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraNearPlaneValue", out cachedState))
+ _nearPlane = float.Parse(cachedState);
+ if (_editor.ProjectCache.TryGetCustomData("CameraFarPlaneValue", out cachedState))
+ _farPlane = float.Parse(cachedState);
+
+ OnCameraMovementProgressChanged();
+
if (useWidgets)
{
- var largestText = "Invert Panning";
+ #region Camera settings widget
+
+ var largestText = "Relative Panning";
var textSize = Style.Current.FontMedium.MeasureText(largestText);
var xLocationForExtras = textSize.X + 5;
- // Camera speed widget
- var camSpeed = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
- var camSpeedCM = new ContextMenu();
- var camSpeedButton = new ViewportWidgetButton(_movementSpeed.ToString(), Editor.Instance.Icons.CamSpeed32, camSpeedCM)
+ var cameraSpeedTextWidth = Style.Current.FontMedium.MeasureText("0.00").X;
+
+ // Camera Settings Widget
+ _cameraWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
+
+ // Camera Settings Menu
+ var cameraCM = new ContextMenu();
+ _cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), Editor.Instance.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth)
{
Tag = this,
- TooltipText = "Camera speed scale"
+ TooltipText = "Camera Settings",
+ Parent = _cameraWidget
};
- _speedWidget = camSpeedButton;
- for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
- {
- var v = EditorViewportCameraSpeedValues[i];
- var button = camSpeedCM.AddButton(v.ToString());
- button.Tag = v;
- }
- camSpeedCM.ButtonClicked += button => MovementSpeed = (float)button.Tag;
- camSpeedCM.VisibleChanged += WidgetCamSpeedShowHide;
- camSpeedButton.Parent = camSpeed;
- camSpeed.Parent = this;
+ _cameraWidget.Parent = this;
+
+ // Orthographic/Perspective Mode Widget
+ _orthographicModeButton = new ViewportWidgetButton(string.Empty, Editor.Instance.Icons.CamSpeed32, null, true)
+ {
+ Checked = !_isOrtho,
+ TooltipText = "Toggle Orthographic/Perspective Mode",
+ Parent = _cameraWidget
+ };
+ _orthographicModeButton.Toggled += OnOrthographicModeToggled;
+
+ // Camera Speed
+ var camSpeedButton = cameraCM.AddButton("Camera Speed");
+ camSpeedButton.CloseMenuOnClick = false;
+ var camSpeedValue = new FloatValueBox(_movementSpeed, xLocationForExtras, 2, 70.0f, _minMovementSpeed, _maxMovementSpeed, 0.5f)
+ {
+ Parent = camSpeedButton
+ };
+
+ camSpeedValue.ValueChanged += () => OnMovementSpeedChanged(camSpeedValue);
+ cameraCM.VisibleChanged += control => camSpeedValue.Value = _movementSpeed;
+
+ // Minimum & Maximum Camera Speed
+ var minCamSpeedButton = cameraCM.AddButton("Min Cam Speed");
+ minCamSpeedButton.CloseMenuOnClick = false;
+ var minCamSpeedValue = new FloatValueBox(_minMovementSpeed, xLocationForExtras, 2, 70.0f, 0.05f, _maxMovementSpeed, 0.5f)
+ {
+ Parent = minCamSpeedButton
+ };
+ var maxCamSpeedButton = cameraCM.AddButton("Max Cam Speed");
+ maxCamSpeedButton.CloseMenuOnClick = false;
+ var maxCamSpeedValue = new FloatValueBox(_maxMovementSpeed, xLocationForExtras, 2, 70.0f, _minMovementSpeed, 1000.0f, 0.5f)
+ {
+ Parent = maxCamSpeedButton
+ };
+
+ minCamSpeedValue.ValueChanged += () =>
+ {
+ OnMinMovementSpeedChanged(minCamSpeedValue);
+
+ maxCamSpeedValue.MinValue = minCamSpeedValue.Value;
+
+ if (Math.Abs(camSpeedValue.MinValue - minCamSpeedValue.Value) > Mathf.Epsilon)
+ camSpeedValue.MinValue = minCamSpeedValue.Value;
+ };
+ cameraCM.VisibleChanged += control => minCamSpeedValue.Value = _minMovementSpeed;
+ maxCamSpeedValue.ValueChanged += () =>
+ {
+ OnMaxMovementSpeedChanged(maxCamSpeedValue);
+
+ minCamSpeedValue.MaxValue = maxCamSpeedValue.Value;
+
+ if (Math.Abs(camSpeedValue.MaxValue - maxCamSpeedValue.Value) > Mathf.Epsilon)
+ camSpeedValue.MaxValue = maxCamSpeedValue.Value;
+ };
+ cameraCM.VisibleChanged += control => maxCamSpeedValue.Value = _maxMovementSpeed;
+
+ // Camera Easing
+ {
+ var useCameraEasing = cameraCM.AddButton("Camera Easing");
+ useCameraEasing.CloseMenuOnClick = false;
+ var useCameraEasingValue = new CheckBox(xLocationForExtras, 2, _useCameraEasing)
+ {
+ Parent = useCameraEasing
+ };
+
+ useCameraEasingValue.StateChanged += OnCameraEasingToggled;
+ cameraCM.VisibleChanged += control => useCameraEasingValue.Checked = _useCameraEasing;
+ }
+
+ // Panning Speed
+ {
+ var panningSpeed = cameraCM.AddButton("Panning Speed");
+ panningSpeed.CloseMenuOnClick = false;
+ var panningSpeedValue = new FloatValueBox(_panningSpeed, xLocationForExtras, 2, 70.0f, 0.01f, 128.0f, 0.1f)
+ {
+ Parent = panningSpeed
+ };
+
+ panningSpeedValue.ValueChanged += () => OnPanningSpeedChanged(panningSpeedValue);
+ cameraCM.VisibleChanged += control =>
+ {
+ panningSpeed.Visible = !_relativePanning;
+ panningSpeedValue.Value = _panningSpeed;
+ };
+ }
+
+ // Relative Panning
+ {
+ var relativePanning = cameraCM.AddButton("Relative Panning");
+ relativePanning.CloseMenuOnClick = false;
+ var relativePanningValue = new CheckBox(xLocationForExtras, 2, _relativePanning)
+ {
+ Parent = relativePanning
+ };
+
+ relativePanningValue.StateChanged += checkBox =>
+ {
+ if (checkBox.Checked != _relativePanning)
+ {
+ OnRelativePanningToggled(checkBox);
+ cameraCM.Hide();
+ }
+ };
+ cameraCM.VisibleChanged += control => relativePanningValue.Checked = _relativePanning;
+ }
+
+ // Invert Panning
+ {
+ var invertPanning = cameraCM.AddButton("Invert Panning");
+ invertPanning.CloseMenuOnClick = false;
+ var invertPanningValue = new CheckBox(xLocationForExtras, 2, _invertPanning)
+ {
+ Parent = invertPanning
+ };
+
+ invertPanningValue.StateChanged += OnInvertPanningToggled;
+ cameraCM.VisibleChanged += control => invertPanningValue.Checked = _invertPanning;
+ }
+
+ cameraCM.AddSeparator();
+
+ // Camera Viewpoints
+ {
+ var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu;
+ for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++)
+ {
+ var co = EditorViewportCameraViewpointValues[i];
+ var button = cameraView.AddButton(co.Name);
+ button.Tag = co.Orientation;
+ }
+
+ cameraView.ButtonClicked += OnViewpointChanged;
+ }
+
+ // Orthographic Mode
+ {
+ var ortho = cameraCM.AddButton("Orthographic");
+ ortho.CloseMenuOnClick = false;
+ var orthoValue = new CheckBox(xLocationForExtras, 2, _isOrtho)
+ {
+ Parent = ortho
+ };
+
+ orthoValue.StateChanged += checkBox =>
+ {
+ if (checkBox.Checked != _isOrtho)
+ {
+ OnOrthographicModeToggled(checkBox);
+ cameraCM.Hide();
+ }
+ };
+ cameraCM.VisibleChanged += control => orthoValue.Checked = _isOrtho;
+ }
+
+ // Field of View
+ {
+ var fov = cameraCM.AddButton("Field Of View");
+ fov.CloseMenuOnClick = false;
+ var fovValue = new FloatValueBox(_fieldOfView, xLocationForExtras, 2, 70.0f, 35.0f, 160.0f, 0.1f)
+ {
+ Parent = fov
+ };
+
+ fovValue.ValueChanged += () => OnFieldOfViewChanged(fovValue);
+ cameraCM.VisibleChanged += control =>
+ {
+ fov.Visible = !_isOrtho;
+ fovValue.Value = _fieldOfView;
+ };
+ }
+
+ // Orthographic Scale
+ {
+ var orthoSize = cameraCM.AddButton("Ortho Scale");
+ orthoSize.CloseMenuOnClick = false;
+ var orthoSizeValue = new FloatValueBox(_orthoSize, xLocationForExtras, 2, 70.0f, 0.001f, 100000.0f, 0.01f)
+ {
+ Parent = orthoSize
+ };
+
+ orthoSizeValue.ValueChanged += () => OnOrthographicSizeChanged(orthoSizeValue);
+ cameraCM.VisibleChanged += control =>
+ {
+ orthoSize.Visible = _isOrtho;
+ orthoSizeValue.Value = _orthoSize;
+ };
+ }
+
+ // Near Plane
+ {
+ var nearPlane = cameraCM.AddButton("Near Plane");
+ nearPlane.CloseMenuOnClick = false;
+ var nearPlaneValue = new FloatValueBox(_nearPlane, xLocationForExtras, 2, 70.0f, 0.001f, 1000.0f)
+ {
+ Parent = nearPlane
+ };
+
+ nearPlaneValue.ValueChanged += () => OnNearPlaneChanged(nearPlaneValue);
+ cameraCM.VisibleChanged += control => nearPlaneValue.Value = _nearPlane;
+ }
+
+ // Far Plane
+ {
+ var farPlane = cameraCM.AddButton("Far Plane");
+ farPlane.CloseMenuOnClick = false;
+ var farPlaneValue = new FloatValueBox(_farPlane, xLocationForExtras, 2, 70.0f, 10.0f)
+ {
+ Parent = farPlane
+ };
+
+ farPlaneValue.ValueChanged += () => OnFarPlaneChanged(farPlaneValue);
+ cameraCM.VisibleChanged += control => farPlaneValue.Value = _farPlane;
+ }
+
+ cameraCM.AddSeparator();
+
+ // Reset Button
+ {
+ var reset = cameraCM.AddButton("Reset to default");
+ reset.ButtonClicked += button =>
+ {
+ SetupViewportOptions();
+
+ // if the context menu is opened without triggering the value changes beforehand,
+ // the movement speed will not be correctly reset to its default value in certain cases
+ // therefore, a UI update needs to be triggered here
+ minCamSpeedValue.Value = _minMovementSpeed;
+ camSpeedValue.Value = _movementSpeed;
+ maxCamSpeedValue.Value = _maxMovementSpeed;
+ };
+ }
+
+ #endregion Camera settings widget
+
+ #region View mode widget
+
+ largestText = "Brightness";
+ textSize = Style.Current.FontMedium.MeasureText(largestText);
+ xLocationForExtras = textSize.X + 5;
- // View mode widget
var viewMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft);
ViewWidgetButtonMenu = new ContextMenu();
var viewModeButton = new ViewportWidgetButton("View", SpriteHandle.Invalid, ViewWidgetButtonMenu)
@@ -484,8 +820,8 @@ namespace FlaxEditor.Viewport
// Show FPS
{
InitFpsCounter();
- _showFpsButon = ViewWidgetShowMenu.AddButton("FPS Counter", () => ShowFpsCounter = !ShowFpsCounter);
- _showFpsButon.CloseMenuOnClick = false;
+ _showFpsButton = ViewWidgetShowMenu.AddButton("FPS Counter", () => ShowFpsCounter = !ShowFpsCounter);
+ _showFpsButton.CloseMenuOnClick = false;
}
}
@@ -610,104 +946,6 @@ namespace FlaxEditor.Viewport
ViewWidgetButtonMenu.AddSeparator();
- // Orthographic
- {
- var ortho = ViewWidgetButtonMenu.AddButton("Orthographic");
- ortho.CloseMenuOnClick = false;
- var orthoValue = new CheckBox(xLocationForExtras, 2, _isOrtho)
- {
- Parent = ortho
- };
- orthoValue.StateChanged += checkBox =>
- {
- if (checkBox.Checked != _isOrtho)
- {
- _isOrtho = checkBox.Checked;
- ViewWidgetButtonMenu.Hide();
- if (_isOrtho)
- {
- var orient = ViewOrientation;
- OrientViewport(ref orient);
- }
- }
- };
- ViewWidgetButtonMenu.VisibleChanged += control => orthoValue.Checked = _isOrtho;
- }
-
- // Camera Viewpoints
- {
- var cameraView = ViewWidgetButtonMenu.AddChildMenu("Viewpoints").ContextMenu;
- for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++)
- {
- var co = EditorViewportCameraViewpointValues[i];
- var button = cameraView.AddButton(co.Name);
- button.Tag = co.Orientation;
- }
- cameraView.ButtonClicked += button =>
- {
- var orient = Quaternion.Euler((Float3)button.Tag);
- OrientViewport(ref orient);
- };
- }
-
- // Field of View
- {
- var fov = ViewWidgetButtonMenu.AddButton("Field Of View");
- fov.CloseMenuOnClick = false;
- var fovValue = new FloatValueBox(1, xLocationForExtras, 2, 70.0f, 35.0f, 160.0f, 0.1f)
- {
- Parent = fov
- };
-
- fovValue.ValueChanged += () => _fieldOfView = fovValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += control =>
- {
- fov.Visible = !_isOrtho;
- fovValue.Value = _fieldOfView;
- };
- }
-
- // Ortho Scale
- {
- var orthoSize = ViewWidgetButtonMenu.AddButton("Ortho Scale");
- orthoSize.CloseMenuOnClick = false;
- var orthoSizeValue = new FloatValueBox(_orthoSize, xLocationForExtras, 2, 70.0f, 0.001f, 100000.0f, 0.01f)
- {
- Parent = orthoSize
- };
-
- orthoSizeValue.ValueChanged += () => _orthoSize = orthoSizeValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += control =>
- {
- orthoSize.Visible = _isOrtho;
- orthoSizeValue.Value = _orthoSize;
- };
- }
-
- // Near Plane
- {
- var nearPlane = ViewWidgetButtonMenu.AddButton("Near Plane");
- nearPlane.CloseMenuOnClick = false;
- var nearPlaneValue = new FloatValueBox(2.0f, xLocationForExtras, 2, 70.0f, 0.001f, 1000.0f)
- {
- Parent = nearPlane
- };
- nearPlaneValue.ValueChanged += () => _nearPlane = nearPlaneValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += control => nearPlaneValue.Value = _nearPlane;
- }
-
- // Far Plane
- {
- var farPlane = ViewWidgetButtonMenu.AddButton("Far Plane");
- farPlane.CloseMenuOnClick = false;
- var farPlaneValue = new FloatValueBox(1000, xLocationForExtras, 2, 70.0f, 10.0f)
- {
- Parent = farPlane
- };
- farPlaneValue.ValueChanged += () => _farPlane = farPlaneValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += control => farPlaneValue.Value = _farPlane;
- }
-
// Brightness
{
var brightness = ViewWidgetButtonMenu.AddButton("Brightness");
@@ -732,24 +970,7 @@ namespace FlaxEditor.Viewport
ViewWidgetButtonMenu.VisibleChanged += control => resolutionValue.Value = ResolutionScale;
}
- // Invert Panning
- {
- var invert = ViewWidgetButtonMenu.AddButton("Invert Panning");
- invert.CloseMenuOnClick = false;
- var invertValue = new CheckBox(xLocationForExtras, 2, _invertPanning)
- {
- Parent = invert
- };
-
- invertValue.StateChanged += checkBox =>
- {
- if (checkBox.Checked != _invertPanning)
- {
- _invertPanning = checkBox.Checked;
- }
- };
- ViewWidgetButtonMenu.VisibleChanged += control => invertValue.Checked = _invertPanning;
- }
+ #endregion View mode widget
}
InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Top").Orientation)));
@@ -766,6 +987,135 @@ namespace FlaxEditor.Viewport
task.Begin += OnRenderBegin;
}
+ ///
+ /// Sets the viewport options to the default values.
+ ///
+ private void SetupViewportOptions()
+ {
+ var options = Editor.Instance.Options.Options;
+ _minMovementSpeed = options.Viewport.MinMovementSpeed;
+ MovementSpeed = options.Viewport.MovementSpeed;
+ _maxMovementSpeed = options.Viewport.MaxMovementSpeed;
+ _useCameraEasing = options.Viewport.UseCameraEasing;
+ _panningSpeed = options.Viewport.PanningSpeed;
+ _invertPanning = options.Viewport.InvertPanning;
+ _relativePanning = options.Viewport.UseRelativePanning;
+
+ _isOrtho = options.Viewport.UseOrthographicProjection;
+ _orthoSize = options.Viewport.OrthographicScale;
+ _fieldOfView = options.Viewport.FieldOfView;
+ _nearPlane = options.Viewport.NearPlane;
+ _farPlane = options.Viewport.FarPlane;
+
+ OnEditorOptionsChanged(options);
+ }
+
+ private void OnMovementSpeedChanged(FloatValueBox control)
+ {
+ var value = Mathf.Clamp(control.Value, _minMovementSpeed, _maxMovementSpeed);
+ MovementSpeed = value;
+
+ OnCameraMovementProgressChanged();
+ _editor.ProjectCache.SetCustomData("CameraMovementSpeedValue", _movementSpeed.ToString());
+ }
+
+ private void OnMinMovementSpeedChanged(FloatValueBox control)
+ {
+ var value = Mathf.Clamp(control.Value, 0.05f, _maxMovementSpeed);
+ _minMovementSpeed = value;
+
+ if (_movementSpeed < value)
+ MovementSpeed = value;
+
+ OnCameraMovementProgressChanged();
+ _editor.ProjectCache.SetCustomData("CameraMinMovementSpeedValue", _minMovementSpeed.ToString());
+ }
+
+ private void OnMaxMovementSpeedChanged(FloatValueBox control)
+ {
+ var value = Mathf.Clamp(control.Value, _minMovementSpeed, 1000.0f);
+ _maxMovementSpeed = value;
+
+ if (_movementSpeed > value)
+ MovementSpeed = value;
+
+ OnCameraMovementProgressChanged();
+ _editor.ProjectCache.SetCustomData("CameraMaxMovementSpeedValue", _maxMovementSpeed.ToString());
+ }
+
+ private void OnCameraEasingToggled(Control control)
+ {
+ _useCameraEasing = !_useCameraEasing;
+
+ OnCameraMovementProgressChanged();
+ _editor.ProjectCache.SetCustomData("UseCameraEasingState", _useCameraEasing.ToString());
+ }
+
+ private void OnPanningSpeedChanged(FloatValueBox control)
+ {
+ _panningSpeed = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraPanningSpeedValue", _panningSpeed.ToString());
+ }
+
+ private void OnRelativePanningToggled(Control control)
+ {
+ _relativePanning = !_relativePanning;
+ _editor.ProjectCache.SetCustomData("CameraRelativePanningState", _relativePanning.ToString());
+ }
+
+ private void OnInvertPanningToggled(Control control)
+ {
+ _invertPanning = !_invertPanning;
+ _editor.ProjectCache.SetCustomData("CameraInvertPanningState", _invertPanning.ToString());
+ }
+
+
+ private void OnViewpointChanged(ContextMenuButton button)
+ {
+ var orient = Quaternion.Euler((Float3)button.Tag);
+ OrientViewport(ref orient);
+ }
+
+ private void OnFieldOfViewChanged(FloatValueBox control)
+ {
+ _fieldOfView = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraFieldOfViewValue", _fieldOfView.ToString());
+ }
+
+ private void OnOrthographicModeToggled(Control control)
+ {
+ _isOrtho = !_isOrtho;
+
+ if (_orthographicModeButton != null)
+ _orthographicModeButton.Checked = !_isOrtho;
+
+ if (_isOrtho)
+ {
+ var orient = ViewOrientation;
+ OrientViewport(ref orient);
+ }
+
+ _editor.ProjectCache.SetCustomData("CameraOrthographicState", _isOrtho.ToString());
+ }
+
+ private void OnOrthographicSizeChanged(FloatValueBox control)
+ {
+ _orthoSize = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraOrthographicSizeValue", _orthoSize.ToString());
+ }
+
+ private void OnNearPlaneChanged(FloatValueBox control)
+ {
+ _nearPlane = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraNearPlaneValue", _nearPlane.ToString());
+ }
+
+ private void OnFarPlaneChanged(FloatValueBox control)
+ {
+ _farPlane = control.Value;
+ _editor.ProjectCache.SetCustomData("CameraNearPlaneValue", _farPlane.ToString());
+ }
+
///
/// Gets a value indicating whether this viewport is using mouse currently (eg. user moving objects).
///
@@ -798,33 +1148,59 @@ namespace FlaxEditor.Viewport
}
}
+ private void OnCameraMovementProgressChanged()
+ {
+ // prevent NaN
+ if (Math.Abs(_minMovementSpeed - _maxMovementSpeed) < Mathf.Epsilon)
+ {
+ _speedStep = 0;
+ return;
+ }
+
+ if (Math.Abs(_movementSpeed - _maxMovementSpeed) < Mathf.Epsilon)
+ {
+ _speedStep = _maxSpeedSteps;
+ return;
+ }
+ else if (Math.Abs(_movementSpeed - _minMovementSpeed) < Mathf.Epsilon)
+ {
+ _speedStep = 0;
+ return;
+ }
+
+ // calculate current linear/eased progress
+ var progress = Mathf.Remap(_movementSpeed, _minMovementSpeed, _maxMovementSpeed, 0.0f, 1.0f);
+
+ if (_useCameraEasing)
+ progress = Mathf.Pow(progress, 1.0f / _cameraEasingDegree);
+
+ _speedStep = Mathf.RoundToInt(progress * _maxSpeedSteps);
+ }
+
///
/// Increases or decreases the camera movement speed.
///
/// The stepping direction for speed adjustment.
protected void AdjustCameraMoveSpeed(int step)
{
- int camValueIndex = -1;
- for (int i = 0; i < EditorViewportCameraSpeedValues.Length; i++)
- {
- if (Mathf.NearEqual(EditorViewportCameraSpeedValues[i], _movementSpeed))
- {
- camValueIndex = i;
- break;
- }
- }
- if (camValueIndex == -1)
- return;
+ _speedStep = Mathf.Clamp(_speedStep + step, 0, _maxSpeedSteps);
- if (step > 0)
- MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Min(camValueIndex + 1, EditorViewportCameraSpeedValues.Length - 1)];
- else if (step < 0)
- MovementSpeed = EditorViewportCameraSpeedValues[Mathf.Max(camValueIndex - 1, 0)];
+ // calculate new linear/eased progress
+ var progress = _useCameraEasing
+ ? Mathf.Pow((float)_speedStep / _maxSpeedSteps, _cameraEasingDegree)
+ : (float)_speedStep / _maxSpeedSteps;
+
+ var speed = Mathf.Lerp(_minMovementSpeed, _maxMovementSpeed, progress);
+ MovementSpeed = (float)Math.Round(speed, 3);
+ _editor.ProjectCache.SetCustomData("CameraMovementSpeedValue", _movementSpeed.ToString());
}
private void OnEditorOptionsChanged(EditorOptions options)
{
_mouseSensitivity = options.Viewport.MouseSensitivity;
+ _maxSpeedSteps = options.Viewport.TotalCameraSpeedSteps;
+ _cameraEasingDegree = options.Viewport.CameraEasingDegree;
+ OnCameraMovementProgressChanged();
}
private void OnRenderBegin(RenderTask task, GPUContext context)
@@ -863,7 +1239,7 @@ namespace FlaxEditor.Viewport
}
private FpsCounter _fpsCounter;
- private ContextMenuButton _showFpsButon;
+ private ContextMenuButton _showFpsButton;
///
/// Gets or sets a value indicating whether show or hide FPS counter.
@@ -875,7 +1251,7 @@ namespace FlaxEditor.Viewport
{
_fpsCounter.Visible = value;
_fpsCounter.Enabled = value;
- _showFpsButon.Icon = value ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
+ _showFpsButton.Icon = value ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
@@ -1016,8 +1392,6 @@ namespace FlaxEditor.Viewport
/// The parent window.
protected virtual void OnControlMouseBegin(Window win)
{
- _wheelMovementChangeDeltaSum = 0;
-
// Hide cursor and start tracking mouse movement
win.StartTrackingMouse(false);
win.Cursor = CursorType.Hidden;
@@ -1113,8 +1487,8 @@ namespace FlaxEditor.Viewport
_camera.Update(deltaTime);
useMovementSpeed = _camera.UseMovementSpeed;
- if (_speedWidget != null)
- _speedWidget.Parent.Visible = useMovementSpeed;
+ if (_cameraButton != null)
+ _cameraButton.Parent.Visible = useMovementSpeed;
}
// Get parent window
@@ -1217,18 +1591,8 @@ namespace FlaxEditor.Viewport
rmbWheel = useMovementSpeed && (_input.IsMouseRightDown || _isVirtualMouseRightDown) && wheelInUse;
if (rmbWheel)
{
- const float step = 4.0f;
- _wheelMovementChangeDeltaSum += _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity;
- if (_wheelMovementChangeDeltaSum >= step)
- {
- _wheelMovementChangeDeltaSum -= step;
- AdjustCameraMoveSpeed(1);
- }
- else if (_wheelMovementChangeDeltaSum <= -step)
- {
- _wheelMovementChangeDeltaSum += step;
- AdjustCameraMoveSpeed(-1);
- }
+ var step = _input.MouseWheelDelta * options.Viewport.MouseWheelSensitivity;
+ AdjustCameraMoveSpeed(step > 0.0f ? 1 : -1);
}
}
@@ -1497,22 +1861,6 @@ namespace FlaxEditor.Viewport
new CameraViewpoint("Bottom", new Float3(-90, 0, 0))
};
- private readonly float[] EditorViewportCameraSpeedValues =
- {
- 0.05f,
- 0.1f,
- 0.25f,
- 0.5f,
- 1.0f,
- 2.0f,
- 4.0f,
- 6.0f,
- 8.0f,
- 16.0f,
- 32.0f,
- 64.0f,
- };
-
private struct ViewModeOptions
{
public readonly string Name;
@@ -1568,24 +1916,6 @@ namespace FlaxEditor.Viewport
new ViewModeOptions(ViewMode.GlobalIllumination, "Global Illumination"),
};
- private void WidgetCamSpeedShowHide(Control cm)
- {
- if (cm.Visible == false)
- return;
-
- var ccm = (ContextMenu)cm;
- foreach (var e in ccm.Items)
- {
- if (e is ContextMenuButton b)
- {
- var v = (float)b.Tag;
- b.Icon = Mathf.Abs(MovementSpeed - v) < 0.001f
- ? Style.Current.CheckBoxTick
- : SpriteHandle.Invalid;
- }
- }
- }
-
private void WidgetViewModeShowHideClicked(ContextMenuButton button)
{
if (button.Tag is ViewMode v)
diff --git a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
index 481bc3f1b..5ff5cdb6d 100644
--- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
+++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs
@@ -19,6 +19,7 @@ namespace FlaxEditor.Viewport.Widgets
private bool _checked;
private bool _autoCheck;
private bool _isMosueDown;
+ private float _forcedTextWidth;
///
/// Event fired when user toggles checked state.
@@ -63,14 +64,16 @@ namespace FlaxEditor.Viewport.Widgets
/// The text.
/// The icon.
/// The context menu.
- /// if set to true will be automatic checked on mouse click.
- public ViewportWidgetButton(string text, SpriteHandle icon, ContextMenu contextMenu = null, bool autoCheck = false)
- : base(0, 0, CalculateButtonWidth(0, icon.IsValid), ViewportWidgetsContainer.WidgetsHeight)
+ /// If set to true will be automatic checked on mouse click.
+ /// Forces the text to be drawn with the specified width.
+ public ViewportWidgetButton(string text, SpriteHandle icon, ContextMenu contextMenu = null, bool autoCheck = false, float textWidth = 0.0f)
+ : base(0, 0, CalculateButtonWidth(textWidth, icon.IsValid), ViewportWidgetsContainer.WidgetsHeight)
{
_text = text;
Icon = icon;
_cm = contextMenu;
_autoCheck = autoCheck;
+ _forcedTextWidth = textWidth;
if (_cm != null)
_cm.VisibleChanged += CmOnVisibleChanged;
@@ -160,7 +163,7 @@ namespace FlaxEditor.Viewport.Widgets
var style = Style.Current;
if (style != null && style.FontMedium)
- Width = CalculateButtonWidth(style.FontMedium.MeasureText(_text).X, Icon.IsValid);
+ Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.MeasureText(_text).X, Icon.IsValid);
}
}
}
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs
index 4face0dc0..16f7c21c6 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.cs
@@ -149,6 +149,7 @@ namespace FlaxEditor.Windows.Assets
// Prefab structure tree
Graph = new LocalSceneGraph(new CustomRootNode(this));
+ Graph.Root.TreeNode.Expand(true);
_tree = new PrefabTree
{
Margin = new Margin(0.0f, 0.0f, -16.0f, _treePanel.ScrollBarsSize), // Hide root node
@@ -317,7 +318,7 @@ namespace FlaxEditor.Windows.Assets
Graph.MainActor = _viewport.Instance;
Selection.Clear();
Select(Graph.Main);
- Graph.Root.TreeNode.ExpandAll(true);
+ Graph.Root.TreeNode.Expand(true);
_undo.Clear();
ClearEditedFlag();
}
@@ -413,7 +414,7 @@ namespace FlaxEditor.Windows.Assets
_focusCamera = true;
Selection.Clear();
Select(Graph.Main);
- Graph.Root.TreeNode.ExpandAll(true);
+ Graph.Root.TreeNode.Expand(true);
_undo.Clear();
ClearEditedFlag();
diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
index 56136cfbf..1071e6b01 100644
--- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
@@ -369,7 +369,7 @@ namespace FlaxEditor.Windows
}
var pluginPath = Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text);
- if (Directory.Exists(pluginPath))
+ if (!IsValidModuleName(nameTextBox.Text) || Directory.Exists(pluginPath))
{
nameTextBox.BorderColor = Color.Red;
nameTextBox.BorderSelectedColor = Color.Red;
@@ -429,6 +429,12 @@ namespace FlaxEditor.Windows
submitButton.Clicked += () =>
{
// TODO: Check all modules in project including plugins
+ if (!IsValidModuleName(nameTextBox.Text))
+ {
+ Editor.LogWarning("Invalid module name. Module names cannot contain spaces, start with a number or contain non-alphanumeric characters.");
+ return;
+ }
+
if (Directory.Exists(Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text)))
{
Editor.LogWarning("Cannot create module due to name conflict.");
@@ -460,5 +466,16 @@ namespace FlaxEditor.Windows
button.ParentContextMenu.Hide();
};
}
+
+ private static bool IsValidModuleName(string text)
+ {
+ if (text.Contains(' '))
+ return false;
+ if (char.IsDigit(text[0]))
+ return false;
+ if (text.Any(c => !char.IsLetterOrDigit(c) && c != '_'))
+ return false;
+ return true;
+ }
}
}
diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs
index 5ece067a0..2ef9c05cf 100644
--- a/Source/Editor/Windows/GameCookerWindow.cs
+++ b/Source/Editor/Windows/GameCookerWindow.cs
@@ -155,29 +155,42 @@ namespace FlaxEditor.Windows
public virtual void OnNotAvailableLayout(LayoutElementsContainer layout)
{
- layout.Label("Missing platform data tools for the target platform.", TextAlignment.Center);
+ string text = "Missing platform data tools for the target platform.";
if (FlaxEditor.Editor.IsOfficialBuild())
{
switch (BuildPlatform)
{
+#if PLATFORM_WINDOWS
case BuildPlatform.Windows32:
case BuildPlatform.Windows64:
case BuildPlatform.UWPx86:
case BuildPlatform.UWPx64:
case BuildPlatform.LinuxX64:
case BuildPlatform.AndroidARM64:
- layout.Label("Use Flax Launcher and download the required package.", TextAlignment.Center);
+ text += "\nUse Flax Launcher and download the required package.";
break;
+#endif
default:
- layout.Label("Engine source is required to target this platform.", TextAlignment.Center);
+ text += "\nEngine source is required to target this platform.";
break;
}
}
else
{
- var label = layout.Label("To target this platform separate engine source package is required.\nTo get access please contact via https://flaxengine.com/contact", TextAlignment.Center);
- label.Label.AutoHeight = true;
+ text += "\nTo target this platform separate engine source package is required.";
+ switch (BuildPlatform)
+ {
+ case BuildPlatform.XboxOne:
+ case BuildPlatform.XboxScarlett:
+ case BuildPlatform.PS4:
+ case BuildPlatform.PS5:
+ case BuildPlatform.Switch:
+ text += "\nTo get access please contact via https://flaxengine.com/contact";
+ break;
+ }
}
+ var label = layout.Label(text, TextAlignment.Center);
+ label.Label.AutoHeight = true;
}
public virtual void Build()
diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h
index 107e42e65..fba8ca823 100644
--- a/Source/Engine/Core/Collections/HashSet.h
+++ b/Source/Engine/Core/Collections/HashSet.h
@@ -82,7 +82,6 @@ public:
private:
int32 _elementsCount = 0;
- int32 _deletedCount = 0;
int32 _size = 0;
AllocationData _allocation;
@@ -109,14 +108,11 @@ public:
/// The other collection to move.
HashSet(HashSet&& other) noexcept
: _elementsCount(other._elementsCount)
- , _deletedCount(other._deletedCount)
, _size(other._size)
{
_elementsCount = other._elementsCount;
- _deletedCount = other._deletedCount;
_size = other._size;
other._elementsCount = 0;
- other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
}
@@ -154,10 +150,8 @@ public:
Clear();
_allocation.Free();
_elementsCount = other._elementsCount;
- _deletedCount = other._deletedCount;
_size = other._size;
other._elementsCount = 0;
- other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
}
@@ -337,12 +331,12 @@ public:
///
void Clear()
{
- if (_elementsCount + _deletedCount != 0)
+ if (_elementsCount != 0)
{
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i].Free();
- _elementsCount = _deletedCount = 0;
+ _elementsCount = 0;
}
}
@@ -377,7 +371,7 @@ public:
oldAllocation.Swap(_allocation);
const int32 oldSize = _size;
const int32 oldElementsCount = _elementsCount;
- _deletedCount = _elementsCount = 0;
+ _elementsCount = 0;
if (capacity != 0 && (capacity & (capacity - 1)) != 0)
{
// Align capacity value to the next power of two (http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2)
@@ -439,7 +433,7 @@ public:
bool Add(const ItemType& item)
{
// Ensure to have enough memory for the next item (in case of new element insertion)
- EnsureCapacity(_elementsCount + _deletedCount + 1);
+ EnsureCapacity(_elementsCount + 1);
// Find location of the item or place to insert it
FindPositionResult pos;
@@ -485,7 +479,6 @@ public:
{
_allocation.Get()[pos.ObjectIndex].Delete();
_elementsCount--;
- _deletedCount++;
return true;
}
return false;
@@ -504,7 +497,6 @@ public:
ASSERT(_allocation.Get()[i._index].IsOccupied());
_allocation.Get()[i._index].Delete();
_elementsCount--;
- _deletedCount++;
return true;
}
return false;
diff --git a/Source/Engine/Core/Delegate.h b/Source/Engine/Core/Delegate.h
index 4e081faef..945dd6ce5 100644
--- a/Source/Engine/Core/Delegate.h
+++ b/Source/Engine/Core/Delegate.h
@@ -511,7 +511,7 @@ public:
_locker = New();
ScopeLock lock(*_locker);
if (_functions == nullptr)
- _functions = New>(32);
+ _functions = New>();
_functions->Add(f);
#endif
}
@@ -583,18 +583,9 @@ public:
template
void Unbind()
{
-#if DELEGATE_USE_ATOMIC
FunctionType f;
f.template Bind();
Unbind(f);
-#else
- if (_functions == nullptr)
- return;
- FunctionType f;
- f.template Bind();
- ScopeLock lock(*_locker);
- _functions->Remove(f);
-#endif
}
///
@@ -604,18 +595,9 @@ public:
template
void Unbind(T* callee)
{
-#if DELEGATE_USE_ATOMIC
FunctionType f;
f.template Bind(callee);
Unbind(f);
-#else
- if (_functions == nullptr)
- return;
- FunctionType f;
- f.template Bind(callee);
- ScopeLock lock(*_locker);
- _functions->Remove(f);
-#endif
}
///
@@ -624,16 +606,8 @@ public:
/// The method.
void Unbind(Signature method)
{
-#if DELEGATE_USE_ATOMIC
FunctionType f(method);
Unbind(f);
-#else
- if (_functions == nullptr)
- return;
- FunctionType f(method);
- ScopeLock lock(*_locker);
- _functions->Remove(f);
-#endif
}
///
diff --git a/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs b/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs
index afd34bbfd..65377acaa 100644
--- a/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs
@@ -13,9 +13,7 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
- {
return true;
- }
return base.CanConvertFrom(context, sourceType);
}
@@ -23,9 +21,7 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
- {
return false;
- }
return base.CanConvertTo(context, destinationType);
}
diff --git a/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs
index 4eebbfce4..e0670df05 100644
--- a/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Double2Converter : TypeConverter
+ internal class Double2Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Double2(double.Parse(v[0], culture), double.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs
index 420e0016c..a66892ecb 100644
--- a/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Double3Converter : TypeConverter
+ internal class Double3Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Double3(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs
index fc1d9a7fe..d085217ef 100644
--- a/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Double4Converter : TypeConverter
+ internal class Double4Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Double4(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture), double.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs
index a41a0f4d5..4b2ffadf5 100644
--- a/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Float2Converter : TypeConverter
+ internal class Float2Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Float2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs
index aded4117e..3739c44ef 100644
--- a/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Float3Converter : TypeConverter
+ internal class Float3Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Float3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs
index 58c76ac65..620f2c838 100644
--- a/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs
@@ -7,15 +7,13 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Float4Converter : TypeConverter
+ internal class VectorConverter : TypeConverter
{
///
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
- {
return true;
- }
return base.CanConvertFrom(context, sourceType);
}
@@ -23,18 +21,32 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
- {
return false;
- }
return base.CanConvertTo(context, destinationType);
}
+ internal static string[] GetParts(string str)
+ {
+ string[] v = str.Split(',');
+ if (v.Length == 1)
+ {
+ // When converting from ToString()
+ v = str.Split(' ');
+ for (int i = 0; i < v.Length; i++)
+ v[i] = v[i].Substring(v[i].IndexOf(':') + 1);
+ }
+ return v;
+ }
+ }
+
+ internal class Float4Converter : VectorConverter
+ {
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Float4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs
index c4989c085..f528aa46b 100644
--- a/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Int2Converter : TypeConverter
+ internal class Int2Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Int2(int.Parse(v[0], culture), int.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs
index fe01f91fd..520f806d0 100644
--- a/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Int3Converter : TypeConverter
+ internal class Int3Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Int3(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs
index 2ce0fc202..e9a27dfda 100644
--- a/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Int4Converter : TypeConverter
+ internal class Int4Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Int4(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture), int.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs b/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs
index 23bb901be..5d9aa206b 100644
--- a/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class QuaternionConverter : TypeConverter
+ internal class QuaternionConverter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Quaternion(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs
index 96d6beadc..acb5b5817 100644
--- a/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Vector2Converter : TypeConverter
+ internal class Vector2Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Vector2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs
index 23ee4df11..66ec831f0 100644
--- a/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Vector3Converter : TypeConverter
+ internal class Vector3Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Vector3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs
index c3b4d074b..f4781f45b 100644
--- a/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
- internal class Vector4Converter : TypeConverter
+ internal class Vector4Converter : VectorConverter
{
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- return false;
- }
- return base.CanConvertTo(context, destinationType);
- }
-
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
- string[] v = str.Split(',');
+ string[] v = GetParts(str);
return new Vector4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
diff --git a/Source/Engine/Core/Math/Vector2.h b/Source/Engine/Core/Math/Vector2.h
index 83deb009a..cea03ec10 100644
--- a/Source/Engine/Core/Math/Vector2.h
+++ b/Source/Engine/Core/Math/Vector2.h
@@ -646,6 +646,12 @@ inline Vector2Base operator/(typename TOtherFloat::Type a, const Vector2Ba
return Vector2Base(a) / b;
}
+template
+inline uint32 GetHash(const Vector2Base& key)
+{
+ return (*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y;
+}
+
namespace Math
{
template
diff --git a/Source/Engine/Core/Math/Vector3.h b/Source/Engine/Core/Math/Vector3.h
index c0ebdf01d..01b55e9bd 100644
--- a/Source/Engine/Core/Math/Vector3.h
+++ b/Source/Engine/Core/Math/Vector3.h
@@ -977,6 +977,12 @@ inline Vector3Base operator/(typename TOtherFloat::Type a, const Vector3Ba
return Vector3Base(a) / b;
}
+template
+inline uint32 GetHash(const Vector3Base& key)
+{
+ return (((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z;
+}
+
namespace Math
{
template
diff --git a/Source/Engine/Core/Math/Vector4.h b/Source/Engine/Core/Math/Vector4.h
index 5c7b24c4a..1cc6d4db8 100644
--- a/Source/Engine/Core/Math/Vector4.h
+++ b/Source/Engine/Core/Math/Vector4.h
@@ -552,6 +552,12 @@ inline Vector4Base operator/(typename TOtherFloat::Type a, const Vector4Ba
return Vector4Base(a) / b;
}
+template
+inline uint32 GetHash(const Vector4Base& key)
+{
+ return (((((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z) * 397) ^*(uint32*)&key.W;
+}
+
namespace Math
{
template
diff --git a/Source/Engine/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp
index a020f7844..a185ce8b3 100644
--- a/Source/Engine/Core/ObjectsRemovalService.cpp
+++ b/Source/Engine/Core/ObjectsRemovalService.cpp
@@ -5,6 +5,7 @@
#include "Collections/Dictionary.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h"
+#include "Engine/Platform/CriticalSection.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ScriptingObject.h"
diff --git a/Source/Engine/Core/Types/Version.cpp b/Source/Engine/Core/Types/Version.cpp
index 4a11a0af6..c0410d1cb 100644
--- a/Source/Engine/Core/Types/Version.cpp
+++ b/Source/Engine/Core/Types/Version.cpp
@@ -7,15 +7,15 @@ Version::Version(int32 major, int32 minor, int32 build, int32 revision)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
- _build = Math::Max(build, 0);
- _revision = Math::Max(revision, 0);
+ _build = Math::Max(build, -1);
+ _revision = Math::Max(revision, -1);
}
Version::Version(int32 major, int32 minor, int32 build)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
- _build = Math::Max(build, 0);
+ _build = Math::Max(build, -1);
_revision = -1;
}
diff --git a/Source/Engine/Core/Utilities.h b/Source/Engine/Core/Utilities.h
index 36339baf5..737e423b2 100644
--- a/Source/Engine/Core/Utilities.h
+++ b/Source/Engine/Core/Utilities.h
@@ -51,10 +51,14 @@ namespace Utilities
int32 i = 0;
double dblSUnits = static_cast(units);
for (; static_cast(units / static_cast(divider)) > 0; i++, units /= divider)
- dblSUnits = units / static_cast(divider);
+ dblSUnits = (double)units / (double)divider;
if (i >= sizes.Length())
i = 0;
- return String::Format(TEXT("{0} {1}"), RoundTo2DecimalPlaces(dblSUnits), sizes[i]);
+ String text = String::Format(TEXT("{}"), RoundTo2DecimalPlaces(dblSUnits));
+ const int32 dot = text.FindLast('.');
+ if (dot != -1)
+ text = text.Left(dot + 3);
+ return String::Format(TEXT("{0} {1}"), text, sizes[i]);
}
// Converts size of the file (in bytes) to the best fitting string
diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs
index 2f74e421c..fea1fadda 100644
--- a/Source/Engine/Engine/NativeInterop.cs
+++ b/Source/Engine/Engine/NativeInterop.cs
@@ -1302,7 +1302,8 @@ namespace FlaxEngine.Interop
#if !USE_AOT
internal bool TryGetDelegate(out Invoker.MarshalAndInvokeDelegate outDeleg, out object outDelegInvoke)
{
- if (invokeDelegate == null)
+ // Skip using in-built delegate for value types (eg. Transform) to properly handle instance value passing to method
+ if (invokeDelegate == null && !method.DeclaringType.IsValueType)
{
List methodTypes = new List();
if (!method.IsStatic)
diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp
index 34d8e7661..f0056e26c 100644
--- a/Source/Engine/Graphics/GPUDevice.cpp
+++ b/Source/Engine/Graphics/GPUDevice.cpp
@@ -313,6 +313,8 @@ bool GPUDevice::Init()
_res->TasksManager.SetExecutor(CreateTasksExecutor());
LOG(Info, "Total graphics memory: {0}", Utilities::BytesToText(TotalGraphicsMemory));
+ if (!Limits.HasCompute)
+ LOG(Warning, "Compute Shaders are not supported");
return false;
}
diff --git a/Source/Engine/Graphics/Shaders/GPUShader.cpp b/Source/Engine/Graphics/Shaders/GPUShader.cpp
index e76ee9996..86d983fd8 100644
--- a/Source/Engine/Graphics/Shaders/GPUShader.cpp
+++ b/Source/Engine/Graphics/Shaders/GPUShader.cpp
@@ -87,7 +87,11 @@ bool GPUShader::Create(MemoryReadStream& stream)
GPUShaderProgramInitializer initializer;
#if !BUILD_RELEASE
initializer.Owner = this;
+ const StringView name = GetName();
+#else
+ const StringView name;
#endif
+ const bool hasCompute = GPUDevice::Instance->Limits.HasCompute;
for (int32 i = 0; i < shadersCount; i++)
{
const ShaderStage type = static_cast(stream.ReadByte());
@@ -117,10 +121,15 @@ bool GPUShader::Create(MemoryReadStream& stream)
stream.ReadBytes(&initializer.Bindings, sizeof(ShaderBindings));
// Create shader program
+ if (type == ShaderStage::Compute && !hasCompute)
+ {
+ LOG(Warning, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
+ continue;
+ }
GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, cache, cacheSize, stream);
if (shader == nullptr)
{
- LOG(Error, "Failed to create {} Shader program '{}'.", ::ToString(type), String(initializer.Name));
+ LOG(Error, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
return true;
}
diff --git a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h
index 09c45506f..31f6638dc 100644
--- a/Source/Engine/Graphics/Shaders/GPUShaderProgram.h
+++ b/Source/Engine/Graphics/Shaders/GPUShaderProgram.h
@@ -122,6 +122,19 @@ public:
///
class GPUShaderProgramVS : public GPUShaderProgram
{
+public:
+ // Input element run-time data (see VertexShaderMeta::InputElement for compile-time data)
+ PACK_STRUCT(struct InputElement
+ {
+ byte Type; // VertexShaderMeta::InputType
+ byte Index;
+ byte Format; // PixelFormat
+ byte InputSlot;
+ uint32 AlignedByteOffset; // Fixed value or INPUT_LAYOUT_ELEMENT_ALIGN if auto
+ byte InputSlotClass; // INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA or INPUT_LAYOUT_ELEMENT_PER_INSTANCE_DATA
+ uint32 InstanceDataStepRate; // 0 if per-vertex
+ });
+
public:
///
/// Gets input layout description handle (platform dependent).
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp
index b58684a4e..52192c634 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp
+++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUShaderDX11.cpp
@@ -15,32 +15,21 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
{
case ShaderStage::Vertex:
{
- D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
-
- // Temporary variables
- byte Type, Format, Index, InputSlot, InputSlotClass;
- uint32 AlignedByteOffset, InstanceDataStepRate;
-
- // Load Input Layout (it may be empty)
+ // Load Input Layout
byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize);
ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS);
+ D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
for (int32 a = 0; a < inputLayoutSize; a++)
{
// Read description
- // TODO: maybe use struct and load at once?
- stream.ReadByte(&Type);
- stream.ReadByte(&Index);
- stream.ReadByte(&Format);
- stream.ReadByte(&InputSlot);
- stream.ReadUint32(&AlignedByteOffset);
- stream.ReadByte(&InputSlotClass);
- stream.ReadUint32(&InstanceDataStepRate);
+ GPUShaderProgramVS::InputElement inputElement;
+ stream.Read(inputElement);
// Get semantic name
const char* semanticName = nullptr;
// TODO: maybe use enum+mapping ?
- switch (Type)
+ switch (inputElement.Type)
{
case 1:
semanticName = "POSITION";
@@ -70,7 +59,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
semanticName = "BLENDWEIGHT";
break;
default:
- LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type);
+ LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type);
break;
}
@@ -78,12 +67,12 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
inputLayoutDesc[a] =
{
semanticName,
- static_cast(Index),
- static_cast(Format),
- static_cast(InputSlot),
- static_cast(AlignedByteOffset),
- static_cast(InputSlotClass),
- static_cast(InstanceDataStepRate)
+ static_cast(inputElement.Index),
+ static_cast(inputElement.Format),
+ static_cast(inputElement.InputSlot),
+ static_cast(inputElement.AlignedByteOffset),
+ static_cast(inputElement.InputSlotClass),
+ static_cast(inputElement.InstanceDataStepRate)
};
}
diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp
index e1e716853..07352b674 100644
--- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp
+++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUShaderDX12.cpp
@@ -20,32 +20,21 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
{
case ShaderStage::Vertex:
{
- D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
-
- // Temporary variables
- byte Type, Format, Index, InputSlot, InputSlotClass;
- uint32 AlignedByteOffset, InstanceDataStepRate;
-
// Load Input Layout (it may be empty)
byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize);
ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS);
+ D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
for (int32 a = 0; a < inputLayoutSize; a++)
{
// Read description
- // TODO: maybe use struct and load at once?
- stream.ReadByte(&Type);
- stream.ReadByte(&Index);
- stream.ReadByte(&Format);
- stream.ReadByte(&InputSlot);
- stream.ReadUint32(&AlignedByteOffset);
- stream.ReadByte(&InputSlotClass);
- stream.ReadUint32(&InstanceDataStepRate);
+ GPUShaderProgramVS::InputElement inputElement;
+ stream.Read(inputElement);
// Get semantic name
const char* semanticName = nullptr;
// TODO: maybe use enum+mapping ?
- switch (Type)
+ switch (inputElement.Type)
{
case 1:
semanticName = "POSITION";
@@ -75,7 +64,7 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
semanticName = "BLENDWEIGHT";
break;
default:
- LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type);
+ LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type);
break;
}
@@ -83,12 +72,12 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
inputLayout[a] =
{
semanticName,
- static_cast(Index),
- static_cast(Format),
- static_cast(InputSlot),
- static_cast(AlignedByteOffset),
- static_cast(InputSlotClass),
- static_cast(InstanceDataStepRate)
+ static_cast(inputElement.Index),
+ static_cast(inputElement.Format),
+ static_cast(inputElement.InputSlot),
+ static_cast(inputElement.AlignedByteOffset),
+ static_cast(inputElement.InputSlotClass),
+ static_cast(inputElement.InstanceDataStepRate)
};
}
diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp
index 5e583d713..852ce5bad 100644
--- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp
+++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.cpp
@@ -14,9 +14,9 @@
#include "Engine/Graphics/PixelFormatExtensions.h"
#if PLATFORM_DESKTOP
-#define VULKAN_UNIFORM_RING_BUFFER_SIZE 24 * 1024 * 1024
+#define VULKAN_UNIFORM_RING_BUFFER_SIZE (24 * 1024 * 1024)
#else
-#define VULKAN_UNIFORM_RING_BUFFER_SIZE 8 * 1024 * 1024
+#define VULKAN_UNIFORM_RING_BUFFER_SIZE (8 * 1024 * 1024)
#endif
UniformBufferUploaderVulkan::UniformBufferUploaderVulkan(GPUDeviceVulkan* device)
@@ -153,10 +153,6 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons
vertexBindingDescriptions[i].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
}
- // Temporary variables
- byte Type, Format, Index, InputSlot, InputSlotClass;
- uint32 AlignedByteOffset, InstanceDataStepRate;
-
// Load Input Layout (it may be empty)
byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize);
@@ -167,32 +163,26 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons
for (int32 a = 0; a < inputLayoutSize; a++)
{
// Read description
- // TODO: maybe use struct and load at once?
- stream.ReadByte(&Type);
- stream.ReadByte(&Index);
- stream.ReadByte(&Format);
- stream.ReadByte(&InputSlot);
- stream.ReadUint32(&AlignedByteOffset);
- stream.ReadByte(&InputSlotClass);
- stream.ReadUint32(&InstanceDataStepRate);
+ GPUShaderProgramVS::InputElement inputElement;
+ stream.Read(inputElement);
- const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)Format);
- if (AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN)
- offset = AlignedByteOffset;
+ const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)inputElement.Format);
+ if (inputElement.AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN)
+ offset = inputElement.AlignedByteOffset;
- auto& vertexBindingDescription = vertexBindingDescriptions[InputSlot];
- vertexBindingDescription.binding = InputSlot;
+ auto& vertexBindingDescription = vertexBindingDescriptions[inputElement.InputSlot];
+ vertexBindingDescription.binding = inputElement.InputSlot;
vertexBindingDescription.stride = Math::Max(vertexBindingDescription.stride, (uint32_t)(offset + size));
- vertexBindingDescription.inputRate = InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
- ASSERT(InstanceDataStepRate == 0 || InstanceDataStepRate == 1);
+ vertexBindingDescription.inputRate = inputElement.InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
+ ASSERT(inputElement.InstanceDataStepRate == 0 || inputElement.InstanceDataStepRate == 1);
auto& vertexAttributeDescription = vertexAttributeDescriptions[a];
vertexAttributeDescription.location = a;
- vertexAttributeDescription.binding = InputSlot;
- vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)Format);
+ vertexAttributeDescription.binding = inputElement.InputSlot;
+ vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)inputElement.Format);
vertexAttributeDescription.offset = offset;
- bindingsCount = Math::Max(bindingsCount, (uint32)InputSlot + 1);
+ bindingsCount = Math::Max(bindingsCount, (uint32)inputElement.InputSlot + 1);
offset += size;
}
diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp
index dfd5786c6..cfdf11a6f 100644
--- a/Source/Engine/Level/Level.cpp
+++ b/Source/Engine/Level/Level.cpp
@@ -416,7 +416,7 @@ public:
}
// Load scene
- if (Level::loadScene(SceneAsset.Get()))
+ if (Level::loadScene(SceneAsset))
{
LOG(Error, "Failed to deserialize scene {0}", SceneId);
CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
@@ -816,40 +816,10 @@ bool LevelImpl::unloadScenes()
return false;
}
-bool Level::loadScene(const Guid& sceneId)
-{
- const auto sceneAsset = Content::LoadAsync(sceneId);
- return loadScene(sceneAsset);
-}
-
-bool Level::loadScene(const String& scenePath)
-{
- LOG(Info, "Loading scene from file. Path: \'{0}\'", scenePath);
-
- // Check for missing file
- if (!FileSystem::FileExists(scenePath))
- {
- LOG(Error, "Missing scene file.");
- return true;
- }
-
- // Load file
- BytesContainer sceneData;
- if (File::ReadAllBytes(scenePath, sceneData))
- {
- LOG(Error, "Cannot load data from file.");
- return true;
- }
-
- return loadScene(sceneData);
-}
-
bool Level::loadScene(JsonAsset* sceneAsset)
{
// Keep reference to the asset (prevent unloading during action)
AssetReference ref = sceneAsset;
-
- // Wait for loaded
if (sceneAsset == nullptr || sceneAsset->WaitForLoaded())
{
LOG(Error, "Cannot load scene asset.");
@@ -879,6 +849,7 @@ bool Level::loadScene(const BytesContainer& sceneData, Scene** outScene)
return true;
}
+ ScopeLock lock(ScenesLock);
return loadScene(document, outScene);
}
@@ -975,6 +946,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
SceneObject** objects = sceneObjects->Get();
if (context.Async)
{
+ ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize)
JobSystem::Execute([&](int32 i)
{
i++; // Start from 1. at index [0] was scene
@@ -992,6 +964,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
else
SceneObjectsFactory::HandleObjectDeserializationError(stream);
}, objectsCount - 1);
+ ScenesLock.Lock();
}
else
{
@@ -1330,6 +1303,7 @@ bool Level::LoadScene(const Guid& id)
}
// Load scene
+ ScopeLock lock(ScenesLock);
if (loadScene(sceneAsset))
{
LOG(Error, "Failed to deserialize scene {0}", id);
diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h
index 1f0acda2d..e09f8e376 100644
--- a/Source/Engine/Level/Level.h
+++ b/Source/Engine/Level/Level.h
@@ -541,8 +541,8 @@ private:
};
static void callActorEvent(ActorEventType eventType, Actor* a, Actor* b);
- static bool loadScene(const Guid& sceneId);
- static bool loadScene(const String& scenePath);
+
+ // All loadScene assume that ScenesLock has been taken by the calling thread
static bool loadScene(JsonAsset* sceneAsset);
static bool loadScene(const BytesContainer& sceneData, Scene** outScene = nullptr);
static bool loadScene(rapidjson_flax::Document& document, Scene** outScene = nullptr);
diff --git a/Source/Engine/Level/Prefabs/PrefabManager.cpp b/Source/Engine/Level/Prefabs/PrefabManager.cpp
index cd9d893ec..de164343b 100644
--- a/Source/Engine/Level/Prefabs/PrefabManager.cpp
+++ b/Source/Engine/Level/Prefabs/PrefabManager.cpp
@@ -39,7 +39,7 @@ PrefabManagerService PrefabManagerServiceInstance;
Actor* PrefabManager::SpawnPrefab(Prefab* prefab)
{
Actor* parent = Level::Scenes.Count() != 0 ? Level::Scenes.Get()[0] : nullptr;
- return SpawnPrefab(prefab, Transform::Identity, parent, nullptr);
+ return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, nullptr);
}
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Vector3& position)
@@ -73,12 +73,12 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, const Transform
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent)
{
- return SpawnPrefab(prefab, Transform::Identity, parent, nullptr);
+ return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, nullptr);
}
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary* objectsCache, bool withSynchronization)
{
- return SpawnPrefab(prefab, Transform::Identity, parent, objectsCache, withSynchronization);
+ return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, objectsCache, withSynchronization);
}
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary* objectsCache, bool withSynchronization)
@@ -191,7 +191,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
parent->Children.Add(root);
// Move root to the right location
- if (transform != Transform::Identity)
+ if (transform.Translation != Vector3::Minimum)
root->SetTransform(transform);
// Link actors hierarchy
diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp
index 5632d8bc7..2936da3da 100644
--- a/Source/Engine/Level/SceneObjectsFactory.cpp
+++ b/Source/Engine/Level/SceneObjectsFactory.cpp
@@ -16,6 +16,7 @@
#include "Engine/Threading/ThreadLocal.h"
#if !BUILD_RELEASE || USE_EDITOR
#include "Engine/Level/Level.h"
+#include "Engine/Threading/Threading.h"
#endif
SceneObjectsFactory::Context::Context(ISerializeModifier* modifier)
@@ -279,9 +280,9 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::
#if USE_EDITOR
// Add dummy script
auto* dummyScript = parent->AddScript();
- const auto parentIdMember = value.FindMember("TypeName");
- if (parentIdMember != value.MemberEnd() && parentIdMember->value.IsString())
- dummyScript->MissingTypeName = parentIdMember->value.GetString();
+ const auto typeNameMember = value.FindMember("TypeName");
+ if (typeNameMember != value.MemberEnd() && typeNameMember->value.IsString())
+ dummyScript->MissingTypeName = typeNameMember->value.GetString();
dummyScript->Data = MoveTemp(bufferStr);
#endif
LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName());
diff --git a/Source/Engine/Navigation/NavCrowd.cpp b/Source/Engine/Navigation/NavCrowd.cpp
index b05f0f7d3..fe1c2f728 100644
--- a/Source/Engine/Navigation/NavCrowd.cpp
+++ b/Source/Engine/Navigation/NavCrowd.cpp
@@ -7,6 +7,7 @@
#include "Engine/Level/Level.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Profiler/ProfilerCPU.h"
+#include "Engine/Threading/Threading.h"
#include
NavCrowd::NavCrowd(const SpawnParams& params)
diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h
index 84834a6e5..967dc0e0c 100644
--- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h
+++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h
@@ -5,7 +5,7 @@
///
/// Current GPU particles emitter shader version.
///
-#define PARTICLE_GPU_GRAPH_VERSION 9
+#define PARTICLE_GPU_GRAPH_VERSION 10
#if COMPILE_WITH_PARTICLE_GPU_GRAPH
diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp
index 788a4263f..e461f332e 100644
--- a/Source/Engine/Particles/ParticleEffect.cpp
+++ b/Source/Engine/Particles/ParticleEffect.cpp
@@ -284,6 +284,7 @@ void ParticleEffect::UpdateSimulation(bool singleFrame)
void ParticleEffect::Play()
{
_isPlaying = true;
+ _isStopped = false;
}
void ParticleEffect::Pause()
@@ -293,6 +294,7 @@ void ParticleEffect::Pause()
void ParticleEffect::Stop()
{
+ _isStopped = true;
_isPlaying = false;
ResetSimulation();
}
@@ -448,7 +450,7 @@ void ParticleEffect::Update()
void ParticleEffect::UpdateExecuteInEditor()
{
// Auto-play in Editor
- if (!Editor::IsPlayMode)
+ if (!Editor::IsPlayMode && !_isStopped)
{
_isPlaying = true;
Update();
diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h
index 9e9792a4c..d3f0cc9d0 100644
--- a/Source/Engine/Particles/ParticleEffect.h
+++ b/Source/Engine/Particles/ParticleEffect.h
@@ -133,7 +133,7 @@ public:
///
/// The particle system instance that plays the particles simulation in the game.
///
-API_CLASS(Attributes="ActorContextMenu(\"New/Visuals/Particle Effects\"), ActorToolbox(\"Visuals\")")
+API_CLASS(Attributes="ActorContextMenu(\"New/Visuals/Particle Effect\"), ActorToolbox(\"Visuals\")")
class FLAXENGINE_API ParticleEffect : public Actor
{
DECLARE_SCENE_OBJECT(ParticleEffect);
@@ -185,6 +185,7 @@ private:
Array _parameters; // Cached for scripting API
Array _parametersOverrides; // Cached parameter modifications to be applied to the parameters
bool _isPlaying = false;
+ bool _isStopped = false;
public:
///
diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp
index 5850b6736..784f28c98 100644
--- a/Source/Engine/Particles/Particles.cpp
+++ b/Source/Engine/Particles/Particles.cpp
@@ -1318,6 +1318,8 @@ void ParticlesSystem::Job(int32 index)
emitterInstance.Buffer = nullptr;
}
}
+ // Stop playing effect.
+ effect->Stop();
return;
}
}
@@ -1332,7 +1334,8 @@ void ParticlesSystem::Job(int32 index)
auto emitter = particleSystem->Emitters[track.AsEmitter.Index].Get();
auto& data = instance.Emitters[track.AsEmitter.Index];
ASSERT(emitter && emitter->IsLoaded());
- ASSERT(emitter->Capacity != 0 && emitter->Graph.Layout.Size != 0);
+ if (emitter->Capacity == 0 || emitter->Graph.Layout.Size == 0)
+ continue;
PROFILE_CPU_ASSET(emitter);
// Calculate new time position
diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp
index 7c180d98d..b2db80b0b 100644
--- a/Source/Engine/Physics/Actors/Cloth.cpp
+++ b/Source/Engine/Physics/Actors/Cloth.cpp
@@ -95,14 +95,17 @@ void Cloth::SetFabric(const FabricSettings& value)
void Cloth::Rebuild()
{
#if WITH_CLOTH
- // Remove old
- if (IsDuringPlay())
- PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth);
- DestroyCloth();
+ if (_cloth)
+ {
+ // Remove old
+ if (IsDuringPlay())
+ PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth);
+ DestroyCloth();
+ }
// Create new
CreateCloth();
- if (IsDuringPlay())
+ if (IsDuringPlay() && _cloth)
PhysicsBackend::AddCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth);
#endif
}
diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h
index 4dc81ce3f..6137ec3b6 100644
--- a/Source/Engine/Physics/Actors/Cloth.h
+++ b/Source/Engine/Physics/Actors/Cloth.h
@@ -232,13 +232,13 @@ private:
public:
///
- /// Gets the mesh to use for the cloth simulation (single mesh from specific LOD).
+ /// Gets the mesh to use for the cloth simulation (single mesh from specific LOD). Always from the parent static or animated model actor.
///
API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Cloth\")")
ModelInstanceActor::MeshReference GetMesh() const;
///
- /// Sets the mesh to use for the cloth simulation (single mesh from specific LOD).
+ /// Sets the mesh to use for the cloth simulation (single mesh from specific LOD). Always from the parent static or animated model actor.
///
API_PROPERTY() void SetMesh(const ModelInstanceActor::MeshReference& value);
diff --git a/Source/Engine/Physics/Actors/PhysicsColliderActor.cpp b/Source/Engine/Physics/Actors/PhysicsColliderActor.cpp
index 02645943c..f2ab5d8a2 100644
--- a/Source/Engine/Physics/Actors/PhysicsColliderActor.cpp
+++ b/Source/Engine/Physics/Actors/PhysicsColliderActor.cpp
@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "PhysicsColliderActor.h"
-#include "Engine/Scripting/Script.h"
#include "RigidBody.h"
PhysicsColliderActor::PhysicsColliderActor(const SpawnParams& params)
diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp
index 684aeca19..5968bdd78 100644
--- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp
+++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp
@@ -117,9 +117,7 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
{
// Skip sending events to removed actors
if (pairHeader.flags & (PxContactPairHeaderFlag::eREMOVED_ACTOR_0 | PxContactPairHeaderFlag::eREMOVED_ACTOR_1))
- {
return;
- }
Collision c;
PxContactPairExtraDataIterator j(pairHeader.extraDataStream, pairHeader.extraDataStreamSize);
@@ -193,7 +191,7 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c
RemovedCollisions.Add(c);
}
}
- ASSERT(!j.nextItemSet());
+ //ASSERT(!j.nextItemSet());
}
void SimulationEventCallback::onTrigger(PxTriggerPair* pairs, PxU32 count)
diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp
index bf79c95c2..d4348b107 100644
--- a/Source/Engine/Platform/Apple/ApplePlatform.cpp
+++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp
@@ -48,6 +48,7 @@ CPUInfo Cpu;
String UserLocale;
double SecondsPerCycle;
NSAutoreleasePool* AutoreleasePool = nullptr;
+int32 AutoreleasePoolInterval = 0;
float ApplePlatform::ScreenScale = 1.0f;
@@ -245,13 +246,40 @@ void ApplePlatform::GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int3
millisecond = time.tv_usec / 1000;
}
+#if !BUILD_RELEASE
+
void ApplePlatform::Log(const StringView& msg)
{
-#if !BUILD_RELEASE && !USE_EDITOR
+#if !USE_EDITOR
NSLog(@"%s", StringAsANSI<>(*msg, msg.Length()).Get());
#endif
}
+bool ApplePlatform::IsDebuggerPresent()
+{
+ // Reference: https://developer.apple.com/library/archive/qa/qa1361/_index.html
+ int mib[4];
+ struct kinfo_proc info;
+
+ // Initialize the flags so that, if sysctl fails for some bizarre reason, we get a predictable result
+ info.kp_proc.p_flag = 0;
+
+ // Initialize mib, which tells sysctl the info we want, in this case we're looking for information about a specific process ID
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = getpid();
+
+ // Call sysctl
+ size_t size = sizeof(info);
+ sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
+
+ // We're being debugged if the P_TRACED flag is set
+ return ((info.kp_proc.p_flag & P_TRACED) != 0);
+}
+
+#endif
+
bool ApplePlatform::Init()
{
if (UnixPlatform::Init())
@@ -332,9 +360,13 @@ bool ApplePlatform::Init()
void ApplePlatform::Tick()
{
- // TODO: do it once every X fames
- [AutoreleasePool drain];
- AutoreleasePool = [[NSAutoreleasePool alloc] init];
+ AutoreleasePoolInterval++;
+ if (AutoreleasePoolInterval >= 60)
+ {
+ AutoreleasePoolInterval = 0;
+ [AutoreleasePool drain];
+ AutoreleasePool = [[NSAutoreleasePool alloc] init];
+ }
}
void ApplePlatform::BeforeExit()
@@ -343,6 +375,11 @@ void ApplePlatform::BeforeExit()
void ApplePlatform::Exit()
{
+ if (AutoreleasePool)
+ {
+ [AutoreleasePool drain];
+ AutoreleasePool = nullptr;
+ }
}
void ApplePlatform::SetHighDpiAwarenessEnabled(bool enable)
@@ -369,9 +406,7 @@ bool ApplePlatform::GetHasFocus()
if (window->IsFocused())
return true;
}
-
- // Default to true if has no windows open
- return WindowsManager::Windows.IsEmpty();
+ return false;
}
void ApplePlatform::CreateGuid(Guid& result)
diff --git a/Source/Engine/Platform/Apple/ApplePlatform.h b/Source/Engine/Platform/Apple/ApplePlatform.h
index f1148d0b6..94b6534d6 100644
--- a/Source/Engine/Platform/Apple/ApplePlatform.h
+++ b/Source/Engine/Platform/Apple/ApplePlatform.h
@@ -79,7 +79,10 @@ public:
static uint64 GetClockFrequency();
static void GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
static void GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
+#if !BUILD_RELEASE
static void Log(const StringView& msg);
+ static bool IsDebuggerPresent();
+#endif
static bool Init();
static void Tick();
static void BeforeExit();
diff --git a/Source/Engine/Platform/Base/WindowsManager.cpp b/Source/Engine/Platform/Base/WindowsManager.cpp
index 0d58008d8..34381b3d2 100644
--- a/Source/Engine/Platform/Base/WindowsManager.cpp
+++ b/Source/Engine/Platform/Base/WindowsManager.cpp
@@ -59,9 +59,11 @@ void WindowsManagerService::Update()
// Update windows
const float deltaTime = Time::Update.UnscaledDeltaTime.GetTotalSeconds();
WindowsManager::WindowsLocker.Lock();
- for (Window* win : WindowsManager::Windows)
+ Array> windows;
+ windows.Add(WindowsManager::Windows);
+ for (Window* win : windows)
{
- if (win && win->IsVisible())
+ if (win->IsVisible())
win->OnUpdate(deltaTime);
}
WindowsManager::WindowsLocker.Unlock();
@@ -71,7 +73,8 @@ void WindowsManagerService::Dispose()
{
// Close remaining windows
WindowsManager::WindowsLocker.Lock();
- auto windows = WindowsManager::Windows;
+ Array> windows;
+ windows.Add(WindowsManager::Windows);
for (Window* win : windows)
{
win->Close(ClosingReason::EngineExit);
diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp
index 4787b4549..36affd8c8 100644
--- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp
+++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp
@@ -2622,9 +2622,7 @@ bool LinuxPlatform::GetHasFocus()
if (window->IsFocused())
return true;
}
-
- // Default to true if has no windows open
- return WindowsManager::Windows.IsEmpty();
+ return false;
}
bool LinuxPlatform::CanOpenUrl(const StringView& url)
diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp
index 000accbfa..38e8c95df 100644
--- a/Source/Engine/Platform/Mac/MacWindow.cpp
+++ b/Source/Engine/Platform/Mac/MacWindow.cpp
@@ -4,7 +4,14 @@
#include "../Window.h"
#include "Engine/Platform/Apple/AppleUtils.h"
+#include "Engine/Platform/WindowsManager.h"
#include "Engine/Platform/IGuiData.h"
+#if USE_EDITOR
+#include "Engine/Platform/CriticalSection.h"
+#include "Engine/Threading/ThreadPoolTask.h"
+#include "Engine/Threading/ThreadPool.h"
+#include "Engine/Engine/Engine.h"
+#endif
#include "Engine/Core/Log.h"
#include "Engine/Input/Input.h"
#include "Engine/Input/Mouse.h"
@@ -14,6 +21,37 @@
#include
#include
+#if USE_EDITOR
+// Data for drawing window while doing drag&drop on Mac (engine is paused during platform tick)
+CriticalSection MacDragLocker;
+NSDraggingSession* MacDragSession = nullptr;
+class DoDragDropJob* MacDragJob = nullptr;
+
+class DoDragDropJob : public ThreadPoolTask
+{
+public:
+ int64 ExitFlag = 0;
+
+ bool Run() override
+ {
+ while (Platform::AtomicRead(&ExitFlag) == 0)
+ {
+ Engine::OnDraw();
+ Platform::Sleep(20);
+ }
+ return false;
+ }
+};
+#endif
+
+inline bool IsWindowInvalid(Window* win)
+{
+ WindowsManager::WindowsLocker.Lock();
+ const bool hasWindow = WindowsManager::Windows.Contains(win);
+ WindowsManager::WindowsLocker.Unlock();
+ return !hasWindow || !win;
+}
+
KeyboardKeys GetKey(NSEvent* event)
{
switch ([event keyCode])
@@ -268,17 +306,20 @@ NSDragOperation GetDragDropOperation(DragDropEffect dragDropEffect)
// Handle resizing to be sure that content has valid size when window was resized
[self windowDidResize:notification];
+ if (IsWindowInvalid(Window)) return;
Window->OnGotFocus();
}
- (void)windowDidResignKey:(NSNotification*)notification
{
+ if (IsWindowInvalid(Window)) return;
Window->OnLostFocus();
}
- (void)windowWillClose:(NSNotification*)notification
{
[self setDelegate: nil];
+ if (IsWindowInvalid(Window)) return;
Window->Close(ClosingReason::User);
}
@@ -311,7 +352,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
@end
-@interface MacViewImpl : NSView
+@interface MacViewImpl : NSView
{
MacWindow* Window;
NSTrackingArea* TrackingArea;
@@ -375,6 +416,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)keyDown:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
KeyboardKeys key = GetKey(event);
if (key != KeyboardKeys::None)
Input::Keyboard->OnKeyDown(key, Window);
@@ -405,6 +447,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)keyUp:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
KeyboardKeys key = GetKey(event);
if (key != KeyboardKeys::None)
Input::Keyboard->OnKeyUp(key, Window);
@@ -412,6 +455,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)flagsChanged:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
int32 modMask;
int32 keyCode = [event keyCode];
if (keyCode == 0x36 || keyCode == 0x37)
@@ -437,6 +481,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)scrollWheel:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
double deltaX = [event scrollingDeltaX];
double deltaY = [event scrollingDeltaY];
@@ -451,32 +496,39 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)mouseMoved:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
- if (!Window->IsMouseTracking() && !IsMouseOver)
+ if (Window->IsMouseTracking())
+ return; // Skip mouse events when tracking mouse (handled in MacWindow::OnUpdate)
+ if (!IsMouseOver)
return;
Input::Mouse->OnMouseMove(Window->ClientToScreen(mousePos), Window);
}
- (void)mouseEntered:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
IsMouseOver = true;
Window->SetIsMouseOver(true);
}
- (void)mouseExited:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
IsMouseOver = false;
Window->SetIsMouseOver(false);
}
- (void)mouseDown:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
+ mousePos = Window->ClientToScreen(mousePos);
MouseButton mouseButton = MouseButton::Left;
if ([event clickCount] == 2)
- Input::Mouse->OnMouseDoubleClick(Window->ClientToScreen(mousePos), mouseButton, Window);
+ Input::Mouse->OnMouseDoubleClick(mousePos, mouseButton, Window);
else
- Input::Mouse->OnMouseDown(Window->ClientToScreen(mousePos), mouseButton, Window);
+ Input::Mouse->OnMouseDown(mousePos, mouseButton, Window);
}
- (void)mouseDragged:(NSEvent*)event
@@ -486,13 +538,28 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)mouseUp:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
+ mousePos = Window->ClientToScreen(mousePos);
MouseButton mouseButton = MouseButton::Left;
- Input::Mouse->OnMouseUp(Window->ClientToScreen(mousePos), mouseButton, Window);
+ Input::Mouse->OnMouseUp(mousePos, mouseButton, Window);
+
+ // Redirect event to any window that tracks the mouse (eg. dock window in Editor)
+ WindowsManager::WindowsLocker.Lock();
+ for (auto* win : WindowsManager::Windows)
+ {
+ if (win->IsVisible() && win->IsMouseTracking() && win != Window)
+ {
+ Input::Mouse->OnMouseUp(mousePos, mouseButton, win);
+ break;
+ }
+ }
+ WindowsManager::WindowsLocker.Unlock();
}
- (void)rightMouseDown:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton = MouseButton::Right;
if ([event clickCount] == 2)
@@ -508,6 +575,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)rightMouseUp:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton = MouseButton::Right;
Input::Mouse->OnMouseUp(Window->ClientToScreen(mousePos), mouseButton, Window);
@@ -515,6 +583,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)otherMouseDown:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton;
switch ([event buttonNumber])
@@ -544,6 +613,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)otherMouseUp:(NSEvent*)event
{
+ if (IsWindowInvalid(Window)) return;
Float2 mousePos = GetMousePosition(Window, event);
MouseButton mouseButton;
switch ([event buttonNumber])
@@ -565,6 +635,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (NSDragOperation)draggingEntered:(id)sender
{
+ if (IsWindowInvalid(Window)) return NSDragOperationNone;
Float2 mousePos;
MacDropData dropData;
GetDragDropData(Window, sender, mousePos, dropData);
@@ -580,6 +651,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (NSDragOperation)draggingUpdated:(id)sender
{
+ if (IsWindowInvalid(Window)) return NSDragOperationNone;
Float2 mousePos;
MacDropData dropData;
GetDragDropData(Window, sender, mousePos, dropData);
@@ -590,6 +662,7 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (BOOL)performDragOperation:(id)sender
{
+ if (IsWindowInvalid(Window)) return NO;
Float2 mousePos;
MacDropData dropData;
GetDragDropData(Window, sender, mousePos, dropData);
@@ -600,9 +673,38 @@ static void ConvertNSRect(NSScreen *screen, NSRect *r)
- (void)draggingExited:(id)sender
{
+ if (IsWindowInvalid(Window)) return;
Window->OnDragLeave();
}
+- (NSDragOperation)draggingSession:(NSDraggingSession*)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
+{
+ if (IsWindowInvalid(Window)) return NSDragOperationNone;
+ return NSDragOperationMove;
+}
+
+- (void)draggingSession:(NSDraggingSession*)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
+{
+#if USE_EDITOR
+ // Stop background worker once the drag ended
+ MacDragLocker.Lock();
+ if (MacDragSession && MacDragSession == session)
+ {
+ Platform::AtomicStore(&MacDragJob->ExitFlag, 1);
+ MacDragJob->Wait();
+ MacDragSession = nullptr;
+ MacDragJob = nullptr;
+ }
+ MacDragLocker.Unlock();
+#endif
+}
+
+- (void)pasteboard:(nullable NSPasteboard*)pasteboard item:(NSPasteboardItem*)item provideDataForType:(NSPasteboardType)type
+{
+ if (IsWindowInvalid(Window)) return;
+ [pasteboard setString:(NSString*)AppleUtils::ToString(Window->GetDragText()) forType:NSPasteboardTypeString];
+}
+
@end
MacWindow::MacWindow(const CreateWindowSettings& settings)
@@ -624,6 +726,8 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
{
styleMask |= NSWindowStyleMaskBorderless;
}
+ if (settings.Fullscreen)
+ styleMask |= NSWindowStyleMaskFullScreen;
if (settings.HasBorder)
{
styleMask |= NSWindowStyleMaskTitled;
@@ -650,9 +754,11 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
[window setMaxSize:NSMakeSize(settings.MaximumSize.X, settings.MaximumSize.Y)];
[window setOpaque:!settings.SupportsTransparency];
[window setContentView:view];
- [window setAcceptsMouseMovedEvents:YES];
+ if (settings.AllowInput)
+ [window setAcceptsMouseMovedEvents:YES];
[window setDelegate:window];
_window = window;
+ _view = view;
if (settings.AllowDragAndDrop)
{
[view registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]];
@@ -664,8 +770,6 @@ MacWindow::MacWindow(const CreateWindowSettings& settings)
layer.contentsScale = screenScale;
// TODO: impl Parent for MacWindow
- // TODO: impl StartPosition for MacWindow
- // TODO: impl Fullscreen for MacWindow
// TODO: impl ShowInTaskbar for MacWindow
// TODO: impl IsTopmost for MacWindow
}
@@ -676,6 +780,7 @@ MacWindow::~MacWindow()
[window close];
[window release];
_window = nullptr;
+ _view = nullptr;
}
void MacWindow::CheckForResize(float width, float height)
@@ -699,7 +804,6 @@ void MacWindow::SetIsMouseOver(bool value)
// Refresh cursor typet
SetCursor(CursorType::Default);
SetCursor(cursor);
-
}
else
{
@@ -714,6 +818,22 @@ void* MacWindow::GetNativePtr() const
return _window;
}
+void MacWindow::OnUpdate(float dt)
+{
+ if (IsMouseTracking())
+ {
+ // Keep sending mouse movement events no matter if window has focus
+ Float2 mousePos = Platform::GetMousePosition();
+ if (_mouseTrackPos != mousePos)
+ {
+ _mouseTrackPos = mousePos;
+ Input::Mouse->OnMouseMove(mousePos, this);
+ }
+ }
+
+ WindowBase::OnUpdate(dt);
+}
+
void MacWindow::Show()
{
if (!_visible)
@@ -728,7 +848,10 @@ void MacWindow::Show()
// Show
NSWindow* window = (NSWindow*)_window;
- [window makeKeyAndOrderFront:window];
+ if (_settings.AllowInput)
+ [window makeKeyAndOrderFront:window];
+ else
+ [window orderFront:window];
if (_settings.ActivateWhenFirstShown)
[NSApp activateIgnoringOtherApps:YES];
_focused = true;
@@ -746,7 +869,7 @@ void MacWindow::Hide()
// Hide
NSWindow* window = (NSWindow*)_window;
- [window orderOut:nil];
+ [window orderOut:window];
// Base
WindowBase::Hide();
@@ -782,14 +905,9 @@ void MacWindow::Restore()
[window zoom:nil];
}
-bool MacWindow::IsClosed() const
-{
- return _window != nullptr;
-}
-
bool MacWindow::IsForegroundWindow() const
{
- return Platform::GetHasFocus() && IsFocused();
+ return IsFocused() && Platform::GetHasFocus();
}
void MacWindow::BringToFront(bool force)
@@ -808,14 +926,13 @@ void MacWindow::SetClientBounds(const Rectangle& clientArea)
NSWindow* window = (NSWindow*)_window;
if (!window)
return;
+ const float screenScale = MacPlatform::ScreenScale;
+
NSRect oldRect = [window frame];
- NSRect newRect = NSMakeRect(0, 0, clientArea.Size.X, clientArea.Size.Y);
+ NSRect newRect = NSMakeRect(0, 0, clientArea.Size.X / screenScale, clientArea.Size.Y / screenScale);
newRect = [window frameRectForContentRect:newRect];
- //newRect.origin.x = oldRect.origin.x;
- //newRect.origin.y = NSMaxY(oldRect) - newRect.size.height;
-
- Float2 pos = AppleUtils::PosToCoca(clientArea.Location);
+ Float2 pos = AppleUtils::PosToCoca(clientArea.Location) / screenScale;
Float2 titleSize = GetWindowTitleSize(this);
newRect.origin.x = pos.X + titleSize.X;
newRect.origin.y = pos.Y - newRect.size.height + titleSize.Y;
@@ -909,8 +1026,63 @@ void MacWindow::SetTitle(const StringView& title)
DragDropEffect MacWindow::DoDragDrop(const StringView& data)
{
- // TODO: implement using beginDraggingSession and NSDraggingSource
- return DragDropEffect::None;
+ NSWindow* window = (NSWindow*)_window;
+ MacViewImpl* view = (MacViewImpl*)_view;
+ _dragText = data;
+
+ // Create mouse drag event
+ NSEvent* event = [NSEvent
+ mouseEventWithType:NSEventTypeLeftMouseDragged
+ location:window.mouseLocationOutsideOfEventStream
+ modifierFlags:0
+ timestamp:NSApp.currentEvent.timestamp
+ windowNumber:window.windowNumber
+ context:nil
+ eventNumber:0
+ clickCount:1
+ pressure:1.0];
+
+ // Create drag item
+ NSPasteboardItem* pasteItem = [NSPasteboardItem new];
+ [pasteItem setDataProvider:view forTypes:[NSArray arrayWithObjects:NSPasteboardTypeString, nil]];
+ NSDraggingItem* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pasteItem];
+ [dragItem setDraggingFrame:NSMakeRect(event.locationInWindow.x, event.locationInWindow.y, 1, 1) contents:nil];
+
+ // Start dragging session
+ NSDraggingSession* draggingSession = [view beginDraggingSessionWithItems:[NSArray arrayWithObject:dragItem] event:event source:view];
+ DragDropEffect result = DragDropEffect::None;
+
+#if USE_EDITOR
+ // Create background worker that will keep updating GUI (perform rendering)
+ MacDragLocker.Lock();
+ ASSERT(!MacDragSession && !MacDragJob);
+ MacDragSession = draggingSession;
+ MacDragJob = New();
+ Task::StartNew(MacDragJob);
+ MacDragLocker.Unlock();
+ while (MacDragJob->GetState() == TaskState::Queued)
+ Platform::Sleep(1);
+ // TODO: maybe wait for the drag end to return result?
+#endif
+
+ return result;
+}
+
+void MacWindow::StartTrackingMouse(bool useMouseScreenOffset)
+{
+ if (_isTrackingMouse || !_window)
+ return;
+ _isTrackingMouse = true;
+ _trackingMouseOffset = Float2::Zero;
+ _isUsingMouseOffset = useMouseScreenOffset;
+ _mouseTrackPos = Float2::Minimum;
+}
+
+void MacWindow::EndTrackingMouse()
+{
+ if (!_isTrackingMouse || !_window)
+ return;
+ _isTrackingMouse = false;
}
void MacWindow::SetCursor(CursorType type)
diff --git a/Source/Engine/Platform/Mac/MacWindow.h b/Source/Engine/Platform/Mac/MacWindow.h
index 8850f80ba..ccd43b354 100644
--- a/Source/Engine/Platform/Mac/MacWindow.h
+++ b/Source/Engine/Platform/Mac/MacWindow.h
@@ -6,6 +6,7 @@
#include "Engine/Platform/Base/WindowBase.h"
#include "Engine/Platform/Platform.h"
+#include "Engine/Core/Math/Vector2.h"
///
/// Implementation of the window class for Mac platform.
@@ -13,28 +14,32 @@
class FLAXENGINE_API MacWindow : public WindowBase
{
private:
-
- void* _window;
+ void* _window = nullptr;
+ void* _view = nullptr;
bool _isMouseOver = false;
+ Float2 _mouseTrackPos = Float2::Minimum;
+ String _dragText;
public:
-
MacWindow(const CreateWindowSettings& settings);
~MacWindow();
void CheckForResize(float width, float height);
void SetIsMouseOver(bool value);
+ const String& GetDragText() const
+ {
+ return _dragText;
+ }
public:
-
// [WindowBase]
void* GetNativePtr() const override;
+ void OnUpdate(float dt) override;
void Show() override;
void Hide() override;
void Minimize() override;
void Maximize() override;
void Restore() override;
- bool IsClosed() const override;
bool IsForegroundWindow() const override;
void BringToFront(bool force = false) override;
void SetClientBounds(const Rectangle& clientArea) override;
@@ -50,6 +55,8 @@ public:
void Focus() override;
void SetTitle(const StringView& title) override;
DragDropEffect DoDragDrop(const StringView& data) override;
+ void StartTrackingMouse(bool useMouseScreenOffset) override;
+ void EndTrackingMouse() override;
void SetCursor(CursorType type) override;
};
diff --git a/Source/Engine/Platform/Unix/UnixNetwork.cpp b/Source/Engine/Platform/Unix/UnixNetwork.cpp
index 5ffdb2c01..ae2d8d9f2 100644
--- a/Source/Engine/Platform/Unix/UnixNetwork.cpp
+++ b/Source/Engine/Platform/Unix/UnixNetwork.cpp
@@ -96,7 +96,11 @@ static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint)
return true;
}
char strPort[6];
+#if __APPLE__
+ snprintf(strPort, sizeof(strPort), "%d", port);
+#else
sprintf(strPort, "%d", port);
+#endif
endPoint.IPVersion = addr->sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4;
memcpy(endPoint.Data, addr, size);
return false;
diff --git a/Source/Engine/Platform/Win32/Win32CriticalSection.h b/Source/Engine/Platform/Win32/Win32CriticalSection.h
index 2c103ef85..95d85efac 100644
--- a/Source/Engine/Platform/Win32/Win32CriticalSection.h
+++ b/Source/Engine/Platform/Win32/Win32CriticalSection.h
@@ -16,16 +16,13 @@ class FLAXENGINE_API Win32CriticalSection
friend Win32ConditionVariable;
private:
-
mutable Windows::CRITICAL_SECTION _criticalSection;
private:
-
Win32CriticalSection(const Win32CriticalSection&);
Win32CriticalSection& operator=(const Win32CriticalSection&);
public:
-
///
/// Initializes a new instance of the class.
///
@@ -43,17 +40,12 @@ public:
}
public:
-
///
/// Locks the critical section.
///
void Lock() const
{
- // Spin first before entering critical section, causing ring-0 transition and context switch
- if (Windows::TryEnterCriticalSection(&_criticalSection) == 0)
- {
- Windows::EnterCriticalSection(&_criticalSection);
- }
+ Windows::EnterCriticalSection(&_criticalSection);
}
///
diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp
index c5bc44ab9..474a92072 100644
--- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp
+++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp
@@ -451,6 +451,7 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri
default:
break;
}
+ flags |= MB_TASKMODAL;
// Show dialog
int result = MessageBoxW(parent ? static_cast(parent->GetNativePtr()) : nullptr, String(text).GetText(), String(caption).GetText(), flags);
diff --git a/Source/Engine/Platform/iOS/iOSPlatform.cpp b/Source/Engine/Platform/iOS/iOSPlatform.cpp
index 7ca27c481..26c3b8f28 100644
--- a/Source/Engine/Platform/iOS/iOSPlatform.cpp
+++ b/Source/Engine/Platform/iOS/iOSPlatform.cpp
@@ -410,7 +410,7 @@ bool iOSWindow::IsClosed() const
bool iOSWindow::IsForegroundWindow() const
{
- return Platform::GetHasFocus() && IsFocused();
+ return IsFocused() && Platform::GetHasFocus();
}
void iOSWindow::BringToFront(bool force)
diff --git a/Source/Engine/Renderer/DepthOfFieldPass.cpp b/Source/Engine/Renderer/DepthOfFieldPass.cpp
index 4c43ccdbf..707a441e6 100644
--- a/Source/Engine/Renderer/DepthOfFieldPass.cpp
+++ b/Source/Engine/Renderer/DepthOfFieldPass.cpp
@@ -202,16 +202,14 @@ GPUTexture* DepthOfFieldPass::getDofBokehShape(DepthOfFieldSettings& dofSettings
void DepthOfFieldPass::Render(RenderContext& renderContext, GPUTexture*& frame, GPUTexture*& tmp)
{
- if (!_platformSupportsDoF || checkIfSkipPass())
+ DepthOfFieldSettings& dofSettings = renderContext.List->Settings.DepthOfField;
+ const bool useDoF = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::DepthOfField) && dofSettings.Enabled;
+ if (!useDoF || !_platformSupportsDoF || checkIfSkipPass())
return;
auto device = GPUDevice::Instance;
auto context = device->GetMainContext();
const auto depthBuffer = renderContext.Buffers->DepthBuffer;
const auto shader = _shader->GetShader();
- DepthOfFieldSettings& dofSettings = renderContext.List->Settings.DepthOfField;
- const bool useDoF = _platformSupportsDoF && EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::DepthOfField) && dofSettings.Enabled;
- if (!useDoF)
- return;
PROFILE_GPU_CPU("Depth Of Field");
context->ResetSR();
diff --git a/Source/Engine/Renderer/EyeAdaptationPass.cpp b/Source/Engine/Renderer/EyeAdaptationPass.cpp
index 12b26ea41..3a840ff01 100644
--- a/Source/Engine/Renderer/EyeAdaptationPass.cpp
+++ b/Source/Engine/Renderer/EyeAdaptationPass.cpp
@@ -35,7 +35,6 @@ PACK_STRUCT(struct EyeAdaptationData {
void EyeAdaptationPass::Render(RenderContext& renderContext, GPUTexture* colorBuffer)
{
- // Cache data
auto device = GPUDevice::Instance;
auto context = device->GetMainContext();
auto& view = renderContext.View;
@@ -45,12 +44,8 @@ void EyeAdaptationPass::Render(RenderContext& renderContext, GPUTexture* colorBu
//const float frameDelta = Time::ElapsedGameTime.GetTotalSeconds();
const float frameDelta = time - renderContext.Buffers->LastEyeAdaptationTime;
renderContext.Buffers->LastEyeAdaptationTime = 0.0f;
-
- // Optionally skip the rendering
- if (checkIfSkipPass() || (view.Flags & ViewFlags::EyeAdaptation) == ViewFlags::None || settings.Mode == EyeAdaptationMode::None)
- {
+ if ((view.Flags & ViewFlags::EyeAdaptation) == ViewFlags::None || settings.Mode == EyeAdaptationMode::None || checkIfSkipPass())
return;
- }
PROFILE_GPU_CPU("Eye Adaptation");
diff --git a/Source/Engine/Renderer/MotionBlurPass.cpp b/Source/Engine/Renderer/MotionBlurPass.cpp
index d425df80f..2030027ed 100644
--- a/Source/Engine/Renderer/MotionBlurPass.cpp
+++ b/Source/Engine/Renderer/MotionBlurPass.cpp
@@ -244,7 +244,7 @@ void MotionBlurPass::RenderDebug(RenderContext& renderContext, GPUTextureView* f
{
auto context = GPUDevice::Instance->GetMainContext();
const auto motionVectors = renderContext.Buffers->MotionVectors;
- if (!motionVectors->IsAllocated() || checkIfSkipPass())
+ if (!motionVectors || !motionVectors->IsAllocated() || checkIfSkipPass())
{
context->Draw(frame);
return;
diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp
index 345147b61..006927639 100644
--- a/Source/Engine/Renderer/PostProcessingPass.cpp
+++ b/Source/Engine/Renderer/PostProcessingPass.cpp
@@ -195,7 +195,7 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input,
bool useLensFlares = EnumHasAnyFlags(view.Flags, ViewFlags::LensFlares) && settings.LensFlares.Intensity > 0.0f && useBloom;
// Ensure to have valid data and if at least one effect should be applied
- if (checkIfSkipPass() || !(useBloom || useToneMapping || useCameraArtifacts))
+ if (!(useBloom || useToneMapping || useCameraArtifacts) || checkIfSkipPass())
{
// Resources are missing. Do not perform rendering. Just copy raw frame
context->SetViewportAndScissors((float)output->Width(), (float)output->Height());
diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp
index efd147917..679ead4e3 100644
--- a/Source/Engine/Scripting/BinaryModule.cpp
+++ b/Source/Engine/Scripting/BinaryModule.cpp
@@ -1270,7 +1270,11 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
// Invoke the method
MObject* exception = nullptr;
+#if USE_NETCORE // NetCore uses the same path for both virtual and non-virtual calls
+ MObject* resultObject = mMethod->Invoke(mInstance, params, &exception);
+#else
MObject* resultObject = withInterfaces ? mMethod->InvokeVirtual((MObject*)mInstance, params, &exception) : mMethod->Invoke(mInstance, params, &exception);
+#endif
if (exception)
{
MException ex(exception);
diff --git a/Source/Engine/Scripting/Object.cs b/Source/Engine/Scripting/Object.cs
index c64532c2b..9fc7d8039 100644
--- a/Source/Engine/Scripting/Object.cs
+++ b/Source/Engine/Scripting/Object.cs
@@ -320,7 +320,7 @@ namespace FlaxEngine
internal static partial Object Internal_Create2(string typeName);
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceCreated", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
- internal static partial void Internal_ManagedInstanceCreated(Object managedInstance, IntPtr theKlass);
+ internal static partial void Internal_ManagedInstanceCreated(Object managedInstance, IntPtr typeClass);
[LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_ManagedInstanceDeleted", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
internal static partial void Internal_ManagedInstanceDeleted(IntPtr nativeInstance);
diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp
index b3e5a0072..47f9b0a7c 100644
--- a/Source/Engine/Scripting/Runtime/DotNet.cpp
+++ b/Source/Engine/Scripting/Runtime/DotNet.cpp
@@ -46,6 +46,7 @@
#include
#include
#include
+#include
#include
typedef char char_t;
#define DOTNET_HOST_MONO_DEBUG 0
@@ -522,7 +523,8 @@ void MCore::GC::FreeMemory(void* ptr, bool coTaskMem)
void MCore::Thread::Attach()
{
-#if DOTNET_HOST_MONO
+ // TODO: find a way to properly register native thread so Mono Stop The World (stw) won't freeze when native threads (eg. Job System) are running native code only
+#if DOTNET_HOST_MONO && 0
if (!IsInMainThread() && !mono_domain_get())
{
mono_thread_attach(MonoDomainHandle);
@@ -2056,7 +2058,7 @@ bool InitHostfxr()
// Setup debugger
{
int32 debuggerLogLevel = 0;
- if (CommandLine::Options.MonoLog.IsTrue())
+ if (CommandLine::Options.MonoLog.IsTrue() || DOTNET_HOST_MONO_DEBUG)
{
LOG(Info, "Using detailed Mono logging");
mono_trace_set_level_string("debug");
@@ -2139,6 +2141,7 @@ bool InitHostfxr()
LOG(Fatal, "Failed to initialize Mono.");
return true;
}
+ mono_gc_init_finalizer_thread();
// Log info
char* buildInfo = mono_get_runtime_build_info();
diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp
index 291910b11..c0fda4d6e 100644
--- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp
+++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp
@@ -460,16 +460,15 @@ bool ShaderCompiler::WriteCustomDataVS(ShaderCompilationContext* context, Shader
auto& element = layout[a];
if (!layoutVisible[a])
continue;
-
- // TODO: serialize whole struct?
-
- output->WriteByte(static_cast(element.Type));
- output->WriteByte(element.Index);
- output->WriteByte(static_cast(element.Format));
- output->WriteByte(element.InputSlot);
- output->WriteUint32(element.AlignedByteOffset);
- output->WriteByte(element.InputSlotClass);
- output->WriteUint32(element.InstanceDataStepRate);
+ GPUShaderProgramVS::InputElement data;
+ data.Type = static_cast(element.Type);
+ data.Index = element.Index;
+ data.Format = static_cast(element.Format);
+ data.InputSlot = element.InputSlot;
+ data.AlignedByteOffset = element.AlignedByteOffset;
+ data.InputSlotClass = element.InputSlotClass;
+ data.InstanceDataStepRate = element.InstanceDataStepRate;
+ output->Write(data);
}
return false;
diff --git a/Source/Platforms/Mac/Default.icns b/Source/Platforms/Mac/Default.icns
index 455acd991..911276d8e 100644
Binary files a/Source/Platforms/Mac/Default.icns and b/Source/Platforms/Mac/Default.icns differ
diff --git a/Source/Platforms/iOS/Binaries/Project/FlaxGame.xcodeproj/project.pbxproj b/Source/Platforms/iOS/Binaries/Project/FlaxGame.xcodeproj/project.pbxproj
index 1dd5d4e01..3b8889487 100644
--- a/Source/Platforms/iOS/Binaries/Project/FlaxGame.xcodeproj/project.pbxproj
+++ b/Source/Platforms/iOS/Binaries/Project/FlaxGame.xcodeproj/project.pbxproj
@@ -222,7 +222,7 @@ ${PBXResourcesGroup}
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- HEADER_SEARCH_PATHS = ${HeaderSearchPaths};
+ HEADER_SEARCH_PATHS = "${HeaderSearchPaths}";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
@@ -275,7 +275,7 @@ ${PBXResourcesGroup}
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- HEADER_SEARCH_PATHS = ${HeaderSearchPaths};
+ HEADER_SEARCH_PATHS = "${HeaderSearchPaths}";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
diff --git a/Source/ThirdParty/stb/stb_image_write.h b/Source/ThirdParty/stb/stb_image_write.h
index 95943eb60..8cae247eb 100644
--- a/Source/ThirdParty/stb/stb_image_write.h
+++ b/Source/ThirdParty/stb/stb_image_write.h
@@ -758,6 +758,8 @@ static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, f
#ifdef __STDC_WANT_SECURE_LIB__
len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
+#elif __APPLE__
+ len = snprintf(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
#else
len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
#endif
diff --git a/Source/ThirdParty/volk/volk.Build.cs b/Source/ThirdParty/volk/volk.Build.cs
index 2f4cfa0d6..67f7593d9 100644
--- a/Source/ThirdParty/volk/volk.Build.cs
+++ b/Source/ThirdParty/volk/volk.Build.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+using System.Collections.Generic;
using System.IO;
using Flax.Build;
using Flax.Build.NativeCpp;
@@ -62,4 +63,12 @@ public class volk : ThirdPartyModule
Log.ErrorOnce("Missing VulkanSDK.", ref _missingSDKError);
}
}
+
+ ///
+ public override void GetFilesToDeploy(List files)
+ {
+ base.GetFilesToDeploy(files);
+
+ files.Add(Path.Combine(FolderPath, "volk.h"));
+ }
}
diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
index 90e96d075..10e4d5846 100644
--- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
+++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs
@@ -3135,14 +3135,17 @@ namespace Flax.Build.Bindings
contents.AppendLine("#pragma once");
contents.AppendLine();
contents.AppendLine($"#define {binaryModuleNameUpper}_NAME \"{binaryModuleName}\"");
- if (version.Build == -1)
+ if (version.Build <= 0)
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION Version({version.Major}, {version.Minor})");
- else
+ else if (version.Revision <= 0)
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION Version({version.Major}, {version.Minor}, {version.Build})");
+ else
+ contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION Version({version.Major}, {version.Minor}, {version.Build}, {version.Revision})");
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_TEXT \"{version}\"");
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_MAJOR {version.Major}");
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_MINOR {version.Minor}");
contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_BUILD {version.Build}");
+ contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_REVISION {version.Revision}");
contents.AppendLine($"#define {binaryModuleNameUpper}_COMPANY \"{project.Company}\"");
contents.AppendLine($"#define {binaryModuleNameUpper}_COPYRIGHT \"{project.Copyright}\"");
contents.AppendLine();
diff --git a/Source/Tools/Flax.Build/Deploy/Deployer.cs b/Source/Tools/Flax.Build/Deploy/Deployer.cs
index 5882bd366..4f12e769f 100644
--- a/Source/Tools/Flax.Build/Deploy/Deployer.cs
+++ b/Source/Tools/Flax.Build/Deploy/Deployer.cs
@@ -44,6 +44,12 @@ namespace Flax.Build
///
[CommandLine("deployCertPass", "Certificate file password for binaries signing.")]
public static string DeployCertPass;
+
+ ///
+ /// Apple keychain profile name to use for app notarize action (installed locally).
+ ///
+ [CommandLine("deployKeychainProfile", "Apple keychain profile name to use for app notarize action (installed locally).")]
+ public static string DeployKeychainProfile;
}
}
@@ -66,12 +72,6 @@ namespace Flax.Deploy
{
Initialize();
- if (Configuration.DeployEditor)
- {
- BuildEditor();
- Deployment.Editor.Package();
- }
-
if (Configuration.DeployPlatforms)
{
if (Configuration.BuildPlatforms == null || Configuration.BuildPlatforms.Length == 0)
@@ -94,6 +94,12 @@ namespace Flax.Deploy
}
}
}
+
+ if (Configuration.DeployEditor)
+ {
+ BuildEditor();
+ Deployment.Editor.Package();
+ }
}
catch (Exception ex)
{
@@ -183,6 +189,10 @@ namespace Flax.Deploy
{
if (Platform.IsPlatformSupported(platform, architecture))
{
+ Log.Info(string.Empty);
+ Log.Info($"Build {platform} {architecture} platform");
+ Log.Info(string.Empty);
+
foreach (var configuration in Configurations)
{
FlaxBuild.Build(Globals.EngineRoot, "FlaxGame", platform, architecture, configuration);
diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs
index 91da6d83d..e1f2ab4e6 100644
--- a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs
+++ b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs
@@ -127,6 +127,33 @@ namespace Flax.Deploy
// Deploy project
DeployFile(RootPath, OutputPath, "Flax.flaxproj");
+ // When deploying Editor with Platforms at once then bundle them inside it
+ if (Configuration.DeployPlatforms && Platforms.PackagedPlatforms != null)
+ {
+ foreach (var platform in Platforms.PackagedPlatforms)
+ {
+ Log.Info(string.Empty);
+ Log.Info($"Bunding {platform} platform with Editor");
+ Log.Info(string.Empty);
+
+ string platformName = platform.ToString();
+ string platformFiles = Path.Combine(Deployer.PackageOutputPath, platformName);
+ string platformData = Path.Combine(OutputPath, "Source", "Platforms", platformName);
+ if (Directory.Exists(platformFiles))
+ {
+ // Copy deployed files
+ Utilities.DirectoryCopy(platformFiles, platformData);
+ }
+ else
+ {
+ // Extract deployed files
+ var packageZipPath = Path.Combine(Deployer.PackageOutputPath, platformName + ".zip");
+ Log.Verbose(packageZipPath + " -> " + platformData);
+ System.IO.Compression.ZipFile.ExtractToDirectory(packageZipPath, platformData, true);
+ }
+ }
+ }
+
// Package Editor into macOS app
if (Platform.BuildTargetPlatform == TargetPlatform.Mac)
{
@@ -164,12 +191,34 @@ namespace Flax.Deploy
var defaultEditorConfig = "Development";
var ediotrBinariesPath = Path.Combine(appContentsPath, "Binaries/Editor/Mac", defaultEditorConfig);
Utilities.DirectoryCopy(ediotrBinariesPath, appBinariesPath, true, true);
+
+ // Sign app resources
+ CodeSign(appPath);
+
+ // Build a disk image
+ var dmgPath = Path.Combine(Deployer.PackageOutputPath, "FlaxEditor.dmg");
+ Log.Info(string.Empty);
+ Log.Info("Building disk image...");
+ if (File.Exists(dmgPath))
+ File.Delete(dmgPath);
+ Utilities.Run("hdiutil", $"create -srcFolder \"{appPath}\" -o \"{dmgPath}\"", null, null, Utilities.RunOptions.Default | Utilities.RunOptions.ThrowExceptionOnError);
+ CodeSign(dmgPath);
+ Log.Info("Output disk image size: " + Utilities.GetFileSize(dmgPath));
+
+ // Notarize disk image
+ if (!string.IsNullOrEmpty(Configuration.DeployKeychainProfile))
+ {
+ Log.Info(string.Empty);
+ Log.Info("Notarizing disk image...");
+ Utilities.Run("xcrun", $"notarytool submit \"{dmgPath}\" --wait --keychain-profile \"{Configuration.DeployKeychainProfile}\"", null, null, Utilities.RunOptions.Default | Utilities.RunOptions.ThrowExceptionOnError);
+ Utilities.Run("xcrun", $"stapler staple \"{dmgPath}\"", null, null, Utilities.RunOptions.Default | Utilities.RunOptions.ThrowExceptionOnError);
+ Log.Info("App notarized for macOS distribution!");
+ }
}
// Compress
if (Configuration.DontCompress)
return;
-
Log.Info(string.Empty);
Log.Info("Compressing editor files...");
string editorPackageZipPath;
@@ -299,6 +348,7 @@ namespace Flax.Deploy
Utilities.Run("strip", "FlaxEditor.dylib", null, dst, Utilities.RunOptions.None);
Utilities.Run("strip", "libMoltenVK.dylib", null, dst, Utilities.RunOptions.None);
+ // Sign binaries
CodeSign(Path.Combine(dst, "FlaxEditor"));
CodeSign(Path.Combine(dst, "FlaxEditor.dylib"));
CodeSign(Path.Combine(dst, "libMoltenVK.dylib"));
diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs
index 832d6deb3..e1d159ed5 100644
--- a/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs
+++ b/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs
@@ -10,8 +10,13 @@ namespace Flax.Deploy
{
public class Platforms
{
+ internal static List PackagedPlatforms;
+
public static void Package(TargetPlatform platform)
{
+ if (PackagedPlatforms == null)
+ PackagedPlatforms = new List();
+ PackagedPlatforms.Add(platform);
var platformsRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms");
Log.Info(string.Empty);
@@ -50,6 +55,20 @@ namespace Flax.Deploy
CodeSign(Path.Combine(binaries, "FlaxGame.exe"));
CodeSign(Path.Combine(binaries, "FlaxEngine.CSharp.dll"));
}
+ else if (platform == TargetPlatform.Mac)
+ {
+ var binaries = Path.Combine(dst, "Binaries", "Game", "arm64", "Debug");
+ CodeSign(Path.Combine(binaries, "FlaxGame"));
+ CodeSign(Path.Combine(binaries, "FlaxGame.dylib"));
+
+ binaries = Path.Combine(dst, "Binaries", "Game", "arm64", "Development");
+ CodeSign(Path.Combine(binaries, "FlaxGame"));
+ CodeSign(Path.Combine(binaries, "FlaxGame.dylib"));
+
+ binaries = Path.Combine(dst, "Binaries", "Game", "arm64", "Release");
+ CodeSign(Path.Combine(binaries, "FlaxGame"));
+ CodeSign(Path.Combine(binaries, "FlaxGame.dylib"));
+ }
// Don't distribute engine deps
Utilities.DirectoryDelete(Path.Combine(dst, "Binaries", "ThirdParty"));
diff --git a/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs b/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs
index 7d3a31061..b4e8b8680 100644
--- a/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs
+++ b/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs
@@ -20,6 +20,7 @@ namespace Flax.Deploy
if (!string.IsNullOrEmpty(Configuration.Compiler))
cmdLine += " -compiler=" + Configuration.Compiler;
+ Log.Info($"Building {target} for {platform} {architecture} {configuration}...");
int result = Utilities.Run(flaxBuildTool, cmdLine, null, root);
if (result != 0)
{
diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs
index 3be0beb76..4a8115fcd 100644
--- a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs
+++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs
@@ -53,10 +53,19 @@ namespace Flax.Build.Platforms
/// App code signing idenity name (from local Mac keychain). Use 'security find-identity -v -p codesigning' to list possible options.
public static void CodeSign(string file, string signIdenity)
{
- if (!File.Exists(file))
+ var isDirectory = Directory.Exists(file);
+ if (!isDirectory && !File.Exists(file))
throw new FileNotFoundException("Missing file to sign.", file);
string cmdLine = string.Format("--force --timestamp -s \"{0}\" \"{1}\"", signIdenity, file);
- if (string.IsNullOrEmpty(Path.GetExtension(file)))
+ if (isDirectory)
+ {
+ // Automatically sign contents
+ cmdLine += " --deep";
+ }
+ {
+ // Enable the hardened runtime
+ cmdLine += " --options=runtime";
+ }
{
// Add entitlements file with some settings for the app execution
cmdLine += string.Format(" --entitlements \"{0}\"", Path.Combine(Globals.EngineRoot, "Source/Platforms/Mac/Default.entitlements"));
diff --git a/Source/Tools/Flax.Build/ProjectInfo.cs b/Source/Tools/Flax.Build/ProjectInfo.cs
index 7830f59c1..ec615fc49 100644
--- a/Source/Tools/Flax.Build/ProjectInfo.cs
+++ b/Source/Tools/Flax.Build/ProjectInfo.cs
@@ -14,9 +14,9 @@ namespace Flax.Build
///
/// Writes the JSON representation of the object.
///
- /// The to write to.
+ /// The to write to.
/// The value.
- /// The calling serializer.
+ /// The calling serializer.
public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
@@ -25,73 +25,68 @@ namespace Flax.Build
///
/// Reads the JSON representation of the object.
///
- /// The to read from.
- /// Type of the object.
- /// The existing property value of the JSON that is being converted.
- /// The calling serializer.
+ /// The to read from.
+ /// Type of the object.
+ /// The serializer options.
/// The object value.
public override Version? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
- {
return null;
- }
- else
+
+ if (reader.TokenType == JsonTokenType.StartObject)
{
- if (reader.TokenType == JsonTokenType.StartObject)
+ try
{
- try
+ reader.Read();
+ var values = new Dictionary();
+ while (reader.TokenType == JsonTokenType.PropertyName)
{
+ var key = reader.GetString();
reader.Read();
- Dictionary values = new Dictionary();
- while (reader.TokenType == JsonTokenType.PropertyName)
- {
- var key = reader.GetString();
- reader.Read();
- var val = reader.GetInt32();
- reader.Read();
- values.Add(key, val);
- }
+ var val = reader.GetInt32();
+ reader.Read();
+ values.Add(key, val);
+ }
- int major = 0, minor = 0, build = 0;
- values.TryGetValue("Major", out major);
- values.TryGetValue("Minor", out minor);
- values.TryGetValue("Build", out build);
+ values.TryGetValue("Major", out var major);
+ values.TryGetValue("Minor", out var minor);
+ if (!values.TryGetValue("Build", out var build))
+ build = -1;
+ if (!values.TryGetValue("Revision", out var revision))
+ revision = -1;
- Version v = new Version(major, minor, build);
- return v;
- }
- catch (Exception ex)
- {
- throw new Exception(String.Format("Error parsing version string: {0}", reader.GetString()), ex);
- }
+ if (build <= 0)
+ return new Version(major, minor);
+ if (revision <= 0)
+ return new Version(major, minor, build);
+ return new Version(major, minor, build, revision);
}
- else if (reader.TokenType == JsonTokenType.String)
+ catch (Exception ex)
{
- try
- {
- Version v = new Version((string)reader.GetString()!);
- return v;
- }
- catch (Exception ex)
- {
- throw new Exception(String.Format("Error parsing version string: {0}", reader.GetString()), ex);
- }
- }
- else
- {
- throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.GetString()));
+ throw new Exception(string.Format("Error parsing version string: {0}", reader.GetString()), ex);
}
}
+
+ if (reader.TokenType == JsonTokenType.String)
+ {
+ try
+ {
+ return new Version((string)reader.GetString()!);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(string.Format("Error parsing version string: {0}", reader.GetString()), ex);
+ }
+ }
+ throw new Exception(string.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.GetString()));
}
///
/// Determines whether this instance can convert the specified object type.
///
/// Type of the object.
- ///
- /// true if this instance can convert the specified object type; otherwise, false.
- ///
+ /// true if this instance can convert the specified object type; otherwise, false.
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Version);
@@ -318,7 +313,7 @@ namespace Flax.Build
Log.Verbose("Loading project file from \"" + path + "\"...");
var contents = File.ReadAllText(path);
var project = JsonSerializer.Deserialize(contents.AsSpan(),
- new JsonSerializerOptions() { Converters = { new FlaxVersionConverter() }, IncludeFields = true, TypeInfoResolver = ProjectInfoSourceGenerationContext.Default });
+ new JsonSerializerOptions() { Converters = { new FlaxVersionConverter() }, IncludeFields = true, TypeInfoResolver = ProjectInfoSourceGenerationContext.Default });
project.ProjectPath = path;
project.ProjectFolderPath = Path.GetDirectoryName(path);
diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs
index a044adc0a..5c4b2dbe0 100644
--- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs
+++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs
@@ -172,7 +172,7 @@ namespace Flax.Build.Projects.VisualStudioCode
json.AddField("label", name);
- bool isDefaultTask = defaultTask && configuration.Configuration == TargetConfiguration.Development && configuration.Platform == Platform.BuildPlatform.Target;
+ bool isDefaultTask = defaultTask && configuration.Configuration == TargetConfiguration.Development && configuration.Platform == Platform.BuildPlatform.Target && configuration.Architecture == Platform.BuildTargetArchitecture;
json.BeginObject("group");
{